grocery-app/backend/tests/location-map.model.test.js

211 lines
6.3 KiB
JavaScript

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"],
}];
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("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();
});
});