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  }