Files
Cibello-app/apps/worker/test/training-export.test.ts
T

107 lines
3.6 KiB
TypeScript

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-3.5-flash-lite",
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-/);
});
});