Files
Cibello-app/packages/inventory-engine/test/reconciliation.test.ts
T

106 lines
3.5 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);
});
});