test: cover custom store location flows
This commit is contained in:
parent
f45473cbff
commit
6b3d267abb
@ -2,12 +2,20 @@ jest.mock("../db/pool", () => ({
|
||||
query: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../models/list.model.v2", () => ({
|
||||
recordItemEvent: jest.fn(),
|
||||
setCatalogItemImage: jest.fn(),
|
||||
}));
|
||||
|
||||
const pool = require("../db/pool");
|
||||
const List = require("../models/list.model.v2");
|
||||
const AvailableItems = require("../models/available-item.model");
|
||||
|
||||
describe("available-item.model", () => {
|
||||
beforeEach(() => {
|
||||
pool.query.mockReset();
|
||||
List.recordItemEvent.mockReset();
|
||||
List.setCatalogItemImage.mockReset();
|
||||
});
|
||||
|
||||
test("lists household store items", async () => {
|
||||
@ -58,6 +66,14 @@ describe("available-item.model", () => {
|
||||
expect.stringContaining("INSERT INTO household_store_items"),
|
||||
[1, 2, "granola", "granola"]
|
||||
);
|
||||
expect(List.recordItemEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventType: "ITEM_ADDED",
|
||||
householdId: 1,
|
||||
storeLocationId: 2,
|
||||
householdStoreItemId: 77,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("updates household store item images and returns refreshed data", async () => {
|
||||
@ -78,19 +94,37 @@ describe("available-item.model", () => {
|
||||
expect(pool.query).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.stringContaining("UPDATE household_store_items"),
|
||||
[1, 2, 55, imageBuffer, "image/jpeg"]
|
||||
[1, 2, 55]
|
||||
);
|
||||
expect(List.setCatalogItemImage).toHaveBeenCalledWith(
|
||||
1,
|
||||
2,
|
||||
55,
|
||||
imageBuffer,
|
||||
"image/jpeg",
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test("deletes the household store item", async () => {
|
||||
pool.query.mockResolvedValueOnce({ rowCount: 1, rows: [] });
|
||||
pool.query
|
||||
.mockResolvedValueOnce({ rowCount: 1, rows: [{ item_id: 55, item_name: "milk" }] })
|
||||
.mockResolvedValueOnce({ rowCount: 1, rows: [] });
|
||||
|
||||
const deleted = await AvailableItems.deleteAvailableItem(1, 2, 55);
|
||||
|
||||
expect(deleted).toBe(true);
|
||||
expect(pool.query).toHaveBeenCalledWith(
|
||||
expect(pool.query).toHaveBeenLastCalledWith(
|
||||
expect.stringContaining("DELETE FROM household_store_items"),
|
||||
[1, 2, 55]
|
||||
);
|
||||
expect(List.recordItemEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventType: "ITEM_DELETED",
|
||||
householdId: 1,
|
||||
storeLocationId: 2,
|
||||
householdStoreItemId: 55,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,6 +9,8 @@ jest.mock("../models/available-item.model", () => ({
|
||||
|
||||
jest.mock("../models/list.model.v2", () => ({
|
||||
deleteClassification: jest.fn(),
|
||||
getZoneByName: jest.fn(),
|
||||
recordItemEvent: jest.fn(),
|
||||
upsertClassification: jest.fn(),
|
||||
}));
|
||||
|
||||
@ -42,7 +44,9 @@ describe("available-items.controller", () => {
|
||||
AvailableItems.deleteAvailableItem.mockResolvedValue(true);
|
||||
AvailableItems.importCurrentListItems.mockResolvedValue(2);
|
||||
AvailableItems.listAvailableItems.mockResolvedValue([]);
|
||||
List.upsertClassification.mockResolvedValue(undefined);
|
||||
List.getZoneByName.mockResolvedValue({ id: 5, name: "Dairy & Refrigerated" });
|
||||
List.recordItemEvent.mockResolvedValue(undefined);
|
||||
List.upsertClassification.mockResolvedValue({ zone_id: 5 });
|
||||
List.deleteClassification.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
@ -58,12 +62,13 @@ describe("available-items.controller", () => {
|
||||
}),
|
||||
},
|
||||
processedImage: null,
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
await controller.createAvailableItem(req, res);
|
||||
|
||||
expect(AvailableItems.createAvailableItem).toHaveBeenCalledWith("1", "2", "milk", null, null);
|
||||
expect(AvailableItems.createAvailableItem).toHaveBeenCalledWith("1", "2", "milk", null, null, 7);
|
||||
expect(List.upsertClassification).toHaveBeenCalledWith(
|
||||
"1",
|
||||
"2",
|
||||
@ -87,6 +92,7 @@ describe("available-items.controller", () => {
|
||||
item_group: "Bread",
|
||||
}),
|
||||
},
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
@ -110,6 +116,7 @@ describe("available-items.controller", () => {
|
||||
classification: "null",
|
||||
},
|
||||
processedImage: null,
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
@ -122,6 +129,7 @@ describe("available-items.controller", () => {
|
||||
test("imports current list items and reports the import count", async () => {
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
@ -138,12 +146,13 @@ describe("available-items.controller", () => {
|
||||
test("deletes a store item", async () => {
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2", itemId: "99" },
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
await controller.deleteAvailableItem(req, res);
|
||||
|
||||
expect(AvailableItems.deleteAvailableItem).toHaveBeenCalledWith("1", "2", 99);
|
||||
expect(AvailableItems.deleteAvailableItem).toHaveBeenCalledWith("1", "2", 99, 7);
|
||||
expect(List.deleteClassification).not.toHaveBeenCalled();
|
||||
expect(res.json).toHaveBeenCalledWith({ message: "Store item deleted" });
|
||||
});
|
||||
@ -152,6 +161,7 @@ describe("available-items.controller", () => {
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
query: {},
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
@ -177,6 +187,7 @@ describe("available-items.controller", () => {
|
||||
item_name: "milk",
|
||||
},
|
||||
processedImage: null,
|
||||
user: { id: 7 },
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
|
||||
@ -11,6 +11,10 @@ jest.mock("../middleware/household", () => ({
|
||||
};
|
||||
next();
|
||||
},
|
||||
locationAccess: (req, res, next) => {
|
||||
req.storeLocation = { id: Number.parseInt(req.params.locationId, 10) };
|
||||
next();
|
||||
},
|
||||
requireHouseholdAdmin: (req, res, next) => {
|
||||
if (["owner", "admin"].includes(req.household?.role)) {
|
||||
return next();
|
||||
@ -65,6 +69,21 @@ jest.mock("../controllers/available-items.controller", () => ({
|
||||
updateAvailableItem: jest.fn((req, res) => res.json({ message: "updated" })),
|
||||
}));
|
||||
|
||||
jest.mock("../controllers/stores.controller", () => ({
|
||||
addLocationToStore: jest.fn((req, res) => res.status(201).json({ message: "location" })),
|
||||
createHouseholdStore: jest.fn((req, res) => res.status(201).json({ message: "store" })),
|
||||
createZone: jest.fn((req, res) => res.status(201).json({ message: "zone" })),
|
||||
deleteHouseholdStore: jest.fn((req, res) => res.json({ message: "deleted store" })),
|
||||
deleteLocation: jest.fn((req, res) => res.json({ message: "deleted location" })),
|
||||
deleteZone: jest.fn((req, res) => res.json({ message: "deleted zone" })),
|
||||
getHouseholdStores: jest.fn((req, res) => res.json([])),
|
||||
getLocationZones: jest.fn((req, res) => res.json({ zones: [] })),
|
||||
setDefaultLocation: jest.fn((req, res) => res.json({ message: "default" })),
|
||||
updateHouseholdStore: jest.fn((req, res) => res.json({ message: "updated store" })),
|
||||
updateLocation: jest.fn((req, res) => res.json({ message: "updated location" })),
|
||||
updateZone: jest.fn((req, res) => res.json({ message: "updated zone" })),
|
||||
}));
|
||||
|
||||
const express = require("express");
|
||||
const request = require("supertest");
|
||||
const router = require("../routes/households.routes");
|
||||
@ -106,4 +125,23 @@ describe("available-items routes", () => {
|
||||
expect(response.status).toBe(201);
|
||||
expect(availableItemsController.createAvailableItem).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("members can create available items on location-scoped routes", async () => {
|
||||
const response = await request(app)
|
||||
.post("/households/1/locations/2/available-items")
|
||||
.set("x-household-role", "member")
|
||||
.send({ item_name: "milk" });
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(availableItemsController.createAvailableItem).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("members cannot delete available items on location-scoped routes", async () => {
|
||||
const response = await request(app)
|
||||
.delete("/households/1/locations/2/available-items/3")
|
||||
.set("x-household-role", "member");
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(availableItemsController.deleteAvailableItem).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@ -24,6 +24,10 @@ describe("list.model.v2 addOrUpdateItem", () => {
|
||||
itemId: 55,
|
||||
householdStoreItemId: 55,
|
||||
itemName: "milk",
|
||||
quantity: 3,
|
||||
previousQuantity: 0,
|
||||
historyQuantity: 3,
|
||||
wasBought: false,
|
||||
isNew: true,
|
||||
});
|
||||
expect(pool.query).toHaveBeenNthCalledWith(
|
||||
@ -41,7 +45,7 @@ describe("list.model.v2 addOrUpdateItem", () => {
|
||||
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 }] })
|
||||
.mockResolvedValueOnce({ rowCount: 1, rows: [{ id: 88, bought: false, quantity: 2 }] })
|
||||
.mockResolvedValueOnce({ rowCount: 1, rows: [] });
|
||||
|
||||
const result = await List.addOrUpdateItem(1, 2, "Milk", 4, 7);
|
||||
@ -51,14 +55,48 @@ describe("list.model.v2 addOrUpdateItem", () => {
|
||||
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, 88]
|
||||
[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", () => {
|
||||
@ -66,7 +104,7 @@ describe("list.model.v2 classification helpers", () => {
|
||||
pool.query.mockReset();
|
||||
});
|
||||
|
||||
test("gets classification using household, store, and household-store item ids", async () => {
|
||||
test("gets classification using household, location, and household-store item ids", async () => {
|
||||
pool.query.mockResolvedValueOnce({
|
||||
rowCount: 1,
|
||||
rows: [
|
||||
@ -95,17 +133,23 @@ describe("list.model.v2 classification helpers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("upserts classification using household-store item conflict target", async () => {
|
||||
pool.query.mockResolvedValueOnce({
|
||||
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_id: 2,
|
||||
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",
|
||||
},
|
||||
@ -123,14 +167,14 @@ describe("list.model.v2 classification helpers", () => {
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
household_id: 1,
|
||||
store_id: 2,
|
||||
store_location_id: 2,
|
||||
household_store_item_id: 55,
|
||||
item_type: "dairy",
|
||||
})
|
||||
);
|
||||
expect(pool.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining("ON CONFLICT (household_id, store_id, household_store_item_id)"),
|
||||
[1, 2, 55, "dairy", "Milk", "Dairy & Refrigerated", 1, "user"]
|
||||
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"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,6 +3,8 @@ jest.mock("../models/list.model.v2", () => ({
|
||||
addOrUpdateItem: jest.fn(),
|
||||
ensureHouseholdStoreItem: jest.fn(),
|
||||
getItemByName: jest.fn(),
|
||||
getZoneByName: jest.fn(),
|
||||
recordItemEvent: jest.fn(),
|
||||
upsertClassification: jest.fn(),
|
||||
}));
|
||||
|
||||
@ -37,7 +39,9 @@ describe("lists.controller.v2 addItem", () => {
|
||||
});
|
||||
List.addHistoryRecord.mockResolvedValue(undefined);
|
||||
List.getItemByName.mockResolvedValue({ id: 42, item_id: 99, item_name: "milk" });
|
||||
List.upsertClassification.mockResolvedValue(undefined);
|
||||
List.getZoneByName.mockResolvedValue({ id: 5, name: "Dairy & Refrigerated" });
|
||||
List.recordItemEvent.mockResolvedValue(undefined);
|
||||
List.upsertClassification.mockResolvedValue({ zone_id: 5 });
|
||||
householdModel.isHouseholdMember.mockResolvedValue(true);
|
||||
});
|
||||
|
||||
@ -54,7 +58,15 @@ describe("lists.controller.v2 addItem", () => {
|
||||
|
||||
expect(householdModel.isHouseholdMember).toHaveBeenCalledWith("1", 9);
|
||||
expect(List.addOrUpdateItem).toHaveBeenCalled();
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, "1", 9);
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, "1", 9, "2");
|
||||
expect(List.recordItemEvent).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventType: "ITEM_ADDED",
|
||||
householdId: "1",
|
||||
storeLocationId: "2",
|
||||
householdStoreItemId: 99,
|
||||
})
|
||||
);
|
||||
expect(res.status).not.toHaveBeenCalledWith(400);
|
||||
});
|
||||
|
||||
@ -71,10 +83,42 @@ describe("lists.controller.v2 addItem", () => {
|
||||
|
||||
expect(householdModel.isHouseholdMember).not.toHaveBeenCalled();
|
||||
expect(List.addOrUpdateItem).toHaveBeenCalled();
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, "1", 7);
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, "1", 7, "2");
|
||||
expect(res.status).not.toHaveBeenCalledWith(400);
|
||||
});
|
||||
|
||||
test("records duplicate-add history with the added quantity instead of the new total", async () => {
|
||||
List.addOrUpdateItem.mockResolvedValueOnce({
|
||||
listId: 42,
|
||||
itemId: 99,
|
||||
householdStoreItemId: 99,
|
||||
itemName: "milk",
|
||||
quantity: 3,
|
||||
previousQuantity: 1,
|
||||
historyQuantity: 2,
|
||||
isNew: false,
|
||||
});
|
||||
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
body: { item_name: "milk", quantity: "3" },
|
||||
user: { id: 7 },
|
||||
processedImage: null,
|
||||
};
|
||||
const res = createResponse();
|
||||
|
||||
await controller.addItem(req, res);
|
||||
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, 2, 7, "2");
|
||||
expect(res.json).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
quantity: 3,
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("records history using request user when added_for_user_id is blank", async () => {
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
@ -88,7 +132,7 @@ describe("lists.controller.v2 addItem", () => {
|
||||
|
||||
expect(householdModel.isHouseholdMember).not.toHaveBeenCalled();
|
||||
expect(List.addOrUpdateItem).toHaveBeenCalled();
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, "1", 7);
|
||||
expect(List.addHistoryRecord).toHaveBeenCalledWith(42, 99, "1", 7, "2");
|
||||
expect(res.status).not.toHaveBeenCalledWith(400);
|
||||
});
|
||||
|
||||
@ -169,7 +213,9 @@ describe("lists.controller.v2 setClassification", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
List.getItemByName.mockResolvedValue({ id: 42, item_id: 99, item_name: "milk" });
|
||||
List.upsertClassification.mockResolvedValue(undefined);
|
||||
List.upsertClassification.mockResolvedValue({ zone_id: 5 });
|
||||
List.recordItemEvent.mockResolvedValue(undefined);
|
||||
List.getZoneByName.mockResolvedValue({ id: 5, name: "Dairy & Refrigerated" });
|
||||
List.ensureHouseholdStoreItem.mockResolvedValue({ id: 99, name: "milk" });
|
||||
});
|
||||
|
||||
@ -216,6 +262,7 @@ describe("lists.controller.v2 setClassification", () => {
|
||||
});
|
||||
|
||||
test("accepts zone-only classification updates", async () => {
|
||||
List.getZoneByName.mockResolvedValueOnce({ id: 6, name: "Checkout Area" });
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
body: {
|
||||
@ -297,6 +344,7 @@ describe("lists.controller.v2 setClassification", () => {
|
||||
});
|
||||
|
||||
test("rejects invalid zone", async () => {
|
||||
List.getZoneByName.mockResolvedValueOnce(null);
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
body: {
|
||||
@ -350,6 +398,7 @@ describe("lists.controller.v2 setClassification", () => {
|
||||
|
||||
test("creates a household store item when classification target is not yet on the list", async () => {
|
||||
List.getItemByName.mockResolvedValueOnce(null);
|
||||
List.getZoneByName.mockResolvedValueOnce({ id: 7, name: "Snacks & Candy" });
|
||||
|
||||
const req = {
|
||||
params: { householdId: "1", storeId: "2" },
|
||||
|
||||
153
backend/tests/store-locations.routes.test.js
Normal file
153
backend/tests/store-locations.routes.test.js
Normal file
@ -0,0 +1,153 @@
|
||||
jest.mock("../middleware/auth", () => (req, res, next) => {
|
||||
req.user = { id: 42, role: "user" };
|
||||
next();
|
||||
});
|
||||
|
||||
jest.mock("../middleware/household", () => ({
|
||||
householdAccess: (req, res, next) => {
|
||||
req.household = {
|
||||
id: Number.parseInt(req.params.householdId, 10),
|
||||
role: req.headers["x-household-role"] || "member",
|
||||
};
|
||||
next();
|
||||
},
|
||||
locationAccess: (req, res, next) => {
|
||||
req.storeLocation = { id: Number.parseInt(req.params.locationId, 10) };
|
||||
next();
|
||||
},
|
||||
requireHouseholdAdmin: (req, res, next) => {
|
||||
if (["owner", "admin"].includes(req.household?.role)) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json({
|
||||
error: { code: "FORBIDDEN", message: "Admin role required" },
|
||||
request_id: req.request_id,
|
||||
});
|
||||
},
|
||||
storeAccess: (req, res, next) => next(),
|
||||
}));
|
||||
|
||||
jest.mock("../middleware/image", () => ({
|
||||
upload: {
|
||||
single: () => (req, res, next) => next(),
|
||||
},
|
||||
processImage: (req, res, next) => next(),
|
||||
}));
|
||||
|
||||
jest.mock("../controllers/households.controller", () => ({
|
||||
createHousehold: jest.fn(),
|
||||
deleteHousehold: jest.fn(),
|
||||
getHousehold: jest.fn(),
|
||||
getMembers: jest.fn(),
|
||||
getUserHouseholds: jest.fn(),
|
||||
joinHousehold: jest.fn(),
|
||||
refreshInviteCode: jest.fn(),
|
||||
removeMember: jest.fn(),
|
||||
updateHousehold: jest.fn(),
|
||||
updateMemberRole: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../controllers/lists.controller.v2", () => ({
|
||||
addItem: jest.fn(),
|
||||
deleteItem: jest.fn(),
|
||||
getClassification: jest.fn(),
|
||||
getItemByName: jest.fn(),
|
||||
getList: jest.fn(),
|
||||
getRecentlyBought: jest.fn(),
|
||||
getSuggestions: jest.fn(),
|
||||
markBought: jest.fn(),
|
||||
setClassification: jest.fn(),
|
||||
updateItem: jest.fn(),
|
||||
updateItemImage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../controllers/available-items.controller", () => ({
|
||||
createAvailableItem: jest.fn(),
|
||||
deleteAvailableItem: jest.fn(),
|
||||
getAvailableItems: jest.fn(),
|
||||
importCurrentItems: jest.fn(),
|
||||
updateAvailableItem: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../controllers/stores.controller", () => ({
|
||||
addLocationToStore: jest.fn((req, res) => res.status(201).json({ message: "location" })),
|
||||
createHouseholdStore: jest.fn((req, res) => res.status(201).json({ message: "store" })),
|
||||
createZone: jest.fn((req, res) => res.status(201).json({ message: "zone" })),
|
||||
deleteHouseholdStore: jest.fn((req, res) => res.json({ message: "deleted store" })),
|
||||
deleteLocation: jest.fn((req, res) => res.json({ message: "deleted location" })),
|
||||
deleteZone: jest.fn((req, res) => res.json({ message: "deleted zone" })),
|
||||
getHouseholdStores: jest.fn((req, res) => res.json([{ id: 2, name: "Costco" }])),
|
||||
getLocationZones: jest.fn((req, res) => res.json({ zones: [] })),
|
||||
setDefaultLocation: jest.fn((req, res) => res.json({ message: "default" })),
|
||||
updateHouseholdStore: jest.fn((req, res) => res.json({ message: "updated store" })),
|
||||
updateLocation: jest.fn((req, res) => res.json({ message: "updated location" })),
|
||||
updateZone: jest.fn((req, res) => res.json({ message: "updated zone" })),
|
||||
}));
|
||||
|
||||
const express = require("express");
|
||||
const request = require("supertest");
|
||||
const router = require("../routes/households.routes");
|
||||
const storesController = require("../controllers/stores.controller");
|
||||
|
||||
describe("store location routes", () => {
|
||||
let app;
|
||||
|
||||
beforeEach(() => {
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use("/households", router);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("members can list household store locations", async () => {
|
||||
const response = await request(app).get("/households/1/stores");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(storesController.getHouseholdStores).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("members cannot create household stores", async () => {
|
||||
const response = await request(app)
|
||||
.post("/households/1/stores")
|
||||
.set("x-household-role", "member")
|
||||
.send({ name: "Costco" });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(storesController.createHouseholdStore).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("admins can create household stores", async () => {
|
||||
const response = await request(app)
|
||||
.post("/households/1/stores")
|
||||
.set("x-household-role", "admin")
|
||||
.send({ name: "Costco", location_name: "Fontana" });
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(storesController.createHouseholdStore).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("members can list zones but cannot create zones", async () => {
|
||||
const listResponse = await request(app)
|
||||
.get("/households/1/locations/2/zones")
|
||||
.set("x-household-role", "member");
|
||||
const createResponse = await request(app)
|
||||
.post("/households/1/locations/2/zones")
|
||||
.set("x-household-role", "member")
|
||||
.send({ name: "Produce", sort_order: 10 });
|
||||
|
||||
expect(listResponse.status).toBe(200);
|
||||
expect(createResponse.status).toBe(403);
|
||||
expect(storesController.getLocationZones).toHaveBeenCalled();
|
||||
expect(storesController.createZone).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("admins can update zone order", async () => {
|
||||
const response = await request(app)
|
||||
.patch("/households/1/locations/2/zones/9")
|
||||
.set("x-household-role", "admin")
|
||||
.send({ sort_order: 20 });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(storesController.updateZone).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user