| 1 | package runs |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | func TestValidKey(t *testing.T) { |
| 9 | for _, k := range []string{ |
| 10 | "a", "Z", "0", ".", "_", ":", "-", |
| 11 | "fleet:import:acct-42:2026-10-24T12.00", |
| 12 | strings.Repeat("k", 128), |
| 13 | "AZaz09._:-", |
| 14 | } { |
| 15 | if !ValidKey(k) { |
| 16 | t.Errorf("ValidKey(%q) = false, want true", k) |
| 17 | } |
| 18 | } |
| 19 | for _, k := range []string{ |
| 20 | "", |
| 21 | strings.Repeat("k", 129), |
| 22 | "a/b", |
| 23 | "a b", |
| 24 | "a\x00b", |
| 25 | "a\nb", |
| 26 | "café", |
| 27 | "a,b", |
| 28 | "a+b", |
| 29 | } { |
| 30 | if ValidKey(k) { |
| 31 | t.Errorf("ValidKey(%q) = true, want false", k) |
| 32 | } |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | func TestScopedKeySeparatesCallers(t *testing.T) { |
| 37 | svc := ScopedKey("svc:cred_1", "K") |
| 38 | if len(svc) != 64 || strings.Trim(svc, "0123456789abcdef") != "" { |
| 39 | t.Fatalf("ScopedKey = %q, want 64 lowercase hex digits", svc) |
| 40 | } |
| 41 | if ScopedKey("svc:cred_1", "K") != svc { |
| 42 | t.Fatal("ScopedKey is not deterministic") |
| 43 | } |
| 44 | for _, other := range []string{ |
| 45 | ScopedKey("svc:cred_2", "K"), |
| 46 | ScopedKey("user:paul@keymail.dev", "K"), |
| 47 | ScopedKey("svc:cred_1", "K2"), |
| 48 | // The separator keeps the scope and the key apart. |
| 49 | ScopedKey("svc:cred_", "1K"), |
| 50 | ScopedKey("svc:cred_1K", ""), |
| 51 | } { |
| 52 | if other == svc { |
| 53 | t.Errorf("two different callers or keys share the scoped key %s", svc) |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | func TestMetaDigestCoversEveryField(t *testing.T) { |
| 59 | hash := strings.Repeat("a", 64) |
| 60 | base := func() Meta { |
| 61 | return Meta{ |
| 62 | Argv: []string{"import", "x.csv"}, |
| 63 | TimeoutS: 60, |
| 64 | OnBehalfOf: "paul@example.com", |
| 65 | Files: []MetaFile{{Name: "x.csv", Size: 5, SHA256: hash}}, |
| 66 | } |
| 67 | } |
| 68 | want := MetaDigest(base()) |
| 69 | if MetaDigest(base()) != want { |
| 70 | t.Fatal("MetaDigest is not deterministic") |
| 71 | } |
| 72 | for name, change := range map[string]func(*Meta){ |
| 73 | "argv": func(m *Meta) { m.Argv[1] = "y.csv" }, |
| 74 | "argv split": func(m *Meta) { m.Argv = []string{"importx.csv"} }, |
| 75 | "argv joined": func(m *Meta) { m.Argv = []string{"import", "x", ".csv"} }, |
| 76 | "timeout": func(m *Meta) { m.TimeoutS = 61 }, |
| 77 | "on_behalf_of": func(m *Meta) { m.OnBehalfOf = "bob@example.com" }, |
| 78 | "no requester": func(m *Meta) { m.OnBehalfOf = "" }, |
| 79 | "file name": func(m *Meta) { m.Files[0].Name = "y.csv" }, |
| 80 | "file size": func(m *Meta) { m.Files[0].Size = 6 }, |
| 81 | "file sha256": func(m *Meta) { m.Files[0].SHA256 = strings.Repeat("b", 64) }, |
| 82 | "no files": func(m *Meta) { m.Files = nil }, |
| 83 | "a second file": func(m *Meta) { m.Files = append(m.Files, MetaFile{Name: "y.csv", Size: 1, SHA256: hash}) }, |
| 84 | } { |
| 85 | m := base() |
| 86 | change(&m) |
| 87 | if MetaDigest(m) == want { |
| 88 | t.Errorf("changing the %s left the digest unchanged", name) |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | func TestValidFileHash(t *testing.T) { |
| 94 | lower := strings.Repeat("0123456789abcdef", 4) |
| 95 | for _, h := range []string{lower, strings.ToUpper(lower), "ABCDEF" + lower[6:]} { |
| 96 | if !ValidFileHash(h) { |
| 97 | t.Errorf("ValidFileHash(%q) = false", h) |
| 98 | } |
| 99 | } |
| 100 | for _, h := range []string{"", lower[:63], lower + "0", "g" + lower[1:], " " + lower[1:]} { |
| 101 | if ValidFileHash(h) { |
| 102 | t.Errorf("ValidFileHash(%q) = true", h) |
| 103 | } |
| 104 | } |
| 105 | if NormalFileHash(strings.ToUpper(lower)) != lower { |
| 106 | t.Error("NormalFileHash does not lowercase") |
| 107 | } |
| 108 | m := Meta{Argv: []string{"a"}, TimeoutS: 1, Files: []MetaFile{{Name: "x", Size: 1, SHA256: lower}}} |
| 109 | u := m |
| 110 | u.Files = []MetaFile{{Name: "x", Size: 1, SHA256: strings.ToUpper(lower)}} |
| 111 | if MetaDigest(m) != MetaDigest(u) { |
| 112 | t.Error("a declared sha256's case changes the digest") |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | func TestCallerOfSeparatesCallers(t *testing.T) { |
| 117 | if CallerOf("svc:a") == CallerOf("user:a") || CallerOf("svc:a") != CallerOf("svc:a") || len(CallerOf("x")) != 64 { |
| 118 | t.Fatal("CallerOf does not name callers apart") |
| 119 | } |
| 120 | if CallerOf("svc:a") == ScopedKey("svc:a", "") { |
| 121 | t.Fatal("CallerOf collides with a scoped key") |
| 122 | } |
| 123 | } |
| 124 | |