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("inventory trust read-time computation", () => { const testDb = createDatabase(process.env.TEST_DATABASE_URL!); const config = loadConfig(); let app: Awaited>; let accessToken: string; let householdId: string; let locationId: string; const userEmail = "trust-read@example.invalid"; async function cleanup() { const existing = await testDb.db .select({ id: schema.users.id }) .from(schema.users) .where(inArray(schema.users.email, [userEmail])); for (const u of existing) { await testDb.db.delete(schema.inventoryTransactions).where(eq(schema.inventoryTransactions.actorUserId, u.id)); const owned = await testDb.db .select({ id: schema.households.id }) .from(schema.households) .innerJoin(schema.householdMembers, eq(schema.householdMembers.householdId, schema.households.id)) .where(eq(schema.householdMembers.userId, u.id)); for (const h of owned) { await testDb.db.delete(schema.inventoryItems).where(eq(schema.inventoryItems.householdId, h.id)); await testDb.db.delete(schema.storageLocations).where(eq(schema.storageLocations.householdId, h.id)); await testDb.db.delete(schema.households).where(eq(schema.households.id, h.id)); } await testDb.db.delete(schema.householdMembers).where(eq(schema.householdMembers.userId, u.id)); await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id)); await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id)); } } beforeAll(async () => { await cleanup(); app = await buildServer(config); await app.ready(); const res = await app.inject({ method: "POST", url: "/v1/auth/register", payload: { email: userEmail, password: "Password123!", displayName: "Trust" }, }); const body = JSON.parse(res.body) as { accessToken: string }; accessToken = body.accessToken; const quick = await app.inject({ method: "POST", url: "/v1/onboarding/quick-start", headers: { authorization: `Bearer ${accessToken}` }, payload: { goals: ["cook_more"], precisionMode: "simple" }, }); const quickBody = JSON.parse(quick.body) as { householdId: string }; householdId = quickBody.householdId; const [location] = await testDb.db .select() .from(schema.storageLocations) .where(eq(schema.storageLocations.householdId, householdId)) .limit(1); locationId = location!.id; }); afterAll(async () => { await cleanup(); await closeDatabase(); await app.close(); }); it("reports decaying/stale for items with old lastVerifiedAt without a write", async () => { const fortyDaysAgo = new Date(Date.now() - 40 * 86_400_000); const [item] = await testDb.db .insert(schema.inventoryItems) .values({ householdId, displayName: "Gammal mjölk", quantity: 1, unit: "LITER", storageLocationId: locationId, source: "manual_search", confidence: 0.6, verifiedByUser: true, lastVerifiedAt: fortyDaysAgo, updatedAt: fortyDaysAgo, }) .returning(); const res = await app.inject({ method: "GET", url: "/v1/inventory", headers: { authorization: `Bearer ${accessToken}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body) as { items: Array<{ id: string; trustState: string; trustScore: number }> }; const found = body.items.find((i) => i.id === item!.id); expect(found).toBeTruthy(); expect(["decaying", "stale"]).toContain(found!.trustState); expect(found!.trustScore).toBeLessThan(60); }); });