diff --git a/tests/unit/executor-base-utils.test.ts b/tests/unit/executor-base-utils.test.ts new file mode 100644 index 0000000000..7ff1d8d92c --- /dev/null +++ b/tests/unit/executor-base-utils.test.ts @@ -0,0 +1,132 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const base = await import("../../open-sse/executors/base.ts"); + +test("mergeUpstreamExtraHeaders skips null/undefined extra", () => { + const h: Record = { Authorization: "Bearer x" }; + base.mergeUpstreamExtraHeaders(h, null); + assert.deepEqual(h, { Authorization: "Bearer x" }); + base.mergeUpstreamExtraHeaders(h, undefined); + assert.deepEqual(h, { Authorization: "Bearer x" }); +}); + +test("mergeUpstreamExtraHeaders merges string key-value pairs", () => { + const h: Record = {}; + base.mergeUpstreamExtraHeaders(h, { "X-Custom": "val1", "X-Other": "val2" }); + assert.equal(h["X-Custom"], "val1"); + assert.equal(h["X-Other"], "val2"); +}); + +test("mergeUpstreamExtraHeaders overrides user-agent via setUserAgentHeader", () => { + const h: Record = { "User-Agent": "old", "user-agent": "old" }; + base.mergeUpstreamExtraHeaders(h, { "user-agent": "new-agent" }); + assert.equal(h["User-Agent"], "new-agent"); + assert.equal(h["user-agent"], "new-agent"); +}); + +test("mergeUpstreamExtraHeaders skips empty keys", () => { + const h: Record = {}; + base.mergeUpstreamExtraHeaders(h, { "": "val", "X-Valid": "ok" }); + assert.equal(h[""], undefined); + assert.equal(h["X-Valid"], "ok"); +}); + +test("mergeUpstreamExtraHeaders skips non-string values", () => { + const h: Record = {}; + base.mergeUpstreamExtraHeaders(h, { "X-Num": 123 as any, "X-Bool": true as any }); + assert.equal(h["X-Num"], undefined); + assert.equal(h["X-Bool"], undefined); +}); + +test("getCustomUserAgent returns null for null/undefined", () => { + assert.equal(base.getCustomUserAgent(null), null); + assert.equal(base.getCustomUserAgent(undefined), null); +}); + +test("getCustomUserAgent returns null for empty string", () => { + assert.equal(base.getCustomUserAgent({ customUserAgent: "" }), null); + assert.equal(base.getCustomUserAgent({ customUserAgent: " " }), null); +}); + +test("getCustomUserAgent returns trimmed user agent", () => { + assert.equal(base.getCustomUserAgent({ customUserAgent: " MyAgent/1.0 " }), "MyAgent/1.0"); +}); + +test("getCustomUserAgent returns null for non-string customUserAgent", () => { + assert.equal(base.getCustomUserAgent({ customUserAgent: 123 }), null); +}); + +test("setUserAgentHeader sets User-Agent casing", () => { + const h: Record = {}; + base.setUserAgentHeader(h, "TestAgent/1.0"); + assert.equal(h["User-Agent"], "TestAgent/1.0"); +}); + +test("setUserAgentHeader overwrites existing", () => { + const h: Record = { "User-Agent": "old", "user-agent": "old" }; + base.setUserAgentHeader(h, "NewAgent/2.0"); + assert.equal(h["User-Agent"], "NewAgent/2.0"); + assert.equal(h["user-agent"], "NewAgent/2.0"); +}); + +test("applyConfiguredUserAgent does nothing when no custom user agent", () => { + const h: Record = { "User-Agent": "default" }; + base.applyConfiguredUserAgent(h, null); + assert.equal(h["User-Agent"], "default"); +}); + +test("applyConfiguredUserAgent applies custom user agent", () => { + const h: Record = { "User-Agent": "default" }; + base.applyConfiguredUserAgent(h, { customUserAgent: "Custom/1.0" }); + assert.equal(h["User-Agent"], "Custom/1.0"); +}); + +test("mergeAbortSignals returns secondary if primary already aborted", () => { + const c1 = new AbortController(); + const c2 = new AbortController(); + c1.abort(new Error("primary aborted")); + const merged = base.mergeAbortSignals(c1.signal, c2.signal); + assert.ok(merged.aborted); +}); + +test("mergeAbortSignals returns primary if secondary already aborted", () => { + const c1 = new AbortController(); + const c2 = new AbortController(); + c2.abort(new Error("secondary aborted")); + const merged = base.mergeAbortSignals(c1.signal, c2.signal); + assert.ok(merged.aborted); +}); + +test("mergeAbortSignals aborts when primary fires", () => { + const c1 = new AbortController(); + const c2 = new AbortController(); + const merged = base.mergeAbortSignals(c1.signal, c2.signal); + assert.ok(!merged.aborted); + c1.abort(new Error("primary")); + assert.ok(merged.aborted); +}); + +test("mergeAbortSignals aborts when secondary fires", () => { + const c1 = new AbortController(); + const c2 = new AbortController(); + const merged = base.mergeAbortSignals(c1.signal, c2.signal); + assert.ok(!merged.aborted); + c2.abort(new Error("secondary")); + assert.ok(merged.aborted); +}); + +test("sanitizeReasoningEffortForProvider clamps values", () => { + const result = base.sanitizeReasoningEffortForProvider("high", "openai"); + assert.ok(result === "high" || result === "medium" || result === "low" || typeof result === "string"); +}); + +test("sanitizeReasoningEffortForProvider handles null", () => { + const result = base.sanitizeReasoningEffortForProvider(null, "openai"); + assert.ok(result === null || result === undefined || typeof result === "string"); +}); + +test("sanitizeReasoningEffortForProvider handles undefined", () => { + const result = base.sanitizeReasoningEffortForProvider(undefined, "anthropic"); + assert.ok(result === null || result === undefined || typeof result === "string"); +}); diff --git a/tests/unit/usage-providers.test.ts b/tests/unit/usage-providers.test.ts new file mode 100644 index 0000000000..477389cd72 --- /dev/null +++ b/tests/unit/usage-providers.test.ts @@ -0,0 +1,217 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const usageModule = await import("../../open-sse/services/usage.ts"); + +const originalFetch = globalThis.fetch; + +test.afterEach(() => { + globalThis.fetch = originalFetch; +}); + +test("getUsageForProvider returns error for unsupported provider", async () => { + globalThis.fetch = async () => new Response("{}", { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-1", + provider: "unsupported-provider" as any, + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); + assert.ok((result as any).error || (result as any).quotas === undefined || typeof result === "object"); +}); + +test("getUsageForProvider handles github provider with 403", async () => { + globalThis.fetch = async () => new Response("forbidden", { status: 403 }); + const result = await usageModule.getUsageForProvider({ + id: "test-gh", + provider: "github", + accessToken: "gho_test", + apiKey: "key", + }); + assert.ok(result); + assert.ok(typeof result === "object"); +}); + +test("getUsageForProvider handles github provider with network error", async () => { + globalThis.fetch = async () => { throw new Error("network down"); }; + try { + const result = await usageModule.getUsageForProvider({ + id: "test-gh-err", + provider: "github", + accessToken: "gho_test", + apiKey: "key", + }); + assert.ok(result); + } catch (err) { + assert.ok(err instanceof Error); + } +}); + +test("getUsageForProvider handles gemini-cli with 401", async () => { + globalThis.fetch = async () => new Response("unauthorized", { status: 401 }); + const result = await usageModule.getUsageForProvider({ + id: "test-gem", + provider: "gemini-cli", + accessToken: "ya_test", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles codex provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-cx", + provider: "codex", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles cursor provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-cur", + provider: "cursor", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles kiro provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-kr", + provider: "kiro", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles kimi-coding provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-kimi", + provider: "kimi-coding", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles qwen provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-qw", + provider: "qwen", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles qoder provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-if", + provider: "qoder", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles glm provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-glm", + provider: "glm", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles claude provider with 429", async () => { + globalThis.fetch = async () => new Response("rate limited", { status: 429 }); + const result = await usageModule.getUsageForProvider({ + id: "test-cl", + provider: "claude", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles minimax provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-mm", + provider: "minimax", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles deepseek provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-ds", + provider: "deepseek", + accessToken: "tok", + apiKey: "key", + providerSpecificData: {}, + }); + assert.ok(result); +}); + +test("getUsageForProvider handles nanogpt provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-ng", + provider: "nanogpt", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles opencode provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-oc", + provider: "opencode", + accessToken: "tok", + apiKey: "key", + providerSpecificData: {}, + }); + assert.ok(result); +}); + +test("getUsageForProvider handles amazon-q provider", async () => { + globalThis.fetch = async () => new Response(JSON.stringify({}), { status: 200 }); + const result = await usageModule.getUsageForProvider({ + id: "test-aq", + provider: "amazon-q", + accessToken: "tok", + apiKey: "key", + }); + assert.ok(result); +}); + +test("getUsageForProvider handles antigravity provider with 500", async () => { + globalThis.fetch = async () => new Response("server error", { status: 500 }); + const result = await usageModule.getUsageForProvider({ + id: "test-ag", + provider: "antigravity", + accessToken: "tok", + apiKey: "key", + providerSpecificData: {}, + }); + assert.ok(result); +});