Files
parking_solution/apps/server/src/entry-flow.test.ts
T
julian 5e9be16f65 test(server): Phase 1 — server-core suites (occupancy, pay, exit, shift)
Completes the anti-fraud/safety core coverage on a fresh in-memory DB:

- occupancy.test.ts (12): the ledger-fold count, the capacity/full gate, and the
  reserved-subscriber-spots model — never double-count a parked subscriber, reserve
  tightens only the TRANSIENT gate.
- pay-station.test.ts (12): quote math against the frozen tariff, the signed-payment
  side effect (+ chain verify), no-session / no-tariff errors, the booth lookup view,
  active-session listing.
- exit-flow.test.ts (9): the GATE — refuse unknown / unpaid / grace-expired (no exit
  signed); a paid-within-grace session signs the exit; the booth transient path has NO
  subscription bypass; a prepaid subscriber leaves via the assist (reopenBarrier) path.
- shift-service.test.ts (14): site-wide single-open invariant, the takings SPLIT by
  source (subscription sales vs out-of-window vs transient tickets), drawer carry-
  forward + cash_in/out vouchers, Z-report sign + listShifts read-back.
- entry-flow.test.ts (5): the exported validateTicketCode Luhn typo-guard. (The
  capacity-gate/print-hold/sign-before-open paths need device fakes — covered in the
  device + route phases.)

Adds test-helpers.ts (real EventLog, silent logger, tariff seeder). server 68/68 green.

Note: apps/vision has 2 PRE-EXISTING failures (test_app.py) — environment drift now
that fast_alpr + the ONNX model are installed (the "stub mode" assertions are stale).
Untouched here; to be fixed in the vision phase.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-21 16:15:56 +02:00

41 lines
1.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { validateTicketCode } from "./entry-flow.js";
// validateTicketCode is the manual-entry typo guard: an all-digit code whose last digit
// is the Luhn check of the rest. The booth uses it to reject a mistyped ticket up front
// (instead of a confusing "session not found"). The capacity-gate / print-hold / sign-
// before-open paths of EntryFlow need device fakes and are exercised in the device +
// route phases; here we pin the pure, exported checksum contract.
describe("validateTicketCode (Luhn)", () => {
it("accepts a well-formed 11-digit id", () => {
// 10-digit body + its Luhn check digit. 0000000000 → check digit 0.
expect(validateTicketCode("00000000000")).toBe(true);
});
it("rejects a single-digit typo", () => {
expect(validateTicketCode("00000000000")).toBe(true);
expect(validateTicketCode("00000000010")).toBe(false); // flipped a digit, checksum now wrong
});
it("rejects non-digit and out-of-length strings", () => {
expect(validateTicketCode("abc")).toBe(false);
expect(validateTicketCode("123")).toBe(false); // too short
expect(validateTicketCode("123456789012345")).toBe(false); // too long
expect(validateTicketCode("")).toBe(false);
});
it("round-trips a generated body+check (Luhn is self-consistent)", () => {
// Construct a valid code: pick a body, compute its check the same way the issuer does.
const body = "4992739871";
// brute the check digit 0..9 — exactly one makes a valid code.
const valid = Array.from({ length: 10 }, (_, d) => body + d).filter(validateTicketCode);
expect(valid).toHaveLength(1);
});
it("accepts a legacy 13-digit id shape", () => {
// 12-digit body 000000000000 → check 0; the validator is length-agnostic in 10..14.
expect(validateTicketCode("0000000000000")).toBe(true);
});
});