jest.mock("../models/available-item.model", () => ({ createAvailableItem: jest.fn(), deleteAvailableItem: jest.fn(), getAvailableItemById: jest.fn(), importCurrentListItems: jest.fn(), listAvailableItems: jest.fn(), updateAvailableItem: jest.fn(), })); jest.mock("../models/list.model.v2", () => ({ deleteClassification: jest.fn(), upsertClassification: jest.fn(), })); jest.mock("../utils/logger", () => ({ logError: jest.fn(), })); const AvailableItems = require("../models/available-item.model"); const List = require("../models/list.model.v2"); const controller = require("../controllers/available-items.controller"); function createResponse() { const res = {}; res.status = jest.fn().mockReturnValue(res); res.json = jest.fn().mockReturnValue(res); return res; } describe("available-items.controller", () => { beforeEach(() => { jest.clearAllMocks(); AvailableItems.createAvailableItem.mockResolvedValue({ item_id: 99, item_name: "milk" }); AvailableItems.getAvailableItemById.mockResolvedValue({ item_id: 99, item_name: "milk", item_type: "dairy", item_group: "Milk", zone: "Dairy & Refrigerated", }); AvailableItems.updateAvailableItem.mockResolvedValue({ item_id: 99, item_name: "milk" }); AvailableItems.deleteAvailableItem.mockResolvedValue(true); AvailableItems.importCurrentListItems.mockResolvedValue(2); AvailableItems.listAvailableItems.mockResolvedValue([]); List.upsertClassification.mockResolvedValue(undefined); List.deleteClassification.mockResolvedValue(false); }); test("creates an available item and persists classification metadata", async () => { const req = { params: { householdId: "1", storeId: "2" }, body: { item_name: "milk", classification: JSON.stringify({ item_type: "dairy", item_group: "Milk", zone: "Dairy & Refrigerated", }), }, processedImage: null, }; const res = createResponse(); await controller.createAvailableItem(req, res); expect(AvailableItems.createAvailableItem).toHaveBeenCalledWith("1", "2", "milk", null, null); expect(List.upsertClassification).toHaveBeenCalledWith( "1", "2", 99, expect.objectContaining({ item_type: "dairy", item_group: "Milk", zone: "Dairy & Refrigerated", }) ); expect(res.status).toHaveBeenCalledWith(201); }); test("rejects invalid item_group values", async () => { const req = { params: { householdId: "1", storeId: "2" }, body: { item_name: "milk", classification: JSON.stringify({ item_type: "dairy", item_group: "Bread", }), }, }; const res = createResponse(); await controller.createAvailableItem(req, res); expect(AvailableItems.createAvailableItem).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(400); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ error: expect.objectContaining({ message: "Invalid item_group for selected item_type", }), }) ); }); test("clears classification on update when classification is explicitly empty", async () => { const req = { params: { householdId: "1", storeId: "2", itemId: "99" }, body: { classification: "null", }, processedImage: null, }; const res = createResponse(); await controller.updateAvailableItem(req, res); expect(List.deleteClassification).toHaveBeenCalledWith("1", "2", 99); expect(res.status).not.toHaveBeenCalledWith(400); }); test("imports current list items and reports the import count", async () => { const req = { params: { householdId: "1", storeId: "2" }, }; const res = createResponse(); await controller.importCurrentItems(req, res); expect(AvailableItems.importCurrentListItems).toHaveBeenCalledWith("1", "2"); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ imported_count: 2, }) ); }); test("deletes a store item", async () => { const req = { params: { householdId: "1", storeId: "2", itemId: "99" }, }; const res = createResponse(); await controller.deleteAvailableItem(req, res); expect(AvailableItems.deleteAvailableItem).toHaveBeenCalledWith("1", "2", 99); expect(List.deleteClassification).not.toHaveBeenCalled(); expect(res.json).toHaveBeenCalledWith({ message: "Store item deleted" }); }); test("returns an empty catalog payload when the available items table is missing", async () => { const req = { params: { householdId: "1", storeId: "2" }, query: {}, }; const res = createResponse(); AvailableItems.listAvailableItems.mockRejectedValueOnce({ code: "42P01", message: 'relation "household_store_items" does not exist', }); await controller.getAvailableItems(req, res); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ items: [], catalog_ready: false, }) ); }); test("returns a setup error when creating while the available items table is missing", async () => { const req = { params: { householdId: "1", storeId: "2" }, body: { item_name: "milk", }, processedImage: null, }; const res = createResponse(); AvailableItems.createAvailableItem.mockRejectedValueOnce({ code: "42P01", message: 'relation "household_store_items" does not exist', }); await controller.createAvailableItem(req, res); expect(res.status).toHaveBeenCalledWith(503); expect(res.json).toHaveBeenCalledWith( expect.objectContaining({ error: expect.objectContaining({ message: expect.stringContaining("latest database migration"), }), }) ); }); });