This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / db / mill_tokens_test.go
8.3 kB 272 lines
1package db 2 3import ( 4 "slices" 5 "testing" 6 "time" 7) 8 9func TestAddExecutorTokenRejectsDuplicateName(t *testing.T) { 10 d := newTestDB(t) 11 12 if err := d.AddExecutorToken("exec-1", "hash-a", nil, nil); err != nil { 13 t.Fatalf("AddExecutorToken: %v", err) 14 } 15 if err := d.AddExecutorToken("exec-1", "hash-b", nil, nil); err == nil { 16 t.Fatal("AddExecutorToken re-registered an existing name; a duplicate must be rejected") 17 } 18 19 name, _, ok, err := d.ResolveExecutorToken("hash-a") 20 if err != nil { 21 t.Fatalf("ResolveExecutorToken(hash-a): %v", err) 22 } 23 if !ok || name != "exec-1" { 24 t.Fatalf("ResolveExecutorToken(hash-a) = (%q, %v), want (exec-1, true)", name, ok) 25 } 26 if _, _, ok, _ := d.ResolveExecutorToken("hash-b"); ok { 27 t.Fatal("rejected duplicate's token resolved; the failed insert leaked a credential") 28 } 29} 30 31func TestResolveExecutorTokenMissAndHit(t *testing.T) { 32 d := newTestDB(t) 33 34 name, _, ok, err := d.ResolveExecutorToken("no-such-hash") 35 if err != nil { 36 t.Fatalf("ResolveExecutorToken(miss): %v", err) 37 } 38 if ok || name != "" { 39 t.Fatalf("ResolveExecutorToken(miss) = (%q, %v), want (\"\", false)", name, ok) 40 } 41 42 if err := d.AddExecutorToken("exec-1", "hash-1", nil, nil); err != nil { 43 t.Fatalf("AddExecutorToken: %v", err) 44 } 45 46 name, _, ok, err = d.ResolveExecutorToken("hash-1") 47 if err != nil { 48 t.Fatalf("ResolveExecutorToken(hit): %v", err) 49 } 50 if !ok || name != "exec-1" { 51 t.Fatalf("ResolveExecutorToken(hash-1) = (%q, %v), want (exec-1, true)", name, ok) 52 } 53 54 if _, _, ok, _ := d.ResolveExecutorToken("hash-unregistered"); ok { 55 t.Fatal("ResolveExecutorToken matched an unregistered hash") 56 } 57} 58 59func TestRevokeExecutorToken(t *testing.T) { 60 d := newTestDB(t) 61 62 if err := d.AddExecutorToken("exec-1", "hash-1", nil, nil); err != nil { 63 t.Fatalf("AddExecutorToken: %v", err) 64 } 65 66 deleted, err := d.RevokeExecutorToken("exec-1") 67 if err != nil { 68 t.Fatalf("RevokeExecutorToken: %v", err) 69 } 70 if !deleted { 71 t.Fatal("RevokeExecutorToken reported no deletion for an existing identity") 72 } 73 74 if _, _, ok, _ := d.ResolveExecutorToken("hash-1"); ok { 75 t.Fatal("revoked token still resolves; revocation is not enforced") 76 } 77 78 if deleted, err := d.RevokeExecutorToken("exec-1"); err != nil || deleted { 79 t.Fatalf("RevokeExecutorToken(already-gone) = (%v, %v), want (false, nil)", deleted, err) 80 } 81 82 if deleted, err := d.RevokeExecutorToken("ghost"); err != nil || deleted { 83 t.Fatalf("RevokeExecutorToken(unknown) = (%v, %v), want (false, nil)", deleted, err) 84 } 85} 86 87func TestExecutorQuarantineIsVisibleAndReversible(t *testing.T) { 88 d := newTestDB(t) 89 if err := d.AddExecutorToken("exec-1", "hash-1", nil, nil); err != nil { 90 t.Fatalf("AddExecutorToken: %v", err) 91 } 92 if err := d.QuarantineExecutor("exec-1", "missed cancel deadline"); err != nil { 93 t.Fatalf("QuarantineExecutor: %v", err) 94 } 95 if _, _, ok, err := d.ResolveExecutorToken("hash-1"); err != nil || ok { 96 t.Fatalf("ResolveExecutorToken(quarantined) = (ok=%v, err=%v), want (false, nil)", ok, err) 97 } 98 tokens, err := d.ListExecutorTokens() 99 if err != nil { 100 t.Fatalf("ListExecutorTokens: %v", err) 101 } 102 if len(tokens) != 1 || tokens[0].QuarantineReason == nil || *tokens[0].QuarantineReason != "missed cancel deadline" || tokens[0].QuarantinedAt == nil { 103 t.Fatalf("quarantined token not surfaced: %+v", tokens) 104 } 105 if cleared, err := d.ClearExecutorQuarantine("exec-1"); err != nil || !cleared { 106 t.Fatalf("ClearExecutorQuarantine = (%v, %v), want (true, nil)", cleared, err) 107 } 108 if _, _, ok, err := d.ResolveExecutorToken("hash-1"); err != nil || !ok { 109 t.Fatalf("ResolveExecutorToken(cleared) = (ok=%v, err=%v), want (true, nil)", ok, err) 110 } 111 if cleared, err := d.ClearExecutorQuarantine("missing"); err != nil || cleared { 112 t.Fatalf("ClearExecutorQuarantine(missing) = (%v, %v), want (false, nil)", cleared, err) 113 } 114} 115 116func TestListExecutorTokens(t *testing.T) { 117 d := newTestDB(t) 118 119 tokens, err := d.ListExecutorTokens() 120 if err != nil { 121 t.Fatalf("ListExecutorTokens(empty): %v", err) 122 } 123 if len(tokens) != 0 { 124 t.Fatalf("ListExecutorTokens on empty table = %d rows, want 0", len(tokens)) 125 } 126 127 // insert out of alphabetical order to test query sorting 128 for _, name := range []string{"charlie", "alice", "bob"} { 129 if err := d.AddExecutorToken(name, "hash-"+name, nil, nil); err != nil { 130 t.Fatalf("AddExecutorToken(%s): %v", name, err) 131 } 132 } 133 134 tokens, err = d.ListExecutorTokens() 135 if err != nil { 136 t.Fatalf("ListExecutorTokens: %v", err) 137 } 138 want := []string{"alice", "bob", "charlie"} 139 if len(tokens) != len(want) { 140 t.Fatalf("ListExecutorTokens = %d rows, want %d", len(tokens), len(want)) 141 } 142 for i := range want { 143 if tokens[i].Name != want[i] { 144 t.Fatalf("ListExecutorTokens[%d].Name = %q, want %q (ordered by name)", i, tokens[i].Name, want[i]) 145 } 146 } 147} 148 149// expired tokens must fail closed 150func TestResolveExecutorTokenExpiry(t *testing.T) { 151 cases := []struct { 152 name string 153 seqno time.Duration 154 noExpiry bool 155 wantOK bool 156 }{ 157 {"future expiry resolves", time.Hour, false, true}, 158 {"past expiry fails closed", -time.Hour, false, false}, 159 {"nil expiry never expires", 0, true, true}, 160 } 161 for _, tc := range cases { 162 t.Run(tc.name, func(t *testing.T) { 163 d := newTestDB(t) 164 165 var expires *time.Time 166 if !tc.noExpiry { 167 exp := time.Now().Add(tc.seqno) 168 expires = &exp 169 } 170 if err := d.AddExecutorToken("exec-1", "hash-1", expires, nil); err != nil { 171 t.Fatalf("AddExecutorToken: %v", err) 172 } 173 174 name, _, ok, err := d.ResolveExecutorToken("hash-1") 175 if err != nil { 176 t.Fatalf("ResolveExecutorToken: %v", err) 177 } 178 if ok != tc.wantOK { 179 t.Fatalf("ResolveExecutorToken ok = %v, want %v", ok, tc.wantOK) 180 } 181 if tc.wantOK && name != "exec-1" { 182 t.Fatalf("ResolveExecutorToken name = %q, want exec-1", name) 183 } 184 if !tc.wantOK && name != "" { 185 t.Fatalf("ResolveExecutorToken name = %q, want \"\" when failing closed", name) 186 } 187 }) 188 } 189} 190 191func TestResolveExecutorTokenRejectsMalformedExpiry(t *testing.T) { 192 d := newTestDB(t) 193 if _, err := d.Exec( 194 `insert into mill_executors (name, token_hash, expires_at) values (?, ?, ?)`, 195 "exec-1", "hash-1", "not-a-timestamp", 196 ); err != nil { 197 t.Fatalf("insert malformed token: %v", err) 198 } 199 200 name, _, ok, err := d.ResolveExecutorToken("hash-1") 201 if err == nil { 202 t.Fatal("ResolveExecutorToken accepted a malformed non-NULL expiry") 203 } 204 if ok || name != "" { 205 t.Fatalf("ResolveExecutorToken = (%q, %v, %v), want (\"\", false, error)", name, ok, err) 206 } 207} 208 209// expiry storage is RFC3339 second precision UTC so 210// round trips compare to the second 211func TestListExecutorTokensSurfacesExpiry(t *testing.T) { 212 d := newTestDB(t) 213 214 exp := time.Now().Add(24 * time.Hour) 215 if err := d.AddExecutorToken("expiring", "hash-exp", &exp, nil); err != nil { 216 t.Fatalf("AddExecutorToken(expiring): %v", err) 217 } 218 if err := d.AddExecutorToken("forever", "hash-forever", nil, nil); err != nil { 219 t.Fatalf("AddExecutorToken(forever): %v", err) 220 } 221 222 tokens, err := d.ListExecutorTokens() 223 if err != nil { 224 t.Fatalf("ListExecutorTokens: %v", err) 225 } 226 227 got := make(map[string]*time.Time, len(tokens)) 228 for _, tok := range tokens { 229 got[tok.Name] = tok.ExpiresAt 230 } 231 232 e, present := got["forever"] 233 if !present { 234 t.Fatal("ListExecutorTokens omitted the non-expiring identity") 235 } 236 if e != nil { 237 t.Fatalf("forever.ExpiresAt = %v, want nil (never expires)", e) 238 } 239 240 e, present = got["expiring"] 241 if !present { 242 t.Fatal("ListExecutorTokens omitted the expiring identity") 243 } 244 if e == nil { 245 t.Fatal("expiring.ExpiresAt = nil, want the stored expiry") 246 } 247 if e.Unix() != exp.Unix() { 248 t.Fatalf("expiring.ExpiresAt = %d (unix), want %d", e.Unix(), exp.Unix()) 249 } 250} 251 252func TestExecutorTokenLabels(t *testing.T) { 253 d := newTestDB(t) 254 255 labels := []string{" foo ", "bar", " foo", ""} 256 if err := d.AddExecutorToken("exec-1", "hash-1", nil, labels); err != nil { 257 t.Fatalf("AddExecutorToken: %v", err) 258 } 259 260 name, resolvedLabels, ok, err := d.ResolveExecutorToken("hash-1") 261 if err != nil { 262 t.Fatalf("ResolveExecutorToken: %v", err) 263 } 264 if !ok || name != "exec-1" { 265 t.Fatalf("ResolveExecutorToken: ok=%v, name=%q, want true, exec-1", ok, name) 266 } 267 268 wantLabels := []string{"bar", "foo"} 269 if !slices.Equal(resolvedLabels, wantLabels) { 270 t.Fatalf("resolved labels = %v, want %v", resolvedLabels, wantLabels) 271 } 272}