fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace the raw number|string transform idiom across every schema (the root-cause NaN bug class); emailOrEmpty + lenient isoDateString/timeString. Security: roles privilege-escalation closed; refresh-token family revocation on reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices gross VAT on all paths; orders-pdf custom-items authz. Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse confirm/cancel, attendance lockUserRow); uniqueness checks moved into create transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO). Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted fields; dashboard invalidation gaps; stale-closure confirm bugs. Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier; tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes), isolated on app_test via .env.test with a hard-throw setup guard. Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,15 @@ const baseOrder = {
|
||||
exchange_rate: 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Fail loudly (instead of the old `if (!("id" in res)) return;`, which silently
|
||||
* passed the test when a create failed) while still narrowing the union via the
|
||||
* `in` operator so the success-branch fields stay typed.
|
||||
*/
|
||||
function failResult(res: unknown): never {
|
||||
throw new Error(`expected a success result, got ${JSON.stringify(res)}`);
|
||||
}
|
||||
|
||||
describe("createOrder (manual, optional linked project)", () => {
|
||||
it("creates an order AND a project sharing one number when create_project=true", async () => {
|
||||
const res = await createOrder({
|
||||
@@ -32,9 +41,8 @@ describe("createOrder (manual, optional linked project)", () => {
|
||||
scope_title: "Ruční zakázka",
|
||||
create_project: true,
|
||||
});
|
||||
expect("id" in res).toBe(true);
|
||||
if (!("id" in res)) return;
|
||||
createdOrderIds.push(res.id);
|
||||
if (!("id" in res)) failResult(res);
|
||||
createdOrderIds.push(res.id!);
|
||||
expect(res.project_id).toBeTruthy();
|
||||
const project = await prisma.projects.findUnique({
|
||||
where: { id: res.project_id! },
|
||||
@@ -46,9 +54,8 @@ describe("createOrder (manual, optional linked project)", () => {
|
||||
|
||||
it("creates only the order when create_project=false", async () => {
|
||||
const res = await createOrder({ ...baseOrder, create_project: false });
|
||||
expect("id" in res).toBe(true);
|
||||
if (!("id" in res)) return;
|
||||
createdOrderIds.push(res.id);
|
||||
if (!("id" in res)) failResult(res);
|
||||
createdOrderIds.push(res.id!);
|
||||
expect(res.project_id).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -56,19 +63,25 @@ describe("createOrder (manual, optional linked project)", () => {
|
||||
describe("shared pool coherence (tracking)", () => {
|
||||
it("order → standalone project → order produce three distinct shared numbers", async () => {
|
||||
const o1 = await createOrder({ ...baseOrder, create_project: true });
|
||||
if ("id" in o1) {
|
||||
createdOrderIds.push(o1.id);
|
||||
if (o1.project_id) createdProjectIds.push(o1.project_id);
|
||||
}
|
||||
const p = await createProject({ name: "Mezi-projekt", status: "aktivni" });
|
||||
if ("project_number" in p) createdProjectIds.push(p.id);
|
||||
const o2 = await createOrder({ ...baseOrder, create_project: false });
|
||||
if ("id" in o2) createdOrderIds.push(o2.id);
|
||||
if (!("id" in o1)) failResult(o1);
|
||||
createdOrderIds.push(o1.id!);
|
||||
if (o1.project_id) createdProjectIds.push(o1.project_id);
|
||||
|
||||
const n1 = "id" in o1 ? o1.order_number : null;
|
||||
const np = "project_number" in p ? p.project_number : null;
|
||||
const n2 = "id" in o2 ? o2.order_number : null;
|
||||
expect(new Set([n1, np, n2]).size).toBe(3);
|
||||
const p = await createProject({ name: "Mezi-projekt", status: "aktivni" });
|
||||
if (!("project_number" in p)) failResult(p);
|
||||
createdProjectIds.push(p.id);
|
||||
|
||||
const o2 = await createOrder({ ...baseOrder, create_project: false });
|
||||
if (!("id" in o2)) failResult(o2);
|
||||
createdOrderIds.push(o2.id!);
|
||||
|
||||
// All three numbers must be present (non-null) AND distinct.
|
||||
expect(o1.order_number).toBeTruthy();
|
||||
expect(p.project_number).toBeTruthy();
|
||||
expect(o2.order_number).toBeTruthy();
|
||||
expect(
|
||||
new Set([o1.order_number, p.project_number, o2.order_number]).size,
|
||||
).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,8 +91,7 @@ describe("createProject (manual, standalone)", () => {
|
||||
name: "Test projekt",
|
||||
status: "aktivni",
|
||||
});
|
||||
expect("project_number" in result).toBe(true);
|
||||
if (!("project_number" in result)) return;
|
||||
if (!("project_number" in result)) failResult(result);
|
||||
createdProjectIds.push(result.id);
|
||||
expect(typeof result.project_number).toBe("string");
|
||||
expect(result.project_number!.length).toBeGreaterThan(0);
|
||||
@@ -112,9 +124,8 @@ describe("createOrder with price line item + attachment", () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect("id" in res).toBe(true);
|
||||
if (!("id" in res)) return;
|
||||
createdOrderIds.push(res.id);
|
||||
if (!("id" in res)) failResult(res);
|
||||
createdOrderIds.push(res.id!);
|
||||
const items = await prisma.order_items.findMany({
|
||||
where: { order_id: res.id },
|
||||
});
|
||||
@@ -122,21 +133,25 @@ describe("createOrder with price line item + attachment", () => {
|
||||
expect(Number(items[0].unit_price)).toBe(12345);
|
||||
});
|
||||
|
||||
it("stores an uploaded PO attachment", async () => {
|
||||
const buf = Buffer.from("%PDF-1.4 fake");
|
||||
it("stores an uploaded PO attachment with the exact bytes", async () => {
|
||||
const buf = Buffer.from("%PDF-1.4 fake-attachment-bytes-éí");
|
||||
const res = await createOrder(
|
||||
{ ...baseOrder, create_project: false },
|
||||
buf,
|
||||
"po.pdf",
|
||||
);
|
||||
expect("id" in res).toBe(true);
|
||||
if (!("id" in res)) return;
|
||||
createdOrderIds.push(res.id);
|
||||
if (!("id" in res)) failResult(res);
|
||||
createdOrderIds.push(res.id!);
|
||||
const order = await prisma.orders.findUnique({
|
||||
where: { id: res.id },
|
||||
select: { attachment_name: true, attachment_data: true },
|
||||
});
|
||||
expect(order?.attachment_name).toBe("po.pdf");
|
||||
// Verify the stored content/size, not just truthiness: round-trip the
|
||||
// bytes and compare length + value against what we uploaded.
|
||||
expect(order?.attachment_data).toBeTruthy();
|
||||
const stored = Buffer.from(order!.attachment_data!);
|
||||
expect(stored.length).toBe(buf.length);
|
||||
expect(stored.equals(buf)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user