376 lines · 14.2 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - the suite seeds and grows real// transcript trees on disk, outside the service's Effect FileSystem.import * as NodeFSP from "node:fs/promises";import * as NodeOS from "node:os";import * as NodePath from "node:path"; import { assert, describe, it } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";import { HostProcessEnvironment } from "@t3tools/shared/hostProcess";import { UsageDay, type UsageSummaryInput } from "@t3tools/contracts";import * as Duration from "effect/Duration";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Scheduler from "effect/Scheduler";import * as TestClock from "effect/testing/TestClock";import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import * as ServerConfig from "../config.ts";import * as ServerSettings from "../serverSettings.ts";import * as UsageService from "./UsageService.ts"; function claudeLine(id: number, outputTokens: number, model = "claude-fable-5"): string {  return `${JSON.stringify({    type: "assistant",    timestamp: "2026-08-01T10:00:00Z",    requestId: `req_${id}`,    sessionId: "session-1",    message: {      id: `msg_${id}`,      model,      usage: { input_tokens: 10, output_tokens: outputTokens },    },  })}\n`;} const WINDOW: UsageSummaryInput = {  timeZone: "UTC",  sinceDay: UsageDay.make("2026-07-31"),  untilDay: UsageDay.make("2026-08-02"),}; const setup = Effect.gen(function* () {  const home = yield* Effect.promise(() =>    NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "usage-service-test-")),  );  yield* Effect.addFinalizer(() =>    Effect.promise(() => NodeFSP.rm(home, { recursive: true, force: true })),  );  const transcriptDir = NodePath.join(home, "claude", "projects", "proj");  yield* Effect.promise(() => NodeFSP.mkdir(transcriptDir, { recursive: true }));  return {    home,    transcript: NodePath.join(transcriptDir, "session.jsonl"),    settings: {      providers: {        claudeAgent: { homePath: NodePath.join(home, "claude") },        codex: { homePath: NodePath.join(home, "codex") },      },    },  };}); const serviceLayers = (input: {  readonly prefix: string;  readonly home: string;  readonly settings: Parameters<typeof ServerSettings.layerTest>[0];  readonly onRatesFetch?: () => void;  /** Defaults to an unparsable document so every scan retries the fetch. */  readonly ratesDocument?: unknown;}) =>  ServerConfig.layerTest(process.cwd(), { prefix: input.prefix }).pipe(    Layer.provideMerge(NodeServices.layer),    Layer.provideMerge(ServerSettings.layerTest(input.settings)),    Layer.provideMerge(      Layer.succeed(        HttpClient.HttpClient,        HttpClient.make((request) =>          Effect.sync(() => {            input.onRatesFetch?.();            // Unparsable rates: every scan retries the fetch, which makes the            // fetch count a boundary-level observation of how many scans ran.            return HttpClientResponse.fromWeb(request, Response.json(input.ratesDocument ?? {}));          }),        ),      ),    ),    Layer.provideMerge(      Layer.succeed(HostProcessEnvironment, { GROK_HOME: NodePath.join(input.home, "grok") }),    ),  ); function totalOutputTokens(summary: { buckets: readonly { totals: { outputTokens: number } }[] }) {  return summary.buckets.reduce((sum, bucket) => sum + bucket.totals.outputTokens, 0);} describe("UsageService", () => {  it.live("reprices unchanged transcripts when custom prices are added, edited, or removed", () =>    Effect.gen(function* () {      const { transcript, settings, home } = yield* setup;      yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5, "example-model")));       yield* Effect.gen(function* () {        const settingsService = yield* ServerSettings.ServerSettingsService;        const service = yield* UsageService.make;         const original = yield* service.readSummary(WINDOW);        assert.strictEqual(original.buckets[0]?.costUsd, 0);        assert.strictEqual(original.buckets[0]?.unpricedRecords, 1);         yield* settingsService.updateSettings({          usagePriceOverrides: {            "example-model": { inputCostPerMillionTokens: 2, outputCostPerMillionTokens: 8 },          },        });        const overridden = yield* service.readSummary(WINDOW);        assert.closeTo(overridden.buckets[0]?.costUsd ?? -1, 0.00006, 1e-12);        assert.strictEqual(overridden.buckets[0]?.costSource, "modelPriced");        assert.strictEqual(overridden.buckets[0]?.unpricedRecords, 0);        assert.deepStrictEqual(overridden.buckets[0]?.totals, original.buckets[0]?.totals);         yield* settingsService.updateSettings({          usagePriceOverrides: {            "example-model": { inputCostPerMillionTokens: 4, outputCostPerMillionTokens: 16 },          },        });        const edited = yield* service.readSummary(WINDOW);        assert.closeTo(edited.buckets[0]?.costUsd ?? -1, 0.00012, 1e-12);         yield* settingsService.updateSettings({ usagePriceOverrides: { "example-model": null } });        const restored = yield* service.readSummary(WINDOW);        assert.deepStrictEqual(restored.buckets, original.buckets);      }).pipe(        Effect.provide(          serviceLayers({ prefix: "usage-service-price-overrides-test", home, settings }),        ),      );    }).pipe(Effect.scoped),  );   it.live("counts appended usage on a rescan of a grown transcript", () =>    Effect.gen(function* () {      const { transcript, settings, home } = yield* setup;      yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5)));       const service = yield* UsageService.make.pipe(        Effect.provide(serviceLayers({ prefix: "usage-service-grow-test", home, settings })),      );       const first = yield* service.readSummary(WINDOW);      assert.strictEqual(totalOutputTokens(first), 5);       yield* Effect.promise(() => NodeFSP.appendFile(transcript, claudeLine(2, 7)));      const second = yield* service.readSummary(WINDOW);      assert.strictEqual(totalOutputTokens(second), 12);    }).pipe(Effect.scoped),  );   it.live("does not share an in-flight scan after custom prices change", () =>    Effect.gen(function* () {      const { transcript, settings, home } = yield* setup;      yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5, "example-model")));       yield* Effect.gen(function* () {        const settingsService = yield* ServerSettings.ServerSettingsService;        const fileSystem = yield* FileSystem.FileSystem;        const firstScanStarted = yield* Deferred.make<void>();        const secondScanStarted = yield* Deferred.make<void>();        const releaseRates = yield* Deferred.make<void>();        let homeProbes = 0;        const service = yield* UsageService.make.pipe(          Effect.provideService(FileSystem.FileSystem, {            ...fileSystem,            exists: (path) =>              fileSystem.exists(path).pipe(                Effect.tap(() => {                  if (path !== NodePath.join(home, "claude", ".claude", "projects"))                    return Effect.void;                  homeProbes += 1;                  return Deferred.succeed(                    homeProbes === 1 ? firstScanStarted : secondScanStarted,                    undefined,                  );                }),              ),          }),          Effect.provideService(            HttpClient.HttpClient,            HttpClient.make((request) =>              Deferred.await(releaseRates).pipe(                Effect.as(HttpClientResponse.fromWeb(request, Response.json({}))),              ),            ),          ),        );         const first = yield* service.readSummary(WINDOW).pipe(Effect.forkChild);        yield* Deferred.await(firstScanStarted);        yield* settingsService.updateSettings({          usagePriceOverrides: {            "example-model": { inputCostPerMillionTokens: 2, outputCostPerMillionTokens: 8 },          },        });        const second = yield* service.readSummary(WINDOW).pipe(Effect.forkChild);        yield* Deferred.await(secondScanStarted);        yield* Deferred.succeed(releaseRates, undefined);         const original = yield* Fiber.join(first);        const updated = yield* Fiber.join(second);        assert.strictEqual(original.buckets[0]?.costUsd, 0);        assert.closeTo(updated.buckets[0]?.costUsd ?? -1, 0.00006, 1e-12);      }).pipe(        Effect.provide(serviceLayers({ prefix: "usage-service-price-race-test", home, settings })),      );    }).pipe(Effect.scoped),  );   it.live("shares one scan between concurrent identical requests", () =>    Effect.gen(function* () {      const { transcript, settings, home } = yield* setup;      yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5)));       let ratesFetches = 0;      const service = yield* UsageService.make.pipe(        Effect.provide(          serviceLayers({            prefix: "usage-service-flight-test",            home,            settings,            onRatesFetch: () => {              ratesFetches += 1;            },          }),        ),      );       const [first, second] = yield* Effect.all(        [service.readSummary(WINDOW), service.readSummary(WINDOW)],        { concurrency: 2 },      );      assert.deepStrictEqual(first, second);      assert.strictEqual(ratesFetches, 1);       // A later request is fresh work again, not a stale cached answer.      yield* service.readSummary(WINDOW);      assert.strictEqual(ratesFetches, 2);    }).pipe(Effect.scoped),  );   it.live("refetches a rate table inside its TTL only when the client asks", () =>    Effect.gen(function* () {      const { transcript, settings, home } = yield* setup;      yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5)));       let ratesFetches = 0;      const service = yield* UsageService.make.pipe(        Effect.provide(          serviceLayers({            prefix: "usage-service-rates-refresh-test",            home,            settings,            ratesDocument: {              "claude-fable-5": { input_cost_per_token: 1e-5, output_cost_per_token: 5e-5 },            },            onRatesFetch: () => {              ratesFetches += 1;            },          }),        ),      );       const first = yield* service.readSummary(WINDOW);      assert.strictEqual(ratesFetches, 1);      assert.strictEqual(first.pricing.status, "fresh");       // Inside the daily TTL a plain rescan keeps the cached table.      yield* TestClock.adjust(Duration.minutes(2));      yield* service.readSummary(WINDOW);      assert.strictEqual(ratesFetches, 1);       // An explicit refresh fetches again so a newly listed model gets priced.      // A burst of refreshes shares that one fetch.      const [refreshed] = yield* Effect.all([service.refreshRates, service.refreshRates], {        concurrency: 2,      });      assert.strictEqual(ratesFetches, 2);      assert.strictEqual(refreshed.status, "fresh");      assert.strictEqual(refreshed.knownModels, 1);    }).pipe(Effect.scoped, Effect.provide(TestClock.layer())),  );   it.live("does not orphan an in-flight scan when its first caller is interrupted", () =>    Effect.gen(function* () {      const { settings, home } = yield* setup;      const service = yield* UsageService.make.pipe(        Effect.provide(          serviceLayers({ prefix: "usage-service-interruption-test", home, settings }),        ),      );       let orphanedAt: number | undefined;      for (let interruptAt = 1; interruptAt <= 31; interruptAt += 1) {        const tasks: Array<() => void> = [];        const dispatcher: Scheduler.SchedulerDispatcher = {          scheduleTask: (task) => tasks.push(task),          flush: () => {            let task: (() => void) | undefined;            while ((task = tasks.shift()) !== undefined) task();          },        };         let requestFiber: Fiber.Fiber<unknown, unknown> | undefined;        let requestChecks = 0;        const scheduler: Scheduler.Scheduler = {          executionMode: "async",          makeDispatcher: () => dispatcher,          shouldYield: (fiber) => {            if (fiber !== requestFiber) return false;            requestChecks += 1;            if (requestChecks !== interruptAt) return false;            fiber.interruptUnsafe();            return true;          },        };         // Each candidate needs a distinct key because the broken case leaves        // its entry in the service's private in-flight map. The invalid window        // keeps the real scan synchronous once its detached fiber starts.        const input: UsageSummaryInput = {          ...WINDOW,          sinceDay: UsageDay.make("2026-09-01"),          untilDay: UsageDay.make(`2026-08-${String(interruptAt).padStart(2, "0")}`),        };        const first = yield* service          .readSummary(input)          .pipe(            Effect.exit,            Effect.provideService(Scheduler.Scheduler, scheduler),            Effect.forkChild,          );        requestFiber = first;        yield* Effect.yieldNow;        dispatcher.flush();         const second = yield* service.readSummary(input).pipe(          Effect.match({            onFailure: (error) => error.reason,            onSuccess: () => "success" as const,          }),          Effect.provideService(Scheduler.Scheduler, scheduler),          Effect.forkChild,        );        yield* Effect.yieldNow;        dispatcher.flush();        const secondExit = second.pollUnsafe();        if (secondExit === undefined) {          second.interruptUnsafe();          orphanedAt = interruptAt;          break;        }        if (Exit.isFailure(secondExit)) {          assert.fail("the matching request fiber was interrupted");        }        assert.strictEqual(secondExit.value, "invalidWindow");      }       assert.isUndefined(        orphanedAt,        `interruption left the next matching request pending at scheduler check ${orphanedAt}`,      );    }).pipe(Effect.scoped),  );});