| 1 | package domains_test |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/domains" |
| 8 | "github.com/carlosframework/platform/internal/release" |
| 9 | ) |
| 10 | |
| 11 | func TestSqidGolden(t *testing.T) { |
| 12 | // FROZEN 2026-08-02 (first green run of the id+23² base-23 scheme). If |
| 13 | // this test fails you are re-homing every platform domain ever minted — |
| 14 | // ops's sqid is baked into every <app>.bab.<apps-domain> URL in the |
| 15 | // wild. Do not "fix" the expectations; fix your code. |
| 16 | golden := map[int64]string{ |
| 17 | 1: "bab", // ops — the built-in account, on every deployment |
| 18 | 2: "bac", |
| 19 | 23: "bba", |
| 20 | 24: "bbb", |
| 21 | 529: "caa", |
| 22 | 1000: "cxn", |
| 23 | } |
| 24 | goldenSeen := map[string]int64{} |
| 25 | for id, want := range golden { |
| 26 | s := domains.Sqid(id) |
| 27 | if s != want { |
| 28 | t.Errorf("Sqid(%d) = %q, want frozen %q", id, s, want) |
| 29 | } |
| 30 | if len(s) < 3 { |
| 31 | t.Errorf("Sqid(%d)=%q shorter than 3", id, s) |
| 32 | } |
| 33 | for _, c := range s { |
| 34 | if !strings.ContainsRune("abcdefghjkmnpqrstuvwxyz", c) { |
| 35 | t.Errorf("Sqid(%d)=%q uses %q outside the frozen alphabet", id, s, c) |
| 36 | } |
| 37 | } |
| 38 | if prev, dup := goldenSeen[s]; dup { |
| 39 | t.Errorf("collision: %d and %d both %q", prev, id, s) |
| 40 | } |
| 41 | goldenSeen[s] = id |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // TestValidSqid pins the predicate the CLI's -account-id gate rests on. The |
| 46 | // case that matters is the i/l/o rows: release.ValidAccount (^[a-z]{3,16}$) |
| 47 | // accepts every one of them, because it gates bucket path segments and every |
| 48 | // account NAME is a legal segment. ValidSqid must not — "ops" passing here |
| 49 | // is `carlos ship -account-id ops` writing to apps/ops/..., exiting 0, and |
| 50 | // serving nothing, when the real ops account is Sqid(1) = "bab". |
| 51 | func TestValidSqid(t *testing.T) { |
| 52 | for _, tc := range []struct { |
| 53 | s string |
| 54 | want bool |
| 55 | }{ |
| 56 | {"bab", true}, // ops, the built-in account |
| 57 | {"zzz", true}, // never minted here, but shaped like something Sqid emits |
| 58 | {"baf", true}, // |
| 59 | {"abc", true}, // the 3-char floor sqidOffset guarantees |
| 60 | {"abcdefghjkmnpqrs", true}, // 16, Sqid's buffer ceiling |
| 61 | |
| 62 | // The lookalikes the alphabet drops. Each of these passes |
| 63 | // release.ValidAccount and is therefore exactly the bug. |
| 64 | {"ops", false}, // o |
| 65 | {"oil", false}, // o, i, l |
| 66 | {"ill", false}, // i, l |
| 67 | {"kass", true}, // a real account NAME that IS sqid-shaped: see below |
| 68 | {"amadan", true}, |
| 69 | |
| 70 | {"ab", false}, // under the 3 floor |
| 71 | {"abcdefghjkmnpqrst", false}, // over the 16 ceiling |
| 72 | {"", false}, // |
| 73 | {"BAB", false}, // uppercase is not the alphabet |
| 74 | {"ba1", false}, // digits are not the alphabet |
| 75 | {"ba-b", false}, // nor is a hyphen |
| 76 | {"ba b", false}, // nor a space (callers must trim first) |
| 77 | } { |
| 78 | if got := domains.ValidSqid(tc.s); got != tc.want { |
| 79 | t.Errorf("ValidSqid(%q) = %v, want %v", tc.s, got, tc.want) |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | // The honest limit of a shape check, stated so nobody mistakes it for |
| 84 | // more: a NAME made only of alphabet letters ("kass", "amadan") is |
| 85 | // indistinguishable from a sqid by shape alone, and passes. -account-id |
| 86 | // is matched exactly against the account space, so such a value simply |
| 87 | // resolves to nothing rather than to the wrong account — the failure is |
| 88 | // a 404, not a silent write under a phantom account. It is the i/l/o |
| 89 | // names, and only those, that a shape check can catch for free. |
| 90 | |
| 91 | // Every sqid the encoder can actually emit must satisfy the predicate: |
| 92 | // the two are frozen together, and this is the direction that would |
| 93 | // break first if the alphabet or the buffer ever changed. |
| 94 | for id := int64(1); id < 20000; id++ { |
| 95 | if s := domains.Sqid(id); !domains.ValidSqid(s) { |
| 96 | t.Fatalf("Sqid(%d) = %q, which ValidSqid rejects", id, s) |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | // The frozen alphabet omits i, l and o, so no id encodes to "legacy" — which is |
| 102 | // what lets release.LegacySource use that word as a sentinel for the |
| 103 | // account-less key space in `carlos accounts migrate -from legacy`. If the |
| 104 | // alphabet ever gains an "l", the sentinel silently becomes a real account's |
| 105 | // sqid and the verb starts reading one as the other. Fail here first. |
| 106 | // |
| 107 | // The emitted character set is derived from Sqid itself rather than read off |
| 108 | // the constant: this test lives outside the package, and asserting on what the |
| 109 | // encoder actually produces is the stronger check anyway. |
| 110 | func TestLegacySourceIsNotASqid(t *testing.T) { |
| 111 | emitted := map[rune]bool{} |
| 112 | for id := int64(1); id < 20000; id++ { |
| 113 | for _, r := range domains.Sqid(id) { |
| 114 | emitted[r] = true |
| 115 | } |
| 116 | } |
| 117 | if len(emitted) != 23 { |
| 118 | t.Fatalf("saw %d distinct characters over 20k ids, want the full 23 — widen the range", len(emitted)) |
| 119 | } |
| 120 | for _, r := range release.LegacySource { |
| 121 | if !emitted[r] { |
| 122 | return // a character the encoder can never emit: no id can collide |
| 123 | } |
| 124 | } |
| 125 | t.Fatalf("every character of %q is one the sqid encoder emits — the sentinel can collide with a real account sqid", |
| 126 | release.LegacySource) |
| 127 | } |
| 128 | |