grocery-app/backend/tests/available-items.routes.test.js

148 lines
5.2 KiB
JavaScript

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"] || "user",
};
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((req, res) => res.status(201).json({ message: "created" })),
deleteAvailableItem: jest.fn((req, res) => res.json({ message: "deleted" })),
getAvailableItems: jest.fn((req, res) => res.json({ items: [] })),
importCurrentItems: jest.fn((req, res) => res.json({ imported_count: 1 })),
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");
const availableItemsController = require("../controllers/available-items.controller");
describe("available-items routes", () => {
let app;
beforeEach(() => {
app = express();
app.use(express.json());
app.use("/households", router);
jest.clearAllMocks();
});
test("members can read available items", async () => {
const response = await request(app).get("/households/1/stores/2/available-items");
expect(response.status).toBe(200);
expect(availableItemsController.getAvailableItems).toHaveBeenCalled();
});
test("members cannot mutate available items", async () => {
const response = await request(app)
.post("/households/1/stores/2/available-items")
.set("x-household-role", "user")
.send({ item_name: "milk" });
expect(response.status).toBe(403);
expect(availableItemsController.createAvailableItem).not.toHaveBeenCalled();
});
test("admins can create available items", async () => {
const response = await request(app)
.post("/households/1/stores/2/available-items")
.set("x-household-role", "admin")
.send({ item_name: "milk" });
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();
});
});