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 }