github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/integrationtesting/ops_test.go (about) 1 //go:build !skipintegrationtests 2 // +build !skipintegrationtests 3 4 package integrationtesting_test 5 6 import ( 7 "context" 8 "fmt" 9 "testing" 10 11 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 12 "github.com/stretchr/testify/require" 13 "google.golang.org/grpc" 14 "google.golang.org/protobuf/types/known/structpb" 15 16 "github.com/authzed/spicedb/internal/datastore/memdb" 17 tf "github.com/authzed/spicedb/internal/testfixtures" 18 "github.com/authzed/spicedb/internal/testserver" 19 core "github.com/authzed/spicedb/pkg/proto/core/v1" 20 "github.com/authzed/spicedb/pkg/tuple" 21 ) 22 23 type stcOp interface { 24 Execute(tester opsTester) error 25 } 26 27 type stcStep struct { 28 op stcOp 29 expectedError string 30 } 31 32 type schemaTestCase struct { 33 name string 34 steps []stcStep 35 } 36 37 type writeSchema struct{ schemaText string } 38 39 func (ws writeSchema) Execute(tester opsTester) error { 40 return tester.WriteSchema(context.Background(), ws.schemaText) 41 } 42 43 type readSchema struct{ expectedSchemaText string } 44 45 func (rs readSchema) Execute(tester opsTester) error { 46 schemaText, err := tester.ReadSchema(context.Background()) 47 if err != nil { 48 return err 49 } 50 if schemaText != rs.expectedSchemaText { 51 return fmt.Errorf("unexpected schema: %#v", schemaText) 52 } 53 return nil 54 } 55 56 type createCaveatedRelationship struct { 57 relString string 58 caveatName string 59 context map[string]any 60 } 61 62 func (wr createCaveatedRelationship) Execute(tester opsTester) error { 63 ctx, err := structpb.NewStruct(wr.context) 64 if err != nil { 65 return err 66 } 67 68 rel := tuple.MustParse(wr.relString) 69 rel.Caveat = &core.ContextualizedCaveat{ 70 CaveatName: wr.caveatName, 71 Context: ctx, 72 } 73 return tester.CreateRelationship(context.Background(), rel) 74 } 75 76 type touchCaveatedRelationship struct { 77 relString string 78 caveatName string 79 context map[string]any 80 } 81 82 func (wr touchCaveatedRelationship) Execute(tester opsTester) error { 83 ctx, err := structpb.NewStruct(wr.context) 84 if err != nil { 85 return err 86 } 87 88 rel := tuple.MustParse(wr.relString) 89 rel.Caveat = &core.ContextualizedCaveat{ 90 CaveatName: wr.caveatName, 91 Context: ctx, 92 } 93 return tester.TouchRelationship(context.Background(), rel) 94 } 95 96 type createRelationship struct{ relString string } 97 98 func (wr createRelationship) Execute(tester opsTester) error { 99 return tester.CreateRelationship(context.Background(), tuple.MustParse(wr.relString)) 100 } 101 102 type deleteRelationship struct{ relString string } 103 104 func (dr deleteRelationship) Execute(tester opsTester) error { 105 return tester.DeleteRelationship(context.Background(), tuple.MustParse(dr.relString)) 106 } 107 108 type deleteCaveatedRelationship struct { 109 relString string 110 caveatName string 111 } 112 113 func (dr deleteCaveatedRelationship) Execute(tester opsTester) error { 114 rel := tuple.MustParse(dr.relString) 115 rel.Caveat = &core.ContextualizedCaveat{ 116 CaveatName: dr.caveatName, 117 } 118 119 return tester.DeleteRelationship(context.Background(), rel) 120 } 121 122 func TestSchemaAndRelationshipsOperations(t *testing.T) { 123 tcs := []schemaTestCase{ 124 // Test: write a basic, valid schema. 125 { 126 "basic valid schema write", 127 []stcStep{ 128 {writeSchema{`definition user {}`}, ""}, 129 }, 130 }, 131 132 // Test: write a basic, invalid schema. 133 { 134 "basic invalid schema write", 135 []stcStep{ 136 {writeSchema{`definitin user {}`}, "Unexpected token at root level"}, 137 }, 138 }, 139 140 // Test: try to remove a relation that has at least one relationship. 141 { 142 "try to remove relation with data", 143 []stcStep{ 144 // Write the schema. 145 { 146 writeSchema{` 147 definition user {} 148 149 definition document { 150 relation viewer: user 151 } 152 `}, "", 153 }, 154 // Write a relationship using the relation. 155 { 156 createRelationship{"document:foo#viewer@user:tom"}, "", 157 }, 158 // Remove the relation, which should fail. 159 { 160 writeSchema{` 161 definition user {} 162 163 definition document { 164 } 165 `}, "cannot delete relation", 166 }, 167 // Delete the relationship. 168 { 169 deleteRelationship{"document:foo#viewer@user:tom"}, "", 170 }, 171 // Remove the relation, which should now succeed. 172 { 173 writeSchema{` 174 definition user {} 175 176 definition document { 177 } 178 `}, "", 179 }, 180 }, 181 }, 182 183 // Test: try to change the types allowed on a relation when existing relationships 184 // use the former type. 185 { 186 "try to modify relation type with data", 187 []stcStep{ 188 // Write the schema. 189 { 190 writeSchema{` 191 definition user {} 192 definition anotheruser {} 193 194 definition document { 195 relation viewer: user | anotheruser 196 } 197 `}, "", 198 }, 199 // Write a relationship using the relation. 200 { 201 createRelationship{"document:foo#viewer@user:tom"}, "", 202 }, 203 // Remove the relation's type, which should fail. 204 { 205 writeSchema{` 206 definition user {} 207 definition anotheruser {} 208 209 definition document { 210 relation viewer: anotheruser 211 } 212 `}, "cannot remove allowed type `user`", 213 }, 214 // Delete the relationship. 215 { 216 deleteRelationship{"document:foo#viewer@user:tom"}, "", 217 }, 218 // Remove the relation, which should now succeed. 219 { 220 writeSchema{` 221 definition user {} 222 definition anotheruser {} 223 224 definition document { 225 relation viewer: anotheruser 226 } 227 `}, "", 228 }, 229 }, 230 }, 231 232 // Test: try to modify the allowed caveat type when it is being used by relationships. 233 { 234 "try to modify relation caveat with data", 235 []stcStep{ 236 // Write the schema. 237 { 238 writeSchema{` 239 caveat somecaveat(someParam int) { 240 someParam == 42 241 } 242 243 definition user {} 244 245 definition document { 246 relation viewer: user with somecaveat | user 247 } 248 `}, "", 249 }, 250 // Write some relationships using the relation. 251 { 252 createRelationship{"document:foo#viewer@user:tom"}, "", 253 }, 254 { 255 createCaveatedRelationship{"document:foo#viewer@user:sarah", "somecaveat", nil}, "", 256 }, 257 // Remove the caveat from the relation, which should fail. 258 { 259 writeSchema{` 260 caveat somecaveat(someParam int) { 261 someParam == 42 262 } 263 264 definition user {} 265 266 definition document { 267 relation viewer: user 268 } 269 `}, "cannot remove allowed type `user with somecaveat`", 270 }, 271 // Delete the relationship. 272 { 273 deleteCaveatedRelationship{"document:foo#viewer@user:sarah", "somecaveat"}, "", 274 }, 275 // Remove the caveat from the relation, which should now succeed. 276 { 277 writeSchema{` 278 caveat somecaveat(someParam int) { 279 someParam == 42 280 } 281 282 definition user {} 283 definition anotheruser {} 284 285 definition document { 286 relation viewer: user 287 } 288 `}, "", 289 }, 290 }, 291 }, 292 293 // Test: try to rename a relation that has relationships. 294 { 295 "try to rename relation with data", 296 []stcStep{ 297 // Write the schema. 298 { 299 writeSchema{` 300 definition user {} 301 302 definition document { 303 relation viewer: user 304 } 305 `}, "", 306 }, 307 // Write a relationship using the relation. 308 { 309 createRelationship{"document:foo#viewer@user:tom"}, "", 310 }, 311 // Rename the relation, which should fail. 312 { 313 writeSchema{` 314 definition user {} 315 316 definition document { 317 relation viewuser: user 318 } 319 `}, "cannot delete relation `viewer`", 320 }, 321 // Delete the relationship. 322 { 323 deleteRelationship{"document:foo#viewer@user:tom"}, "", 324 }, 325 // Rename the relation, which should now succeed. 326 { 327 writeSchema{` 328 definition user {} 329 330 definition document { 331 relation viewuser: user 332 } 333 `}, "", 334 }, 335 }, 336 }, 337 338 // Test: attempt to write a relationship with an unknown resource type. 339 { 340 "write relationship with unknown resource type", 341 []stcStep{ 342 // Write the schema. 343 { 344 writeSchema{` 345 definition user {} 346 347 definition document { 348 relation viewer: user 349 } 350 `}, "", 351 }, 352 // Write a relationship using the relation, but with an undefined type, which should fail. 353 { 354 createRelationship{"doc:foo#viewer@user:tom"}, 355 "object definition `doc` not found", 356 }, 357 }, 358 }, 359 360 // Test: attempt to write a relationship with an unknown relation. 361 { 362 "write relationship with unknown relation", 363 []stcStep{ 364 // Write the schema. 365 { 366 writeSchema{` 367 definition user {} 368 369 definition document { 370 relation viewer: user 371 } 372 `}, "", 373 }, 374 // Write a relationship using an unknown relation, which should fail. 375 { 376 createRelationship{"document:foo#viewguy@user:tom"}, 377 "relation/permission `viewguy` not found", 378 }, 379 }, 380 }, 381 382 // Test: attempt to write a relationship with an unknown subject type. 383 { 384 "write relationship with unknown subject type", 385 []stcStep{ 386 // Write the schema. 387 { 388 writeSchema{` 389 definition user {} 390 391 definition document { 392 relation viewer: user 393 } 394 `}, "", 395 }, 396 // Write a relationship using the relation, but with an undefined type, which should fail. 397 { 398 createRelationship{"document:foo#viewer@anothersubject:tom"}, 399 "object definition `anothersubject` not found", 400 }, 401 }, 402 }, 403 404 // Test: attempt to write a relationship with a disallowed subject type. 405 { 406 "try to write relationship subject type that is not allowed", 407 []stcStep{ 408 // Write the schema. 409 { 410 writeSchema{` 411 definition user {} 412 definition anothersubject {} 413 414 definition document { 415 relation viewer: user 416 } 417 `}, "", 418 }, 419 // Write a relationship using the relation, but with the wrong type, which should fail. 420 { 421 createRelationship{"document:foo#viewer@anothersubject:tom"}, 422 "subjects of type `anothersubject` are not allowed", 423 }, 424 // Update the schema to add. 425 { 426 writeSchema{` 427 definition user {} 428 definition anothersubject {} 429 430 definition document { 431 relation viewer: user | anothersubject 432 } 433 `}, "", 434 }, 435 // Write a relationship, which should succeed now. 436 { 437 createRelationship{"document:foo#viewer@anothersubject:tom"}, "", 438 }, 439 }, 440 }, 441 442 // Test: attempt to write a relationship with a disallowed caveat type. 443 { 444 "try to write relationship subject caveat type that is not allowed", 445 []stcStep{ 446 // Write the schema. 447 { 448 writeSchema{` 449 definition user {} 450 451 definition document { 452 relation viewer: user 453 } 454 `}, "", 455 }, 456 // Write a relationship using the relation, but with the wrong caveat, which should fail. 457 { 458 createCaveatedRelationship{"document:foo#viewer@user:tom", "somecaveat", nil}, 459 "subjects of type `user with somecaveat` are not allowed", 460 }, 461 }, 462 }, 463 464 // Test: attempt to write a relationship with the wrong context parameter type. 465 { 466 "try to write relationship subject caveat with wrong context parameter type", 467 []stcStep{ 468 // Write the schema. 469 { 470 writeSchema{` 471 caveat somecaveat(someParam int) { 472 someParam == 42 473 } 474 475 definition user {} 476 477 definition document { 478 relation viewer: user with somecaveat 479 } 480 `}, "", 481 }, 482 // Write a relationship using the caveat, but with the wrong context value. 483 { 484 createCaveatedRelationship{ 485 "document:foo#viewer@user:tom", 486 "somecaveat", 487 map[string]any{ 488 "someParam": "42e", 489 }, 490 }, 491 "a int64 value is required, but found invalid string value `42e`", 492 }, 493 // Write a relationship using the caveat, but with the correct context value. 494 { 495 createCaveatedRelationship{ 496 "document:foo#viewer@user:tom", 497 "somecaveat", 498 map[string]any{ 499 "someParam": "42", 500 }, 501 }, 502 "", 503 }, 504 }, 505 }, 506 507 // Test: attempt to write a relationship with an unknown context parameter type. 508 { 509 "try to write relationship subject caveat with an unknown context parameter type", 510 []stcStep{ 511 // Write the schema. 512 { 513 writeSchema{` 514 caveat somecaveat(someParam int) { 515 someParam == 42 516 } 517 518 definition user {} 519 520 definition document { 521 relation viewer: user with somecaveat 522 } 523 `}, "", 524 }, 525 // Write a relationship using the caveat, but with an unknown context value. 526 { 527 createCaveatedRelationship{ 528 "document:foo#viewer@user:tom", 529 "somecaveat", 530 map[string]any{ 531 "someUnknownParam": "", 532 }, 533 }, 534 "unknown parameter `someUnknownParam`", 535 }, 536 }, 537 }, 538 539 // Test: attempt to change the parameters on a caveat. 540 { 541 "caveat parameter changes", 542 []stcStep{ 543 // Write the initial schema. 544 { 545 writeSchema{` 546 caveat somecaveat(someParam int, anotherParam int) { 547 someParam == 42 && anotherParam == 43 548 } 549 550 definition user {} 551 552 definition document { 553 relation viewer: user with somecaveat 554 } 555 `}, "", 556 }, 557 // Try to add a parameter to the caveat, which should succeed. 558 { 559 writeSchema{` 560 caveat somecaveat(someParam int, anotherParam int, newParam int) { 561 someParam == 42 && anotherParam == 43 && newParam == 44 562 } 563 564 definition user {} 565 566 definition document { 567 relation viewer: user with somecaveat 568 } 569 `}, "", 570 }, 571 // Try to remove a parameter from the caveat, which should fail. 572 { 573 writeSchema{` 574 caveat somecaveat(someParam int, anotherParam int) { 575 someParam == 42 && anotherParam == 43 576 } 577 578 definition user {} 579 580 definition document { 581 relation viewer: user with somecaveat 582 } 583 `}, "cannot remove parameter `newParam` on caveat `somecaveat`", 584 }, 585 // Try to change the type of a parameter on the caveat, which should fail. 586 { 587 writeSchema{` 588 caveat somecaveat(someParam int, anotherParam int, newParam bool) { 589 someParam == 42 && anotherParam == 43 && newParam 590 } 591 592 definition user {} 593 594 definition document { 595 relation viewer: user with somecaveat 596 } 597 `}, "cannot change the type of parameter `newParam` on caveat `somecaveat`", 598 }, 599 }, 600 }, 601 602 // Test: write relationships differing only by caveat 603 { 604 "attempt to write two relationships on the same relation, one without caveat and one with", 605 []stcStep{ 606 // Write the schema. 607 { 608 writeSchema{` 609 caveat somecaveat(someParam int) { 610 someParam == 42 611 } 612 613 definition user {} 614 615 definition document { 616 relation viewer: user with somecaveat | user 617 } 618 `}, "", 619 }, 620 // Write a relationship without the caveat. 621 { 622 createRelationship{ 623 "document:foo#viewer@user:tom", 624 }, "", 625 }, 626 // Write a relationship using the caveat, which should fail since the relationship already exists. 627 { 628 createCaveatedRelationship{ 629 "document:foo#viewer@user:tom", 630 "somecaveat", 631 nil, 632 }, 633 "as it already existed", 634 }, 635 }, 636 }, 637 638 // Test: touch relationships differing only by caveat 639 { 640 "touch a relationship, changing its caveat", 641 []stcStep{ 642 // Write the schema. 643 { 644 writeSchema{` 645 caveat somecaveat(someParam int) { 646 someParam == 42 647 } 648 649 definition user {} 650 651 definition document { 652 relation viewer: user with somecaveat | user 653 } 654 `}, "", 655 }, 656 // Create a relationship without the caveat. 657 { 658 createRelationship{ 659 "document:foo#viewer@user:tom", 660 }, "", 661 }, 662 // Touch a relationship using the caveat, which should update the caveat reference. 663 { 664 touchCaveatedRelationship{ 665 "document:foo#viewer@user:tom", 666 "somecaveat", 667 nil, 668 }, 669 "", 670 }, 671 // Attempt to remove the caveat, which should fail since there is a relationship using it. 672 { 673 writeSchema{` 674 caveat somecaveat(someParam int) { 675 someParam == 42 676 } 677 678 definition user {} 679 680 definition document { 681 relation viewer: user 682 } 683 `}, "cannot remove allowed type `user with somecaveat`", 684 }, 685 }, 686 }, 687 688 // Test: write a schema, add a caveat definition, read back, then remove, and continue. 689 { 690 "add and remove caveat in schema", 691 []stcStep{ 692 // Write the initial schema. 693 { 694 writeSchema{` 695 definition user {} 696 697 definition document { 698 relation viewer: user 699 } 700 `}, "", 701 }, 702 // Read back. 703 { 704 readSchema{"definition document {\n\trelation viewer: user\n}\n\ndefinition user {}"}, "", 705 }, 706 // Add a caveat definition. 707 { 708 writeSchema{` 709 definition user {} 710 711 caveat someCaveat(somecondition int) { 712 somecondition == 42 713 } 714 715 definition document { 716 relation viewer: user 717 } 718 `}, "", 719 }, 720 // Read back. 721 { 722 readSchema{"caveat someCaveat(somecondition int) {\n\tsomecondition == 42\n}\n\ndefinition document {\n\trelation viewer: user\n}\n\ndefinition user {}"}, "", 723 }, 724 // Remove the caveat definition. 725 { 726 writeSchema{` 727 definition user {} 728 729 definition document { 730 relation viewer: user 731 } 732 `}, "", 733 }, 734 // Read back. 735 { 736 readSchema{"definition document {\n\trelation viewer: user\n}\n\ndefinition user {}"}, "", 737 }, 738 }, 739 }, 740 } 741 742 testers := map[string]func(conn grpc.ClientConnInterface) opsTester{ 743 "v1": func(conn grpc.ClientConnInterface) opsTester { 744 return v1OpsTester{ 745 schemaClient: v1.NewSchemaServiceClient(conn), 746 baseOpsTester: baseOpsTester{permClient: v1.NewPermissionsServiceClient(conn)}, 747 } 748 }, 749 } 750 751 for _, tc := range tcs { 752 tc := tc 753 t.Run(tc.name, func(t *testing.T) { 754 for _, testerName := range []string{"v1"} { 755 testerName := testerName 756 t.Run(testerName, func(t *testing.T) { 757 conn, cleanup, _, _ := testserver.NewTestServer(require.New(t), 0, memdb.DisableGC, false, tf.EmptyDatastore) 758 t.Cleanup(cleanup) 759 760 tester := testers[testerName](conn) 761 762 for _, step := range tc.steps { 763 err := step.op.Execute(tester) 764 if step.expectedError != "" { 765 require.NotNil(t, err) 766 require.Contains(t, err.Error(), step.expectedError) 767 } else { 768 require.NoError(t, err) 769 } 770 } 771 }) 772 } 773 }) 774 } 775 } 776 777 type opsTester interface { 778 Name() string 779 ReadSchema(ctx context.Context) (string, error) 780 WriteSchema(ctx context.Context, schemaString string) error 781 CreateRelationship(ctx context.Context, relationship *core.RelationTuple) error 782 TouchRelationship(ctx context.Context, relationship *core.RelationTuple) error 783 DeleteRelationship(ctx context.Context, relationship *core.RelationTuple) error 784 } 785 786 type baseOpsTester struct { 787 permClient v1.PermissionsServiceClient 788 } 789 790 func (st baseOpsTester) CreateRelationship(ctx context.Context, relationship *core.RelationTuple) error { 791 _, err := st.permClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{ 792 Updates: []*v1.RelationshipUpdate{tuple.UpdateToRelationshipUpdate(tuple.Create(relationship))}, 793 }) 794 return err 795 } 796 797 func (st baseOpsTester) TouchRelationship(ctx context.Context, relationship *core.RelationTuple) error { 798 _, err := st.permClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{ 799 Updates: []*v1.RelationshipUpdate{tuple.UpdateToRelationshipUpdate(tuple.Touch(relationship))}, 800 }) 801 return err 802 } 803 804 func (st baseOpsTester) DeleteRelationship(ctx context.Context, relationship *core.RelationTuple) error { 805 _, err := st.permClient.WriteRelationships(ctx, &v1.WriteRelationshipsRequest{ 806 Updates: []*v1.RelationshipUpdate{tuple.UpdateToRelationshipUpdate(tuple.Delete(relationship))}, 807 }) 808 return err 809 } 810 811 type v1OpsTester struct { 812 baseOpsTester 813 schemaClient v1.SchemaServiceClient 814 } 815 816 func (st v1OpsTester) Name() string { 817 return "v1" 818 } 819 820 func (st v1OpsTester) WriteSchema(ctx context.Context, schemaString string) error { 821 _, err := st.schemaClient.WriteSchema(ctx, &v1.WriteSchemaRequest{ 822 Schema: schemaString, 823 }) 824 return err 825 } 826 827 func (st v1OpsTester) ReadSchema(ctx context.Context) (string, error) { 828 resp, err := st.schemaClient.ReadSchema(ctx, &v1.ReadSchemaRequest{}) 829 if err != nil { 830 return "", err 831 } 832 return resp.SchemaText, nil 833 }