From 34c818b0358b32277476601aae64d98c30b083c8 Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 4 Jun 2026 04:50:25 -0700 Subject: [PATCH] fix: scope map object zone links --- backend/models/location-map.model.js | 24 +++++++- backend/tests/location-map.model.test.js | 76 ++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 backend/tests/location-map.model.test.js diff --git a/backend/models/location-map.model.js b/backend/models/location-map.model.js index ff4d384..04ae8e6 100644 --- a/backend/models/location-map.model.js +++ b/backend/models/location-map.model.js @@ -280,9 +280,26 @@ async function getMapItems(db, householdId, locationId) { })); } -async function insertObjects(db, mapId, objects) { +async function getActiveZoneIds(db, householdId, locationId) { + const result = await db.query( + `SELECT id + FROM store_location_zones + WHERE household_id = $1 + AND store_location_id = $2 + AND is_active = TRUE`, + [householdId, locationId] + ); + + return new Set(result.rows.map((row) => Number(row.id))); +} + +async function insertObjects(db, mapId, objects, validZoneIds = null) { for (let index = 0; index < objects.length; index += 1) { const object = sanitizeObjectPayload(objects[index], index); + const scopedZoneId = + object.zone_id && validZoneIds && !validZoneIds.has(Number(object.zone_id)) + ? null + : object.zone_id; await db.query( `INSERT INTO location_map_objects ( @@ -303,7 +320,7 @@ async function insertObjects(db, mapId, objects) { VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13::jsonb)`, [ mapId, - object.zone_id, + scopedZoneId, object.type, object.label, object.x, @@ -442,8 +459,9 @@ exports.saveDraft = async (householdId, locationId, userId, payload = {}) => { try { await client.query("BEGIN"); const map = await upsertDraftMap(client, householdId, locationId, userId, payload.map || {}); + const validZoneIds = await getActiveZoneIds(client, householdId, locationId); await client.query("DELETE FROM location_map_objects WHERE location_map_id = $1", [map.id]); - await insertObjects(client, map.id, Array.isArray(payload.objects) ? payload.objects : []); + await insertObjects(client, map.id, Array.isArray(payload.objects) ? payload.objects : [], validZoneIds); await client.query("COMMIT"); return exports.getMapState(householdId, locationId, true); } catch (error) { diff --git a/backend/tests/location-map.model.test.js b/backend/tests/location-map.model.test.js new file mode 100644 index 0000000..a8af49e --- /dev/null +++ b/backend/tests/location-map.model.test.js @@ -0,0 +1,76 @@ +jest.mock("../db/pool", () => ({ + connect: jest.fn(), +})); + +const pool = require("../db/pool"); +const LocationMap = require("../models/location-map.model"); + +function createClient() { + return { + query: jest.fn(), + release: jest.fn(), + }; +} + +describe("location-map.model", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + 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(); + }); +});