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

@@ -1,20 +1,45 @@
import { describe, it, expect } from "vitest";
import { Prisma } from "@prisma/client";
import { invoiceTotalWithVat } from "../services/invoices.service";
// `invoiceTotalWithVat` receives a Prisma row whose money columns are real
// `Prisma.Decimal` instances (the model maps `@db.Decimal` → Decimal). Using
// real Decimals here — instead of the old `{ toNumber: () => N }` duck types —
// means a regression in genuine Decimal handling (e.g. swapping `.toNumber()`
// for `Number(decimal)`) is actually caught.
const dec = (n: number | string) => new Prisma.Decimal(n);
// Helper so each test reads as a small declarative table of lines.
function buildInvoice(opts: {
applyVat: boolean;
vatRate: number | null;
currency?: string;
items: Array<{
quantity: number | null;
unit_price: number | null;
vat_rate: number | null;
}>;
}) {
return {
apply_vat: opts.applyVat,
vat_rate: opts.vatRate === null ? null : dec(opts.vatRate),
currency: opts.currency ?? "CZK",
invoice_items: opts.items.map((i) => ({
quantity: i.quantity === null ? null : dec(i.quantity),
unit_price: i.unit_price === null ? null : dec(i.unit_price),
vat_rate: i.vat_rate === null ? null : dec(i.vat_rate),
})),
};
}
describe("invoiceTotalWithVat", () => {
it("calculates subtotal without VAT when apply_vat is false", () => {
const inv = {
apply_vat: false,
vat_rate: { toNumber: () => 21 },
currency: "CZK",
invoice_items: [
{
quantity: { toNumber: () => 2 },
unit_price: { toNumber: () => 100 },
vat_rate: { toNumber: () => 21 },
},
],
};
const inv = buildInvoice({
applyVat: false,
vatRate: 21,
items: [{ quantity: 2, unit_price: 100, vat_rate: 21 }],
});
expect(invoiceTotalWithVat(inv)).toBe(200);
});
@@ -24,32 +49,72 @@ describe("invoiceTotalWithVat", () => {
// Total VAT = 7.00 * 3 = 21.00
// Subtotal = 33.33 * 3 = 99.99
// Total = 99.99 + 21.00 = 120.99
const inv = {
apply_vat: true,
vat_rate: { toNumber: () => 21 },
currency: "CZK",
invoice_items: Array.from({ length: 3 }, () => ({
quantity: { toNumber: () => 1 },
unit_price: { toNumber: () => 33.33 },
vat_rate: { toNumber: () => 21 },
const inv = buildInvoice({
applyVat: true,
vatRate: 21,
items: Array.from({ length: 3 }, () => ({
quantity: 1,
unit_price: 33.33,
vat_rate: 21,
})),
};
});
expect(invoiceTotalWithVat(inv)).toBe(120.99);
});
it("handles null quantity and unit_price gracefully", () => {
const inv = {
apply_vat: true,
vat_rate: { toNumber: () => 21 },
currency: "CZK",
invoice_items: [
{
quantity: { toNumber: () => 0 },
unit_price: { toNumber: () => 0 },
vat_rate: { toNumber: () => 21 },
},
it("treats a null quantity (and null unit_price) as 0 for that line", () => {
// The line is genuinely null here (not 0): quantity?.toNumber() === undefined
// -> Number(undefined) is NaN -> `|| 0` -> base 0. A second, well-formed
// line proves the null line contributes nothing while the rest still totals.
const inv = buildInvoice({
applyVat: true,
vatRate: 21,
items: [
{ quantity: null, unit_price: null, vat_rate: 21 },
{ quantity: 1, unit_price: 100, vat_rate: 21 }, // base 100, vat 21
],
};
});
expect(invoiceTotalWithVat(inv)).toBe(121);
});
it("returns 0 when every line is null", () => {
const inv = buildInvoice({
applyVat: true,
vatRate: 21,
items: [{ quantity: null, unit_price: null, vat_rate: null }],
});
expect(invoiceTotalWithVat(inv)).toBe(0);
});
it("applies mixed per-line vat_rate values, falling back to the invoice rate when a line rate is 0/absent", () => {
// Line 1: 2 x 100 @ 21% -> base 200, vat 42
// Line 2: 1 x 50 @ 12% -> base 50, vat 6
// Line 3: 1 x 100 @ 0% -> a line rate of 0 falls through to the invoice
// default (15%) per the `|| inv.vat_rate || 21` semantics -> vat 15
// Subtotal = 350, VAT = 63, Total = 413
const inv = buildInvoice({
applyVat: true,
vatRate: 15,
items: [
{ quantity: 2, unit_price: 100, vat_rate: 21 },
{ quantity: 1, unit_price: 50, vat_rate: 12 },
{ quantity: 1, unit_price: 100, vat_rate: 0 },
],
});
expect(invoiceTotalWithVat(inv)).toBe(413);
});
it("handles a negative (discount) line correctly", () => {
// Line 1: 1 x 1000 @ 21% -> base 1000, vat 210
// Line 2 (discount): 1 x -200 @ 21% -> base -200, vat -42
// Subtotal = 800, VAT = 168, Total = 968
const inv = buildInvoice({
applyVat: true,
vatRate: 21,
items: [
{ quantity: 1, unit_price: 1000, vat_rate: 21 },
{ quantity: 1, unit_price: -200, vat_rate: 21 },
],
});
expect(invoiceTotalWithVat(inv)).toBe(968);
});
});