文件

143 行
4.2 KiB
JavaScript

const { AiProviderError, buildAiProvider, __testables } = require("../../../src/services/ai/provider");
describe("AI provider service", () => {
const originalFetch = global.fetch;
afterEach(() => {
global.fetch = originalFetch;
vi.restoreAllMocks();
});
it("preserves base path for openai-compatible chat completions endpoint", () => {
expect(__testables.resolveChatCompletionsUrl("https://example.com/v1")).toBe(
"https://example.com/v1/chat/completions",
);
expect(__testables.resolveChatCompletionsUrl("https://example.com/v1/")).toBe(
"https://example.com/v1/chat/completions",
);
expect(__testables.resolveChatCompletionsUrl("https://example.com/chat/completions")).toBe(
"https://example.com/chat/completions",
);
});
it("returns deterministic mock suggestion payload", async () => {
const provider = buildAiProvider({ aiProvider: "mock" });
const result = await provider.generateSuggestion({
targetType: "reimbursement",
snapshot: {
targetId: "r-1",
formNumber: "BX-001",
status: "reviewing",
totalAmount: "123.00",
itemCount: 2,
budgetHint: { exceedsBudget: false, exceedsThreshold: true },
invoiceSummary: { abnormalCount: 1 },
},
promptSpec: { systemPrompt: "system", userPrompt: "user" },
});
expect(result.provider).toBe("mock");
expect(result.suggestion.summary).toContain("BX-001");
expect(result.suggestion.riskHints.length).toBeGreaterThan(0);
});
it("maps http 4xx failures to upstream_4xx", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 400,
json: vi.fn().mockResolvedValue({ error: "bad_request" }),
});
const provider = buildAiProvider({
aiProvider: "http",
aiBaseUrl: "https://example.com/v1",
aiModel: "gpt-test",
aiTimeoutMs: 500,
});
await expect(
provider.generateSuggestion({
targetType: "reimbursement",
snapshot: {},
promptSpec: { systemPrompt: "system", userPrompt: "user" },
}),
).rejects.toMatchObject({ code: "upstream_4xx" });
});
it("maps http 5xx failures to upstream_5xx", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 503,
json: vi.fn().mockResolvedValue({ error: "service_unavailable" }),
});
const provider = buildAiProvider({
aiProvider: "http",
aiBaseUrl: "https://example.com/v1",
aiModel: "gpt-test",
aiTimeoutMs: 500,
});
await expect(
provider.generateSuggestion({
targetType: "purchase_request",
snapshot: {},
promptSpec: { systemPrompt: "system", userPrompt: "user" },
}),
).rejects.toMatchObject({ code: "upstream_5xx" });
});
it("maps aborted requests to timeout", async () => {
global.fetch = vi.fn().mockRejectedValue({ name: "AbortError" });
const provider = buildAiProvider({
aiProvider: "http",
aiBaseUrl: "https://example.com/v1",
aiModel: "gpt-test",
aiTimeoutMs: 500,
});
await expect(
provider.generateSuggestion({
targetType: "asset_card",
snapshot: {},
promptSpec: { systemPrompt: "system", userPrompt: "user" },
}),
).rejects.toMatchObject({ code: "timeout" });
});
it("rejects invalid json payload from upstream", async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: vi.fn().mockResolvedValue({
choices: [{ message: { content: "```json\nnot-valid-json\n```" } }],
usage: {},
}),
});
const provider = buildAiProvider({
aiProvider: "http",
aiBaseUrl: "https://example.com/v1",
aiModel: "gpt-test",
aiTimeoutMs: 500,
});
await expect(
provider.generateSuggestion({
targetType: "asset_card",
snapshot: {},
promptSpec: { systemPrompt: "system", userPrompt: "user" },
}),
).rejects.toBeInstanceOf(AiProviderError);
await expect(
provider.generateSuggestion({
targetType: "asset_card",
snapshot: {},
promptSpec: { systemPrompt: "system", userPrompt: "user" },
}),
).rejects.toMatchObject({ code: "invalid_response" });
});
});