diff --git a/package.json b/package.json index a010cff1..56d3baed 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:integration": "vitest run --include 'tests/integration/**/*.test.ts'", "test:stress": "vitest run --config tests/vitest.config.ts", "test:real": "vitest run --config tests/real/vitest.config.ts", + "test:local-chat": "vitest run tests/local-chat/chat-completions.test.ts", "dev": "tsx watch src/index.ts", "dev:web": "cd web && npx vite", "build:web": "cd web && npx vite build", diff --git a/tests/local-chat/chat-completions.test.ts b/tests/local-chat/chat-completions.test.ts new file mode 100644 index 00000000..be29b9be --- /dev/null +++ b/tests/local-chat/chat-completions.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect } from "vitest"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; + +const execFileAsync = promisify(execFile); +const baseUrl = "http://localhost:8080/v1/chat/completions"; +const apiKey = "sk-ssujojoqwer1234aaaa"; + +async function runCurl(payload: Record) { + const { stdout } = await execFileAsync( + "curl", + [ + "-sS", + "-w", + "\n%{http_code}", + baseUrl, + "-H", + "Content-Type: application/json", + "-H", + `Authorization: Bearer ${apiKey}`, + "-d", + JSON.stringify(payload), + ], + { timeout: 30000 } + ); + + const lines = stdout.split("\n"); + const statusLine = lines.pop() ?? ""; + const body = lines.join("\n"); + const status = Number(statusLine.trim()); + + return { status, body }; +} + +describe("local chat completions (curl)", () => { + it("gpt-5.4 returns OK", async () => { + const { status, body } = await runCurl({ + model: "gpt-5.4", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); + + it("gpt-5.4-mini returns OK", async () => { + const { status, body } = await runCurl({ + model: "gpt-5.4-mini", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); + + it("gpt-5.4-mini-fast returns OK", async () => { + const { status, body } = await runCurl({ + model: "gpt-5.4-mini-fast", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); + + it("gpt-5.4-xhigh-fast returns OK", async () => { + const { status, body } = await runCurl({ + model: "gpt-5.4-xhigh-fast", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); + + it("gpt-5.4-xhigh returns OK", async () => { + const { status, body } = await runCurl({ + model: "gpt-5.4-xhigh", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); + + it("openrouter/free returns OK", async () => { + const { status, body } = await runCurl({ + model: "openrouter/free", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); + + it("minimaxai/minimax-m2.7 returns OK", async () => { + const { status, body } = await runCurl({ + model: "minimaxai/minimax-m2.7", + messages: [{ role: "user", content: "Directly reply OK." }], + }); + + expect(status).toBe(200); + expect(body).toContain("OK"); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 505e8be3..e1f585ad 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ "tests/unit/**/*.{test,spec}.ts", "tests/integration/**/*.{test,spec}.ts", "tests/e2e/**/*.{test,spec}.ts", + "tests/local-chat/**/*.{test,spec}.ts", "packages/electron/__tests__/**/*.{test,spec}.ts", ], },