Files
Sven (AAMOS AI) c32a7e33c7
CI / Typecheck, test & build (push) Failing after 2s
ci: trigga på master + formatfix inför Gitea Actions
2026-08-13 17:25:14 +07:00

127 lines
3.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildReconciliationCandidates } from "../src/reconciliation.js";
const PINNED = new Date("2026-08-07T00:00:00.000Z");
function baseItem(
overrides: Partial<Parameters<typeof buildReconciliationCandidates>[0][number]> = {},
) {
return {
id: "itm-1",
displayName: "Mjölk",
quantity: 1,
unit: "l",
locationName: "Kylen",
confidence: 1,
verifiedByUser: true,
lastVerifiedAt: PINNED,
updatedAt: PINNED,
...overrides,
};
}
describe("buildReconciliationCandidates", () => {
it("returns empty when no items match", () => {
const result = buildReconciliationCandidates(
[baseItem({ quantity: 0, depletedAt: PINNED })],
{
plannedRecipeIngredientIds: new Map(),
daysLeftByItemId: new Map(),
dailyConsumptionRate: new Map(),
priceMinorByItemId: new Map(),
},
PINNED,
);
expect(result).toEqual([]);
});
it("prioritises planned recipe ingredients", () => {
const a = baseItem({ id: "a", displayName: "Grädde", confidence: 1, verifiedByUser: true });
const b = baseItem({ id: "b", displayName: "Ost", confidence: 1, verifiedByUser: true });
const result = buildReconciliationCandidates(
[b, a],
{
plannedRecipeIngredientIds: new Map([["a", ["rec-1"]]]),
daysLeftByItemId: new Map(),
dailyConsumptionRate: new Map(),
priceMinorByItemId: new Map(),
},
PINNED,
15,
);
expect(result.map((r) => r.itemId)).toEqual(["a"]);
expect(result[0]?.reasons).toContainEqual({ kind: "planned_recipe", recipeIds: ["rec-1"] });
});
it("flags expiring soon and sorts by urgency", () => {
const a = baseItem({ id: "a", displayName: "Yoghurt", confidence: 0.5 });
const b = baseItem({ id: "b", displayName: "Juice", confidence: 0.5 });
const result = buildReconciliationCandidates(
[b, a],
{
plannedRecipeIngredientIds: new Map(),
daysLeftByItemId: new Map([
["a", 1],
["b", 6],
]),
dailyConsumptionRate: new Map(),
priceMinorByItemId: new Map(),
},
PINNED,
);
expect(result.map((r) => r.itemId)).toEqual(["a", "b"]);
});
it("flags low confidence", () => {
const item = baseItem({ confidence: 0.3, verifiedByUser: false });
const result = buildReconciliationCandidates(
[item],
{
plannedRecipeIngredientIds: new Map(),
daysLeftByItemId: new Map(),
dailyConsumptionRate: new Map(),
priceMinorByItemId: new Map(),
},
PINNED,
);
expect(result[0]?.reasons).toContainEqual({ kind: "low_confidence", confidence: 0.3 });
});
it("suggests depleted when remaining days < 0.5", () => {
const item = baseItem({ quantity: 0.1 });
const result = buildReconciliationCandidates(
[item],
{
plannedRecipeIngredientIds: new Map(),
daysLeftByItemId: new Map(),
dailyConsumptionRate: new Map([["itm-1", 1]]),
priceMinorByItemId: new Map(),
},
PINNED,
);
expect(result[0]?.suggestedAction).toBe("depleted");
});
it("caps results at maxItems", () => {
const items = Array.from({ length: 20 }, (_, i) =>
baseItem({
id: `itm-${i}`,
displayName: `Vara ${String(i).padStart(2, "0")}`,
confidence: 0.1,
}),
);
const result = buildReconciliationCandidates(
items,
{
plannedRecipeIngredientIds: new Map(),
daysLeftByItemId: new Map(),
dailyConsumptionRate: new Map(),
priceMinorByItemId: new Map(),
},
PINNED,
5,
);
expect(result).toHaveLength(5);
});
});