import "./setup-env.js"; import { describe, expect, it, beforeAll, afterAll } from "vitest"; import { eq, inArray } from "drizzle-orm"; import { buildServer } from "../src/server.js"; import { loadConfig } from "../src/config.js"; import { createDatabase, closeDatabase, schema } from "@app/database"; describe("GET /v1/inventory/natural-search", () => { const testDb = createDatabase(process.env.TEST_DATABASE_URL!); const config = loadConfig(); let app: Awaited>; beforeAll(async () => { app = await buildServer(config); await app.ready(); }); afterAll(async () => { await closeDatabase(); await app.close(); }); async function registerUser(email: string) { const res = await app.inject({ method: "POST", url: "/v1/auth/register", payload: { email, password: "Password123!", displayName: "Search Test" }, }); const body = JSON.parse(res.body) as { accessToken: string }; const token = body.accessToken; const userId = (JSON.parse(atob(token.split(".")[1]!)) as { sub: string }).sub; await app.inject({ method: "POST", url: "/v1/onboarding/quick-start", headers: { authorization: `Bearer ${token}` }, payload: { goals: ["less_waste"], precisionMode: "simple" }, }); return { token, userId }; } async function cleanupUser(email: string) { const existing = await testDb.db .select({ id: schema.users.id }) .from(schema.users) .where(eq(schema.users.email, email)); for (const u of existing) { const memberships = await testDb.db .select({ householdId: schema.householdMembers.householdId }) .from(schema.householdMembers) .where(eq(schema.householdMembers.userId, u.id)); const householdIds = memberships.map((m) => m.householdId); if (householdIds.length > 0) { await testDb.db .delete(schema.inventoryItems) .where(inArray(schema.inventoryItems.householdId, householdIds)); await testDb.db .delete(schema.storageLocations) .where(inArray(schema.storageLocations.householdId, householdIds)); await testDb.db .delete(schema.householdMembers) .where(inArray(schema.householdMembers.householdId, householdIds)); await testDb.db .delete(schema.households) .where(inArray(schema.households.id, householdIds)); } await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id)); } } it("returnerar ett mänskligt svar med plats och trust-hedge", async () => { const email = "inventory-search-1@example.invalid"; await cleanupUser(email); const { token, userId } = await registerUser(email); const [household] = await testDb.db .select({ id: schema.households.id }) .from(schema.households) .innerJoin( schema.householdMembers, eq(schema.households.id, schema.householdMembers.householdId), ) .where(eq(schema.householdMembers.userId, userId)) .limit(1); const [fridge] = await testDb.db .insert(schema.storageLocations) .values({ householdId: household!.id, type: "fridge", name: "Kylen", sublocations: ["mellanmålshyllan"], }) .returning(); await testDb.db.insert(schema.inventoryItems).values({ householdId: household!.id, displayName: "Mjölk", quantity: 1, unit: "LITER", storageLocationId: fridge!.id, sublocation: "mellanmålshyllan", source: "manual_search", confidence: 1, verifiedByUser: true, lastVerifiedAt: new Date(), trustState: "trusted", }); const res = await app.inject({ method: "GET", url: "/v1/inventory/natural-search?q=mj%C3%B6lk&languageTag=sv-SE", headers: { authorization: `Bearer ${token}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body) as { response: string; items: unknown[] }; expect(body.response).toContain("Mjölk"); expect(body.response).toContain("Kylen"); expect(body.response).toContain("mellanmålshyllan"); expect(body.response).toContain("säker"); expect(body.items).toHaveLength(1); await cleanupUser(email); }); it("hanterar noll träffar på flera språk", async () => { const email = "inventory-search-2@example.invalid"; await cleanupUser(email); const { token } = await registerUser(email); for (const languageTag of ["sv-SE", "en-GB", "de-DE"]) { const res = await app.inject({ method: "GET", url: `/v1/inventory/natural-search?q=unobtainium&languageTag=${encodeURIComponent(languageTag)}`, headers: { authorization: `Bearer ${token}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body) as { response: string; items: unknown[] }; expect(body.items).toHaveLength(0); expect(body.response.length).toBeGreaterThan(10); } await cleanupUser(email); }); });