github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/datastore/test/namespace.go (about)

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/stretchr/testify/require"
    10  	"google.golang.org/protobuf/testing/protocmp"
    11  
    12  	"github.com/authzed/spicedb/internal/testfixtures"
    13  	"github.com/authzed/spicedb/pkg/datastore"
    14  	ns "github.com/authzed/spicedb/pkg/namespace"
    15  	"github.com/authzed/spicedb/pkg/schemadsl/compiler"
    16  	"github.com/authzed/spicedb/pkg/schemadsl/generator"
    17  	"github.com/authzed/spicedb/pkg/schemadsl/input"
    18  	"github.com/authzed/spicedb/pkg/testutil"
    19  	"github.com/authzed/spicedb/pkg/tuple"
    20  )
    21  
    22  var (
    23  	testNamespace = ns.Namespace("foo/bar",
    24  		ns.MustRelation("editor", nil, ns.AllowedRelation(testUserNS.Name, "...")),
    25  	)
    26  
    27  	updatedNamespace = ns.Namespace(testNamespace.Name,
    28  		ns.MustRelation("reader", nil, ns.AllowedRelation(testUserNS.Name, "...")),
    29  		ns.MustRelation("editor", nil, ns.AllowedRelation(testUserNS.Name, "...")),
    30  	)
    31  )
    32  
    33  // NamespaceNotFoundTest tests to ensure that an unknown namespace returns the expected
    34  // error.
    35  func NamespaceNotFoundTest(t *testing.T, tester DatastoreTester) {
    36  	require := require.New(t)
    37  
    38  	ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
    39  	require.NoError(err)
    40  
    41  	ctx := context.Background()
    42  
    43  	startRevision, err := ds.HeadRevision(ctx)
    44  	require.NoError(err)
    45  
    46  	_, _, err = ds.SnapshotReader(startRevision).ReadNamespaceByName(ctx, "unknown")
    47  	require.True(errors.As(err, &datastore.ErrNamespaceNotFound{}))
    48  }
    49  
    50  // NamespaceWriteTest tests whether or not the requirements for writing
    51  // namespaces hold for a particular datastore.
    52  func NamespaceWriteTest(t *testing.T, tester DatastoreTester) {
    53  	require := require.New(t)
    54  
    55  	ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
    56  	require.NoError(err)
    57  
    58  	ctx := context.Background()
    59  
    60  	startRevision, err := ds.HeadRevision(ctx)
    61  	require.NoError(err)
    62  
    63  	nsDefs, err := ds.SnapshotReader(startRevision).ListAllNamespaces(ctx)
    64  	require.NoError(err)
    65  	require.Equal(0, len(nsDefs))
    66  
    67  	writtenRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
    68  		return rwt.WriteNamespaces(ctx, testUserNS)
    69  	})
    70  	require.NoError(err)
    71  	require.True(writtenRev.GreaterThan(startRevision))
    72  
    73  	nsDefs, err = ds.SnapshotReader(writtenRev).ListAllNamespaces(ctx)
    74  	require.NoError(err)
    75  	require.Equal(1, len(nsDefs))
    76  	require.Equal(testUserNS.Name, nsDefs[0].Definition.Name)
    77  
    78  	secondWritten, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
    79  		return rwt.WriteNamespaces(ctx, testNamespace)
    80  	})
    81  	require.NoError(err)
    82  	require.True(secondWritten.GreaterThan(writtenRev))
    83  
    84  	nsDefs, err = ds.SnapshotReader(secondWritten).ListAllNamespaces(ctx)
    85  	require.NoError(err)
    86  	require.Equal(2, len(nsDefs))
    87  
    88  	_, _, err = ds.SnapshotReader(writtenRev).ReadNamespaceByName(ctx, testNamespace.Name)
    89  	require.Error(err)
    90  
    91  	nsDefs, err = ds.SnapshotReader(writtenRev).ListAllNamespaces(ctx)
    92  	require.NoError(err)
    93  	require.Equal(1, len(nsDefs))
    94  
    95  	found, createdRev, err := ds.SnapshotReader(secondWritten).ReadNamespaceByName(ctx, testNamespace.Name)
    96  	require.NoError(err)
    97  	require.False(createdRev.GreaterThan(secondWritten))
    98  	require.True(createdRev.GreaterThan(startRevision))
    99  	foundDiff := cmp.Diff(testNamespace, found, protocmp.Transform())
   100  	require.Empty(foundDiff)
   101  
   102  	updatedRevision, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   103  		return rwt.WriteNamespaces(ctx, updatedNamespace)
   104  	})
   105  	require.NoError(err)
   106  
   107  	checkUpdated, createdRev, err := ds.SnapshotReader(updatedRevision).ReadNamespaceByName(ctx, testNamespace.Name)
   108  	require.NoError(err)
   109  	require.False(createdRev.GreaterThan(updatedRevision))
   110  	require.True(createdRev.GreaterThan(startRevision))
   111  	foundUpdated := cmp.Diff(updatedNamespace, checkUpdated, protocmp.Transform())
   112  	require.Empty(foundUpdated)
   113  
   114  	checkOld, createdRev, err := ds.SnapshotReader(writtenRev).ReadNamespaceByName(ctx, testUserNamespace)
   115  	require.NoError(err)
   116  	require.False(createdRev.GreaterThan(writtenRev))
   117  	require.True(createdRev.GreaterThan(startRevision))
   118  	require.Empty(cmp.Diff(testUserNS, checkOld, protocmp.Transform()))
   119  
   120  	checkOldList, err := ds.SnapshotReader(writtenRev).ListAllNamespaces(ctx)
   121  	require.NoError(err)
   122  	require.Equal(1, len(checkOldList))
   123  	require.Equal(testUserNS.Name, checkOldList[0].Definition.Name)
   124  	require.Empty(cmp.Diff(testUserNS, checkOldList[0].Definition, protocmp.Transform()))
   125  
   126  	checkLookup, err := ds.SnapshotReader(secondWritten).LookupNamespacesWithNames(ctx, []string{testNamespace.Name})
   127  	require.NoError(err)
   128  	require.Equal(1, len(checkLookup))
   129  	require.Equal(testNamespace.Name, checkLookup[0].Definition.Name)
   130  	require.Empty(cmp.Diff(testNamespace, checkLookup[0].Definition, protocmp.Transform()))
   131  
   132  	checkLookupMultiple, err := ds.SnapshotReader(secondWritten).LookupNamespacesWithNames(ctx, []string{testNamespace.Name, testUserNS.Name})
   133  	require.NoError(err)
   134  	require.Equal(2, len(checkLookupMultiple))
   135  
   136  	emptyLookup, err := ds.SnapshotReader(secondWritten).LookupNamespacesWithNames(ctx, []string{"anothername"})
   137  	require.NoError(err)
   138  	require.Equal(0, len(emptyLookup))
   139  }
   140  
   141  // NamespaceDeleteTest tests whether or not the requirements for deleting
   142  // namespaces hold for a particular datastore.
   143  func NamespaceDeleteTest(t *testing.T, tester DatastoreTester) {
   144  	require := require.New(t)
   145  
   146  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   147  	require.NoError(err)
   148  
   149  	ds, revision := testfixtures.StandardDatastoreWithData(rawDS, require)
   150  	ctx := context.Background()
   151  
   152  	tRequire := testfixtures.TupleChecker{Require: require, DS: ds}
   153  	docTpl := tuple.Parse(testfixtures.StandardTuples[0])
   154  	require.NotNil(docTpl)
   155  	tRequire.TupleExists(ctx, docTpl, revision)
   156  
   157  	folderTpl := tuple.Parse(testfixtures.StandardTuples[2])
   158  	require.NotNil(folderTpl)
   159  	tRequire.TupleExists(ctx, folderTpl, revision)
   160  
   161  	deletedRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   162  		return rwt.DeleteNamespaces(ctx, testfixtures.DocumentNS.Name)
   163  	})
   164  	require.NoError(err)
   165  	require.True(deletedRev.GreaterThan(revision))
   166  
   167  	_, _, err = ds.SnapshotReader(deletedRev).ReadNamespaceByName(ctx, testfixtures.DocumentNS.Name)
   168  	require.True(errors.As(err, &datastore.ErrNamespaceNotFound{}))
   169  
   170  	found, nsCreatedRev, err := ds.SnapshotReader(deletedRev).ReadNamespaceByName(ctx, testfixtures.FolderNS.Name)
   171  	require.NotNil(found)
   172  	require.True(nsCreatedRev.LessThan(deletedRev))
   173  	require.NoError(err)
   174  
   175  	allNamespaces, err := ds.SnapshotReader(deletedRev).ListAllNamespaces(ctx)
   176  	require.NoError(err)
   177  	for _, ns := range allNamespaces {
   178  		require.NotEqual(testfixtures.DocumentNS.Name, ns.Definition.Name, "deleted namespace '%s' should not be in namespace list", ns.Definition.Name)
   179  	}
   180  
   181  	deletedRevision, err := ds.HeadRevision(ctx)
   182  	require.NoError(err)
   183  
   184  	iter, err := ds.SnapshotReader(deletedRevision).QueryRelationships(ctx, datastore.RelationshipsFilter{
   185  		OptionalResourceType: testfixtures.DocumentNS.Name,
   186  	})
   187  	require.NoError(err)
   188  	tRequire.VerifyIteratorResults(iter)
   189  
   190  	tRequire.TupleExists(ctx, folderTpl, deletedRevision)
   191  }
   192  
   193  func NamespaceMultiDeleteTest(t *testing.T, tester DatastoreTester) {
   194  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   195  	require.NoError(t, err)
   196  
   197  	ds, revision := testfixtures.StandardDatastoreWithData(rawDS, require.New(t))
   198  	ctx := context.Background()
   199  
   200  	namespaces, err := ds.SnapshotReader(revision).ListAllNamespaces(ctx)
   201  	require.NoError(t, err)
   202  
   203  	nsNames := make([]string, 0, len(namespaces))
   204  	for _, ns := range namespaces {
   205  		nsNames = append(nsNames, ns.Definition.Name)
   206  	}
   207  
   208  	deletedRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   209  		return rwt.DeleteNamespaces(ctx, nsNames...)
   210  	})
   211  	require.NoError(t, err)
   212  
   213  	namespacesAfterDel, err := ds.SnapshotReader(deletedRev).ListAllNamespaces(ctx)
   214  	require.NoError(t, err)
   215  	require.Len(t, namespacesAfterDel, 0)
   216  }
   217  
   218  // EmptyNamespaceDeleteTest tests deleting an empty namespace in the datastore.
   219  func EmptyNamespaceDeleteTest(t *testing.T, tester DatastoreTester) {
   220  	require := require.New(t)
   221  
   222  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   223  	require.NoError(err)
   224  
   225  	ds, revision := testfixtures.StandardDatastoreWithData(rawDS, require)
   226  	ctx := context.Background()
   227  
   228  	deletedRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   229  		return rwt.DeleteNamespaces(ctx, testfixtures.UserNS.Name)
   230  	})
   231  	require.NoError(err)
   232  	require.True(deletedRev.GreaterThan(revision))
   233  
   234  	_, _, err = ds.SnapshotReader(deletedRev).ReadNamespaceByName(ctx, testfixtures.UserNS.Name)
   235  	require.True(errors.As(err, &datastore.ErrNamespaceNotFound{}))
   236  }
   237  
   238  // StableNamespaceReadWriteTest tests writing a namespace to the datastore and reading it back,
   239  // ensuring that it does not change in any way and that the deserialized data matches that stored.
   240  func StableNamespaceReadWriteTest(t *testing.T, tester DatastoreTester) {
   241  	require := require.New(t)
   242  
   243  	schemaString := `caveat foo(someParam int) {
   244  	someParam == 42
   245  }
   246  
   247  definition document {
   248  	relation viewer: user | user:*
   249  	relation editor: user | group#member with foo
   250  	relation parent: organization
   251  	permission edit = editor
   252  	permission view = viewer + edit + parent->view
   253  	permission other = viewer - edit
   254  	permission intersect = viewer & edit
   255  	permission with_nil = (viewer - edit) & parent->view & nil
   256  }`
   257  
   258  	// Compile namespace to write to the datastore.
   259  	compiled, err := compiler.Compile(compiler.InputSchema{
   260  		Source:       input.Source("schema"),
   261  		SchemaString: schemaString,
   262  	}, compiler.AllowUnprefixedObjectType())
   263  	require.NoError(err)
   264  	require.Equal(2, len(compiled.OrderedDefinitions))
   265  
   266  	// Write the namespace definition to the datastore.
   267  	ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   268  	require.NoError(err)
   269  
   270  	ctx := context.Background()
   271  	updatedRevision, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   272  		err := rwt.WriteCaveats(ctx, compiled.CaveatDefinitions)
   273  		if err != nil {
   274  			return err
   275  		}
   276  
   277  		return rwt.WriteNamespaces(ctx, compiled.ObjectDefinitions...)
   278  	})
   279  	require.NoError(err)
   280  
   281  	// Read the namespace definition back from the datastore and compare.
   282  	nsConfig := compiled.ObjectDefinitions[0]
   283  	readNsDef, _, err := ds.SnapshotReader(updatedRevision).ReadNamespaceByName(ctx, nsConfig.Name)
   284  	require.NoError(err)
   285  	testutil.RequireProtoEqual(t, nsConfig, readNsDef, "found changed namespace definition")
   286  
   287  	// Read the caveat back from the datastore and compare.
   288  	caveatDef := compiled.CaveatDefinitions[0]
   289  	readCaveatDef, _, err := ds.SnapshotReader(updatedRevision).ReadCaveatByName(ctx, caveatDef.Name)
   290  	require.NoError(err)
   291  	testutil.RequireProtoEqual(t, caveatDef, readCaveatDef, "found changed caveat definition")
   292  
   293  	// Ensure the read namespace's string form matches the input as an extra check.
   294  	generated, _, err := generator.GenerateSchema([]compiler.SchemaDefinition{readCaveatDef, readNsDef})
   295  	require.NoError(err)
   296  	require.Equal(schemaString, generated)
   297  }