ci: trigga på master + formatfix inför Gitea Actions
CI / Typecheck, test & build (push) Failing after 2s
CI / Typecheck, test & build (push) Failing after 2s
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { verifyCandidate, type CanonicalIngredientLookup, type SimilarityLookup } from "../src/verification.js";
|
||||
import {
|
||||
verifyCandidate,
|
||||
type CanonicalIngredientLookup,
|
||||
type SimilarityLookup,
|
||||
} from "../src/verification.js";
|
||||
import type { RecipeCandidate } from "../src/types.js";
|
||||
|
||||
const mockIngredients: CanonicalIngredientLookup = {
|
||||
@@ -10,8 +14,14 @@ const mockIngredients: CanonicalIngredientLookup = {
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: {
|
||||
kcal: 165, proteinG: 31, carbsG: 0, fatG: 3.6,
|
||||
saturatedFatG: 1, fiberG: 0, sugarG: 0, saltG: 0.1,
|
||||
kcal: 165,
|
||||
proteinG: 31,
|
||||
carbsG: 0,
|
||||
fatG: 3.6,
|
||||
saturatedFatG: 1,
|
||||
fiberG: 0,
|
||||
sugarG: 0,
|
||||
saltG: 0.1,
|
||||
},
|
||||
},
|
||||
defaultUnit: "GRAM",
|
||||
@@ -31,8 +41,14 @@ const mockIngredients: CanonicalIngredientLookup = {
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: {
|
||||
kcal: 130, proteinG: 2.7, carbsG: 28, fatG: 0.3,
|
||||
saturatedFatG: 0.1, fiberG: 0.4, sugarG: 0.1, saltG: 0,
|
||||
kcal: 130,
|
||||
proteinG: 2.7,
|
||||
carbsG: 28,
|
||||
fatG: 0.3,
|
||||
saturatedFatG: 0.1,
|
||||
fiberG: 0.4,
|
||||
sugarG: 0.1,
|
||||
saltG: 0,
|
||||
},
|
||||
},
|
||||
defaultUnit: "GRAM",
|
||||
@@ -47,13 +63,19 @@ const mockIngredients: CanonicalIngredientLookup = {
|
||||
isBeef: false,
|
||||
isAlcohol: false,
|
||||
},
|
||||
"krossade_tomater": {
|
||||
krossade_tomater: {
|
||||
id: "krossade_tomater",
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: {
|
||||
kcal: 32, proteinG: 1.6, carbsG: 5.8, fatG: 0.3,
|
||||
saturatedFatG: 0, fiberG: 1.2, sugarG: 4, saltG: 0.1,
|
||||
kcal: 32,
|
||||
proteinG: 1.6,
|
||||
carbsG: 5.8,
|
||||
fatG: 0.3,
|
||||
saturatedFatG: 0,
|
||||
fiberG: 1.2,
|
||||
sugarG: 4,
|
||||
saltG: 0.1,
|
||||
},
|
||||
},
|
||||
defaultUnit: "GRAM",
|
||||
@@ -73,8 +95,14 @@ const mockIngredients: CanonicalIngredientLookup = {
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: {
|
||||
kcal: 165, proteinG: 31, carbsG: 0, fatG: 3.6,
|
||||
saturatedFatG: 1, fiberG: 0, sugarG: 0, saltG: 0.1,
|
||||
kcal: 165,
|
||||
proteinG: 31,
|
||||
carbsG: 0,
|
||||
fatG: 3.6,
|
||||
saturatedFatG: 1,
|
||||
fiberG: 0,
|
||||
sugarG: 0,
|
||||
saltG: 0.1,
|
||||
},
|
||||
},
|
||||
defaultUnit: "GRAM",
|
||||
@@ -93,7 +121,16 @@ const mockIngredients: CanonicalIngredientLookup = {
|
||||
id: "sesame_seeds",
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: { kcal: 600, proteinG: 18, carbsG: 12, fatG: 50, saturatedFatG: 7, fiberG: 12, sugarG: 0, saltG: 0 },
|
||||
values: {
|
||||
kcal: 600,
|
||||
proteinG: 18,
|
||||
carbsG: 12,
|
||||
fatG: 50,
|
||||
saturatedFatG: 7,
|
||||
fiberG: 12,
|
||||
sugarG: 0,
|
||||
saltG: 0,
|
||||
},
|
||||
},
|
||||
defaultUnit: "GRAM",
|
||||
densityGPerMl: 0.6,
|
||||
@@ -111,7 +148,16 @@ const mockIngredients: CanonicalIngredientLookup = {
|
||||
id: "milk_3",
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: { kcal: 60, proteinG: 3.4, carbsG: 4.7, fatG: 3, saturatedFatG: 1.9, fiberG: 0, sugarG: 4.7, saltG: 0.1 },
|
||||
values: {
|
||||
kcal: 60,
|
||||
proteinG: 3.4,
|
||||
carbsG: 4.7,
|
||||
fatG: 3,
|
||||
saturatedFatG: 1.9,
|
||||
fiberG: 0,
|
||||
sugarG: 4.7,
|
||||
saltG: 0.1,
|
||||
},
|
||||
},
|
||||
defaultUnit: "DECILITER",
|
||||
densityGPerMl: 1.03,
|
||||
@@ -148,13 +194,46 @@ function makeCandidate(overrides?: Partial<RecipeCandidate>): RecipeCandidate {
|
||||
portions: 4,
|
||||
spiceLevel: 1,
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "kycklingfile", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "krossade_tomater", displayNameSv: "krossade tomater", quantity: 400, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "kycklingfile",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 500,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "krossade_tomater",
|
||||
displayNameSv: "krossade tomater",
|
||||
quantity: 400,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Stek kycklingen i en panna.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt tomater och ris, låt koka.", timerSeconds: 900, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Stek kycklingen i en panna.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt tomater och ris, låt koka.",
|
||||
timerSeconds: 900,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
storageGuidanceSv: "Förvara i kylskåp upp till 3 dagar.",
|
||||
mealPrepFriendly: false,
|
||||
@@ -178,7 +257,14 @@ describe("verifyCandidate", () => {
|
||||
it("avvisar kandidat med okänd ingrediens", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "fantasi_gronsak", displayNameSv: "fantasigrönsak", quantity: 100, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "fantasi_gronsak",
|
||||
displayNameSv: "fantasigrönsak",
|
||||
quantity: 100,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
@@ -188,13 +274,25 @@ describe("verifyCandidate", () => {
|
||||
|
||||
it("markerar unverified vid orimlig tid", async () => {
|
||||
const c = makeCandidate({ prepTimeMinutes: 200, cookTimeMinutes: 10, totalTimeMinutes: 210 });
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity, { maxPrepTimeMinutes: 60 });
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity, {
|
||||
maxPrepTimeMinutes: 60,
|
||||
});
|
||||
expect(result.status).toBe("unverified");
|
||||
expect(result.reasons.some((r) => r.includes("Förberedelsetid"))).toBe(true);
|
||||
});
|
||||
|
||||
it("markerar unverified vid för få steg", async () => {
|
||||
const c = makeCandidate({ steps: [{ stepNumber: 1, instructionSv: "Gör allt.", timerSeconds: null, temperatureC: null, tip: null }] });
|
||||
const c = makeCandidate({
|
||||
steps: [
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Gör allt.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity, { minSteps: 2 });
|
||||
expect(result.status).toBe("unverified");
|
||||
expect(result.reasons.some((r) => r.includes("steg"))).toBe(true);
|
||||
@@ -203,7 +301,14 @@ describe("verifyCandidate", () => {
|
||||
it("härleder allergener korrekt", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "kycklingfile", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "kycklingfile",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 500,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
@@ -215,7 +320,16 @@ describe("verifyCandidate", () => {
|
||||
id: "milk_3",
|
||||
nutritionPer100: {
|
||||
basis: "per_100_g",
|
||||
values: { kcal: 60, proteinG: 3.4, carbsG: 4.7, fatG: 3, saturatedFatG: 1.9, fiberG: 0, sugarG: 4.7, saltG: 0.1 },
|
||||
values: {
|
||||
kcal: 60,
|
||||
proteinG: 3.4,
|
||||
carbsG: 4.7,
|
||||
fatG: 3,
|
||||
saturatedFatG: 1.9,
|
||||
fiberG: 0,
|
||||
sugarG: 4.7,
|
||||
saltG: 0.1,
|
||||
},
|
||||
},
|
||||
defaultUnit: "DECILITER",
|
||||
densityGPerMl: 1.03,
|
||||
@@ -237,7 +351,14 @@ describe("verifyCandidate", () => {
|
||||
const c = makeCandidate({
|
||||
aiClaimedAllergens: [],
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "milk_3", displayNameSv: "mjölk", quantity: 5, unit: "DECILITER", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "milk_3",
|
||||
displayNameSv: "mjölk",
|
||||
quantity: 5,
|
||||
unit: "DECILITER",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, lookup, noSimilarity);
|
||||
@@ -248,9 +369,30 @@ describe("verifyCandidate", () => {
|
||||
const c = makeCandidate({
|
||||
aiClaimedAllergens: ["gluten"],
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "kycklingfile", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "krossade_tomater", displayNameSv: "krossade tomater", quantity: 400, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "kycklingfile",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 500,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "krossade_tomater",
|
||||
displayNameSv: "krossade tomater",
|
||||
quantity: 400,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
@@ -272,63 +414,194 @@ describe("verifyCandidate", () => {
|
||||
it("kräver genomstekningssteg för rå fågel (positivt)", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "chicken_breast", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "krossade_tomater", displayNameSv: "krossade tomater", quantity: 400, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "chicken_breast",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 500,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "krossade_tomater",
|
||||
displayNameSv: "krossade tomater",
|
||||
quantity: 400,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Stek kycklingen tills den är genomstekt och innertemperaturen är 72°C.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt ris och tomater, koka klart.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Stek kycklingen tills den är genomstekt och innertemperaturen är 72°C.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt ris och tomater, koka klart.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
expect(result.status).toBe("verified");
|
||||
expect(result.reasons).not.toContain("Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.");
|
||||
expect(result.reasons).not.toContain(
|
||||
"Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
|
||||
);
|
||||
});
|
||||
|
||||
it("markerar unverified när rå fågel bara beskrivs som gyllenbrun (utseende räcker inte)", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "chicken_breast", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "chicken_breast",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 500,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Stek kycklingen tills den är gyllenbrun.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt ris och koka klart.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Stek kycklingen tills den är gyllenbrun.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt ris och koka klart.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
expect(result.status).toBe("unverified");
|
||||
expect(result.reasons).toContain("Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.");
|
||||
expect(result.reasons).toContain(
|
||||
"Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
|
||||
);
|
||||
});
|
||||
|
||||
it("markerar unverified när rå fågel saknar genomstekningssteg (negativt)", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "chicken_breast", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "krossade_tomater", displayNameSv: "krossade tomater", quantity: 400, unit: "GRAM", optional: false, note: null },
|
||||
{
|
||||
canonicalIngredientId: "chicken_breast",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 500,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "krossade_tomater",
|
||||
displayNameSv: "krossade tomater",
|
||||
quantity: 400,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Stek kycklingen i pannan.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt ris och tomater, koka klart.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Stek kycklingen i pannan.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt ris och tomater, koka klart.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
expect(result.status).toBe("unverified");
|
||||
expect(result.reasons).toContain("Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.");
|
||||
expect(result.reasons).toContain(
|
||||
"Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
|
||||
);
|
||||
});
|
||||
|
||||
it("härleder allergen även från valfri ingrediens (optional sesame)", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "kycklingfile", displayNameSv: "kycklingfilé", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "sesame_seeds", displayNameSv: "sesamfrön till garnering", quantity: 10, unit: "GRAM", optional: true, note: null },
|
||||
{
|
||||
canonicalIngredientId: "kycklingfile",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "sesame_seeds",
|
||||
displayNameSv: "sesamfrön till garnering",
|
||||
quantity: 10,
|
||||
unit: "GRAM",
|
||||
optional: true,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Stek kycklingen genomstekt.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt ris och sesamefrön.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Stek kycklingen genomstekt.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt ris och sesamefrön.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
@@ -338,30 +611,98 @@ describe("verifyCandidate", () => {
|
||||
it("kräver genomstekningssteg även för optional rå fågel", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "krossade_tomater", displayNameSv: "krossade tomater", quantity: 400, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "chicken_breast", displayNameSv: "kycklingfilé", quantity: 300, unit: "GRAM", optional: true, note: null },
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "krossade_tomater",
|
||||
displayNameSv: "krossade tomater",
|
||||
quantity: 400,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "chicken_breast",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: true,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Koka riset enligt anvisningen.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt tomater och kyckling.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Koka riset enligt anvisningen.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt tomater och kyckling.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
expect(result.status).toBe("unverified");
|
||||
expect(result.reasons).toContain("Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.");
|
||||
expect(result.reasons).toContain(
|
||||
"Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
|
||||
);
|
||||
});
|
||||
|
||||
it("härleder mjölk-allergen från optional mjölk", async () => {
|
||||
const c = makeCandidate({
|
||||
ingredients: [
|
||||
{ canonicalIngredientId: "kycklingfile", displayNameSv: "kycklingfilé", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
|
||||
{ canonicalIngredientId: "milk_3", displayNameSv: "mjölk till servering", quantity: 2, unit: "DECILITER", optional: true, note: null },
|
||||
{
|
||||
canonicalIngredientId: "kycklingfile",
|
||||
displayNameSv: "kycklingfilé",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "ris",
|
||||
displayNameSv: "ris",
|
||||
quantity: 300,
|
||||
unit: "GRAM",
|
||||
optional: false,
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
canonicalIngredientId: "milk_3",
|
||||
displayNameSv: "mjölk till servering",
|
||||
quantity: 2,
|
||||
unit: "DECILITER",
|
||||
optional: true,
|
||||
note: null,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{ stepNumber: 1, instructionSv: "Stek kycklingen genomstekt.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{ stepNumber: 2, instructionSv: "Tillsätt ris och mjölk.", timerSeconds: null, temperatureC: null, tip: null },
|
||||
{
|
||||
stepNumber: 1,
|
||||
instructionSv: "Stek kycklingen genomstekt.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
{
|
||||
stepNumber: 2,
|
||||
instructionSv: "Tillsätt ris och mjölk.",
|
||||
timerSeconds: null,
|
||||
temperatureC: null,
|
||||
tip: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
|
||||
|
||||
Reference in New Issue
Block a user