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
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user