Files
app/src/__tests__/env.test.ts
BOHA 519edce373 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>
2026-06-09 06:45:26 +02:00

69 lines
2.7 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { config } from "../config/env";
describe("env validation", () => {
it("has numeric port within valid range", () => {
expect(typeof config.port).toBe("number");
expect(config.port).toBeGreaterThan(0);
expect(config.port).toBeLessThanOrEqual(65535);
});
it("has JWT_SECRET defined", () => {
expect(config.jwt.secret).toBeTruthy();
expect(config.jwt.secret.length).toBeGreaterThanOrEqual(32);
});
it("has TOTP_ENCRYPTION_KEY defined", () => {
expect(config.totp.encryptionKey).toBeTruthy();
expect(config.totp.encryptionKey.length).toBeGreaterThanOrEqual(32);
});
it("has positive JWT expiry values", () => {
expect(config.jwt.accessTokenExpiry).toBeGreaterThan(0);
expect(config.jwt.refreshTokenSessionExpiry).toBeGreaterThan(0);
expect(config.jwt.refreshTokenRememberExpiry).toBeGreaterThan(0);
});
it("has positive maxUploadSize", () => {
expect(config.nas.maxUploadSize).toBeGreaterThan(0);
});
it("has DATABASE_URL defined", () => {
expect(config.db.url).toBeTruthy();
});
});
describe("env validation — failure path", () => {
// The config singleton runs its validation at module-load time and is then
// cached by the module system, so we cannot re-import it with bad env to
// observe a throw without complex module-registry resetting. Instead we test
// the exact predicates config/env.ts uses to reject bad input — this is the
// logic that protects the boot, and the loaded config above proves the
// happy path. (Predicates kept in sync with src/config/env.ts.)
const HEX64_RE = /^[0-9a-fA-F]{64}$/;
it("rejects a too-short / non-hex JWT_SECRET", () => {
expect(HEX64_RE.test("short")).toBe(false);
expect(HEX64_RE.test("a".repeat(63))).toBe(false); // one char short
expect(HEX64_RE.test("z".repeat(64))).toBe(false); // 64 chars, not hex
// The real config's secret must pass the very same check.
expect(HEX64_RE.test(config.jwt.secret)).toBe(true);
});
it("rejects an out-of-range or non-numeric PORT", () => {
const portValid = (p: number) => !Number.isNaN(p) && p >= 1 && p <= 65535;
expect(portValid(parseInt("0", 10))).toBe(false);
expect(portValid(parseInt("70000", 10))).toBe(false);
expect(portValid(parseInt("notaport", 10))).toBe(false); // NaN
expect(portValid(config.port)).toBe(true);
});
it("rejects a non-positive ACCESS_TOKEN_EXPIRY", () => {
const expiryValid = (n: number) => !Number.isNaN(n) && n > 0;
expect(expiryValid(0)).toBe(false);
expect(expiryValid(-1)).toBe(false);
expect(expiryValid(parseInt("oops", 10))).toBe(false); // NaN
expect(expiryValid(config.jwt.accessTokenExpiry)).toBe(true);
});
});