import test from "node:test"; import assert from "node:assert/strict"; import request from "supertest"; import { setup, signup } from "./helpers.js"; async function bootstrapPlan(agent, csrf) { const r = await agent .post("/api/plans") .set("x-csrf-token", csrf) .send({ plan: { fields: { 0.1: "Test coffee" } } }); return r.body.plan.id; } test("inventory: auth and CSRF are required on every route", async () => { const { app } = await setup(); const anon = request.agent(app); assert.equal((await anon.get("/api/inventory")).status, 401); assert.equal((await anon.post("/api/inventory").send({})).status, 401); const { agent, csrf } = await (async () => { const a = request.agent(app); const { csrf: c } = await signup(a, "owner@example.com"); return { agent: a, csrf: c }; })(); assert.equal( (await agent.post("/api/inventory").send({ origin: "X", initialWeightG: 100 })).status, 403, ); const created = await agent .post("/api/inventory") .set("x-csrf-token", csrf) .send({ origin: "Huila", initialWeightG: 1000 }); assert.equal(created.status, 201); assert.equal( (await agent.put(`/api/inventory/${created.body.lot.id}`).send({ origin: "Y" })).status, 403, ); assert.equal( (await agent.delete(`/api/inventory/${created.body.lot.id}`)).status, 403, ); assert.equal( ( await agent .post(`/api/inventory/${created.body.lot.id}/consume`) .send({ weightG: 10 }) ).status, 403, ); }); test("inventory: create, list, and ownership isolation", async () => { const { app } = await setup(); const first = request.agent(app); const second = request.agent(app); const { csrf: firstCsrf } = await signup(first, "one@example.com"); await signup(second, "two@example.com"); const bad = await first .post("/api/inventory") .set("x-csrf-token", firstCsrf) .send({ origin: "", initialWeightG: 100 }); assert.equal(bad.status, 400); const badWeight = await first .post("/api/inventory") .set("x-csrf-token", firstCsrf) .send({ origin: "Huila", initialWeightG: -5 }); assert.equal(badWeight.status, 400); const created = await first .post("/api/inventory") .set("x-csrf-token", firstCsrf) .send({ origin: "Huila, Colombia", variety: "Caturra", initialWeightG: 2000 }); assert.equal(created.status, 201); assert.equal(created.body.lot.remainingWeightG, 2000); assert.equal((await first.get("/api/inventory")).body.lots.length, 1); assert.equal((await second.get("/api/inventory")).body.lots.length, 0); assert.equal((await second.get(`/api/inventory/${created.body.lot.id}`)).status, 404); }); test("inventory: consume decrements remaining, allows negative, is idempotent per plan, and logs", async () => { const { app } = await setup(); const agent = request.agent(app); const { csrf } = await signup(agent, "roaster@example.com"); const planId = await bootstrapPlan(agent, csrf); const lot = ( await agent .post("/api/inventory") .set("x-csrf-token", csrf) .send({ origin: "Huila", initialWeightG: 300 }) ).body.lot; const first = await agent .post(`/api/inventory/${lot.id}/consume`) .set("x-csrf-token", csrf) .send({ weightG: 250, roastPlanId: planId }); assert.equal(first.status, 201); assert.equal(first.body.lot.remainingWeightG, 50); // A second draw against the SAME plan is rejected — one draw-down per roast plan, ever. const dupe = await agent .post(`/api/inventory/${lot.id}/consume`) .set("x-csrf-token", csrf) .send({ weightG: 10, roastPlanId: planId }); assert.equal(dupe.status, 409); assert.equal(dupe.body.code, "already_consumed"); assert.equal( (await agent.get(`/api/inventory/${lot.id}`)).body.lot.remainingWeightG, 50, "remaining must be unchanged after the rejected duplicate", ); // A manual (plan-less) draw is unlimited and can push remaining negative — an honest // signal of paperwork/shelf drift, not clamped. const manual = await agent .post(`/api/inventory/${lot.id}/consume`) .set("x-csrf-token", csrf) .send({ weightG: 100 }); assert.equal(manual.status, 201); assert.equal(manual.body.lot.remainingWeightG, -50); const zero = await agent .post(`/api/inventory/${lot.id}/consume`) .set("x-csrf-token", csrf) .send({ weightG: 0 }); assert.equal(zero.status, 400); const withLog = await agent.get(`/api/inventory/${lot.id}`); assert.equal(withLog.body.log.length, 2); // Newest first: the manual (plan-less) draw has no plan title; the earlier draw does. assert.equal(withLog.body.log[0].roastPlanId, null); assert.equal(withLog.body.log[0].planTitle, null); assert.equal(withLog.body.log[1].roastPlanId, planId); assert.equal(withLog.body.log[1].planTitle, "Test coffee"); }); test("inventory: consuming against another user's plan is refused", async () => { const { app } = await setup(); const owner = request.agent(app); const attacker = request.agent(app); const { csrf: ownerCsrf } = await signup(owner, "owner@example.com"); const { csrf: attackerCsrf } = await signup(attacker, "attacker@example.com"); const planId = await bootstrapPlan(owner, ownerCsrf); const lot = ( await attacker .post("/api/inventory") .set("x-csrf-token", attackerCsrf) .send({ origin: "Huila", initialWeightG: 500 }) ).body.lot; const consume = await attacker .post(`/api/inventory/${lot.id}/consume`) .set("x-csrf-token", attackerCsrf) .send({ weightG: 50, roastPlanId: planId }); assert.equal(consume.status, 404); }); test("inventory: edit never accepts remainingWeightG directly, but shifting initialWeightG shifts remaining by the delta", async () => { const { app } = await setup(); const agent = request.agent(app); const { csrf } = await signup(agent, "roaster@example.com"); const lot = ( await agent .post("/api/inventory") .set("x-csrf-token", csrf) .send({ origin: "Huila", initialWeightG: 1000 }) ).body.lot; await agent .post(`/api/inventory/${lot.id}/consume`) .set("x-csrf-token", csrf) .send({ weightG: 400 }); // Attempting to set remainingWeightG directly is silently ignored. const sneaky = await agent .put(`/api/inventory/${lot.id}`) .set("x-csrf-token", csrf) .send({ remainingWeightG: 999999 }); assert.equal(sneaky.body.lot.remainingWeightG, 600); // Correcting the recorded initial weight (e.g. a scale error) shifts remaining by the delta. const corrected = await agent .put(`/api/inventory/${lot.id}`) .set("x-csrf-token", csrf) .send({ initialWeightG: 1100 }); assert.equal(corrected.body.lot.initialWeightG, 1100); assert.equal(corrected.body.lot.remainingWeightG, 700); const archived = await agent .put(`/api/inventory/${lot.id}`) .set("x-csrf-token", csrf) .send({ archived: true }); assert.equal(archived.body.lot.archived, true); assert.equal( (await agent.delete(`/api/inventory/${lot.id}`).set("x-csrf-token", csrf)).status, 200, ); assert.equal((await agent.get(`/api/inventory/${lot.id}`)).status, 404); });