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"; /** * Regression test: what-to-eat must work for a brand-new user who has not * created a household yet (empty pantry, single-person context). */ describe("what-to-eat without household", () => { const testDb = createDatabase(process.env.TEST_DATABASE_URL); const config = loadConfig({ ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL!, }); let app: Awaited>; let accessToken: string; const userEmail = "what-to-eat-repro@example.invalid"; async function cleanup() { const emails = [userEmail, "goals-multi@example.invalid"]; const existing = await testDb.db .select({ id: schema.users.id }) .from(schema.users) .where(inArray(schema.users.email, emails)); for (const u of existing) { 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: "Repro" }, }); const body = JSON.parse(res.body) as { accessToken: string }; accessToken = body.accessToken; await testDb.db .insert(schema.userPreferences) .values({ userId: (JSON.parse(atob(accessToken.split(".")[1]!)) as { sub: string }).sub, primaryGoal: "cook_more" }) .onConflictDoNothing(); }); afterAll(async () => { await cleanup(); await closeDatabase(); await app.close(); }); it("returns recommendations for a new user without a household and a craving set", async () => { const res = await app.inject({ method: "GET", url: "/v1/recommendations/what-to-eat?limit=5&craving=asiatiskt", headers: { authorization: `Bearer ${accessToken}` }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body) as { recommendations: unknown[]; mealBoxSuggestions: unknown[]; context: { persons: number; craving: { cuisine: string } | null }; }; expect(body.recommendations).toBeDefined(); expect(body.mealBoxSuggestions).toBeDefined(); expect(body.context.persons).toBe(1); expect(body.context.craving).not.toBeNull(); expect(body.context.craving?.cuisine).toBe("thai"); }); it("quick-start stores goals array and sets primaryGoal to the first goal", async () => { const registerRes = await app.inject({ method: "POST", url: "/v1/auth/register", payload: { email: "goals-multi@example.invalid", password: "Password123!", displayName: "Goals" }, }); const { accessToken: token } = JSON.parse(registerRes.body) as { accessToken: string }; const userId = (JSON.parse(atob(token.split(".")[1]!)) as { sub: string }).sub; const quickRes = await app.inject({ method: "POST", url: "/v1/onboarding/quick-start", headers: { authorization: `Bearer ${token}` }, payload: { goals: ["cook_more", "less_waste"], precisionMode: "simple" }, }); expect(quickRes.statusCode).toBe(200); const [prefs] = await testDb.db .select() .from(schema.userPreferences) .where(eq(schema.userPreferences.userId, userId)) .limit(1); expect(prefs?.goals).toEqual(["cook_more", "less_waste"]); expect(prefs?.primaryGoal).toBe("cook_more"); }); });