310 lines · 11.0 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema";import * as TestClock from "effect/testing/TestClock";import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import { creditRedeemRequestId, makeCliproxyApi } from "./cliproxyApi.ts"; const config = {  kind: "cliproxy",  url: "http://hub.test:8317",  managementKey: "management-secret",  enabled: true,} as const;const accounts = [  {    id: "first.json",    auth_index: "a",    provider: "codex",    email: "first@example.com",    id_token: { chatgpt_account_id: "account-a" },  },  {    id: "second.json",    auth_index: "b",    provider: "codex",    email: "second@example.com",    id_token: { chatgpt_account_id: "account-b" },  },];const credit = (id: string, expires_at = "2099-01-01T00:00:00Z") => ({  id,  expires_at,  status: "available",  reset_type: "codex_rate_limits",});const RequestBody = Schema.Struct({  auth_index: Schema.String,  method: Schema.optional(Schema.String),  url: Schema.optional(Schema.String),  header: Schema.optional(Schema.Record(Schema.String, Schema.String)),  data: Schema.optional(Schema.String),});type RequestBody = typeof RequestBody.Type;const decodeRequest = Schema.decodeUnknownSync(Schema.fromJsonString(RequestBody));const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); function fixture(  options: {    accounts?: Array<(typeof accounts)[number] & { disabled?: boolean }>;    upstream?: (request: RequestBody) => { status: number; body: unknown };    cooldownStatus?: number;  } = {},) {  const requests: Array<{ path: string; body?: RequestBody }> = [];  const http = HttpClient.make((request) =>    Effect.sync(() => {      expect(request.headers.authorization).toBe("Bearer management-secret");      const path = new URL(request.url).pathname;      const body =        request.body._tag === "Uint8Array"          ? decodeRequest(new TextDecoder().decode(request.body.body))          : undefined;      requests.push({ path, ...(body ? { body } : {}) });      if (path.endsWith("/auth-files"))        return HttpClientResponse.fromWeb(          request,          Response.json({ files: options.accounts ?? accounts }),        );      if (path.endsWith("/reset-quota"))        return HttpClientResponse.fromWeb(          request,          Response.json({}, { status: options.cooldownStatus ?? 200 }),        );      expect(path).toBe("/v0/management/api-call");      expect(body?.header?.Authorization).toBe("Bearer $TOKEN$");      const upstream = options.upstream?.(body!) ?? {        status: 200,        body: body?.url?.endsWith("/consume")          ? { code: "reset" }          : body?.url?.endsWith("/rate-limit-reset-credits")            ? {                credits: [                  credit("later", "2099-02-01T00:00:00Z"),                  credit("first"),                  credit("expired", "2000-01-01T00:00:00Z"),                  { ...credit("used"), status: "redeemed" },                ],              }            : {                plan_type: "pro",                rate_limit: {                  secondary_window: {                    used_percent: 78,                    reset_at: 4070908800,                    limit_window_seconds: 604800,                  },                },              },      };      return HttpClientResponse.fromWeb(        request,        Response.json({ status_code: upstream.status, body: encodeJson(upstream.body) }),      );    }),  );  return {    requests,    api: makeCliproxyApi.pipe(Effect.provideService(HttpClient.HttpClient, http)),  };} describe("CLIProxyAPI built-in management API", () => {  it.effect(    "reads both accounts and their earliest unexpired credits without plugin endpoints",    () =>      Effect.gen(function* () {        yield* TestClock.setTime(1788710400000);        const test = fixture();        const api = yield* test.api;        const result = yield* api.readAccounts(config);        expect(result.map((account) => account.usageLimits.resetCredits)).toEqual([          { availableCount: 2, nextCreditId: "first", nextExpiresAt: "2099-01-01T00:00:00.000Z" },          { availableCount: 2, nextCreditId: "first", nextExpiresAt: "2099-01-01T00:00:00.000Z" },        ]);        expect(result[0]?.usageLimits.windows).toMatchObject([          { id: "secondary", usedPercent: 78, kind: "weekly" },        ]);        const calls = test.requests.filter((request) => request.body?.url);        expect(calls.map((request) => request.body?.auth_index).sort()).toEqual([          "a",          "a",          "b",          "b",        ]);        expect(          calls.find((request) => request.body?.auth_index === "b")?.body?.header?.[            "Chatgpt-Account-Id"          ],        ).toBe("account-b");      }),  );   it.effect("keeps usage when the credits endpoint fails", () =>    Effect.gen(function* () {      const test = fixture({        upstream: (request) =>          request.url?.endsWith("rate-limit-reset-credits")            ? { status: 503, body: { token: "do-not-publish" } }            : { status: 200, body: { rate_limit: { primary_window: { used_percent: 12 } } } },      });      const api = yield* test.api;      const result = yield* api.readAccounts(config);      expect(result[0]?.usageLimits.windows[0]?.usedPercent).toBe(12);      expect(result[0]?.usageLimits.resetCredits).toBeUndefined();    }),  );   it.effect("isolates a failed account and never publishes upstream error bodies", () =>    Effect.gen(function* () {      const test = fixture({        upstream: (request) =>          request.auth_index === "a"            ? { status: 401, body: { token: "do-not-publish" } }            : {                status: 200,                body: request.url?.endsWith("rate-limit-reset-credits")                  ? { credits: [] }                  : { rate_limit: { primary_window: { used_percent: 12 } } },              },      });      const api = yield* test.api;      const result = yield* api.readAccounts(config);      expect(result[0]?.usageLimits.unavailable?.reason).toBe("probeFailed");      expect(result[1]?.usageLimits.windows[0]?.usedPercent).toBe(12);      expect(encodeJson(result)).not.toContain("do-not-publish");    }),  );   it.effect("maps Claude scoped windows without a scheduler plugin", () =>    Effect.gen(function* () {      const test = fixture({        accounts: [{ ...accounts[0]!, provider: "claude" }],        upstream: () => ({          status: 200,          body: {            five_hour: { utilization: 10, resets_at: null },            seven_day: { utilization: 50, resets_at: "2099-01-01T00:00:00Z" },            limits: [              {                kind: "weekly_scoped",                percent: 80,                resets_at: null,                scope: { model: { display_name: "Fable" } },              },            ],          },        }),      });      const api = yield* test.api;      const result = yield* api.readAccounts(config);      expect(        result[0]?.usageLimits.windows.map((window) => [window.id, window.usedPercent]),      ).toEqual([        ["five_hour", 10],        ["seven_day", 50],        ["seven_day_fable", 80],      ]);    }),  );   it.effect("pins redemption to the displayed credit and clears only that account's cooldown", () =>    Effect.gen(function* () {      const test = fixture();      const api = yield* test.api;      expect(yield* api.consume(config, "second.json", "credit-b")).toEqual({ outcome: "reset" });      expect(yield* api.consume(config, "second.json", "credit-b")).toEqual({ outcome: "reset" });      const redemptions = test.requests.filter((request) =>        request.body?.url?.endsWith("/consume"),      );      expect(redemptions).toHaveLength(2);      expect(redemptions[0]?.body?.data).toBe(redemptions[1]?.body?.data);      expect(redemptions[0]?.body?.data).toBe(        encodeJson({          redeem_request_id: creditRedeemRequestId("account-b", "credit-b"),          credit_id: "credit-b",        }),      );      expect(        test.requests          .filter((request) => request.path.endsWith("/reset-quota"))          .map((request) => request.body?.auth_index),      ).toEqual(["b", "b"]);    }),  );   for (const [code, outcome] of [    ["nothing_to_reset", "nothingToReset"],    ["no_credit", "noCredit"],    ["already_redeemed", "alreadyRedeemed"],  ] as const) {    it.effect(`reports ${code} accurately`, () =>      Effect.gen(function* () {        const test = fixture({ upstream: () => ({ status: 200, body: { code } }) });        const api = yield* test.api;        expect(yield* api.consume(config, "first.json", "credit")).toEqual({ outcome });        expect(test.requests.some((request) => request.path.endsWith("/reset-quota"))).toBe(          code === "already_redeemed",        );      }),    );  }   it.effect("reports redemption success even if cooldown clearing fails", () =>    Effect.gen(function* () {      const api = yield* fixture({ cooldownStatus: 404 }).api;      const result = yield* api.consume(config, "first.json", "credit");      expect(result.outcome).toBe("reset");      expect(result.warning).toContain("cooldown");    }),  );   it.effect("skips disabled accounts and rejects redemption on them", () =>    Effect.gen(function* () {      const test = fixture({ accounts: [{ ...accounts[0]!, disabled: true }] });      const api = yield* test.api;      expect(yield* api.readAccounts(config)).toEqual([]);      expect((yield* api.consume(config, "first.json", "credit").pipe(Effect.result))._tag).toBe(        "Failure",      );      expect(test.requests.every((request) => request.path.endsWith("/auth-files"))).toBe(true);    }),  );   it.effect("keeps the same redemption id after an uncertain upstream failure", () =>    Effect.gen(function* () {      let attempts = 0;      const test = fixture({        upstream: () =>          ++attempts === 1            ? { status: 503, body: {} }            : { status: 200, body: { code: "already_redeemed" } },      });      const api = yield* test.api;      expect((yield* api.consume(config, "first.json", "credit").pipe(Effect.result))._tag).toBe(        "Failure",      );      expect(yield* api.consume(config, "first.json", "credit")).toEqual({        outcome: "alreadyRedeemed",      });      const data = test.requests        .filter((request) => request.body?.url?.endsWith("/consume"))        .map((request) => request.body?.data);      expect(data[0]).toBe(data[1]);      expect(test.requests.filter((request) => request.path.endsWith("/reset-quota"))).toHaveLength(        1,      );    }),  );   it.effect("rejects unknown accounts without forwarding a redemption", () =>    Effect.gen(function* () {      const test = fixture();      const api = yield* test.api;      const result = yield* api.consume(config, "missing.json", "credit").pipe(Effect.result);      expect(result._tag).toBe("Failure");      expect(test.requests).toHaveLength(1);    }),  );});