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 household store item metadata when creating a new list item", async () => { pool.query .mockResolvedValueOnce({ rowCount: 0, rows: [] }) .mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 55, name: "milk" }] }) .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, householdStoreItemId: 55, itemName: "milk", quantity: 3, previousQuantity: 0, historyQuantity: 3, wasBought: false, isNew: true, }); expect(pool.query).toHaveBeenNthCalledWith( 1, expect.stringContaining("FROM household_store_items"), [1, 2, "milk"] ); expect(pool.query).toHaveBeenNthCalledWith( 2, expect.stringContaining("INSERT INTO household_store_items"), [1, 2, "milk", "milk"] ); }); test("returns household store item metadata when updating an existing list item", async () => { pool.query .mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 55, name: "milk" }] }) .mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 88, bought: false, quantity: 2 }] }) .mockResolvedValueOnce({ rowCount: 1, rows: [] }); const result = await List.addOrUpdateItem(1, 2, "Milk", 4, 7); expect(result).toEqual({ listId: 88, itemId: 55, householdStoreItemId: 55, itemName: "milk", quantity: 4, previousQuantity: 2, historyQuantity: 2, wasBought: false, isNew: false, }); expect(pool.query).toHaveBeenNthCalledWith( 3, expect.stringContaining("UPDATE household_lists"), [4, undefined, 88] ); }); test("uses the full requested quantity when reopening a bought list item", async () => { pool.query .mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 55, name: "milk" }] }) .mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 88, bought: true, quantity: 2 }] }) .mockResolvedValueOnce({ rowCount: 1, rows: [] }); const result = await List.addOrUpdateItem(1, 2, "Milk", 4, 7); expect(result).toEqual( expect.objectContaining({ listId: 88, quantity: 4, previousQuantity: 2, historyQuantity: 4, wasBought: true, isNew: false, }) ); }); test("limits added_by_users to history entries that account for current quantity", async () => { pool.query.mockResolvedValueOnce({ rowCount: 0, rows: [] }); await List.getHouseholdStoreList(1, 2); const sql = pool.query.mock.calls[0][0]; expect(sql).toContain("ORDER BY hlh.added_on DESC, hlh.id DESC"); expect(sql).toContain("active_history.newer_quantity < GREATEST(hl.quantity, 0)"); }); }); describe("list.model.v2 classification helpers", () => { beforeEach(() => { pool.query.mockReset(); }); test("gets classification using household, location, and household-store 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("household_store_item_id = $3"), [1, 2, 55] ); }); test("upserts classification using household-location item conflict target", async () => { pool.query .mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 12, name: "Dairy & Refrigerated", sort_order: 60 }], }) .mockResolvedValueOnce({ rowCount: 1, rows: [ { household_id: 1, store_location_id: 2, household_store_item_id: 55, item_type: "dairy", item_group: "Milk", zone: "Dairy & Refrigerated", zone_id: 12, 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_location_id: 2, household_store_item_id: 55, item_type: "dairy", }) ); expect(pool.query).toHaveBeenLastCalledWith( expect.stringContaining("ON CONFLICT (household_id, store_location_id, household_store_item_id)"), [1, 2, 55, "dairy", "Milk", "Dairy & Refrigerated", 12, 1, "user"] ); }); });