grocery-app/backend/tests/list.model.v2.test.js
2026-03-28 22:35:34 -07:00

177 lines
4.7 KiB
JavaScript

jest.mock("../db/pool", () => ({
query: jest.fn(),
}));
const pool = require("../db/pool");
const List = require("../models/list.model.v2");
describe("list.model.v2 addOrUpdateItem", () => {
beforeEach(() => {
pool.query.mockReset();
});
test("returns item metadata when creating a new household list item", async () => {
pool.query
.mockResolvedValueOnce({ rowCount: 0, rows: [] })
.mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 55 }] })
.mockResolvedValueOnce({ rowCount: 0, rows: [] })
.mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 88 }] });
const result = await List.addOrUpdateItem(1, 2, "Milk", 3, 7);
expect(result).toEqual({
listId: 88,
itemId: 55,
itemName: "milk",
isNew: true,
});
expect(pool.query).toHaveBeenNthCalledWith(
1,
"SELECT id FROM items WHERE name ILIKE $1",
["milk"]
);
expect(pool.query).toHaveBeenNthCalledWith(
2,
"INSERT INTO items (name) VALUES ($1) RETURNING id",
["milk"]
);
});
test("returns item metadata when updating an existing household list item", async () => {
pool.query
.mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 55 }] })
.mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 88, bought: false }] })
.mockResolvedValueOnce({ rowCount: 1, rows: [] });
const result = await List.addOrUpdateItem(1, 2, "Milk", 4, 7);
expect(result).toEqual({
listId: 88,
itemId: 55,
itemName: "milk",
isNew: false,
});
expect(pool.query).toHaveBeenNthCalledWith(
3,
expect.stringContaining("UPDATE household_lists"),
[4, 88]
);
});
});
describe("list.model.v2 classification helpers", () => {
beforeEach(() => {
pool.query.mockReset();
});
test("gets classification using household, store, and item ids", async () => {
pool.query.mockResolvedValueOnce({
rowCount: 1,
rows: [
{
item_type: "dairy",
item_group: "Milk",
zone: "Dairy & Refrigerated",
confidence: 1,
source: "user",
},
],
});
const result = await List.getClassification(1, 2, 55);
expect(result).toEqual({
item_type: "dairy",
item_group: "Milk",
zone: "Dairy & Refrigerated",
confidence: 1,
source: "user",
});
expect(pool.query).toHaveBeenCalledWith(
expect.stringContaining("WHERE household_id = $1 AND store_id = $2 AND item_id = $3"),
[1, 2, 55]
);
});
test("upserts classification using store-scoped conflict target", async () => {
pool.query.mockResolvedValueOnce({
rowCount: 1,
rows: [
{
household_id: 1,
store_id: 2,
item_id: 55,
item_type: "dairy",
item_group: "Milk",
zone: "Dairy & Refrigerated",
confidence: 1,
source: "user",
},
],
});
const result = await List.upsertClassification(1, 2, 55, {
item_type: "dairy",
item_group: "Milk",
zone: "Dairy & Refrigerated",
confidence: 1,
source: "user",
});
expect(result).toEqual(
expect.objectContaining({
household_id: 1,
store_id: 2,
item_id: 55,
item_type: "dairy",
})
);
expect(pool.query).toHaveBeenCalledWith(
expect.stringContaining("ON CONFLICT (household_id, store_id, item_id)"),
[1, 2, 55, "dairy", "Milk", "Dairy & Refrigerated", 1, "user"]
);
});
});
describe("list.model.v2 suggestions", () => {
beforeEach(() => {
pool.query.mockReset();
});
test("returns catalog suggestions when a household-store catalog exists", async () => {
pool.query
.mockResolvedValueOnce({ rowCount: 1, rows: [{ "?column?": 1 }] })
.mockResolvedValueOnce({
rowCount: 1,
rows: [{ item_name: "milk", sort_order: 0 }],
});
const result = await List.getSuggestions("mi", 1, 2);
expect(result).toEqual([{ item_name: "milk", sort_order: 0 }]);
expect(pool.query).toHaveBeenNthCalledWith(
1,
expect.stringContaining("FROM household_store_available_items"),
[1, 2]
);
});
test("falls back to legacy suggestions when catalog is empty", async () => {
pool.query
.mockResolvedValueOnce({ rowCount: 0, rows: [] })
.mockResolvedValueOnce({
rowCount: 1,
rows: [{ item_name: "milk", sort_order: 1 }],
});
const result = await List.getSuggestions("mi", 1, 2);
expect(result).toEqual([{ item_name: "milk", sort_order: 1 }]);
expect(pool.query).toHaveBeenNthCalledWith(
2,
expect.stringContaining("LEFT JOIN household_lists"),
["%mi%", 1, 2]
);
});
});