Files
Cibello-app/packages/ai-contracts/test/gemini.test.ts
T
Sven (AAMOS AI) c32a7e33c7
CI / Typecheck, test & build (push) Failing after 2s
ci: trigga på master + formatfix inför Gitea Actions
2026-08-13 17:25:14 +07:00

160 lines
4.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { GeminiAamosClient, MemoryBudgetStore } from "../src/gemini.js";
import type { AamosRequestEnvelope } from "../src/tasks.js";
const FIXTURE_RESPONSE = {
candidates: [
{
content: {
parts: [
{
text: JSON.stringify({
items: [
{
produkt: "Mellanmjölk 1,5% fett",
varumarke: "Arla",
kvantitet: 1,
enhet: "liter",
kategori: "Mejeri",
bastaFore: null,
sistaForbruk: null,
konfidens: 0.98,
},
{
produkt: "Smör",
varumarke: "Arla",
kvantitet: "500 g",
enhet: "g",
kategori: "Mejeri",
bastaFore: null,
konfidens: 0.95,
},
],
imageQualityIssues: [],
}),
},
],
},
},
],
usageMetadata: { promptTokenCount: 392, candidatesTokenCount: 121 },
};
function makeFetch(imageBytes: Buffer): typeof fetch {
return async (url: string | URL | Request, init?: RequestInit) => {
const urlStr = url.toString();
if (urlStr.includes("/mock-s3/")) {
return new Response(imageBytes, { status: 200, headers: { "content-type": "image/jpeg" } });
}
if (urlStr.includes("/models/") && init?.method === "POST") {
return new Response(JSON.stringify(FIXTURE_RESPONSE), {
status: 200,
headers: { "content-type": "application/json" },
});
}
return new Response("not found", { status: 404 });
};
}
// A tiny 1x1 JPEG.
const JPEG_BYTES = Buffer.from(
"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8Af//Z",
"base64",
);
describe("GeminiAamosClient", () => {
it("analyzes fridge image with recorded fixture", async () => {
const budget = new MemoryBudgetStore();
const client = new GeminiAamosClient({
apiKey: "test-key",
model: "gemini-2.5-flash",
fetchImpl: makeFetch(JPEG_BYTES),
budgetStore: budget,
dailyBudgetUsd: 10,
});
const result = await client.runTask("ANALYZE_FRIDGE_IMAGE", {
imageUrls: ["http://localhost/v1/mock-s3/fridge.jpg"],
locationType: "fridge",
marketLocale: "sv-SE",
knownItems: [],
});
expect(result.status).toBe("ok");
expect(result.output).not.toBeNull();
expect(result.modelVersion).toBe("gemini-2.5-flash");
expect(result.costUsd).toBeGreaterThan(0);
expect(result.inputTokens).toBe(392);
expect(result.outputTokens).toBe(121);
expect(result.output).not.toBeNull();
const items = result.output!.items;
expect(items).toHaveLength(2);
expect(items[0]?.detectedName).toBe("Mellanmjölk 1,5% fett");
expect(items[0]?.brand).toBe("Arla");
expect(items[0]?.estimatedQuantity).toBe(1);
expect(items[0]?.unit).toBe("LITER");
expect(items[0]?.confidence).toBe(0.98);
expect(items[1]?.detectedName).toBe("Smör");
expect(items[1]?.estimatedQuantity).toBe(500);
expect(items[1]?.unit).toBe("GRAM");
const spend = await budget.getDailySpendUsd();
expect(spend).toBe(result.costUsd);
});
it("blocks call when daily budget exceeded", async () => {
const budget = new MemoryBudgetStore();
const client = new GeminiAamosClient({
apiKey: "test-key",
fetchImpl: makeFetch(JPEG_BYTES),
budgetStore: budget,
dailyBudgetUsd: 0.00001, // essentially zero
});
const result = await client.runTask("ANALYZE_FRIDGE_IMAGE", {
imageUrls: ["http://localhost/v1/mock-s3/fridge.jpg"],
locationType: "fridge",
marketLocale: "sv-SE",
knownItems: [],
});
expect(result.status).toBe("failed");
expect(result.error).toContain("budget");
});
it("rejects unsupported task types", async () => {
const client = new GeminiAamosClient({
apiKey: "test-key",
fetchImpl: makeFetch(JPEG_BYTES),
});
const result = await client.runTask("DEDUPLICATE_INVENTORY", { items: [] });
expect(result.status).toBe("failed");
expect(result.error).toContain("DEDUPLICATE_INVENTORY");
});
it("validates input before calling Gemini", async () => {
const client = new GeminiAamosClient({
apiKey: "test-key",
fetchImpl: makeFetch(JPEG_BYTES),
});
const result = await client.runTask("ANALYZE_FRIDGE_IMAGE", {
imageUrls: [],
locationType: "fridge",
marketLocale: "sv-SE",
knownItems: [],
} as unknown as {
imageUrls: string[];
locationType: string;
marketLocale: string;
knownItems: string[];
});
expect(result.status).toBe("failed");
expect(result.error).toContain("Kontraktsfel");
});
});