package controllers import ( "fmt" "testing" ) // The scrambling exists to remove an information leak, but the property that // MUST survive it is uniqueness: trackingno and bookingno are UNIQUE columns, // and a collision is a rider standing at a door unable to complete a pickup. // Every test here is ultimately about that. // No two sequence values may ever produce the same tracking number. Checked // over a large contiguous run, which is exactly the shape real traffic takes. func TestTrackingScrambleIsCollisionFree(t *testing.T) { const sample = 200_000 seen := make(map[uint64]int64, sample) for seq := int64(cxTrackingBase); seq < cxTrackingBase+sample; seq++ { got, ok := cxScrambledTracking(seq) if !ok { t.Fatalf("seq %d reported out of range inside the domain", seq) } if prev, dup := seen[got]; dup { t.Fatalf("COLLISION: seq %d and seq %d both produced %d — "+ "the UNIQUE constraint would reject the second booking", prev, seq, got) } seen[got] = seq } if len(seen) != sample { t.Errorf("produced %d distinct numbers from %d inputs", len(seen), sample) } } func TestBookingScrambleIsCollisionFree(t *testing.T) { // The booking domain is only 900,000, so the whole thing is checkable — // this is an exhaustive proof of bijectivity, not a sample. seen := make(map[uint64]int64, cxBookingDomain) for seq := int64(cxBookingBase); seq < cxBookingBase+cxBookingDomain; seq++ { got, ok := cxScrambledBooking(seq) if !ok { t.Fatalf("seq %d reported out of range inside the domain", seq) } if prev, dup := seen[got]; dup { t.Fatalf("COLLISION: seq %d and seq %d both produced %d", prev, seq, got) } seen[got] = seq } if len(seen) != cxBookingDomain { t.Fatalf("the permutation is not a bijection: %d distinct outputs from %d inputs", len(seen), cxBookingDomain) } } // Output must stay inside the digit range, or the format silently changes // width and every label, column and deep link that assumed it breaks. func TestScrambledIdentifiersKeepTheirWidth(t *testing.T) { for _, seq := range []int64{ cxTrackingBase, cxTrackingBase + 1, cxTrackingBase + 12_345, cxTrackingBase + cxTrackingDomain - 1, } { got, ok := cxScrambledTracking(seq) if !ok { t.Fatalf("seq %d out of range", seq) } if got < cxTrackingBase || got > 99_999_999 { t.Errorf("seq %d produced %d, outside the eight-digit range", seq, got) } if formatted := fmt.Sprintf("DMX%08d", got); len(formatted) != 11 { t.Errorf("formatted as %q (%d chars), want 11", formatted, len(formatted)) } } for _, seq := range []int64{ cxBookingBase, cxBookingBase + 1, cxBookingBase + cxBookingDomain - 1, } { got, ok := cxScrambledBooking(seq) if !ok { t.Fatalf("seq %d out of range", seq) } if got < cxBookingBase || got > 999_999 { t.Errorf("seq %d produced %d, outside the six-digit range", seq, got) } if formatted := fmt.Sprintf("DM-%06d", got); len(formatted) != 9 { t.Errorf("formatted as %q (%d chars), want 9", formatted, len(formatted)) } } } // The point of the whole exercise: consecutive sequence values must NOT produce // adjacent identifiers. This is the leak being closed. func TestConsecutiveSequenceValuesAreNotAdjacent(t *testing.T) { const run = 500 var previous uint64 adjacent := 0 for i := 0; i < run; i++ { got, _ := cxScrambledTracking(int64(cxTrackingBase + i)) if i > 0 { diff := int64(got) - int64(previous) if diff < 0 { diff = -diff } if diff < 100 { adjacent++ } } previous = got } // In a well-scattered 90,000,000-wide range, landing within 100 of the // previous value should essentially never happen. if adjacent > 2 { t.Errorf("%d of %d consecutive pairs landed within 100 of each other — "+ "the identifiers are still walkable", adjacent, run-1) } } // A multi-destination pickup hands ONE customer several consecutive sequence // values at once. If the mapping were linear — multiply by a coprime, the // obvious one-line trick — the differences between those tracking numbers would // all equal the multiplier, and that single booking would hand over the key to // the whole range. This is the test that rejects that design. func TestOneBookingDoesNotLeakTheMapping(t *testing.T) { // Three orders minted back to back, as a three-destination pickup would. a, _ := cxScrambledTracking(cxTrackingBase + 5000) b, _ := cxScrambledTracking(cxTrackingBase + 5001) c, _ := cxScrambledTracking(cxTrackingBase + 5002) d1 := int64(b) - int64(a) d2 := int64(c) - int64(b) if d1 == d2 { t.Fatalf("consecutive differences are identical (%d) — the mapping is "+ "linear, so one multi-destination booking reveals it and the whole "+ "range becomes enumerable", d1) } // And knowing two neighbours must not predict the third. if int64(c) == int64(b)+d1 { t.Error("the third identifier is predictable from the first two") } } // The mapping is deterministic — the same sequence value always yields the same // identifier. It is computed at insert time and stored, so this matters only // for reasoning and tests, but a non-deterministic mapping would mean the // scrambling depended on something it should not. func TestScramblingIsDeterministic(t *testing.T) { for _, seq := range []int64{cxTrackingBase, cxTrackingBase + 99, cxTrackingBase + 123_456} { first, _ := cxScrambledTracking(seq) for i := 0; i < 5; i++ { again, _ := cxScrambledTracking(seq) if again != first { t.Fatalf("seq %d produced %d then %d", seq, first, again) } } } } // Past the fixed-width range the caller must be told, so it can let the // identifier grow a digit rather than wrap onto one already issued. Wrapping // would be a duplicate, and a duplicate is a failed booking. func TestExhaustedDomainIsReportedNotWrapped(t *testing.T) { if _, ok := cxScrambledTracking(cxTrackingBase + cxTrackingDomain); ok { t.Error("the first sequence value past the tracking domain was accepted — " + "it would wrap onto an identifier already issued") } if _, ok := cxScrambledBooking(cxBookingBase + cxBookingDomain); ok { t.Error("the first sequence value past the booking domain was accepted") } // And the generator falls back to plain sequential formatting there, which // grows a digit rather than colliding. if got := fmt.Sprintf("DM-%06d", cxBookingBase+cxBookingDomain); len(got) != 10 { t.Errorf("the overflow reference formats as %q; it should simply grow a digit", got) } } // A value below the sequence start is not a valid index and must be refused // rather than producing a negative or wrapped result. func TestBelowBaseIsRefused(t *testing.T) { if _, ok := cxScrambledTracking(0); ok { t.Error("seq 0 accepted for tracking") } if _, ok := cxScrambledBooking(cxBookingBase - 1); ok { t.Error("a sequence value below the booking base was accepted") } } // The Feistel itself is a permutation over the full power-of-two space. This is // the property everything else rests on, so it is checked directly rather than // only through its callers. func TestFeistelIsAPermutation(t *testing.T) { const halfBits = 8 // a 16-bit space, small enough to check exhaustively full := uint64(1) << (2 * halfBits) seen := make(map[uint64]uint64, full) for x := uint64(0); x < full; x++ { y := cxFeistelEncrypt(x, halfBits, cxScrambleKey) if y >= full { t.Fatalf("encrypt(%d) = %d, outside the %d-wide space", x, y, full) } if prev, dup := seen[y]; dup { t.Fatalf("not a permutation: %d and %d both map to %d", prev, x, y) } seen[y] = x } if uint64(len(seen)) != full { t.Fatalf("covered %d of %d values", len(seen), full) } } // A different key must produce a different permutation — otherwise the key is // not actually keying anything. func TestKeyChangesThePermutation(t *testing.T) { const halfBits = 8 differences := 0 for x := uint64(0); x < 256; x++ { if cxFeistelEncrypt(x, halfBits, []byte("key-one")) != cxFeistelEncrypt(x, halfBits, []byte("key-two")) { differences++ } } if differences < 250 { t.Errorf("only %d of 256 values differed between keys — the key has "+ "little effect on the mapping", differences) } } // Every surface — customer app, miler app, admin console, hub console — reads // the SAME column, so format consistency is structural: one generator, one // stored value. These assert the generators themselves produce the documented // shape, including on the fallback path that runs when the sequence cannot be // read (no database in a test, which is exactly what exercises it here). func TestGeneratorsProduceTheDocumentedFormat(t *testing.T) { for i := 0; i < 50; i++ { booking := generateBookingNo() if len(booking) < 9 || booking[:3] != "DM-" { t.Fatalf("generateBookingNo() = %q, want DM- followed by at least six digits", booking) } for _, r := range booking[3:] { if r < '0' || r > '9' { t.Fatalf("generateBookingNo() = %q — the part after DM- must be digits only", booking) } } tracking := generateTrackingNo() if len(tracking) < 11 || tracking[:3] != "DMX" { t.Fatalf("generateTrackingNo() = %q, want DMX followed by at least eight digits", tracking) } for _, r := range tracking[3:] { if r < '0' || r > '9' { t.Fatalf("generateTrackingNo() = %q — the part after DMX must be digits only", tracking) } } // A tracking number must never be mistakeable for a booking reference: // the assistant and the consoles tell them apart by prefix alone. if tracking[:3] == "DM-" { t.Fatalf("tracking number %q collides with the booking reference prefix", tracking) } } } // The fallback path must never emit a short number that formats with leading // zeros — DM-000042 reads as a broken reference, and a padded id is a support // call. func TestFallbackNumberNeverGoesShort(t *testing.T) { for i := 0; i < 200; i++ { if n := fallbackNumber(6); n < 100_000 || n > 999_999 { t.Fatalf("fallbackNumber(6) = %d, outside the six-digit range", n) } if n := fallbackNumber(8); n < 10_000_000 || n > 99_999_999 { t.Fatalf("fallbackNumber(8) = %d, outside the eight-digit range", n) } } }