96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import { describe, expect, it, afterAll } from "vitest";
|
|
import { config as loadDotenv } from "dotenv";
|
|
import path from "node:path";
|
|
import { createDatabase, closeDatabase } from "@app/database";
|
|
import { SEED_RECIPES } from "@app/database/seed";
|
|
import { verifyCandidate } from "../src/verification.js";
|
|
|
|
// Ladda monorepo-root .env (samma mönster som @app/database seed/tests)
|
|
loadDotenv({ path: path.resolve(process.cwd(), "../../.env") });
|
|
|
|
const { db } = createDatabase();
|
|
|
|
async function buildLookup() {
|
|
const allIngs = await db.query.canonicalIngredients.findMany();
|
|
return {
|
|
getById(id: string) {
|
|
return allIngs.find((i) => i.id === id) ?? undefined;
|
|
},
|
|
};
|
|
}
|
|
|
|
afterAll(async () => {
|
|
await closeDatabase();
|
|
});
|
|
|
|
describe("allergen invariant", () => {
|
|
it("varje seedat recept har allergens som matchar färsk derivering från canonical_ingredients", async () => {
|
|
const lookup = await buildLookup();
|
|
const seedTitles = new Set(SEED_RECIPES.map((r) => r.titleSv));
|
|
const recipes = (await db.query.recipes.findMany()).filter((r) => seedTitles.has(r.titleSv));
|
|
|
|
const mismatches: string[] = [];
|
|
|
|
for (const recipe of recipes) {
|
|
const ingredients = await db.query.recipeIngredients.findMany({
|
|
where: (t, { eq }) => eq(t.recipeId, recipe.id),
|
|
});
|
|
const steps = await db.query.recipeSteps.findMany({
|
|
where: (t, { eq }) => eq(t.recipeId, recipe.id),
|
|
});
|
|
|
|
if (ingredients.length === 0 || steps.length === 0) {
|
|
// Inkompletta recept får aldrig lagras som verified i produktion.
|
|
continue;
|
|
}
|
|
|
|
const candidate = {
|
|
titleSv: recipe.titleSv,
|
|
descriptionSv: recipe.descriptionSv,
|
|
cuisine: recipe.cuisine,
|
|
mealTypes: recipe.mealTypes,
|
|
prepTimeMinutes: recipe.prepTimeMinutes,
|
|
cookTimeMinutes: recipe.cookTimeMinutes,
|
|
totalTimeMinutes: recipe.totalTimeMinutes,
|
|
portions: recipe.portions,
|
|
spiceLevel: recipe.spiceLevel,
|
|
ingredients: ingredients.map((i) => ({
|
|
canonicalIngredientId: i.canonicalIngredientId,
|
|
displayNameSv: i.displayNameSv,
|
|
quantity: i.quantity,
|
|
unit: i.unit,
|
|
optional: i.optional,
|
|
note: i.note,
|
|
})),
|
|
steps: steps.map((s) => ({
|
|
stepNumber: s.stepNumber,
|
|
instructionSv: s.instructionSv,
|
|
timerSeconds: s.timerSeconds,
|
|
temperatureC: s.temperatureC,
|
|
tip: s.tip,
|
|
})),
|
|
storageGuidanceSv: recipe.storageGuidanceSv,
|
|
mealPrepFriendly: recipe.mealPrepFriendly,
|
|
freezerFriendly: recipe.freezerFriendly,
|
|
sourceType: recipe.sourceType,
|
|
confidence: 1,
|
|
};
|
|
|
|
const result = await verifyCandidate(candidate as any, lookup, null);
|
|
const derived = [...result.allergens].sort();
|
|
const stored = [...(recipe.allergens ?? [])].sort();
|
|
|
|
if (JSON.stringify(derived) !== JSON.stringify(stored)) {
|
|
mismatches.push(
|
|
`${recipe.titleSv}: stored=${JSON.stringify(stored)} derived=${JSON.stringify(derived)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (mismatches.length > 0) {
|
|
console.error("Allergen mismatches:\n" + mismatches.join("\n"));
|
|
}
|
|
expect(mismatches).toEqual([]);
|
|
});
|
|
});
|