import { describe, it, beforeEach, afterEach } from "node:test"; import assert from "node:assert/strict"; const originalFetch = globalThis.fetch; const originalEnv = { ...process.env }; afterEach(() => { globalThis.fetch = originalFetch; process.env.CLIPROXYAPI_HOST = originalEnv.CLIPROXYAPI_HOST; process.env.CLIPROXYAPI_PORT = originalEnv.CLIPROXYAPI_PORT; }); describe("CliproxyapiExecutor", () => { let CliproxyapiExecutor; beforeEach(async () => { process.env.CLIPROXYAPI_HOST = ""; process.env.CLIPROXYAPI_PORT = ""; const mod = await import("../../open-sse/executors/cliproxyapi.ts"); CliproxyapiExecutor = mod.CliproxyapiExecutor; }); describe("constructor", () => { it("should default to 127.0.0.1:8317", () => { const exec = new CliproxyapiExecutor(); assert.equal(exec.getProvider(), "cliproxyapi"); }); it("should respect CLIPROXYAPI_HOST env", () => { process.env.CLIPROXYAPI_HOST = "192.168.1.1"; const exec = new CliproxyapiExecutor(); assert.equal(exec.getProvider(), "cliproxyapi"); }); it("should respect CLIPROXYAPI_PORT env", () => { process.env.CLIPROXYAPI_PORT = "9999"; const exec = new CliproxyapiExecutor(); assert.equal(exec.getProvider(), "cliproxyapi"); }); }); describe("buildUrl", () => { it("should always return /v1/chat/completions", () => { process.env.CLIPROXYAPI_HOST = "127.0.0.1"; process.env.CLIPROXYAPI_PORT = "8317"; const exec = new CliproxyapiExecutor(); const url = exec.buildUrl("any-model", true); assert.equal(url, "http://127.0.0.1:8317/v1/chat/completions"); }); it("should ignore model parameter", () => { const exec = new CliproxyapiExecutor(); const url = exec.buildUrl("gpt-4", false); assert.equal(url, "http://127.0.0.1:8317/v1/chat/completions"); }); it("should use custom host/port", () => { process.env.CLIPROXYAPI_HOST = "10.0.0.1"; process.env.CLIPROXYAPI_PORT = "9090"; const exec = new CliproxyapiExecutor(); const url = exec.buildUrl("model", true); assert.equal(url, "http://10.0.0.1:9090/v1/chat/completions"); }); }); describe("buildHeaders", () => { it("should return content-type without auth when no credentials", () => { const exec = new CliproxyapiExecutor(); const headers = exec.buildHeaders({}); assert.equal(headers["Content-Type"], "application/json"); assert.equal(headers["Authorization"], undefined); }); it("should add Authorization with apiKey", () => { const exec = new CliproxyapiExecutor(); const headers = exec.buildHeaders({ apiKey: "test-key" }); assert.equal(headers["Authorization"], "Bearer test-key"); }); it("should add Authorization with accessToken", () => { const exec = new CliproxyapiExecutor(); const headers = exec.buildHeaders({ accessToken: "test-token" }); assert.equal(headers["Authorization"], "Bearer test-token"); }); it("should prefer apiKey over accessToken", () => { const exec = new CliproxyapiExecutor(); const headers = exec.buildHeaders({ apiKey: "key", accessToken: "token" }); assert.equal(headers["Authorization"], "Bearer key"); }); it("should add Accept header for streaming", () => { const exec = new CliproxyapiExecutor(); const headers = exec.buildHeaders({}, true); assert.equal(headers["Accept"], "text/event-stream"); }); it("should not add Accept header for non-streaming", () => { const exec = new CliproxyapiExecutor(); const headers = exec.buildHeaders({}, false); assert.equal(headers["Accept"], undefined); }); }); describe("transformRequest", () => { it("should update model if body.model differs", () => { const exec = new CliproxyapiExecutor(); const body = { model: "old-model", messages: [] }; const result = exec.transformRequest("new-model", body, true, {}); assert.equal(result.model, "new-model"); assert.deepEqual(result.messages, []); }); it("should return body unchanged if model matches", () => { const exec = new CliproxyapiExecutor(); const body = { model: "same-model", messages: [] }; const result = exec.transformRequest("same-model", body, true, {}); assert.equal(result.model, "same-model"); }); it("should handle non-object body", () => { const exec = new CliproxyapiExecutor(); const result = exec.transformRequest("model", "not-an-object", true, {}); assert.equal(result, "not-an-object"); }); it("should handle null body", () => { const exec = new CliproxyapiExecutor(); const result = exec.transformRequest("model", null, true, {}); assert.equal(result, null); }); }); describe("execute", () => { it("should make fetch request with correct URL, headers, and body", async () => { let capturedUrl, capturedOptions; globalThis.fetch = async (url, options) => { capturedUrl = url; capturedOptions = options; return { status: 200, ok: true }; }; const exec = new CliproxyapiExecutor(); const result = await exec.execute({ model: "test-model", body: { messages: [{ role: "user", content: "hi" }] }, stream: true, credentials: {}, }); assert.equal(capturedUrl, "http://127.0.0.1:8317/v1/chat/completions"); assert.equal(capturedOptions.method, "POST"); assert.ok(capturedOptions.signal); const parsed = JSON.parse(capturedOptions.body); assert.equal(parsed.messages[0].content, "hi"); assert.ok(result.response); }); it("should pass credentials to headers", async () => { let capturedHeaders; globalThis.fetch = async (_url, options) => { capturedHeaders = options.headers; return { status: 200, ok: true }; }; const exec = new CliproxyapiExecutor(); await exec.execute({ model: "test", body: {}, stream: false, credentials: { apiKey: "secret-key" }, }); assert.equal(capturedHeaders["Authorization"], "Bearer secret-key"); }); it("should merge upstream extra headers", async () => { let capturedHeaders; globalThis.fetch = async (_url, options) => { capturedHeaders = options.headers; return { status: 200, ok: true }; }; const exec = new CliproxyapiExecutor(); await exec.execute({ model: "test", body: {}, stream: false, credentials: {}, upstreamExtraHeaders: { "X-Custom": "value" }, }); assert.equal(capturedHeaders["X-Custom"], "value"); }); it("should handle rate limited response", async () => { globalThis.fetch = async () => ({ status: 429, ok: false }); const log = { warn: (tag, msg) => {} }; let logged = false; log.warn = () => { logged = true; }; const exec = new CliproxyapiExecutor(); const result = await exec.execute({ model: "test", body: {}, stream: false, credentials: {}, log, }); assert.equal(result.response.status, 429); }); it("should return url, headers, and transformedBody", async () => { globalThis.fetch = async () => ({ status: 200, ok: true }); const exec = new CliproxyapiExecutor(); const result = await exec.execute({ model: "test", body: { messages: [] }, stream: true, credentials: {}, }); assert.ok(result.url); assert.ok(result.headers); assert.ok(result.transformedBody); }); }); });