cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/ociauth/scope_test.go (about) 1 package ociauth 2 3 import ( 4 "testing" 5 6 "github.com/go-quicktest/qt" 7 ) 8 9 var parseScopeTests = []struct { 10 testName string 11 in string 12 canonicalString string 13 wantScopes []ResourceScope 14 }{{ 15 testName: "SingleRepository", 16 in: "repository:foo/bar/baz:pull", 17 canonicalString: "repository:foo/bar/baz:pull", 18 wantScopes: []ResourceScope{{ 19 ResourceType: "repository", 20 Resource: "foo/bar/baz", 21 Action: "pull", 22 }}, 23 }, { 24 testName: "SingleRepositoryMultipleAction", 25 in: "repository:foo/bar/baz:push,pull", 26 canonicalString: "repository:foo/bar/baz:pull,push", 27 wantScopes: []ResourceScope{{ 28 ResourceType: "repository", 29 Resource: "foo/bar/baz", 30 Action: "pull", 31 }, { 32 ResourceType: "repository", 33 Resource: "foo/bar/baz", 34 Action: "push", 35 }}, 36 }, { 37 testName: "MultipleRepositories", 38 in: "repository:foo/bar/baz:push,pull repository:other:pull", 39 canonicalString: "repository:foo/bar/baz:pull,push repository:other:pull", 40 wantScopes: []ResourceScope{{ 41 ResourceType: "repository", 42 Resource: "foo/bar/baz", 43 Action: "pull", 44 }, { 45 ResourceType: "repository", 46 Resource: "foo/bar/baz", 47 Action: "push", 48 }, { 49 ResourceType: "repository", 50 Resource: "other", 51 Action: "pull", 52 }}, 53 }, { 54 testName: "MultipleRepositoriesWithCatalog", 55 in: "repository:foo/bar/baz:push,pull registry:catalog:* repository:other:pull", 56 canonicalString: "registry:catalog:* repository:foo/bar/baz:pull,push repository:other:pull", 57 wantScopes: []ResourceScope{CatalogScope, { 58 ResourceType: "repository", 59 Resource: "foo/bar/baz", 60 Action: "pull", 61 }, { 62 ResourceType: "repository", 63 Resource: "foo/bar/baz", 64 Action: "push", 65 }, { 66 ResourceType: "repository", 67 Resource: "other", 68 Action: "pull", 69 }}, 70 }, { 71 testName: "UnknownScope", 72 in: "otherScope", 73 canonicalString: "otherScope", 74 wantScopes: []ResourceScope{{ 75 ResourceType: "otherScope", 76 }}, 77 }, { 78 testName: "UnknownAction", 79 in: "repository:foo/bar/baz:delete,push,pull", 80 canonicalString: "repository:foo/bar/baz:delete,pull,push", 81 wantScopes: []ResourceScope{{ 82 ResourceType: "repository", 83 Resource: "foo/bar/baz", 84 Action: "delete", 85 }, { 86 ResourceType: "repository", 87 Resource: "foo/bar/baz", 88 Action: "pull", 89 }, { 90 ResourceType: "repository", 91 Resource: "foo/bar/baz", 92 Action: "push", 93 }}, 94 }, { 95 testName: "SeveralUnknown", 96 in: "repository:foo/bar/baz:delete,pull,push repository:other:pull otherScope", 97 canonicalString: "otherScope repository:foo/bar/baz:delete,pull,push repository:other:pull", 98 wantScopes: []ResourceScope{{ 99 ResourceType: "otherScope", 100 }, { 101 ResourceType: "repository", 102 Resource: "foo/bar/baz", 103 Action: "delete", 104 }, { 105 ResourceType: "repository", 106 Resource: "foo/bar/baz", 107 Action: "pull", 108 }, { 109 ResourceType: "repository", 110 Resource: "foo/bar/baz", 111 Action: "push", 112 }, { 113 ResourceType: "repository", 114 Resource: "other", 115 Action: "pull", 116 }}, 117 }, { 118 testName: "duplicates", 119 in: "repository:foo/bar/baz:delete,pull,push otherScope repository:foo/bar/baz:pull,push repository:other:pull otherScope", 120 canonicalString: "otherScope repository:foo/bar/baz:delete,pull,push repository:other:pull", 121 wantScopes: []ResourceScope{{ 122 ResourceType: "otherScope", 123 }, { 124 ResourceType: "repository", 125 Resource: "foo/bar/baz", 126 Action: "delete", 127 }, { 128 ResourceType: "repository", 129 Resource: "foo/bar/baz", 130 Action: "pull", 131 }, { 132 ResourceType: "repository", 133 Resource: "foo/bar/baz", 134 Action: "push", 135 }, { 136 ResourceType: "repository", 137 Resource: "other", 138 Action: "pull", 139 }}, 140 }} 141 142 func TestParseScope(t *testing.T) { 143 for _, test := range parseScopeTests { 144 t.Run(test.testName, func(t *testing.T) { 145 scope := ParseScope(test.in) 146 t.Logf("parsed scope: %#v", scope) 147 qt.Check(t, qt.Equals(scope.Canonical().String(), test.canonicalString)) 148 qt.Check(t, qt.Equals(scope.String(), test.in)) 149 qt.Check(t, qt.DeepEquals(all(scope.Iter()), test.wantScopes)) 150 checkStrictOrder(t, scope.Iter(), ResourceScope.Compare) 151 // Check that it does actually preserve identity on round-trip. 152 scope1 := ParseScope(scope.String()) 153 qt.Check(t, qt.Equals(scope1.Equal(scope), true)) 154 }) 155 } 156 } 157 158 var scopeUnionTests = []struct { 159 testName string 160 s1 string 161 s2 string 162 want string 163 wantUnlimited bool 164 }{{ 165 testName: "Empty", 166 s1: "", 167 s2: "", 168 want: "", 169 }, { 170 testName: "EmptyAndSingle", 171 s1: "", 172 s2: "repository:foo:pull", 173 want: "repository:foo:pull", 174 }, { 175 testName: "SingleAndEmpty", 176 s1: "repository:foo:pull", 177 s2: "", 178 want: "repository:foo:pull", 179 }, { 180 testName: "UnlimitedAndSomething", 181 s1: "*", 182 s2: "repository:foo:pull", 183 want: "*", 184 wantUnlimited: true, 185 }, { 186 testName: "SomethingAndUnlimited", 187 s1: "repository:foo:pull", 188 s2: "*", 189 want: "*", 190 wantUnlimited: true, 191 }, { 192 testName: "UnlimitedAndUnlimited", 193 s1: "*", 194 s2: "*", 195 want: "*", 196 wantUnlimited: true, 197 }, { 198 testName: "Multiple", 199 s1: "anotherScope:bad otherScope repository:arble:pull repository:foo:pull,push", 200 s2: "otherScope registry:catalog:* repository:foo:delete repository:bar/baz:pull yetAnotherScope", 201 want: "anotherScope:bad otherScope registry:catalog:* repository:arble:pull repository:bar/baz:pull repository:foo:delete,pull,push yetAnotherScope", 202 }, { 203 testName: "Identical", 204 s1: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 205 s2: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 206 want: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 207 }, { 208 testName: "Identical", 209 s1: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 210 s2: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 211 want: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 212 }, { 213 testName: "StringPreservedWhenResultEqual", 214 s1: "repository:bar/baz:something,pull arble", 215 s2: "arble", 216 want: "repository:bar/baz:something,pull arble", 217 }} 218 219 func TestScopeUnion(t *testing.T) { 220 for _, test := range scopeUnionTests { 221 t.Run(test.testName, func(t *testing.T) { 222 s1 := parseScopeMaybeUnlimited(test.s1) 223 s2 := parseScopeMaybeUnlimited(test.s2) 224 u1 := s1.Union(s2) 225 qt.Check(t, qt.Equals(u1.String(), test.want)) 226 qt.Check(t, qt.Equals(u1.IsUnlimited(), test.wantUnlimited)) 227 228 // Check that it's commutative. 229 u2 := s2.Union(s1) 230 qt.Check(t, qt.Equals(u1.String(), test.want)) 231 qt.Check(t, qt.Equals(u1.IsUnlimited(), test.wantUnlimited)) 232 233 qt.Check(t, qt.IsTrue(u1.Equal(u2))) 234 }) 235 } 236 } 237 238 var scopeHoldsTests = []struct { 239 testName string 240 s string 241 holds ResourceScope 242 want bool 243 }{{ 244 testName: "Empty", 245 s: "", 246 holds: ResourceScope{"repository", "foo", "pull"}, 247 want: false, 248 }, { 249 testName: "RepoMemberPresent", 250 s: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 251 holds: ResourceScope{"repository", "bar/baz", "pull"}, 252 want: true, 253 }, { 254 testName: "RepoMemberNotPresent", 255 s: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 256 holds: ResourceScope{"repository", "bar/baz", "push"}, 257 want: false, 258 }, { 259 testName: "CatalogScopePresent", 260 s: "otherScope registry:catalog:* repository:bar/baz:pull repository:foo:delete yetAnotherScope", 261 holds: CatalogScope, 262 want: true, 263 }, { 264 testName: "CatalogScopeNotPresent", 265 s: "otherScope repository:bar/baz:pull repository:foo:delete yetAnotherScope", 266 holds: CatalogScope, 267 want: false, 268 }, { 269 testName: "OtherScopePresent", 270 s: "otherScope repository:bar/baz:pull repository:foo:delete yetAnotherScope", 271 holds: ResourceScope{"otherScope", "", ""}, 272 want: true, 273 }, { 274 testName: "OtherScopeNotPresent", 275 s: "otherScope repository:bar/baz:pull repository:foo:delete yetAnotherScope", 276 holds: ResourceScope{"notThere", "", ""}, 277 want: false, 278 }, { 279 testName: "Unlimited", 280 s: "*", 281 holds: ResourceScope{"repository", "bar/baz", "push"}, 282 want: true, 283 }} 284 285 func TestScopeHolds(t *testing.T) { 286 for _, test := range scopeHoldsTests { 287 t.Run(test.testName, func(t *testing.T) { 288 qt.Assert(t, qt.Equals(parseScopeMaybeUnlimited(test.s).Holds(test.holds), test.want)) 289 }) 290 } 291 } 292 293 var scopeContainsTests = []struct { 294 testName string 295 s1 string 296 s2 string 297 want bool 298 }{{ 299 testName: "EmptyContainsEmpty", 300 s1: "", 301 s2: "", 302 want: true, 303 }, { 304 testName: "SomethingContainsEmpty", 305 s1: "foo", 306 s2: "", 307 want: true, 308 }, { 309 testName: "UnlimitedContainsSomething", 310 s1: "*", 311 s2: "foo", 312 want: true, 313 }, { 314 testName: "SomethingDoesNotContainUnlimited", 315 s1: "foo", 316 s2: "*", 317 want: false, 318 }, { 319 testName: "UnlimitedContainsUnlimited", 320 s1: "*", 321 s2: "*", 322 want: true, 323 }, { 324 testName: "MultipleContainsMultiple", 325 s1: "otherScope registry:catalog:* repository:bar/baz:push,pull repository:foo:delete yetAnotherScope", 326 s2: "otherScope registry:catalog:* repository:bar/baz:pull", 327 want: true, 328 }, { 329 testName: "MultipleDoesNotContainMultiple", 330 s1: "otherScope registry:catalog:* repository:bar/baz:push repository:foo:delete yetAnotherScope", 331 s2: "otherScope registry:catalog:* repository:bar/baz:pull", 332 want: false, 333 }, { 334 testName: "RepositoryNotPresent", 335 s1: "otherScope registry:catalog:* repository:bar/baz:push repository:foo:delete yetAnotherScope", 336 s2: "repository:other:pull", 337 want: false, 338 }, { 339 testName: "OtherNotPresent#1", 340 s1: "otherScope registry:catalog:* repository:bar/baz:push repository:foo:delete yetAnotherScope", 341 s2: "arble zaphod", 342 want: false, 343 }, { 344 testName: "OtherNotPresent#2", 345 s1: "otherScope registry:catalog:* repository:bar/baz:push repository:foo:delete yetAnotherScope", 346 s2: "arble", 347 want: false, 348 }} 349 350 func TestScopeContains(t *testing.T) { 351 for _, test := range scopeContainsTests { 352 t.Run(test.testName, func(t *testing.T) { 353 s1 := parseScopeMaybeUnlimited(test.s1) 354 s2 := parseScopeMaybeUnlimited(test.s2) 355 qt.Assert(t, qt.Equals(s1.Contains(s2), test.want)) 356 if s1.Equal(s2) { 357 qt.Assert(t, qt.IsTrue(s2.Contains(s1))) 358 } else if test.want { 359 qt.Assert(t, qt.IsFalse(s2.Contains(s1))) 360 } 361 }) 362 } 363 } 364 365 var scopeLenTests = []struct { 366 scope Scope 367 want int 368 }{{ 369 scope: ParseScope("repository:foo:pull,push repository:bar:pull,delete other registry:catalog:*"), 370 want: 6, 371 }, { 372 scope: NewScope(), 373 want: 0, 374 }, { 375 scope: ParseScope("repository:foo:pull,push repository:bar:pull,delete other").Union( 376 ParseScope("repository:bar:pull repository:bar:push repository:baz:pull more"), 377 ), 378 want: 8, 379 }, { 380 scope: NewScope(CatalogScope), 381 want: 1, 382 }} 383 384 func TestScopeLen(t *testing.T) { 385 for _, test := range scopeLenTests { 386 t.Run(test.scope.String(), func(t *testing.T) { 387 qt.Assert(t, qt.Equals(test.scope.Len(), test.want), qt.Commentf("%v", test.scope)) 388 }) 389 } 390 } 391 392 func TestScopeLenOnUnlimitedScopePanics(t *testing.T) { 393 qt.Assert(t, qt.PanicMatches(func() { 394 UnlimitedScope().Len() 395 }, "Len called on unlimited scope")) 396 } 397 398 func parseScopeMaybeUnlimited(s string) Scope { 399 if s == "*" { 400 return UnlimitedScope() 401 } 402 return ParseScope(s) 403 } 404 405 func checkStrictOrder[T any](t *testing.T, iter func(func(T) bool), cmp func(T, T) int) { 406 hasPrev := false 407 var prev T 408 i := -1 409 iter(func(x T) bool { 410 i++ 411 if !hasPrev { 412 prev = x 413 hasPrev = true 414 return true 415 } 416 if c := cmp(prev, x); c != -1 { 417 t.Fatalf("unexpected ordering at index %d: %v >= %v", i, prev, x) 418 } 419 prev = x 420 return true 421 }) 422 } 423 424 func all[T any](iter func(func(T) bool)) []T { 425 xs := []T{} 426 iter(func(x T) bool { 427 xs = append(xs, x) 428 return true 429 }) 430 return xs 431 }