import test from "node:test"; import assert from "node:assert/strict"; import request from "supertest"; import { setup, signup } from "./helpers.js"; function planBody(overrides = {}) { return { fields: { "2.1": "single", "1.4": "7:30", "1.6": "0", "5.6": "" }, planActual: { charge: { actualBt: "" }, tp: { actualBt: "" }, yellow: { actualBt: "" }, fc: { actualTime: "", actualBt: "" }, drop: { actualBt: "" }, }, afterRoast: { oneChange: "" }, inventory: { lotId: "", lotLabel: "", consumed: null }, ...overrides, }; } test("machine-profile: requires auth", async () => { const { app } = await setup(); const anon = request.agent(app); assert.equal((await anon.get("/api/machine-profile")).status, 401); }); test("machine-profile: a brand-new account gets the reference defaults, not an error", async () => { const { app } = await setup(); const agent = request.agent(app); await signup(agent, "fresh@example.com"); const r = await agent.get("/api/machine-profile"); assert.equal(r.status, 200); assert.equal(r.body.ok, true); assert.equal(r.body.profile.pace.value, 1); assert.equal(r.body.profile.pace.source, "reference"); assert.equal(r.body.profile.totalPlans, 0); }); test("machine-profile: learns a pace factor from this account's own completed roasts, and only this account's", async () => { const { app } = await setup(); const owner = request.agent(app); const { csrf } = await signup(owner, "owner@example.com"); // Every roast here ran 20% slower than its own anchor (7:30=450s -> actual 9:00=540s, ratio 1.2). await owner .post("/api/plans") .set("x-csrf-token", csrf) .send({ plan: planBody({ planActual: { ...planBody().planActual, fc: { actualTime: "9:00", actualBt: "" } } }) }); await owner .post("/api/plans") .set("x-csrf-token", csrf) .send({ plan: planBody({ fields: { "2.1": "single", "1.4": "8:00", "1.6": "0", "5.6": "" }, // 480s planActual: { ...planBody().planActual, fc: { actualTime: "9:36", actualBt: "" } }, // 576s, ratio 1.2 }), }); const other = request.agent(app); await signup(other, "other@example.com"); const ownerProfile = (await owner.get("/api/machine-profile")).body.profile; assert.equal(ownerProfile.pace.n, 2); assert.equal(ownerProfile.pace.source, "learned"); assert.ok(Math.abs(ownerProfile.pace.value - 1.2) < 0.001, `expected ~1.2, got ${ownerProfile.pace.value}`); // The other account's history is empty — it must not see the owner's learned pace. const otherProfile = (await other.get("/api/machine-profile")).body.profile; assert.equal(otherProfile.pace.source, "reference"); assert.equal(otherProfile.pace.value, 1); }); test("last-refine: requires auth and 404s on a lot that doesn't belong to the caller", async () => { const { app } = await setup(); const ownerAgent = request.agent(app); const { csrf } = await signup(ownerAgent, "lotowner@example.com"); const lot = ( await ownerAgent .post("/api/inventory") .set("x-csrf-token", csrf) .send({ origin: "Huila", initialWeightG: 1000 }) ).body.lot; const anon = request.agent(app); assert.equal((await anon.get(`/api/inventory/${lot.id}/last-refine`)).status, 401); const otherAgent = request.agent(app); await signup(otherAgent, "notlotowner@example.com"); assert.equal((await otherAgent.get(`/api/inventory/${lot.id}/last-refine`)).status, 404); }); test("last-refine: null when no past roast against the lot has a filled-in one-change note", async () => { const { app } = await setup(); const agent = request.agent(app); const { csrf } = await signup(agent, "norefine@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/plans") .set("x-csrf-token", csrf) .send({ plan: planBody({ inventory: { lotId: lot.id, lotLabel: "Huila", consumed: null } }) }); const r = await agent.get(`/api/inventory/${lot.id}/last-refine`); assert.equal(r.status, 200); assert.equal(r.body.refine, null); }); test("last-refine: returns the most recent filled-in note for that lot, ignoring blank ones and other lots", async () => { const { app } = await setup(); const agent = request.agent(app); const { csrf } = await signup(agent, "refine@example.com"); const lotA = ( await agent.post("/api/inventory").set("x-csrf-token", csrf).send({ origin: "Lot A", initialWeightG: 1000 }) ).body.lot; const lotB = ( await agent.post("/api/inventory").set("x-csrf-token", csrf).send({ origin: "Lot B", initialWeightG: 1000 }) ).body.lot; // Older roast on lot A, with a note. await agent.post("/api/plans").set("x-csrf-token", csrf).send({ plan: planBody({ inventory: { lotId: lotA.id, lotLabel: "Lot A", consumed: null }, afterRoast: { oneChange: "First crack +0:30" }, }), }); // A blank-note roast on lot A (should never win over a real note, regardless of recency). await agent.post("/api/plans").set("x-csrf-token", csrf).send({ plan: planBody({ inventory: { lotId: lotA.id, lotLabel: "Lot A", consumed: null } }), }); // A roast on the OTHER lot with a note — must not leak into lot A's answer. await agent.post("/api/plans").set("x-csrf-token", csrf).send({ plan: planBody({ inventory: { lotId: lotB.id, lotLabel: "Lot B", consumed: null }, afterRoast: { oneChange: "Development −0:15" }, }), }); const r = await agent.get(`/api/inventory/${lotA.id}/last-refine`); assert.equal(r.status, 200); assert.equal(r.body.refine.oneChange, "First crack +0:30"); });