jest.mock("../db/pool", () => ({ query: jest.fn(), connect: jest.fn(), })); jest.mock("../models/store.model", () => ({ getLocationById: jest.fn(), })); const pool = require("../db/pool"); const storeModel = require("../models/store.model"); const LocationMap = require("../models/location-map.model"); function createClient() { return { query: jest.fn(), release: jest.fn(), }; } describe("location-map.model", () => { beforeEach(() => { jest.clearAllMocks(); }); function mockMapStateQueries() { const location = { id: 10, household_id: 1, name: "Costco", location_name: "Eastvale", }; const draftMap = { id: 900, household_id: 1, store_location_id: 10, name: "Draft Map", width: 1000, height: 700, status: "draft", version: 3, }; const publishedMap = { id: 901, household_id: 1, store_location_id: 10, name: "Published Map", width: 1000, height: 700, status: "published", version: 2, }; const draftObject = { id: 1100, location_map_id: 900, zone_id: 501, type: "zone", label: "Draft Bakery", x: 10, y: 20, width: 200, height: 120, rotation: 0, locked: false, visible: true, sort_order: 1, }; const publishedObject = { ...draftObject, id: 1200, location_map_id: 901, label: "Published Bakery", }; const zones = [{ id: 501, name: "Bakery", sort_order: 10, item_count: 1 }]; const items = [ { id: 3001, item_id: 2001, item_name: "sourdough", quantity: 1, bought: false, zone_id: 501, zone: "Bakery", added_by_users: ["Owner"], }, { id: 3002, item_id: 2002, item_name: "bagels", quantity: 1, bought: true, zone_id: 501, zone: "Bakery", added_by_users: ["Owner"], }, ]; storeModel.getLocationById.mockResolvedValue(location); pool.query.mockImplementation(async (sql, params = []) => { const query = String(sql); if (query.includes("FROM location_maps") && query.includes("status = ANY")) { const statuses = params[2] || []; return { rows: [draftMap, publishedMap].filter((map) => statuses.includes(map.status)), }; } if (query.includes("FROM location_map_objects")) { const mapId = params[0]; return { rows: mapId === 900 ? [draftObject] : mapId === 901 ? [publishedObject] : [], }; } if (query.includes("FROM store_location_zones")) { return { rows: zones }; } if (query.includes("FROM household_lists hl")) { return { rows: items }; } return { rows: [], rowCount: 0 }; }); return { draftMap, publishedMap, draftObject, publishedObject, location, zones, items }; } test("hides draft map state from non-managers", async () => { const { publishedMap, publishedObject, location, zones, items } = mockMapStateQueries(); const state = await LocationMap.getMapState(1, 10, false); expect(storeModel.getLocationById).toHaveBeenCalledWith(1, 10); expect(pool.query).toHaveBeenCalledWith( expect.stringContaining("FROM location_maps"), [1, 10, ["published"]] ); expect(state).toEqual(expect.objectContaining({ location, map: expect.objectContaining({ id: publishedMap.id, status: "published" }), draft_map: null, published_map: expect.objectContaining({ id: publishedMap.id, status: "published" }), objects: [expect.objectContaining({ id: publishedObject.id, label: "Published Bakery" })], draft_objects: [], published_objects: [expect.objectContaining({ id: publishedObject.id })], zones, items, unmapped_count: 0, })); }); test("includes draft and published map state for managers", async () => { const { draftMap, publishedMap, draftObject, publishedObject } = mockMapStateQueries(); const state = await LocationMap.getMapState(1, 10, true); expect(pool.query).toHaveBeenCalledWith( expect.stringContaining("FROM location_maps"), [1, 10, ["draft", "published"]] ); expect(state.map).toEqual(expect.objectContaining({ id: draftMap.id, status: "draft" })); expect(state.draft_map).toEqual(expect.objectContaining({ id: draftMap.id, status: "draft" })); expect(state.published_map).toEqual(expect.objectContaining({ id: publishedMap.id, status: "published" })); expect(state.objects).toEqual([expect.objectContaining({ id: draftObject.id, label: "Draft Bakery" })]); expect(state.draft_objects).toEqual([expect.objectContaining({ id: draftObject.id })]); expect(state.published_objects).toEqual([expect.objectContaining({ id: publishedObject.id })]); }); test("loads active and recent bought rows for map item layers", async () => { const { items } = mockMapStateQueries(); const state = await LocationMap.getMapState(1, 10, true); expect(state.items).toEqual(items); expect(state.items.some((item) => item.bought)).toBe(true); expect(pool.query).toHaveBeenCalledWith( expect.stringContaining("OR hl.modified_on >= NOW() - INTERVAL '24 hours'"), [1, 10] ); }); test("unlinks saved map objects from zones outside the current location", async () => { const client = createClient(); pool.connect.mockResolvedValue(client); const getMapStateSpy = jest.spyOn(LocationMap, "getMapState").mockResolvedValue({ draft_map: { id: 900 }, draft_objects: [], }); client.query.mockImplementation(async (sql) => { if (sql === "BEGIN" || sql === "COMMIT") { return { rows: [], rowCount: 0 }; } if (String(sql).includes("INSERT INTO location_maps")) { return { rows: [ { id: 900, household_id: 1, store_location_id: 10, name: "Store Map", width: 1000, height: 700, status: "draft", version: 1, }, ], }; } if (String(sql).includes("FROM store_location_zones")) { return { rows: [{ id: 501 }] }; } return { rows: [], rowCount: 0 }; }); await LocationMap.saveDraft(1, 10, 42, { objects: [ { zone_id: 501, label: "Valid zone" }, { zone_id: 999, label: "Foreign zone" }, ], }); const insertCalls = client.query.mock.calls.filter(([sql]) => String(sql).includes("INSERT INTO location_map_objects") ); expect(insertCalls).toHaveLength(2); expect(insertCalls[0][1][1]).toBe(501); expect(insertCalls[1][1][1]).toBeNull(); expect(client.query).toHaveBeenCalledWith( expect.stringContaining("FROM store_location_zones"), [1, 10] ); expect(getMapStateSpy).toHaveBeenCalledWith(1, 10, true); expect(client.query).toHaveBeenCalledWith("COMMIT"); expect(client.release).toHaveBeenCalled(); }); });