github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/datastore/test/tuples.go (about) 1 package test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strconv" 8 "sync" 9 "testing" 10 "time" 11 12 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 13 "github.com/stretchr/testify/require" 14 "golang.org/x/sync/errgroup" 15 "google.golang.org/grpc/codes" 16 17 "github.com/authzed/grpcutil" 18 19 "github.com/authzed/spicedb/internal/datastore/common" 20 "github.com/authzed/spicedb/internal/testfixtures" 21 "github.com/authzed/spicedb/pkg/datastore" 22 "github.com/authzed/spicedb/pkg/datastore/options" 23 "github.com/authzed/spicedb/pkg/genutil/mapz" 24 core "github.com/authzed/spicedb/pkg/proto/core/v1" 25 "github.com/authzed/spicedb/pkg/tuple" 26 ) 27 28 const ( 29 testUserNamespace = "test/user" 30 testResourceNamespace = "test/resource" 31 testGroupNamespace = "test/group" 32 testReaderRelation = "reader" 33 testEditorRelation = "editor" 34 testMemberRelation = "member" 35 ellipsis = "..." 36 ) 37 38 // SimpleTest tests whether or not the requirements for simple reading and 39 // writing of relationships hold for a particular datastore. 40 func SimpleTest(t *testing.T, tester DatastoreTester) { 41 testCases := []int{1, 2, 4, 32, 256} 42 43 for _, numTuples := range testCases { 44 numTuples := numTuples 45 t.Run(strconv.Itoa(numTuples), func(t *testing.T) { 46 require := require.New(t) 47 48 ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 49 require.NoError(err) 50 defer ds.Close() 51 52 ctx := context.Background() 53 54 ok, err := ds.ReadyState(ctx) 55 require.NoError(err) 56 require.True(ok.IsReady) 57 58 setupDatastore(ds, require) 59 60 tRequire := testfixtures.TupleChecker{Require: require, DS: ds} 61 62 var testTuples []*core.RelationTuple 63 for i := 0; i < numTuples; i++ { 64 resourceName := fmt.Sprintf("resource%d", i) 65 userName := fmt.Sprintf("user%d", i) 66 67 newTuple := makeTestTuple(resourceName, userName) 68 testTuples = append(testTuples, newTuple) 69 } 70 71 lastRevision, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, testTuples...) 72 require.NoError(err) 73 74 for _, toCheck := range testTuples { 75 tRequire.TupleExists(ctx, toCheck, lastRevision) 76 } 77 78 // Write a duplicate tuple to make sure the datastore rejects it 79 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, testTuples...) 80 require.Error(err) 81 82 dsReader := ds.SnapshotReader(lastRevision) 83 for _, tupleToFind := range testTuples { 84 tupleSubject := tupleToFind.Subject 85 86 // Check that we can find the tuple a number of ways 87 iter, err := dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 88 OptionalResourceType: tupleToFind.ResourceAndRelation.Namespace, 89 OptionalResourceIds: []string{tupleToFind.ResourceAndRelation.ObjectId}, 90 }) 91 require.NoError(err) 92 tRequire.VerifyIteratorResults(iter, tupleToFind) 93 94 // Check without a resource type. 95 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 96 OptionalResourceIds: []string{tupleToFind.ResourceAndRelation.ObjectId}, 97 }) 98 require.NoError(err) 99 tRequire.VerifyIteratorResults(iter, tupleToFind) 100 101 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 102 OptionalResourceType: tupleToFind.ResourceAndRelation.Namespace, 103 OptionalResourceIds: []string{tupleToFind.ResourceAndRelation.ObjectId}, 104 OptionalResourceRelation: tupleToFind.ResourceAndRelation.Relation, 105 }) 106 require.NoError(err) 107 tRequire.VerifyIteratorResults(iter, tupleToFind) 108 109 iter, err = dsReader.ReverseQueryRelationships( 110 ctx, 111 onrToSubjectsFilter(tupleSubject), 112 options.WithResRelation(&options.ResourceRelation{ 113 Namespace: tupleToFind.ResourceAndRelation.Namespace, 114 Relation: tupleToFind.ResourceAndRelation.Relation, 115 }), 116 ) 117 require.NoError(err) 118 tRequire.VerifyIteratorResults(iter, tupleToFind) 119 120 iter, err = dsReader.ReverseQueryRelationships( 121 ctx, 122 onrToSubjectsFilter(tupleSubject), 123 options.WithResRelation(&options.ResourceRelation{ 124 Namespace: tupleToFind.ResourceAndRelation.Namespace, 125 Relation: tupleToFind.ResourceAndRelation.Relation, 126 }), 127 options.WithLimitForReverse(options.LimitOne), 128 ) 129 require.NoError(err) 130 tRequire.VerifyIteratorResults(iter, tupleToFind) 131 132 // Check that we fail to find the tuple with the wrong filters 133 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 134 OptionalResourceType: tupleToFind.ResourceAndRelation.Namespace, 135 OptionalResourceIds: []string{tupleToFind.ResourceAndRelation.ObjectId}, 136 OptionalResourceRelation: "fake", 137 }) 138 require.NoError(err) 139 tRequire.VerifyIteratorResults(iter) 140 141 incorrectUserset := &core.ObjectAndRelation{ 142 Namespace: tupleSubject.Namespace, 143 ObjectId: tupleSubject.ObjectId, 144 Relation: "fake", 145 } 146 147 iter, err = dsReader.ReverseQueryRelationships( 148 ctx, 149 onrToSubjectsFilter(incorrectUserset), 150 options.WithResRelation(&options.ResourceRelation{ 151 Namespace: tupleToFind.ResourceAndRelation.Namespace, 152 Relation: tupleToFind.ResourceAndRelation.Relation, 153 }), 154 ) 155 require.NoError(err) 156 tRequire.VerifyIteratorResults(iter) 157 } 158 159 // Check a query that returns a number of tuples 160 iter, err := dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 161 OptionalResourceType: testResourceNamespace, 162 }) 163 require.NoError(err) 164 tRequire.VerifyIteratorResults(iter, testTuples...) 165 166 // Filter it down to a single tuple with a userset 167 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 168 OptionalResourceType: testResourceNamespace, 169 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 170 { 171 OptionalSubjectType: testUserNamespace, 172 OptionalSubjectIds: []string{"user0"}, 173 }, 174 }, 175 }) 176 require.NoError(err) 177 tRequire.VerifyIteratorResults(iter, testTuples[0]) 178 179 // Check for larger reverse queries. 180 iter, err = dsReader.ReverseQueryRelationships(ctx, datastore.SubjectsFilter{ 181 SubjectType: testUserNamespace, 182 }) 183 require.NoError(err) 184 tRequire.VerifyIteratorResults(iter, testTuples...) 185 186 // Check limit. 187 if len(testTuples) > 1 { 188 limit := uint64(len(testTuples) - 1) 189 iter, err := dsReader.ReverseQueryRelationships(ctx, datastore.SubjectsFilter{ 190 SubjectType: testUserNamespace, 191 }, options.WithLimitForReverse(&limit)) 192 require.NoError(err) 193 defer iter.Close() 194 tRequire.VerifyIteratorCount(iter, len(testTuples)-1) 195 } 196 197 // Check that we can find the group of tuples too 198 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 199 OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace, 200 }) 201 require.NoError(err) 202 tRequire.VerifyIteratorResults(iter, testTuples...) 203 204 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 205 OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace, 206 OptionalResourceRelation: testTuples[0].ResourceAndRelation.Relation, 207 }) 208 require.NoError(err) 209 tRequire.VerifyIteratorResults(iter, testTuples...) 210 211 // Try some bad queries 212 iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 213 OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace, 214 OptionalResourceIds: []string{"fakeobectid"}, 215 }) 216 require.NoError(err) 217 tRequire.VerifyIteratorResults(iter) 218 219 // Delete the first tuple 220 deletedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, testTuples[0]) 221 require.NoError(err) 222 223 // Delete it AGAIN (idempotent delete) and make sure there's no error 224 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, testTuples[0]) 225 require.NoError(err) 226 227 // Verify it can still be read at the old revision 228 tRequire.TupleExists(ctx, testTuples[0], lastRevision) 229 230 // Verify that it does not show up at the new revision 231 tRequire.NoTupleExists(ctx, testTuples[0], deletedAt) 232 alreadyDeletedIter, err := ds.SnapshotReader(deletedAt).QueryRelationships( 233 ctx, 234 datastore.RelationshipsFilter{ 235 OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace, 236 }, 237 ) 238 require.NoError(err) 239 tRequire.VerifyIteratorResults(alreadyDeletedIter, testTuples[1:]...) 240 241 // Write it back 242 returnedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, testTuples[0]) 243 require.NoError(err) 244 tRequire.TupleExists(ctx, testTuples[0], returnedAt) 245 246 // Delete with DeleteRelationship 247 deletedAt, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 248 _, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{ 249 ResourceType: testResourceNamespace, 250 }) 251 require.NoError(err) 252 return err 253 }) 254 require.NoError(err) 255 tRequire.NoTupleExists(ctx, testTuples[0], deletedAt) 256 }) 257 } 258 } 259 260 func ObjectIDsTest(t *testing.T, tester DatastoreTester) { 261 testCases := []string{ 262 "simple", 263 "google|123123123123", 264 "--=base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", 265 "veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylong", 266 } 267 268 for _, tc := range testCases { 269 t.Run(tc, func(t *testing.T) { 270 ctx := context.Background() 271 require := require.New(t) 272 273 ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 274 require.NoError(err) 275 defer ds.Close() 276 277 tpl := makeTestTuple(tc, tc) 278 require.NoError(tpl.Validate()) 279 280 // Write the test tuple 281 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 282 return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{ 283 { 284 Operation: core.RelationTupleUpdate_CREATE, 285 Tuple: tpl, 286 }, 287 }) 288 }) 289 require.NoError(err) 290 291 // Read it back 292 rev, err := ds.HeadRevision(ctx) 293 require.NoError(err) 294 iter, err := ds.SnapshotReader(rev).QueryRelationships(ctx, datastore.RelationshipsFilter{ 295 OptionalResourceType: testResourceNamespace, 296 OptionalResourceIds: []string{tc}, 297 }) 298 require.NoError(err) 299 defer iter.Close() 300 301 first := iter.Next() 302 require.NotNil(first) 303 require.Equal(tc, first.ResourceAndRelation.ObjectId) 304 require.Equal(tc, first.Subject.ObjectId) 305 306 shouldBeNil := iter.Next() 307 require.Nil(shouldBeNil) 308 require.NoError(iter.Err()) 309 }) 310 } 311 } 312 313 // DeleteRelationshipsTest tests whether or not the requirements for deleting 314 // relationships hold for a particular datastore. 315 func DeleteRelationshipsTest(t *testing.T, tester DatastoreTester) { 316 var testTuples []*core.RelationTuple 317 for i := 0; i < 10; i++ { 318 newTuple := makeTestTuple(fmt.Sprintf("resource%d", i), fmt.Sprintf("user%d", i%2)) 319 testTuples = append(testTuples, newTuple) 320 } 321 testTuples[len(testTuples)-1].ResourceAndRelation.Relation = "writer" 322 323 table := []struct { 324 name string 325 inputTuples []*core.RelationTuple 326 filter *v1.RelationshipFilter 327 expectedExistingTuples []*core.RelationTuple 328 expectedNonExistingTuples []*core.RelationTuple 329 }{ 330 { 331 "resourceID", 332 testTuples, 333 &v1.RelationshipFilter{ 334 ResourceType: testResourceNamespace, 335 OptionalResourceId: "resource0", 336 }, 337 testTuples[1:], 338 testTuples[:1], 339 }, 340 { 341 "only resourceID", 342 testTuples, 343 &v1.RelationshipFilter{ 344 OptionalResourceId: "resource0", 345 }, 346 testTuples[1:], 347 testTuples[:1], 348 }, 349 { 350 "only relation", 351 testTuples, 352 &v1.RelationshipFilter{ 353 OptionalRelation: "writer", 354 }, 355 testTuples[:len(testTuples)-1], 356 []*core.RelationTuple{testTuples[len(testTuples)-1]}, 357 }, 358 { 359 "relation", 360 testTuples, 361 &v1.RelationshipFilter{ 362 ResourceType: testResourceNamespace, 363 OptionalRelation: "writer", 364 }, 365 testTuples[:len(testTuples)-1], 366 []*core.RelationTuple{testTuples[len(testTuples)-1]}, 367 }, 368 { 369 "subjectID", 370 testTuples, 371 &v1.RelationshipFilter{ 372 ResourceType: testResourceNamespace, 373 OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: testUserNamespace, OptionalSubjectId: "user0"}, 374 }, 375 []*core.RelationTuple{testTuples[1], testTuples[3], testTuples[5], testTuples[7], testTuples[9]}, 376 []*core.RelationTuple{testTuples[0], testTuples[2], testTuples[4], testTuples[6], testTuples[8]}, 377 }, 378 { 379 "subjectID without resource type", 380 testTuples, 381 &v1.RelationshipFilter{ 382 OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: testUserNamespace, OptionalSubjectId: "user0"}, 383 }, 384 []*core.RelationTuple{testTuples[1], testTuples[3], testTuples[5], testTuples[7], testTuples[9]}, 385 []*core.RelationTuple{testTuples[0], testTuples[2], testTuples[4], testTuples[6], testTuples[8]}, 386 }, 387 { 388 "subjectRelation", 389 testTuples, 390 &v1.RelationshipFilter{ 391 ResourceType: testResourceNamespace, 392 OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: testUserNamespace, OptionalRelation: &v1.SubjectFilter_RelationFilter{Relation: ""}}, 393 }, 394 nil, 395 testTuples, 396 }, 397 { 398 "duplicates", 399 append(testTuples, testTuples[0]), 400 &v1.RelationshipFilter{ 401 ResourceType: testResourceNamespace, 402 OptionalResourceId: "resource0", 403 }, 404 testTuples[1:], 405 testTuples[:1], 406 }, 407 } 408 409 for _, tt := range table { 410 tt := tt 411 t.Run(tt.name, func(t *testing.T) { 412 require := require.New(t) 413 ctx := context.Background() 414 415 ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 416 require.NoError(err) 417 defer ds.Close() 418 419 setupDatastore(ds, require) 420 421 tRequire := testfixtures.TupleChecker{Require: require, DS: ds} 422 423 // NOTE: we write tuples in multiple calls to ReadWriteTransaction because it is not allowed to change 424 // the same tuple in the same transaction. 425 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 426 for _, tpl := range tt.inputTuples { 427 update := tuple.Touch(tpl) 428 err := rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{update}) 429 if err != nil { 430 return err 431 } 432 } 433 return nil 434 }) 435 require.NoError(err) 436 437 deletedAt, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 438 _, err := rwt.DeleteRelationships(ctx, tt.filter) 439 require.NoError(err) 440 return err 441 }) 442 require.NoError(err) 443 444 for _, tpl := range tt.expectedExistingTuples { 445 tRequire.TupleExists(ctx, tpl, deletedAt) 446 } 447 448 for _, tpl := range tt.expectedNonExistingTuples { 449 tRequire.NoTupleExists(ctx, tpl, deletedAt) 450 } 451 }) 452 } 453 } 454 455 // InvalidReadsTest tests whether or not the requirements for reading via 456 // invalid revisions hold for a particular datastore. 457 func InvalidReadsTest(t *testing.T, tester DatastoreTester) { 458 t.Run("revision expiration", func(t *testing.T) { 459 testGCDuration := 600 * time.Millisecond 460 461 require := require.New(t) 462 463 ds, err := tester.New(0, veryLargeGCInterval, testGCDuration, 1) 464 require.NoError(err) 465 defer ds.Close() 466 467 setupDatastore(ds, require) 468 469 ctx := context.Background() 470 471 // Check that we get an error when there are no revisions 472 err = ds.CheckRevision(ctx, datastore.NoRevision) 473 474 revisionErr := datastore.ErrInvalidRevision{} 475 require.True(errors.As(err, &revisionErr)) 476 477 newTuple := makeTestTuple("one", "one") 478 firstWrite, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, newTuple) 479 require.NoError(err) 480 481 // Check that we can read at the just written revision 482 err = ds.CheckRevision(ctx, firstWrite) 483 require.NoError(err) 484 485 // Wait the duration required to allow the revision to expire 486 time.Sleep(testGCDuration * 2) 487 488 // Write another tuple which will allow the first revision to expire 489 nextWrite, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, newTuple) 490 require.NoError(err) 491 492 // Check that we can read at the just written revision 493 err = ds.CheckRevision(ctx, nextWrite) 494 require.NoError(err) 495 496 // Check that we can no longer read the old revision (now allowed to expire) 497 err = ds.CheckRevision(ctx, firstWrite) 498 require.True(errors.As(err, &revisionErr)) 499 require.Equal(datastore.RevisionStale, revisionErr.Reason()) 500 }) 501 } 502 503 // DeleteNotExistantTest tests the deletion of a non-existant relationship. 504 func DeleteNotExistantTest(t *testing.T, tester DatastoreTester) { 505 require := require.New(t) 506 507 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 508 require.NoError(err) 509 510 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 511 ctx := context.Background() 512 513 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 514 err := rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{ 515 tuple.Delete(tuple.MustParse("document:foo#viewer@user:tom#...")), 516 }) 517 require.NoError(err) 518 519 return nil 520 }) 521 require.NoError(err) 522 } 523 524 // DeleteAlreadyDeletedTest tests the deletion of an already-deleted relationship. 525 func DeleteAlreadyDeletedTest(t *testing.T, tester DatastoreTester) { 526 require := require.New(t) 527 528 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 529 require.NoError(err) 530 531 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 532 ctx := context.Background() 533 534 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 535 // Write the relationship. 536 return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{ 537 tuple.Create(tuple.MustParse("document:foo#viewer@user:tom#...")), 538 }) 539 }) 540 require.NoError(err) 541 542 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 543 // Delete the relationship. 544 return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{ 545 tuple.Delete(tuple.MustParse("document:foo#viewer@user:tom#...")), 546 }) 547 }) 548 require.NoError(err) 549 550 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 551 // Delete the relationship again. 552 return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{ 553 tuple.Delete(tuple.MustParse("document:foo#viewer@user:tom#...")), 554 }) 555 }) 556 require.NoError(err) 557 } 558 559 // WriteDeleteWriteTest tests writing a relationship, deleting it, and then writing it again. 560 func WriteDeleteWriteTest(t *testing.T, tester DatastoreTester) { 561 require := require.New(t) 562 563 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 564 require.NoError(err) 565 566 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 567 ctx := context.Background() 568 569 tpl := makeTestTuple("foo", "tom") 570 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl) 571 require.NoError(err) 572 573 ensureTuples(ctx, require, ds, tpl) 574 575 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl) 576 require.NoError(err) 577 578 ensureNotTuples(ctx, require, ds, tpl) 579 580 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl) 581 require.NoError(err) 582 583 ensureTuples(ctx, require, ds, tpl) 584 } 585 586 // CreateAlreadyExistingTest tests creating a relationship twice. 587 func CreateAlreadyExistingTest(t *testing.T, tester DatastoreTester) { 588 require := require.New(t) 589 590 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 591 require.NoError(err) 592 593 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 594 ctx := context.Background() 595 596 tpl1 := makeTestTuple("foo", "tom") 597 tpl2 := makeTestTuple("foo", "sarah") 598 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2) 599 require.NoError(err) 600 601 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1) 602 require.ErrorAs(err, &common.CreateRelationshipExistsError{}) 603 require.Contains(err.Error(), "could not CREATE relationship ") 604 grpcutil.RequireStatus(t, codes.AlreadyExists, err) 605 606 f := func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 607 _, err := rwt.BulkLoad(ctx, testfixtures.NewBulkTupleGenerator(testResourceNamespace, testReaderRelation, testUserNamespace, 1, t)) 608 return err 609 } 610 _, _ = ds.ReadWriteTx(ctx, f) 611 _, err = ds.ReadWriteTx(ctx, f) 612 grpcutil.RequireStatus(t, codes.AlreadyExists, err) 613 } 614 615 // TouchAlreadyExistingTest tests touching a relationship twice. 616 func TouchAlreadyExistingTest(t *testing.T, tester DatastoreTester) { 617 require := require.New(t) 618 619 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 620 require.NoError(err) 621 622 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 623 ctx := context.Background() 624 625 tpl1 := makeTestTuple("foo", "tom") 626 tpl2 := makeTestTuple("foo", "sarah") 627 628 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2) 629 require.NoError(err) 630 631 ensureTuples(ctx, require, ds, tpl1, tpl2) 632 633 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2) 634 require.NoError(err) 635 636 ensureTuples(ctx, require, ds, tpl1, tpl2) 637 638 tpl3 := makeTestTuple("foo", "fred") 639 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl3) 640 require.NoError(err) 641 642 ensureTuples(ctx, require, ds, tpl1, tpl2, tpl3) 643 } 644 645 // CreateDeleteTouchTest tests writing a relationship, deleting it, and then touching it. 646 func CreateDeleteTouchTest(t *testing.T, tester DatastoreTester) { 647 require := require.New(t) 648 649 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 650 require.NoError(err) 651 652 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 653 ctx := context.Background() 654 655 tpl1 := makeTestTuple("foo", "tom") 656 tpl2 := makeTestTuple("foo", "sarah") 657 658 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2) 659 require.NoError(err) 660 661 ensureTuples(ctx, require, ds, tpl1, tpl2) 662 663 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl1, tpl2) 664 require.NoError(err) 665 666 ensureNotTuples(ctx, require, ds, tpl1, tpl2) 667 668 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2) 669 require.NoError(err) 670 671 ensureTuples(ctx, require, ds, tpl1, tpl2) 672 } 673 674 // DeleteOneThousandIndividualInOneCallTest tests deleting 1000 relationships, individually. 675 func DeleteOneThousandIndividualInOneCallTest(t *testing.T, tester DatastoreTester) { 676 require := require.New(t) 677 678 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 679 require.NoError(err) 680 681 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 682 ctx := context.Background() 683 684 // Write the 1000 relationships. 685 tuples := make([]*core.RelationTuple, 0, 1000) 686 for i := 0; i < 1000; i++ { 687 tpl := makeTestTuple("foo", fmt.Sprintf("user%d", i)) 688 tuples = append(tuples, tpl) 689 } 690 691 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tuples...) 692 require.NoError(err) 693 ensureTuples(ctx, require, ds, tuples...) 694 695 // Add an extra tuple. 696 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, makeTestTuple("foo", "extra")) 697 require.NoError(err) 698 ensureTuples(ctx, require, ds, makeTestTuple("foo", "extra")) 699 700 // Delete the first 1000 tuples. 701 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tuples...) 702 require.NoError(err) 703 ensureNotTuples(ctx, require, ds, tuples...) 704 705 // Ensure the extra tuple is still present. 706 ensureTuples(ctx, require, ds, makeTestTuple("foo", "extra")) 707 } 708 709 // DeleteWithLimitTest tests deleting relationships with a limit. 710 func DeleteWithLimitTest(t *testing.T, tester DatastoreTester) { 711 require := require.New(t) 712 713 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 714 require.NoError(err) 715 716 ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require) 717 ctx := context.Background() 718 719 // Write the 1000 relationships. 720 tuples := make([]*core.RelationTuple, 0, 1000) 721 for i := 0; i < 1000; i++ { 722 tpl := makeTestTuple("foo", fmt.Sprintf("user%d", i)) 723 tuples = append(tuples, tpl) 724 } 725 726 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tuples...) 727 require.NoError(err) 728 ensureTuples(ctx, require, ds, tuples...) 729 730 // Delete 100 tuples. 731 var deleteLimit uint64 = 100 732 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 733 limitReached, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{ 734 ResourceType: testResourceNamespace, 735 }, options.WithDeleteLimit(&deleteLimit)) 736 require.NoError(err) 737 require.True(limitReached) 738 return nil 739 }) 740 require.NoError(err) 741 742 // Ensure 900 tuples remain. 743 found := countTuples(ctx, require, ds, testResourceNamespace) 744 require.Equal(900, found) 745 746 // Delete the remainder. 747 deleteLimit = 1000 748 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 749 limitReached, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{ 750 ResourceType: testResourceNamespace, 751 }, options.WithDeleteLimit(&deleteLimit)) 752 require.NoError(err) 753 require.False(limitReached) 754 return nil 755 }) 756 require.NoError(err) 757 758 found = countTuples(ctx, require, ds, testResourceNamespace) 759 require.Equal(0, found) 760 } 761 762 // DeleteCaveatedTupleTest tests deleting a relationship with a caveat. 763 func DeleteCaveatedTupleTest(t *testing.T, tester DatastoreTester) { 764 require := require.New(t) 765 766 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 767 require.NoError(err) 768 769 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 770 ctx := context.Background() 771 772 tpl := tuple.Parse("test/resource:someresource#viewer@test/user:someuser[somecaveat]") 773 774 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl) 775 require.NoError(err) 776 ensureTuples(ctx, require, ds, tpl) 777 778 // Delete the tuple. 779 withoutCaveat := tuple.Parse("test/resource:someresource#viewer@test/user:someuser") 780 781 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, withoutCaveat) 782 require.NoError(err) 783 ensureNotTuples(ctx, require, ds, tpl, withoutCaveat) 784 } 785 786 // DeleteRelationshipsWithVariousFiltersTest tests deleting relationships with various filters. 787 func DeleteRelationshipsWithVariousFiltersTest(t *testing.T, tester DatastoreTester) { 788 tcs := []struct { 789 name string 790 filter *v1.RelationshipFilter 791 relationships []string 792 expectedDeleted []string 793 }{ 794 { 795 name: "resource type", 796 filter: &v1.RelationshipFilter{ 797 ResourceType: "document", 798 }, 799 relationships: []string{ 800 "document:first#viewer@user:tom", 801 "document:second#viewer@user:tom", 802 "folder:secondfolder#viewer@user:tom", 803 "folder:someotherfolder#viewer@user:tom", 804 }, 805 expectedDeleted: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"}, 806 }, 807 { 808 name: "resource id", 809 filter: &v1.RelationshipFilter{ 810 ResourceType: "document", 811 OptionalResourceId: "first", 812 }, 813 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"}, 814 expectedDeleted: []string{"document:first#viewer@user:tom"}, 815 }, 816 { 817 name: "resource id without resource type", 818 filter: &v1.RelationshipFilter{ 819 OptionalResourceId: "first", 820 }, 821 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"}, 822 expectedDeleted: []string{"document:first#viewer@user:tom"}, 823 }, 824 { 825 name: "resource id prefix with resource type", 826 filter: &v1.RelationshipFilter{ 827 ResourceType: "document", 828 OptionalResourceIdPrefix: "f", 829 }, 830 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:fourth#viewer@user:tom", "folder:fsomething#viewer@user:tom"}, 831 expectedDeleted: []string{"document:first#viewer@user:tom", "document:fourth#viewer@user:tom"}, 832 }, 833 { 834 name: "resource id prefix without resource type", 835 filter: &v1.RelationshipFilter{ 836 OptionalResourceIdPrefix: "f", 837 }, 838 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:fourth#viewer@user:tom", "folder:fsomething#viewer@user:tom"}, 839 expectedDeleted: []string{"document:first#viewer@user:tom", "document:fourth#viewer@user:tom", "folder:fsomething#viewer@user:tom"}, 840 }, 841 { 842 name: "resource relation", 843 filter: &v1.RelationshipFilter{ 844 OptionalRelation: "viewer", 845 }, 846 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:third#editor@user:tom", "folder:fsomething#viewer@user:tom"}, 847 expectedDeleted: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "folder:fsomething#viewer@user:tom"}, 848 }, 849 { 850 name: "subject id", 851 filter: &v1.RelationshipFilter{ 852 OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: "user", OptionalSubjectId: "tom"}, 853 }, 854 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:third#editor@user:tom", "document:first#viewer@user:alice"}, 855 expectedDeleted: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:third#editor@user:tom"}, 856 }, 857 { 858 name: "subject filter with relation", 859 filter: &v1.RelationshipFilter{ 860 OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: "user", OptionalRelation: &v1.SubjectFilter_RelationFilter{Relation: "something"}}, 861 }, 862 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom#something"}, 863 expectedDeleted: []string{"document:second#viewer@user:tom#something"}, 864 }, 865 { 866 name: "full match", 867 filter: &v1.RelationshipFilter{ 868 ResourceType: "document", 869 OptionalResourceId: "first", 870 OptionalRelation: "viewer", 871 OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: "user", OptionalSubjectId: "tom"}, 872 }, 873 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:first#editor@user:tom", "document:firster#viewer@user:tom"}, 874 expectedDeleted: []string{"document:first#viewer@user:tom"}, 875 }, 876 } 877 878 for _, tc := range tcs { 879 tc := tc 880 t.Run(tc.name, func(t *testing.T) { 881 for _, withLimit := range []bool{false, true} { 882 t.Run(fmt.Sprintf("withLimit=%v", withLimit), func(t *testing.T) { 883 require := require.New(t) 884 885 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 886 require.NoError(err) 887 888 // Write the initial relationships. 889 ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require) 890 ctx := context.Background() 891 892 allRelationships := mapz.NewSet[string]() 893 for _, rel := range tc.relationships { 894 allRelationships.Add(rel) 895 896 tpl := tuple.MustParse(rel) 897 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl) 898 require.NoError(err) 899 } 900 901 writtenRev, err := ds.HeadRevision(ctx) 902 require.NoError(err) 903 904 var delLimit *uint64 905 if withLimit { 906 limit := uint64(len(tc.expectedDeleted)) 907 delLimit = &limit 908 } 909 910 // Delete the relationships and ensure matching are no longer found. 911 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 912 _, err := rwt.DeleteRelationships(ctx, tc.filter, options.WithDeleteLimit(delLimit)) 913 return err 914 }) 915 require.NoError(err) 916 917 // Read the updated relationships and ensure no matching relationships are found. 918 headRev, err := ds.HeadRevision(ctx) 919 require.NoError(err) 920 921 filter, err := datastore.RelationshipsFilterFromPublicFilter(tc.filter) 922 require.NoError(err) 923 924 reader := ds.SnapshotReader(headRev) 925 iter, err := reader.QueryRelationships(ctx, filter) 926 require.NoError(err) 927 t.Cleanup(iter.Close) 928 929 found := iter.Next() 930 if found != nil { 931 require.Nil(found, "got relationship: %s", tuple.MustString(found)) 932 } 933 iter.Close() 934 935 // Ensure the expected relationships were deleted. 936 resourceTypes := mapz.NewSet[string]() 937 for _, rel := range tc.relationships { 938 tpl := tuple.MustParse(rel) 939 resourceTypes.Add(tpl.ResourceAndRelation.Namespace) 940 } 941 942 allRemainingRelationships := mapz.NewSet[string]() 943 for _, resourceType := range resourceTypes.AsSlice() { 944 iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 945 OptionalResourceType: resourceType, 946 }) 947 require.NoError(err) 948 t.Cleanup(iter.Close) 949 950 for { 951 rel := iter.Next() 952 if rel == nil { 953 break 954 } 955 allRemainingRelationships.Add(tuple.MustString(rel)) 956 } 957 iter.Close() 958 } 959 960 deletedRelationships := allRelationships.Subtract(allRemainingRelationships).AsSlice() 961 require.ElementsMatch(tc.expectedDeleted, deletedRelationships) 962 963 // Ensure the initial relationships are still present at the previous revision. 964 allInitialRelationships := mapz.NewSet[string]() 965 olderReader := ds.SnapshotReader(writtenRev) 966 for _, resourceType := range resourceTypes.AsSlice() { 967 iter, err := olderReader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 968 OptionalResourceType: resourceType, 969 }) 970 require.NoError(err) 971 t.Cleanup(iter.Close) 972 973 for { 974 rel := iter.Next() 975 if rel == nil { 976 break 977 } 978 allInitialRelationships.Add(tuple.MustString(rel)) 979 } 980 iter.Close() 981 } 982 983 require.ElementsMatch(tc.relationships, allInitialRelationships.AsSlice()) 984 }) 985 } 986 }) 987 } 988 } 989 990 func RecreateRelationshipsAfterDeleteWithFilter(t *testing.T, tester DatastoreTester) { 991 require := require.New(t) 992 993 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 994 require.NoError(err) 995 996 ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require) 997 ctx := context.Background() 998 999 relationships := make([]*core.RelationTuple, 100) 1000 for i := 0; i < 100; i++ { 1001 relationships[i] = tuple.MustParse(fmt.Sprintf("document:%d#owner@user:first", i)) 1002 } 1003 1004 writeRelationships := func() error { 1005 _, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, relationships...) 1006 return err 1007 } 1008 1009 deleteRelationships := func() error { 1010 _, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 1011 delLimit := uint64(100) 1012 _, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{ 1013 OptionalRelation: "owner", 1014 OptionalSubjectFilter: &v1.SubjectFilter{ 1015 SubjectType: "user", 1016 OptionalSubjectId: "first", 1017 }, 1018 }, options.WithDeleteLimit(&delLimit)) 1019 return err 1020 }) 1021 return err 1022 } 1023 1024 // Write the initial relationships. 1025 require.NoError(writeRelationships()) 1026 1027 // Delete the relationships. 1028 require.NoError(deleteRelationships()) 1029 1030 // Write the same relationships again. 1031 require.NoError(writeRelationships()) 1032 } 1033 1034 // QueryRelationshipsWithVariousFiltersTest tests various relationship filters for query relationships. 1035 func QueryRelationshipsWithVariousFiltersTest(t *testing.T, tester DatastoreTester) { 1036 tcs := []struct { 1037 name string 1038 filter datastore.RelationshipsFilter 1039 relationships []string 1040 expected []string 1041 }{ 1042 { 1043 name: "resource type", 1044 filter: datastore.RelationshipsFilter{ 1045 OptionalResourceType: "document", 1046 }, 1047 relationships: []string{ 1048 "document:first#viewer@user:tom", 1049 "document:second#viewer@user:tom", 1050 "folder:secondfolder#viewer@user:tom", 1051 "folder:someotherfolder#viewer@user:tom", 1052 }, 1053 expected: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"}, 1054 }, 1055 { 1056 name: "resource id", 1057 filter: datastore.RelationshipsFilter{ 1058 OptionalResourceIds: []string{"first"}, 1059 }, 1060 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"}, 1061 expected: []string{"document:first#viewer@user:tom"}, 1062 }, 1063 { 1064 name: "resource id prefix", 1065 filter: datastore.RelationshipsFilter{ 1066 OptionalResourceIDPrefix: "first", 1067 }, 1068 relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"}, 1069 expected: []string{"document:first#viewer@user:tom"}, 1070 }, 1071 { 1072 name: "resource id different prefix", 1073 filter: datastore.RelationshipsFilter{ 1074 OptionalResourceIDPrefix: "s", 1075 }, 1076 relationships: []string{ 1077 "document:first#viewer@user:tom", 1078 "document:second#viewer@user:tom", 1079 "folder:secondfolder#viewer@user:tom", 1080 "folder:someotherfolder#viewer@user:tom", 1081 }, 1082 expected: []string{ 1083 "document:second#viewer@user:tom", 1084 "folder:secondfolder#viewer@user:tom", 1085 "folder:someotherfolder#viewer@user:tom", 1086 }, 1087 }, 1088 { 1089 name: "resource id different longer prefix", 1090 filter: datastore.RelationshipsFilter{ 1091 OptionalResourceIDPrefix: "se", 1092 }, 1093 relationships: []string{ 1094 "document:first#viewer@user:tom", 1095 "document:second#viewer@user:tom", 1096 "folder:secondfolder#viewer@user:tom", 1097 "folder:someotherfolder#viewer@user:tom", 1098 }, 1099 expected: []string{ 1100 "document:second#viewer@user:tom", 1101 "folder:secondfolder#viewer@user:tom", 1102 }, 1103 }, 1104 { 1105 name: "resource type and resource id different prefix", 1106 filter: datastore.RelationshipsFilter{ 1107 OptionalResourceType: "folder", 1108 OptionalResourceIDPrefix: "s", 1109 }, 1110 relationships: []string{ 1111 "document:first#viewer@user:tom", 1112 "document:second#viewer@user:tom", 1113 "folder:secondfolder#viewer@user:tom", 1114 "folder:someotherfolder#viewer@user:tom", 1115 }, 1116 expected: []string{ 1117 "folder:secondfolder#viewer@user:tom", 1118 "folder:someotherfolder#viewer@user:tom", 1119 }, 1120 }, 1121 { 1122 name: "full resource", 1123 filter: datastore.RelationshipsFilter{ 1124 OptionalResourceType: "folder", 1125 OptionalResourceIDPrefix: "s", 1126 OptionalResourceRelation: "viewer", 1127 }, 1128 relationships: []string{ 1129 "document:first#viewer@user:tom", 1130 "document:second#viewer@user:tom", 1131 "folder:secondfolder#viewer@user:tom", 1132 "folder:someotherfolder#viewer@user:tom", 1133 }, 1134 expected: []string{ 1135 "folder:secondfolder#viewer@user:tom", 1136 "folder:someotherfolder#viewer@user:tom", 1137 }, 1138 }, 1139 { 1140 name: "full resource mismatch", 1141 filter: datastore.RelationshipsFilter{ 1142 OptionalResourceType: "folder", 1143 OptionalResourceIDPrefix: "s", 1144 OptionalResourceRelation: "someotherelation", 1145 }, 1146 relationships: []string{ 1147 "document:first#viewer@user:tom", 1148 "document:second#viewer@user:tom", 1149 "folder:secondfolder#viewer@user:tom", 1150 "folder:someotherfolder#viewer@user:tom", 1151 }, 1152 expected: []string{}, 1153 }, 1154 { 1155 name: "subject type", 1156 filter: datastore.RelationshipsFilter{ 1157 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1158 { 1159 OptionalSubjectType: "user", 1160 }, 1161 }, 1162 }, 1163 relationships: []string{ 1164 "document:first#viewer@user:tom", 1165 "document:second#viewer@anotheruser:tom", 1166 "folder:secondfolder#viewer@anotheruser:tom", 1167 "folder:someotherfolder#viewer@user:tom", 1168 }, 1169 expected: []string{ 1170 "document:first#viewer@user:tom", 1171 "folder:someotherfolder#viewer@user:tom", 1172 }, 1173 }, 1174 { 1175 name: "subject ids", 1176 filter: datastore.RelationshipsFilter{ 1177 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1178 { 1179 OptionalSubjectIds: []string{"tom"}, 1180 }, 1181 }, 1182 }, 1183 relationships: []string{ 1184 "document:first#viewer@user:tom", 1185 "document:second#viewer@anotheruser:fred", 1186 "folder:secondfolder#viewer@anotheruser:fred", 1187 "folder:someotherfolder#viewer@user:tom", 1188 }, 1189 expected: []string{ 1190 "document:first#viewer@user:tom", 1191 "folder:someotherfolder#viewer@user:tom", 1192 }, 1193 }, 1194 { 1195 name: "multiple subject ids", 1196 filter: datastore.RelationshipsFilter{ 1197 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1198 { 1199 OptionalSubjectIds: []string{"tom", "fred"}, 1200 }, 1201 }, 1202 }, 1203 relationships: []string{ 1204 "document:first#viewer@user:tom", 1205 "document:second#viewer@anotheruser:fred", 1206 "folder:secondfolder#viewer@anotheruser:sarah", 1207 "folder:someotherfolder#viewer@user:tom", 1208 }, 1209 expected: []string{ 1210 "document:first#viewer@user:tom", 1211 "document:second#viewer@anotheruser:fred", 1212 "folder:someotherfolder#viewer@user:tom", 1213 }, 1214 }, 1215 { 1216 name: "non ellipsis subject relation", 1217 filter: datastore.RelationshipsFilter{ 1218 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1219 { 1220 RelationFilter: datastore.SubjectRelationFilter{ 1221 NonEllipsisRelation: "something", 1222 }, 1223 }, 1224 }, 1225 }, 1226 relationships: []string{ 1227 "document:first#viewer@user:tom#...", 1228 "document:second#viewer@anotheruser:fred#something", 1229 "folder:secondfolder#viewer@anotheruser:sarah", 1230 "folder:someotherfolder#viewer@user:tom", 1231 }, 1232 expected: []string{ 1233 "document:second#viewer@anotheruser:fred#something", 1234 }, 1235 }, 1236 { 1237 name: "specific subject relation and ellipsis", 1238 filter: datastore.RelationshipsFilter{ 1239 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1240 { 1241 RelationFilter: datastore.SubjectRelationFilter{ 1242 NonEllipsisRelation: "something", 1243 IncludeEllipsisRelation: true, 1244 }, 1245 }, 1246 }, 1247 }, 1248 relationships: []string{ 1249 "document:first#viewer@user:tom", 1250 "document:second#viewer@anotheruser:fred#something", 1251 "folder:secondfolder#viewer@anotheruser:sarah", 1252 "folder:someotherfolder#viewer@user:tom", 1253 }, 1254 expected: []string{ 1255 "document:first#viewer@user:tom", 1256 "document:second#viewer@anotheruser:fred#something", 1257 "folder:secondfolder#viewer@anotheruser:sarah", 1258 "folder:someotherfolder#viewer@user:tom", 1259 }, 1260 }, 1261 { 1262 name: "specific subject relation and no non-ellipsis", 1263 filter: datastore.RelationshipsFilter{ 1264 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1265 { 1266 RelationFilter: datastore.SubjectRelationFilter{ 1267 NonEllipsisRelation: "something", 1268 OnlyNonEllipsisRelations: true, 1269 }, 1270 }, 1271 }, 1272 }, 1273 relationships: []string{ 1274 "document:first#viewer@user:tom", 1275 "document:second#viewer@anotheruser:fred#something", 1276 "folder:secondfolder#viewer@anotheruser:sarah", 1277 "folder:someotherfolder#viewer@user:tom", 1278 }, 1279 expected: []string{ 1280 "document:second#viewer@anotheruser:fred#something", 1281 }, 1282 }, 1283 { 1284 name: "only ellipsis", 1285 filter: datastore.RelationshipsFilter{ 1286 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1287 { 1288 RelationFilter: datastore.SubjectRelationFilter{ 1289 IncludeEllipsisRelation: true, 1290 }, 1291 }, 1292 }, 1293 }, 1294 relationships: []string{ 1295 "document:first#viewer@user:tom", 1296 "document:second#viewer@anotheruser:fred#something", 1297 "folder:secondfolder#viewer@anotheruser:sarah", 1298 "folder:someotherfolder#viewer@user:tom", 1299 }, 1300 expected: []string{ 1301 "document:first#viewer@user:tom", 1302 "folder:secondfolder#viewer@anotheruser:sarah", 1303 "folder:someotherfolder#viewer@user:tom", 1304 }, 1305 }, 1306 { 1307 name: "multiple subject filters", 1308 filter: datastore.RelationshipsFilter{ 1309 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1310 { 1311 RelationFilter: datastore.SubjectRelationFilter{ 1312 NonEllipsisRelation: "something", 1313 }, 1314 }, 1315 { 1316 OptionalSubjectIds: []string{"tom"}, 1317 }, 1318 }, 1319 }, 1320 relationships: []string{ 1321 "document:first#viewer@user:tom", 1322 "document:second#viewer@anotheruser:fred#something", 1323 "folder:secondfolder#viewer@anotheruser:sarah", 1324 "folder:someotherfolder#viewer@user:tom", 1325 }, 1326 expected: []string{ 1327 "document:first#viewer@user:tom", 1328 "document:second#viewer@anotheruser:fred#something", 1329 "folder:someotherfolder#viewer@user:tom", 1330 }, 1331 }, 1332 { 1333 name: "multiple subject filters and resource ID prefix", 1334 filter: datastore.RelationshipsFilter{ 1335 OptionalResourceIDPrefix: "s", 1336 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1337 { 1338 RelationFilter: datastore.SubjectRelationFilter{ 1339 NonEllipsisRelation: "something", 1340 }, 1341 }, 1342 { 1343 OptionalSubjectIds: []string{"tom"}, 1344 }, 1345 }, 1346 }, 1347 relationships: []string{ 1348 "document:first#viewer@user:tom", 1349 "document:second#viewer@anotheruser:fred#something", 1350 "folder:secondfolder#viewer@anotheruser:sarah", 1351 "folder:someotherfolder#viewer@user:tom", 1352 }, 1353 expected: []string{ 1354 "document:second#viewer@anotheruser:fred#something", 1355 "folder:someotherfolder#viewer@user:tom", 1356 }, 1357 }, 1358 } 1359 1360 for _, tc := range tcs { 1361 tc := tc 1362 t.Run(tc.name, func(t *testing.T) { 1363 require := require.New(t) 1364 1365 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1366 require.NoError(err) 1367 1368 ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require) 1369 ctx := context.Background() 1370 1371 for _, rel := range tc.relationships { 1372 tpl := tuple.MustParse(rel) 1373 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl) 1374 require.NoError(err) 1375 } 1376 1377 headRev, err := ds.HeadRevision(ctx) 1378 require.NoError(err) 1379 1380 reader := ds.SnapshotReader(headRev) 1381 iter, err := reader.QueryRelationships(ctx, tc.filter) 1382 require.NoError(err) 1383 1384 var results []string 1385 for { 1386 tpl := iter.Next() 1387 if tpl == nil { 1388 err := iter.Err() 1389 require.NoError(err) 1390 break 1391 } 1392 1393 results = append(results, tuple.MustString(tpl)) 1394 } 1395 iter.Close() 1396 1397 require.ElementsMatch(tc.expected, results) 1398 }) 1399 } 1400 } 1401 1402 // TypedTouchAlreadyExistingTest tests touching a relationship twice, when valid type information is provided. 1403 func TypedTouchAlreadyExistingTest(t *testing.T, tester DatastoreTester) { 1404 require := require.New(t) 1405 1406 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1407 require.NoError(err) 1408 1409 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 1410 ctx := context.Background() 1411 1412 tpl1 := tuple.Parse("document:foo#viewer@user:tom") 1413 1414 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1) 1415 require.NoError(err) 1416 ensureTuples(ctx, require, ds, tpl1) 1417 1418 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1) 1419 require.NoError(err) 1420 ensureTuples(ctx, require, ds, tpl1) 1421 } 1422 1423 // TypedTouchAlreadyExistingWithCaveatTest tests touching a relationship twice, when valid type information is provided. 1424 func TypedTouchAlreadyExistingWithCaveatTest(t *testing.T, tester DatastoreTester) { 1425 require := require.New(t) 1426 1427 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1428 require.NoError(err) 1429 1430 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 1431 ctx := context.Background() 1432 1433 ctpl1 := tuple.Parse("document:foo#caveated_viewer@user:tom[test:{\"foo\":\"bar\"}]") 1434 1435 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, ctpl1) 1436 require.NoError(err) 1437 ensureTuples(ctx, require, ds, ctpl1) 1438 1439 ctpl1Updated := tuple.Parse("document:foo#caveated_viewer@user:tom[test:{\"foo\":\"baz\"}]") 1440 1441 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, ctpl1Updated) 1442 require.NoError(err) 1443 ensureTuples(ctx, require, ds, ctpl1Updated) 1444 } 1445 1446 // CreateTouchDeleteTouchTest tests writing a relationship, touching it, deleting it, and then touching it. 1447 func CreateTouchDeleteTouchTest(t *testing.T, tester DatastoreTester) { 1448 require := require.New(t) 1449 1450 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1451 require.NoError(err) 1452 1453 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 1454 ctx := context.Background() 1455 1456 tpl1 := makeTestTuple("foo", "tom") 1457 tpl2 := makeTestTuple("foo", "sarah") 1458 1459 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2) 1460 require.NoError(err) 1461 1462 ensureTuples(ctx, require, ds, tpl1, tpl2) 1463 1464 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2) 1465 require.NoError(err) 1466 1467 ensureTuples(ctx, require, ds, tpl1, tpl2) 1468 1469 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl1, tpl2) 1470 require.NoError(err) 1471 1472 ensureNotTuples(ctx, require, ds, tpl1, tpl2) 1473 1474 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2) 1475 require.NoError(err) 1476 1477 ensureTuples(ctx, require, ds, tpl1, tpl2) 1478 } 1479 1480 // TouchAlreadyExistingCaveatedTest tests touching a relationship twice. 1481 func TouchAlreadyExistingCaveatedTest(t *testing.T, tester DatastoreTester) { 1482 require := require.New(t) 1483 1484 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1485 require.NoError(err) 1486 1487 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 1488 ctx := context.Background() 1489 1490 tpl1 := tuple.MustWithCaveat(makeTestTuple("foo", "tom"), "formercaveat") 1491 tpl2 := makeTestTuple("foo", "sarah") 1492 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2) 1493 require.NoError(err) 1494 1495 ensureTuples(ctx, require, ds, tpl1, tpl2) 1496 1497 ctpl1 := tuple.MustWithCaveat(makeTestTuple("foo", "tom"), "somecaveat") 1498 tpl3 := makeTestTuple("foo", "fred") 1499 1500 _, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, ctpl1, tpl3) 1501 require.NoError(err) 1502 1503 ensureTuples(ctx, require, ds, tpl2, tpl3, ctpl1) 1504 } 1505 1506 func MultipleReadsInRWTTest(t *testing.T, tester DatastoreTester) { 1507 require := require.New(t) 1508 1509 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1510 require.NoError(err) 1511 1512 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 1513 ctx := context.Background() 1514 1515 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 1516 it, err := rwt.QueryRelationships(ctx, datastore.RelationshipsFilter{ 1517 OptionalResourceType: "document", 1518 }) 1519 require.NoError(err) 1520 it.Close() 1521 1522 it, err = rwt.QueryRelationships(ctx, datastore.RelationshipsFilter{ 1523 OptionalResourceType: "folder", 1524 }) 1525 require.NoError(err) 1526 it.Close() 1527 1528 return nil 1529 }) 1530 require.NoError(err) 1531 } 1532 1533 // ConcurrentWriteSerializationTest uses goroutines and channels to intentionally set up a 1534 // deadlocking dependency between transactions. 1535 func ConcurrentWriteSerializationTest(t *testing.T, tester DatastoreTester) { 1536 require := require.New(t) 1537 1538 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1539 require.NoError(err) 1540 1541 ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require) 1542 ctx := context.Background() 1543 1544 g := errgroup.Group{} 1545 1546 waitToStart := make(chan struct{}) 1547 waitToFinish := make(chan struct{}) 1548 waitToStartCloser := sync.Once{} 1549 waitToFinishCloser := sync.Once{} 1550 1551 startTime := time.Now() 1552 1553 g.Go(func() error { 1554 _, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 1555 iter, err := rwt.QueryRelationships(ctx, datastore.RelationshipsFilter{ 1556 OptionalResourceType: testResourceNamespace, 1557 }) 1558 iter.Close() 1559 require.NoError(err) 1560 1561 // We do NOT assert the error here because serialization problems can manifest as errors 1562 // on the individual writes. 1563 rtu := tuple.Touch(makeTestTuple("new_resource", "new_user")) 1564 err = rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu}) 1565 1566 waitToStartCloser.Do(func() { 1567 close(waitToStart) 1568 }) 1569 <-waitToFinish 1570 1571 return err 1572 }) 1573 require.NoError(err) 1574 return nil 1575 }) 1576 1577 <-waitToStart 1578 1579 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 1580 defer waitToFinishCloser.Do(func() { 1581 close(waitToFinish) 1582 }) 1583 1584 rtu := tuple.Touch(makeTestTuple("another_resource", "another_user")) 1585 return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu}) 1586 }) 1587 require.NoError(err) 1588 require.NoError(g.Wait()) 1589 require.Less(time.Since(startTime), 10*time.Second) 1590 } 1591 1592 func BulkDeleteRelationshipsTest(t *testing.T, tester DatastoreTester) { 1593 require := require.New(t) 1594 1595 rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1) 1596 require.NoError(err) 1597 1598 ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require) 1599 ctx := context.Background() 1600 1601 // Write a bunch of relationships. 1602 t.Log(time.Now(), "starting write") 1603 _, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 1604 _, err := rwt.BulkLoad(ctx, testfixtures.NewBulkTupleGenerator(testResourceNamespace, testReaderRelation, testUserNamespace, 1000, t)) 1605 if err != nil { 1606 return err 1607 } 1608 1609 _, err = rwt.BulkLoad(ctx, testfixtures.NewBulkTupleGenerator(testResourceNamespace, testEditorRelation, testUserNamespace, 1000, t)) 1610 if err != nil { 1611 return err 1612 } 1613 1614 return nil 1615 }) 1616 require.NoError(err) 1617 1618 // Issue a deletion for the first set of relationships. 1619 t.Log(time.Now(), "starting delete") 1620 deleteCount := 0 1621 deletedRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error { 1622 t.Log(time.Now(), "deleting") 1623 deleteCount++ 1624 _, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{ 1625 ResourceType: testResourceNamespace, 1626 OptionalRelation: testReaderRelation, 1627 }) 1628 return err 1629 }) 1630 require.NoError(err) 1631 require.Equal(1, deleteCount) 1632 t.Log(time.Now(), "finished delete") 1633 1634 // Ensure the relationships were removed. 1635 t.Log(time.Now(), "starting check") 1636 reader := ds.SnapshotReader(deletedRev) 1637 iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 1638 OptionalResourceType: testResourceNamespace, 1639 OptionalResourceRelation: testReaderRelation, 1640 }) 1641 require.NoError(err) 1642 defer iter.Close() 1643 1644 require.Nil(iter.Next(), "expected no results") 1645 } 1646 1647 func onrToSubjectsFilter(onr *core.ObjectAndRelation) datastore.SubjectsFilter { 1648 return datastore.SubjectsFilter{ 1649 SubjectType: onr.Namespace, 1650 OptionalSubjectIds: []string{onr.ObjectId}, 1651 RelationFilter: datastore.SubjectRelationFilter{}.WithNonEllipsisRelation(onr.Relation), 1652 } 1653 } 1654 1655 func ensureTuples(ctx context.Context, require *require.Assertions, ds datastore.Datastore, tpls ...*core.RelationTuple) { 1656 ensureTuplesStatus(ctx, require, ds, tpls, true) 1657 } 1658 1659 func ensureNotTuples(ctx context.Context, require *require.Assertions, ds datastore.Datastore, tpls ...*core.RelationTuple) { 1660 ensureTuplesStatus(ctx, require, ds, tpls, false) 1661 } 1662 1663 func ensureTuplesStatus(ctx context.Context, require *require.Assertions, ds datastore.Datastore, tpls []*core.RelationTuple, mustExist bool) { 1664 headRev, err := ds.HeadRevision(ctx) 1665 require.NoError(err) 1666 1667 reader := ds.SnapshotReader(headRev) 1668 1669 for _, tpl := range tpls { 1670 iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 1671 OptionalResourceType: tpl.ResourceAndRelation.Namespace, 1672 OptionalResourceIds: []string{tpl.ResourceAndRelation.ObjectId}, 1673 OptionalResourceRelation: tpl.ResourceAndRelation.Relation, 1674 OptionalSubjectsSelectors: []datastore.SubjectsSelector{ 1675 { 1676 OptionalSubjectType: tpl.Subject.Namespace, 1677 OptionalSubjectIds: []string{tpl.Subject.ObjectId}, 1678 }, 1679 }, 1680 }) 1681 require.NoError(err) 1682 defer iter.Close() 1683 1684 found := iter.Next() 1685 1686 if mustExist { 1687 require.NotNil(found, "expected tuple %s", tuple.MustString(tpl)) 1688 } else { 1689 require.Nil(found, "expected tuple %s to not exist", tuple.MustString(tpl)) 1690 } 1691 1692 iter.Close() 1693 1694 if mustExist { 1695 require.Equal(tuple.MustString(tpl), tuple.MustString(found)) 1696 } 1697 } 1698 } 1699 1700 func countTuples(ctx context.Context, require *require.Assertions, ds datastore.Datastore, resourceType string) int { 1701 headRev, err := ds.HeadRevision(ctx) 1702 require.NoError(err) 1703 1704 reader := ds.SnapshotReader(headRev) 1705 1706 iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{ 1707 OptionalResourceType: resourceType, 1708 }) 1709 require.NoError(err) 1710 defer iter.Close() 1711 1712 counter := 0 1713 for { 1714 rel := iter.Next() 1715 if rel == nil { 1716 break 1717 } 1718 1719 counter++ 1720 } 1721 1722 return counter 1723 }