github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/typesystem/typesystem_test.go (about)

     1  package typesystem
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/authzed/spicedb/pkg/genutil/mapz"
    11  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    12  
    13  	"github.com/authzed/spicedb/internal/datastore/memdb"
    14  	datastoremw "github.com/authzed/spicedb/internal/middleware/datastore"
    15  	"github.com/authzed/spicedb/pkg/caveats"
    16  	"github.com/authzed/spicedb/pkg/datastore"
    17  	ns "github.com/authzed/spicedb/pkg/namespace"
    18  	"github.com/authzed/spicedb/pkg/schemadsl/compiler"
    19  	"github.com/authzed/spicedb/pkg/schemadsl/input"
    20  	"github.com/authzed/spicedb/pkg/tuple"
    21  )
    22  
    23  func TestTypeSystem(t *testing.T) {
    24  	emptyEnv := caveats.NewEnvironment()
    25  
    26  	testCases := []struct {
    27  		name            string
    28  		toCheck         *core.NamespaceDefinition
    29  		otherNamespaces []*core.NamespaceDefinition
    30  		caveats         []*core.CaveatDefinition
    31  		expectedError   string
    32  	}{
    33  		{
    34  			"invalid relation in computed_userset",
    35  			ns.Namespace(
    36  				"document",
    37  				ns.MustRelation("owner", nil),
    38  				ns.MustRelation("editor", ns.Union(
    39  					ns.ComputedUserset("owner"),
    40  				)),
    41  				ns.MustRelation("parent", nil),
    42  				ns.MustRelation("lock", nil),
    43  				ns.MustRelation("viewer", ns.Union(
    44  					ns.ComputedUserset("editors"),
    45  					ns.TupleToUserset("parent", "viewer"),
    46  				)),
    47  			),
    48  			[]*core.NamespaceDefinition{},
    49  			nil,
    50  			"relation/permission `editors` not found under definition `document`",
    51  		},
    52  		{
    53  			"invalid relation in tuple_to_userset",
    54  			ns.Namespace(
    55  				"document",
    56  				ns.MustRelation("owner", nil),
    57  				ns.MustRelation("editor", ns.Union(
    58  					ns.ComputedUserset("owner"),
    59  				)),
    60  				ns.MustRelation("parent", nil),
    61  				ns.MustRelation("lock", nil),
    62  				ns.MustRelation("viewer", ns.Union(
    63  					ns.ComputedUserset("editor"),
    64  					ns.TupleToUserset("parents", "viewer"),
    65  				)),
    66  			),
    67  			[]*core.NamespaceDefinition{},
    68  			nil,
    69  			"relation/permission `parents` not found under definition `document`",
    70  		},
    71  		{
    72  			"use of permission in tuple_to_userset",
    73  			ns.Namespace(
    74  				"document",
    75  				ns.MustRelation("owner", nil),
    76  				ns.MustRelation("editor", ns.Union(
    77  					ns.ComputedUserset("owner"),
    78  				)),
    79  				ns.MustRelation("parent", nil),
    80  				ns.MustRelation("lock", nil),
    81  				ns.MustRelation("viewer", ns.Union(
    82  					ns.TupleToUserset("editor", "viewer"),
    83  				)),
    84  			),
    85  			[]*core.NamespaceDefinition{},
    86  			nil,
    87  			"under permission `viewer` under definition `document`: permissions cannot be used on the left hand side of an arrow (found `editor`)",
    88  		},
    89  		{
    90  			"rewrite without this and types",
    91  			ns.Namespace(
    92  				"document",
    93  				ns.MustRelation("owner", nil),
    94  				ns.MustRelation("editor", ns.Union(
    95  					ns.ComputedUserset("owner"),
    96  				), ns.AllowedRelation("document", "owner")),
    97  			),
    98  			[]*core.NamespaceDefinition{},
    99  			nil,
   100  			"direct relations are not allowed under relation `editor`",
   101  		},
   102  		{
   103  			"relation in relation types has invalid namespace",
   104  			ns.Namespace(
   105  				"document",
   106  				ns.MustRelation("owner", nil, ns.AllowedRelation("someinvalidns", "...")),
   107  				ns.MustRelation("editor", ns.Union(
   108  					ns.ComputedUserset("owner"),
   109  				)),
   110  				ns.MustRelation("parent", nil),
   111  				ns.MustRelation("lock", nil),
   112  				ns.MustRelation("viewer", ns.Union(
   113  					ns.ComputedUserset("editor"),
   114  					ns.TupleToUserset("parent", "viewer"),
   115  				)),
   116  			),
   117  			[]*core.NamespaceDefinition{},
   118  			nil,
   119  			"could not lookup definition `someinvalidns` for relation `owner`: object definition `someinvalidns` not found",
   120  		},
   121  		{
   122  			"relation in relation types has invalid relation",
   123  			ns.Namespace(
   124  				"document",
   125  				ns.MustRelation("owner", nil, ns.AllowedRelation("anotherns", "foobar")),
   126  				ns.MustRelation("editor", ns.Union(
   127  					ns.ComputedUserset("owner"),
   128  				)),
   129  				ns.MustRelation("parent", nil),
   130  				ns.MustRelation("lock", nil),
   131  				ns.MustRelation("viewer", ns.Union(
   132  					ns.ComputedUserset("editor"),
   133  					ns.TupleToUserset("parent", "viewer"),
   134  				)),
   135  			),
   136  			[]*core.NamespaceDefinition{
   137  				ns.Namespace(
   138  					"anotherns",
   139  				),
   140  			},
   141  			nil,
   142  			"relation/permission `foobar` not found under definition `anotherns`",
   143  		},
   144  		{
   145  			"full type check",
   146  			ns.Namespace(
   147  				"document",
   148  				ns.MustRelation("owner", nil, ns.AllowedRelation("user", "...")),
   149  				ns.MustRelation("can_comment",
   150  					nil,
   151  					ns.AllowedRelation("user", "..."),
   152  					ns.AllowedRelation("folder", "can_comment"),
   153  				),
   154  				ns.MustRelation("editor",
   155  					ns.Union(
   156  						ns.ComputedUserset("owner"),
   157  					),
   158  				),
   159  				ns.MustRelation("parent", nil, ns.AllowedRelation("folder", "...")),
   160  				ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedPublicNamespace("user")),
   161  				ns.MustRelation("view", ns.Union(
   162  					ns.ComputedUserset("viewer"),
   163  					ns.ComputedUserset("editor"),
   164  					ns.TupleToUserset("parent", "view"),
   165  				)),
   166  			),
   167  			[]*core.NamespaceDefinition{
   168  				ns.Namespace("user"),
   169  				ns.Namespace(
   170  					"folder",
   171  					ns.MustRelation("can_comment", nil, ns.AllowedRelation("user", "...")),
   172  					ns.MustRelation("parent", nil, ns.AllowedRelation("folder", "...")),
   173  				),
   174  			},
   175  			nil,
   176  			"",
   177  		},
   178  		{
   179  			"transitive wildcard type check",
   180  			ns.Namespace(
   181  				"document",
   182  				ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelation("group", "member")),
   183  			),
   184  			[]*core.NamespaceDefinition{
   185  				ns.Namespace("user"),
   186  				ns.Namespace(
   187  					"group",
   188  					ns.MustRelation("member", nil, ns.AllowedRelation("user", "..."), ns.AllowedPublicNamespace("user")),
   189  				),
   190  			},
   191  			nil,
   192  			"for relation `viewer`: relation/permission `group#member` includes wildcard type `user` via relation `group#member`: wildcard relations cannot be transitively included",
   193  		},
   194  		{
   195  			"ttu wildcard type check",
   196  			ns.Namespace(
   197  				"folder",
   198  				ns.MustRelation("parent", nil, ns.AllowedRelation("folder", "..."), ns.AllowedPublicNamespace("folder")),
   199  				ns.MustRelation("viewer", ns.Union(
   200  					ns.TupleToUserset("parent", "viewer"),
   201  				)),
   202  			),
   203  			[]*core.NamespaceDefinition{
   204  				ns.Namespace("user"),
   205  			},
   206  			nil,
   207  			"for arrow under permission `viewer`: relation `folder#parent` includes wildcard type `folder` via relation `folder#parent`: wildcard relations cannot be used on the left side of arrows",
   208  		},
   209  		{
   210  			"recursive transitive wildcard type check",
   211  			ns.Namespace(
   212  				"document",
   213  				ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelation("group", "member")),
   214  			),
   215  			[]*core.NamespaceDefinition{
   216  				ns.Namespace("user"),
   217  				ns.Namespace(
   218  					"group",
   219  					ns.MustRelation("member", nil, ns.AllowedRelation("group", "manager"), ns.AllowedRelation("user", "...")),
   220  					ns.MustRelation("manager", nil, ns.AllowedRelation("group", "member"), ns.AllowedPublicNamespace("user")),
   221  				),
   222  			},
   223  			nil,
   224  			"for relation `viewer`: relation/permission `group#member` includes wildcard type `user` via relation `group#manager`: wildcard relations cannot be transitively included",
   225  		},
   226  		{
   227  			"redefinition of allowed relation",
   228  			ns.Namespace(
   229  				"document",
   230  				ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelation("user", "...")),
   231  			),
   232  			[]*core.NamespaceDefinition{
   233  				ns.Namespace("user"),
   234  			},
   235  			nil,
   236  			"found duplicate allowed subject type `user` on relation `viewer` under definition `document`",
   237  		},
   238  		{
   239  			"redefinition of allowed public relation",
   240  			ns.Namespace(
   241  				"document",
   242  				ns.MustRelation("viewer", nil, ns.AllowedPublicNamespace("user"), ns.AllowedPublicNamespace("user")),
   243  			),
   244  			[]*core.NamespaceDefinition{
   245  				ns.Namespace("user"),
   246  			},
   247  			nil,
   248  			"found duplicate allowed subject type `user:*` on relation `viewer` under definition `document`",
   249  		},
   250  		{
   251  			"no redefinition of allowed relation",
   252  			ns.Namespace(
   253  				"document",
   254  				ns.MustRelation("viewer", nil, ns.AllowedPublicNamespace("user"), ns.AllowedRelation("user", "..."), ns.AllowedRelation("user", "viewer")),
   255  			),
   256  			[]*core.NamespaceDefinition{
   257  				ns.Namespace("user", ns.MustRelation("viewer", nil)),
   258  			},
   259  			nil,
   260  			"",
   261  		},
   262  		{
   263  			"unknown caveat",
   264  			ns.Namespace(
   265  				"document",
   266  				ns.MustRelation("viewer", nil, ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("unknown"))),
   267  			),
   268  			[]*core.NamespaceDefinition{
   269  				ns.Namespace("user"),
   270  			},
   271  			nil,
   272  			"could not lookup caveat `unknown` for relation `viewer`: caveat with name `unknown` not found",
   273  		},
   274  		{
   275  			"valid caveat",
   276  			ns.Namespace(
   277  				"document",
   278  				ns.MustRelation("viewer", nil, ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat"))),
   279  			),
   280  			[]*core.NamespaceDefinition{
   281  				ns.Namespace("user"),
   282  			},
   283  			[]*core.CaveatDefinition{
   284  				ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"),
   285  			},
   286  			"",
   287  		},
   288  		{
   289  			"valid optional caveat",
   290  			ns.Namespace(
   291  				"document",
   292  				ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat"))),
   293  			),
   294  			[]*core.NamespaceDefinition{
   295  				ns.Namespace("user"),
   296  			},
   297  			[]*core.CaveatDefinition{
   298  				ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"),
   299  			},
   300  			"",
   301  		},
   302  		{
   303  			"duplicate caveat",
   304  			ns.Namespace(
   305  				"document",
   306  				ns.MustRelation("viewer", nil, ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat")), ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat"))),
   307  			),
   308  			[]*core.NamespaceDefinition{
   309  				ns.Namespace("user"),
   310  			},
   311  			[]*core.CaveatDefinition{
   312  				ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"),
   313  			},
   314  			"found duplicate allowed subject type `user with definedcaveat` on relation `viewer` under definition `document`",
   315  		},
   316  		{
   317  			"valid wildcard caveat",
   318  			ns.Namespace(
   319  				"document",
   320  				ns.MustRelation("viewer", nil, ns.AllowedRelation("user", "..."), ns.AllowedPublicNamespaceWithCaveat("user", ns.AllowedCaveat("definedcaveat"))),
   321  			),
   322  			[]*core.NamespaceDefinition{
   323  				ns.Namespace("user"),
   324  			},
   325  			[]*core.CaveatDefinition{
   326  				ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"),
   327  			},
   328  			"",
   329  		},
   330  		{
   331  			"valid all the caveats",
   332  			ns.Namespace(
   333  				"document",
   334  				ns.MustRelation("viewer", nil,
   335  					ns.AllowedRelation("user", "..."),
   336  					ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("definedcaveat")),
   337  					ns.AllowedPublicNamespaceWithCaveat("user", ns.AllowedCaveat("definedcaveat")),
   338  					ns.AllowedRelationWithCaveat("team", "member", ns.AllowedCaveat("definedcaveat")),
   339  				),
   340  			),
   341  			[]*core.NamespaceDefinition{
   342  				ns.Namespace("user"),
   343  				ns.Namespace("team",
   344  					ns.MustRelation("member", nil),
   345  				),
   346  			},
   347  			[]*core.CaveatDefinition{
   348  				ns.MustCaveatDefinition(emptyEnv, "definedcaveat", "1 == 2"),
   349  			},
   350  			"",
   351  		},
   352  	}
   353  
   354  	for _, tc := range testCases {
   355  		tc := tc
   356  		t.Run(tc.name, func(t *testing.T) {
   357  			require := require.New(t)
   358  
   359  			ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC)
   360  			require.NoError(err)
   361  
   362  			ctx := context.Background()
   363  
   364  			lastRevision, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   365  				for _, otherNS := range tc.otherNamespaces {
   366  					if err := rwt.WriteNamespaces(ctx, otherNS); err != nil {
   367  						return err
   368  					}
   369  				}
   370  				cw := rwt.(datastore.CaveatStorer)
   371  				return cw.WriteCaveats(ctx, tc.caveats)
   372  			})
   373  			require.NoError(err)
   374  
   375  			ts, err := NewNamespaceTypeSystem(tc.toCheck, ResolverForDatastoreReader(ds.SnapshotReader(lastRevision)))
   376  			require.NoError(err)
   377  
   378  			_, terr := ts.Validate(ctx)
   379  			if tc.expectedError == "" {
   380  				require.NoError(terr)
   381  			} else {
   382  				require.Error(terr)
   383  				require.Equal(tc.expectedError, terr.Error())
   384  			}
   385  		})
   386  	}
   387  }
   388  
   389  type tsTester func(t *testing.T, ts *ValidatedNamespaceTypeSystem)
   390  
   391  func noError[T any](result T, err error) T {
   392  	if err != nil {
   393  		panic(err)
   394  	}
   395  
   396  	return result
   397  }
   398  
   399  func requireSameAllowedRelations(t *testing.T, found []*core.AllowedRelation, expected ...*core.AllowedRelation) {
   400  	foundSet := mapz.NewSet[string]()
   401  	for _, f := range found {
   402  		foundSet.Add(SourceForAllowedRelation(f))
   403  	}
   404  
   405  	expectSet := mapz.NewSet[string]()
   406  	for _, e := range expected {
   407  		expectSet.Add(SourceForAllowedRelation(e))
   408  	}
   409  
   410  	foundSlice := foundSet.AsSlice()
   411  	expectedSlice := expectSet.AsSlice()
   412  
   413  	sort.Strings(foundSlice)
   414  	sort.Strings(expectedSlice)
   415  
   416  	require.Equal(t, expectedSlice, foundSlice)
   417  }
   418  
   419  func requireSameSubjectRelations(t *testing.T, found []*core.RelationReference, expected ...*core.RelationReference) {
   420  	foundSet := mapz.NewSet[string]()
   421  	for _, f := range found {
   422  		foundSet.Add(tuple.StringRR(f))
   423  	}
   424  
   425  	expectSet := mapz.NewSet[string]()
   426  	for _, e := range expected {
   427  		expectSet.Add(tuple.StringRR(e))
   428  	}
   429  
   430  	foundSlice := foundSet.AsSlice()
   431  	expectedSlice := expectSet.AsSlice()
   432  
   433  	sort.Strings(foundSlice)
   434  	sort.Strings(expectedSlice)
   435  
   436  	require.Equal(t, expectedSlice, foundSlice)
   437  }
   438  
   439  func TestTypeSystemAccessors(t *testing.T) {
   440  	tcs := []struct {
   441  		name       string
   442  		schema     string
   443  		namespaces map[string]tsTester
   444  	}{
   445  		{
   446  			"basic schema",
   447  			`definition user {}
   448  		
   449  			definition resource {
   450  				relation editor: user
   451  				relation viewer: user
   452  
   453  				permission edit = editor
   454  				permission view = viewer + edit
   455  			}`,
   456  			map[string]tsTester{
   457  				"user": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) {
   458  					require.False(t, vts.IsPermission("somenonpermission"))
   459  				},
   460  				"resource": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) {
   461  					t.Run("GetRelationOrError", func(t *testing.T) {
   462  						require.NotNil(t, noError(vts.GetRelationOrError("editor")))
   463  						require.NotNil(t, noError(vts.GetRelationOrError("viewer")))
   464  
   465  						_, err := vts.GetRelationOrError("someunknownrel")
   466  						require.Error(t, err)
   467  						require.ErrorAs(t, err, &ErrRelationNotFound{})
   468  						require.ErrorContains(t, err, "relation/permission `someunknownrel` not found")
   469  					})
   470  
   471  					t.Run("HasIndirectSubjects", func(t *testing.T) {
   472  						require.False(t, noError(vts.HasIndirectSubjects("editor")))
   473  						require.False(t, noError(vts.HasIndirectSubjects("viewer")))
   474  					})
   475  
   476  					t.Run("IsPermission", func(t *testing.T) {
   477  						require.False(t, vts.IsPermission("somenonpermission"))
   478  
   479  						require.False(t, vts.IsPermission("viewer"))
   480  						require.False(t, vts.IsPermission("editor"))
   481  
   482  						require.True(t, vts.IsPermission("view"))
   483  						require.True(t, vts.IsPermission("edit"))
   484  					})
   485  
   486  					t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) {
   487  						ok, err := vts.RelationDoesNotAllowCaveatsForSubject("viewer", "user")
   488  						require.NoError(t, err)
   489  						require.True(t, ok)
   490  
   491  						ok, err = vts.RelationDoesNotAllowCaveatsForSubject("editor", "user")
   492  						require.NoError(t, err)
   493  						require.True(t, ok)
   494  					})
   495  
   496  					t.Run("IsAllowedPublicNamespace", func(t *testing.T) {
   497  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("editor", "user")))
   498  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("viewer", "user")))
   499  
   500  						_, err := vts.IsAllowedPublicNamespace("unknown", "user")
   501  						require.Error(t, err)
   502  					})
   503  
   504  					t.Run("IsAllowedDirectNamespace", func(t *testing.T) {
   505  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("editor", "user")))
   506  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("viewer", "user")))
   507  
   508  						_, err := vts.IsAllowedPublicNamespace("unknown", "user")
   509  						require.Error(t, err)
   510  					})
   511  
   512  					t.Run("GetAllowedDirectNamespaceSubjectRelations", func(t *testing.T) {
   513  						require.Equal(t, []string{"..."}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("editor", "user")).AsSlice())
   514  						_, err := vts.GetAllowedDirectNamespaceSubjectRelations("unknown", "user")
   515  						require.Error(t, err)
   516  					})
   517  
   518  					t.Run("IsAllowedDirectRelation", func(t *testing.T) {
   519  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("editor", "user", "...")))
   520  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "...")))
   521  
   522  						require.Equal(t, DirectRelationNotValid, noError(vts.IsAllowedDirectRelation("editor", "user", "other")))
   523  						require.Equal(t, DirectRelationNotValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "other")))
   524  
   525  						_, err := vts.IsAllowedDirectRelation("unknown", "user", "...")
   526  						require.Error(t, err)
   527  					})
   528  
   529  					t.Run("HasAllowedRelation", func(t *testing.T) {
   530  						userDirect := ns.AllowedRelation("user", "...")
   531  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("editor", userDirect)))
   532  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", userDirect)))
   533  
   534  						userWithCaveat := ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat"))
   535  						require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("editor", userWithCaveat)))
   536  						require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("viewer", userWithCaveat)))
   537  
   538  						_, err := vts.HasAllowedRelation("unknown", userDirect)
   539  						require.Error(t, err)
   540  					})
   541  
   542  					t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) {
   543  						userDirect := ns.AllowedRelation("user", "...")
   544  						allowed := noError(vts.AllowedDirectRelationsAndWildcards("editor"))
   545  						requireSameAllowedRelations(t, allowed, userDirect)
   546  
   547  						_, err := vts.AllowedDirectRelationsAndWildcards("unknown")
   548  						require.Error(t, err)
   549  					})
   550  
   551  					t.Run("AllowedSubjectRelations", func(t *testing.T) {
   552  						userDirect := ns.RelationReference("user", "...")
   553  						allowed := noError(vts.AllowedSubjectRelations("editor"))
   554  						requireSameSubjectRelations(t, allowed, userDirect)
   555  
   556  						_, err := vts.AllowedSubjectRelations("unknown")
   557  						require.Error(t, err)
   558  					})
   559  				},
   560  			},
   561  		},
   562  		{
   563  			"schema with wildcards",
   564  			`definition user {}
   565  		
   566  			definition resource {
   567  				relation editor: user
   568  				relation viewer: user | user:*
   569  				permission view = viewer + editor
   570  			}`,
   571  			map[string]tsTester{
   572  				"resource": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) {
   573  					t.Run("IsPermission", func(t *testing.T) {
   574  						require.False(t, vts.IsPermission("viewer"))
   575  						require.True(t, vts.IsPermission("view"))
   576  					})
   577  
   578  					t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) {
   579  						ok, err := vts.RelationDoesNotAllowCaveatsForSubject("viewer", "user")
   580  						require.NoError(t, err)
   581  						require.True(t, ok)
   582  
   583  						ok, err = vts.RelationDoesNotAllowCaveatsForSubject("editor", "user")
   584  						require.NoError(t, err)
   585  						require.True(t, ok)
   586  					})
   587  
   588  					t.Run("HasIndirectSubjects", func(t *testing.T) {
   589  						require.False(t, noError(vts.HasIndirectSubjects("editor")))
   590  						require.False(t, noError(vts.HasIndirectSubjects("viewer")))
   591  					})
   592  
   593  					t.Run("IsAllowedPublicNamespace", func(t *testing.T) {
   594  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("editor", "user")))
   595  						require.Equal(t, PublicSubjectAllowed, noError(vts.IsAllowedPublicNamespace("viewer", "user")))
   596  					})
   597  
   598  					t.Run("IsAllowedDirectNamespace", func(t *testing.T) {
   599  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("editor", "user")))
   600  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("viewer", "user")))
   601  					})
   602  
   603  					t.Run("GetAllowedDirectNamespaceSubjectRelations", func(t *testing.T) {
   604  						require.Equal(t, []string{"..."}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("viewer", "user")).AsSlice())
   605  						_, err := vts.GetAllowedDirectNamespaceSubjectRelations("unknown", "user")
   606  						require.Error(t, err)
   607  					})
   608  
   609  					t.Run("IsAllowedDirectRelation", func(t *testing.T) {
   610  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("editor", "user", "...")))
   611  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "...")))
   612  					})
   613  
   614  					t.Run("HasAllowedRelation", func(t *testing.T) {
   615  						userDirect := ns.AllowedRelation("user", "...")
   616  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("editor", userDirect)))
   617  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", userDirect)))
   618  
   619  						userWildcard := ns.AllowedPublicNamespace("user")
   620  						require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("editor", userWildcard)))
   621  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", userWildcard)))
   622  					})
   623  
   624  					t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) {
   625  						userDirect := ns.AllowedRelation("user", "...")
   626  						userWildcard := ns.AllowedPublicNamespace("user")
   627  
   628  						allowed := noError(vts.AllowedDirectRelationsAndWildcards("editor"))
   629  						requireSameAllowedRelations(t, allowed, userDirect)
   630  
   631  						allowed = noError(vts.AllowedDirectRelationsAndWildcards("viewer"))
   632  						requireSameAllowedRelations(t, allowed, userDirect, userWildcard)
   633  					})
   634  
   635  					t.Run("AllowedSubjectRelations", func(t *testing.T) {
   636  						userDirect := ns.RelationReference("user", "...")
   637  						allowed := noError(vts.AllowedSubjectRelations("viewer"))
   638  						requireSameSubjectRelations(t, allowed, userDirect)
   639  					})
   640  				},
   641  			},
   642  		},
   643  		{
   644  			"schema with subject relations",
   645  			`definition user {}
   646  
   647  			definition thirdtype {}
   648  
   649  			definition group {
   650  				relation member: user | group#member
   651  				relation other: user
   652  				relation three: user | group#member | group#other
   653  			}`,
   654  			map[string]tsTester{
   655  				"group": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) {
   656  					t.Run("IsPermission", func(t *testing.T) {
   657  						require.False(t, vts.IsPermission("member"))
   658  					})
   659  
   660  					t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) {
   661  						ok, err := vts.RelationDoesNotAllowCaveatsForSubject("member", "user")
   662  						require.NoError(t, err)
   663  						require.True(t, ok)
   664  
   665  						ok, err = vts.RelationDoesNotAllowCaveatsForSubject("member", "group")
   666  						require.NoError(t, err)
   667  						require.True(t, ok)
   668  					})
   669  
   670  					t.Run("HasIndirectSubjects", func(t *testing.T) {
   671  						require.True(t, noError(vts.HasIndirectSubjects("member")))
   672  					})
   673  
   674  					t.Run("IsAllowedPublicNamespace", func(t *testing.T) {
   675  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("member", "user")))
   676  					})
   677  
   678  					t.Run("IsAllowedDirectNamespace", func(t *testing.T) {
   679  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("member", "user")))
   680  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("member", "group")))
   681  						require.Equal(t, AllowedNamespaceNotValid, noError(vts.IsAllowedDirectNamespace("member", "thirdtype")))
   682  					})
   683  
   684  					t.Run("GetAllowedDirectNamespaceSubjectRelations", func(t *testing.T) {
   685  						require.Equal(t, []string{"..."}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("member", "user")).AsSlice())
   686  						require.Equal(t, []string{"member"}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("member", "group")).AsSlice())
   687  						require.ElementsMatch(t, []string{"member", "other"}, noError(vts.GetAllowedDirectNamespaceSubjectRelations("three", "group")).AsSlice())
   688  						_, err := vts.GetAllowedDirectNamespaceSubjectRelations("unknown", "user")
   689  						require.Error(t, err)
   690  					})
   691  
   692  					t.Run("IsAllowedDirectRelation", func(t *testing.T) {
   693  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("member", "user", "...")))
   694  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("member", "group", "member")))
   695  						require.Equal(t, DirectRelationNotValid, noError(vts.IsAllowedDirectRelation("member", "group", "...")))
   696  					})
   697  
   698  					t.Run("HasAllowedRelation", func(t *testing.T) {
   699  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("member", ns.AllowedRelation("user", "..."))))
   700  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("member", ns.AllowedRelation("group", "member"))))
   701  						require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("member", ns.AllowedRelation("group", "..."))))
   702  					})
   703  
   704  					t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) {
   705  						userDirect := ns.AllowedRelation("user", "...")
   706  						groupMember := ns.AllowedRelation("group", "member")
   707  
   708  						allowed := noError(vts.AllowedDirectRelationsAndWildcards("member"))
   709  						requireSameAllowedRelations(t, allowed, userDirect, groupMember)
   710  					})
   711  
   712  					t.Run("AllowedSubjectRelations", func(t *testing.T) {
   713  						userDirect := ns.RelationReference("user", "...")
   714  						groupMember := ns.RelationReference("group", "member")
   715  
   716  						allowed := noError(vts.AllowedSubjectRelations("member"))
   717  						requireSameSubjectRelations(t, allowed, userDirect, groupMember)
   718  					})
   719  				},
   720  			},
   721  		},
   722  		{
   723  			"schema with caveats",
   724  			`definition user {}
   725  
   726  			caveat somecaveat(somecondition int) {
   727  				somecondition == 42
   728  			}
   729  
   730  			definition resource {
   731  				relation editor: user
   732  				relation viewer: user | user with somecaveat
   733  				relation onlycaveated: user with somecaveat
   734  			}`,
   735  			map[string]tsTester{
   736  				"resource": func(t *testing.T, vts *ValidatedNamespaceTypeSystem) {
   737  					t.Run("IsPermission", func(t *testing.T) {
   738  						require.False(t, vts.IsPermission("editor"))
   739  						require.False(t, vts.IsPermission("viewer"))
   740  						require.False(t, vts.IsPermission("onlycaveated"))
   741  					})
   742  
   743  					t.Run("RelationDoesNotAllowCaveatsForSubject", func(t *testing.T) {
   744  						ok, err := vts.RelationDoesNotAllowCaveatsForSubject("viewer", "user")
   745  						require.NoError(t, err)
   746  						require.False(t, ok)
   747  
   748  						ok, err = vts.RelationDoesNotAllowCaveatsForSubject("editor", "user")
   749  						require.NoError(t, err)
   750  						require.True(t, ok)
   751  
   752  						ok, err = vts.RelationDoesNotAllowCaveatsForSubject("onlycaveated", "user")
   753  						require.NoError(t, err)
   754  						require.False(t, ok)
   755  					})
   756  
   757  					t.Run("HasIndirectSubjects", func(t *testing.T) {
   758  						require.False(t, noError(vts.HasIndirectSubjects("editor")))
   759  						require.False(t, noError(vts.HasIndirectSubjects("viewer")))
   760  						require.False(t, noError(vts.HasIndirectSubjects("onlycaveated")))
   761  					})
   762  
   763  					t.Run("IsAllowedPublicNamespace", func(t *testing.T) {
   764  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("editor", "user")))
   765  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("viewer", "user")))
   766  						require.Equal(t, PublicSubjectNotAllowed, noError(vts.IsAllowedPublicNamespace("onlycaveated", "user")))
   767  					})
   768  
   769  					t.Run("IsAllowedDirectNamespace", func(t *testing.T) {
   770  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("editor", "user")))
   771  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("viewer", "user")))
   772  						require.Equal(t, AllowedNamespaceValid, noError(vts.IsAllowedDirectNamespace("onlycaveated", "user")))
   773  					})
   774  
   775  					t.Run("IsAllowedDirectRelation", func(t *testing.T) {
   776  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("editor", "user", "...")))
   777  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("viewer", "user", "...")))
   778  						require.Equal(t, DirectRelationValid, noError(vts.IsAllowedDirectRelation("onlycaveated", "user", "...")))
   779  					})
   780  
   781  					t.Run("HasAllowedRelation", func(t *testing.T) {
   782  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("editor", ns.AllowedRelation("user", "..."))))
   783  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", ns.AllowedRelation("user", "..."))))
   784  						require.Equal(t, AllowedRelationNotValid, noError(vts.HasAllowedRelation("onlycaveated", ns.AllowedRelation("user", "..."))))
   785  
   786  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("viewer", ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat")))))
   787  						require.Equal(t, AllowedRelationValid, noError(vts.HasAllowedRelation("onlycaveated", ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat")))))
   788  					})
   789  
   790  					t.Run("AllowedDirectRelationsAndWildcards", func(t *testing.T) {
   791  						userDirect := ns.AllowedRelation("user", "...")
   792  						caveatedUser := ns.AllowedRelationWithCaveat("user", "...", ns.AllowedCaveat("somecaveat"))
   793  
   794  						allowed := noError(vts.AllowedDirectRelationsAndWildcards("editor"))
   795  						requireSameAllowedRelations(t, allowed, userDirect)
   796  
   797  						allowed = noError(vts.AllowedDirectRelationsAndWildcards("viewer"))
   798  						requireSameAllowedRelations(t, allowed, userDirect, caveatedUser)
   799  
   800  						allowed = noError(vts.AllowedDirectRelationsAndWildcards("onlycaveated"))
   801  						requireSameAllowedRelations(t, allowed, caveatedUser)
   802  					})
   803  
   804  					t.Run("AllowedSubjectRelations", func(t *testing.T) {
   805  						userDirect := ns.RelationReference("user", "...")
   806  
   807  						allowed := noError(vts.AllowedSubjectRelations("editor"))
   808  						requireSameSubjectRelations(t, allowed, userDirect)
   809  
   810  						allowed = noError(vts.AllowedSubjectRelations("viewer"))
   811  						requireSameSubjectRelations(t, allowed, userDirect)
   812  
   813  						allowed = noError(vts.AllowedSubjectRelations("onlycaveated"))
   814  						requireSameSubjectRelations(t, allowed, userDirect)
   815  					})
   816  				},
   817  			},
   818  		},
   819  	}
   820  
   821  	for _, tc := range tcs {
   822  		tc := tc
   823  		t.Run(tc.name, func(t *testing.T) {
   824  			require := require.New(t)
   825  
   826  			ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC)
   827  			require.NoError(err)
   828  
   829  			ctx := datastoremw.ContextWithDatastore(context.Background(), ds)
   830  
   831  			compiled, err := compiler.Compile(compiler.InputSchema{
   832  				Source:       input.Source("schema"),
   833  				SchemaString: tc.schema,
   834  			}, compiler.AllowUnprefixedObjectType())
   835  			require.NoError(err)
   836  
   837  			lastRevision, err := ds.HeadRevision(context.Background())
   838  			require.NoError(err)
   839  
   840  			for _, nsDef := range compiled.ObjectDefinitions {
   841  				reader := ds.SnapshotReader(lastRevision)
   842  				ts, err := NewNamespaceTypeSystem(nsDef,
   843  					ResolverForDatastoreReader(reader).WithPredefinedElements(PredefinedElements{
   844  						Namespaces: compiled.ObjectDefinitions,
   845  						Caveats:    compiled.CaveatDefinitions,
   846  					}))
   847  				require.NoError(err)
   848  
   849  				vts, terr := ts.Validate(ctx)
   850  				require.NoError(terr)
   851  
   852  				require.Equal(vts.Namespace(), nsDef)
   853  
   854  				tester, ok := tc.namespaces[nsDef.Name]
   855  				if ok {
   856  					tester := tester
   857  					vts := vts
   858  					t.Run(nsDef.Name, func(t *testing.T) {
   859  						for _, relation := range nsDef.Relation {
   860  							require.True(vts.IsPermission(relation.Name) || vts.HasTypeInformation(relation.Name))
   861  						}
   862  
   863  						tester(t, vts)
   864  					})
   865  				}
   866  			}
   867  		})
   868  	}
   869  }