107 lines
3.6 KiB
TypeScript
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-/);
|
|
});
|
|
});
|