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:
BOHA
2026-06-09 06:45:26 +02:00
parent c454d1a3fc
commit 519edce373
179 changed files with 7179 additions and 2844 deletions

View File

@@ -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);
});
});