import "./setup-env.js"; import { describe, it, expect, beforeAll, afterAll } from "vitest"; import { eq } from "drizzle-orm"; import { createDatabase, closeDatabase, schema } from "@app/database"; import { processTrainingExport } from "../src/processors/maintenance.js"; describe("BUILD_TRAINING_SAMPLE banks locally", () => { const testDb = createDatabase(process.env.TEST_DATABASE_URL!); const email = "training-export-test@example.invalid"; async function cleanup() { const existing = await testDb.db .select({ id: schema.users.id }) .from(schema.users) .where(eq(schema.users.email, email)); for (const u of existing) { await testDb.db.delete(schema.aiCorrections).where(eq(schema.aiCorrections.userId, u.id)); await testDb.db.delete(schema.userConsents).where(eq(schema.userConsents.userId, u.id)); await testDb.db.delete(schema.scanJobs).where(eq(schema.scanJobs.userId, u.id)); await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id)); } } beforeAll(async () => { await cleanup(); }); afterAll(async () => { await cleanup(); await closeDatabase(); }); it("banks eligible corrections locally and does not throw in gemini mode", async () => { const [user] = await testDb.db .insert(schema.users) .values({ email: "training-export-test@example.invalid", passwordHash: "not-used", displayName: "Training Export Test", }) .returning(); const userId = user!.id; await testDb.db.insert(schema.userConsents).values([ { userId, kind: "anonymized_improvement", status: "granted" }, { userId, kind: "image_training", status: "granted" }, ]); const [job] = await testDb.db .insert(schema.scanJobs) .values({ userId, scanType: "fridge", jobType: "ANALYZE_FRIDGE_IMAGE", status: "completed", s3Keys: ["fridge-scans/train.jpg"], result: { items: [] }, }) .returning(); const [correction] = await testDb.db .insert(schema.aiCorrections) .values({ scanJobId: job!.id, userId, taskType: "ANALYZE_FRIDGE_IMAGE", aiOutput: { raw: { items: [] } }, proposal: { detectedName: "Mellanmjölk", confidence: 0.98 }, userCorrection: { action: "accept", corrected: { displayName: "Mellanmjölk", quantity: 1, unit: "LITER" }, }, imageS3Key: "fridge-scans/train.jpg", modelVersion: "gemini-2.5-flash", promptVersion: "gemini-fridge-v1", consentSnapshot: { anonymized_improvement: "granted", image_training: "granted" }, }) .returning(); // Force gemini mode in environment so we prove the job does not call AAMOS. const previousMode = process.env.AAMOS_MODE; process.env.AAMOS_MODE = "gemini"; const exported = await processTrainingExport({ db: testDb.db } as never); process.env.AAMOS_MODE = previousMode; expect(exported).toBe(1); const banked = await testDb.db .select() .from(schema.aiTrainingBank) .where(eq(schema.aiTrainingBank.correctionId, correction!.id)); expect(banked).toHaveLength(1); expect(banked[0]!.version).toBe("v1"); expect(banked[0]!.imageS3Key).toBe("fridge-scans/train.jpg"); expect(banked[0]!.action).toBe("accept"); const updated = await testDb.db .select() .from(schema.aiCorrections) .where(eq(schema.aiCorrections.id, correction!.id)); expect(updated[0]!.exportedToTraining).not.toBeNull(); expect(updated[0]!.trainingBatchId).toMatch(/^cibello-local-v1-/); }); });