github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/typesystem/reachabilitygraph_test.go (about) 1 package typesystem 2 3 import ( 4 "context" 5 "sort" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/authzed/spicedb/internal/datastore/memdb" 11 datastoremw "github.com/authzed/spicedb/internal/middleware/datastore" 12 "github.com/authzed/spicedb/pkg/datastore" 13 ns "github.com/authzed/spicedb/pkg/namespace" 14 core "github.com/authzed/spicedb/pkg/proto/core/v1" 15 "github.com/authzed/spicedb/pkg/schemadsl/compiler" 16 "github.com/authzed/spicedb/pkg/schemadsl/input" 17 "github.com/authzed/spicedb/pkg/tuple" 18 ) 19 20 func TestRelationsEncounteredForSubject(t *testing.T) { 21 tcs := []struct { 22 name string 23 schema string 24 subjectType string 25 relation string 26 expectedPermissions []string 27 }{ 28 { 29 "simple permission", 30 `definition user {} 31 32 definition document { 33 relation viewer: user 34 permission view = viewer 35 }`, 36 37 "document", 38 "viewer", 39 []string{"document#view"}, 40 }, 41 { 42 "simple permission and user", 43 `definition user {} 44 45 definition document { 46 relation viewer: user 47 permission view = viewer 48 }`, 49 50 "user", 51 "...", 52 []string{"document#view", "document#viewer"}, 53 }, 54 { 55 "multiple permissions using the same relation", 56 `definition user {} 57 58 definition document { 59 relation viewer: user 60 relation editor: user 61 62 permission edit = editor 63 permission view = viewer + editor 64 }`, 65 66 "document", 67 "editor", 68 []string{"document#view", "document#edit"}, 69 }, 70 { 71 "multiple permissions using the same relation indirectly", 72 `definition user {} 73 74 definition document { 75 relation viewer: user 76 relation editor: user 77 78 permission edit = editor 79 permission view = viewer + edit 80 }`, 81 82 "document", 83 "editor", 84 []string{"document#view", "document#edit"}, 85 }, 86 { 87 "permission referencing subject relation", 88 `definition user {} 89 90 definition group { 91 relation member: user 92 } 93 94 definition document { 95 relation viewer: user | group#member 96 permission view = viewer 97 }`, 98 "group", 99 "member", 100 []string{"document#view", "document#viewer"}, 101 }, 102 { 103 "simple arrow", 104 `definition user {} 105 106 definition organization { 107 relation admin: user 108 } 109 110 definition document { 111 relation org: organization 112 permission view = org->admin 113 }`, 114 "organization", 115 "admin", 116 []string{"document#view"}, 117 }, 118 { 119 "complex schema", 120 `definition user {} 121 122 definition organization { 123 relation admin: user 124 relation direct_member: user 125 126 permission member = direct_member + admin 127 } 128 129 definition group { 130 relation admin: user 131 relation direct_member: user | group#member 132 permission member = direct_member + admin 133 } 134 135 definition document { 136 relation viewer: user | group#member 137 relation owner: user 138 relation org: organization 139 140 permission view = viewer + owner + org->admin 141 }`, 142 "user", 143 "...", 144 []string{ 145 "document#viewer", "document#owner", "document#view", 146 "group#admin", "group#direct_member", "group#member", 147 "organization#member", "organization#admin", 148 "organization#direct_member", 149 }, 150 }, 151 { 152 "schema with different user types", 153 `definition user {} 154 155 definition platformuser {} 156 157 definition document { 158 relation viewer: user | platformuser 159 relation editor: platformuser 160 161 permission edit = editor 162 permission view = viewer + edit 163 }`, 164 165 "user", 166 "...", 167 168 []string{"document#view", "document#viewer"}, 169 }, 170 } 171 172 for _, tc := range tcs { 173 tc := tc 174 t.Run(tc.name, func(t *testing.T) { 175 require := require.New(t) 176 177 ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC) 178 require.NoError(err) 179 180 ctx := datastoremw.ContextWithDatastore(context.Background(), ds) 181 182 compiled, err := compiler.Compile(compiler.InputSchema{ 183 Source: input.Source("schema"), 184 SchemaString: tc.schema, 185 }, compiler.AllowUnprefixedObjectType()) 186 require.NoError(err) 187 188 // Write the schema. 189 _, err = ds.ReadWriteTx(context.Background(), func(ctx context.Context, tx datastore.ReadWriteTransaction) error { 190 for _, nsDef := range compiled.ObjectDefinitions { 191 if err := tx.WriteNamespaces(ctx, nsDef); err != nil { 192 return err 193 } 194 } 195 196 return nil 197 }) 198 require.NoError(err) 199 200 lastRevision, err := ds.HeadRevision(context.Background()) 201 require.NoError(err) 202 203 reader := ds.SnapshotReader(lastRevision) 204 205 _, vts, err := ReadNamespaceAndTypes(ctx, tc.subjectType, reader) 206 require.NoError(err) 207 208 rg := ReachabilityGraphFor(vts) 209 210 relations, err := rg.RelationsEncounteredForSubject(ctx, compiled.ObjectDefinitions, &core.RelationReference{ 211 Namespace: tc.subjectType, 212 Relation: tc.relation, 213 }) 214 require.NoError(err) 215 216 relationStrs := make([]string, 0, len(relations)) 217 for _, relation := range relations { 218 relationStrs = append(relationStrs, tuple.StringRR(relation)) 219 } 220 221 sort.Strings(relationStrs) 222 sort.Strings(tc.expectedPermissions) 223 224 require.Equal(tc.expectedPermissions, relationStrs) 225 }) 226 } 227 } 228 229 func TestRelationsEncounteredForResource(t *testing.T) { 230 tcs := []struct { 231 name string 232 schema string 233 resourceType string 234 permission string 235 expectedRelations []string 236 }{ 237 { 238 "simple relation", 239 `definition user {} 240 241 definition document { 242 relation viewer: user 243 }`, 244 245 "document", 246 "viewer", 247 []string{"document#viewer"}, 248 }, 249 { 250 "simple permission", 251 `definition user {} 252 253 definition document { 254 relation viewer: user 255 permission view = viewer + nil 256 }`, 257 "document", 258 "view", 259 []string{"document#viewer", "document#view"}, 260 }, 261 { 262 "permission with multiple relations", 263 `definition user {} 264 265 definition document { 266 relation viewer: user 267 relation editor: user 268 relation owner: user 269 permission view = viewer + editor + owner 270 }`, 271 "document", 272 "view", 273 []string{"document#editor", "document#owner", "document#viewer", "document#view"}, 274 }, 275 { 276 "permission with multiple relations under intersection", 277 `definition user {} 278 279 definition document { 280 relation viewer: user 281 relation editor: user 282 relation owner: user 283 284 permission view = viewer & editor & owner 285 }`, 286 "document", 287 "view", 288 []string{"document#viewer", "document#view", "document#editor", "document#owner"}, 289 }, 290 { 291 "permission with multiple relations under exclusion", 292 `definition user {} 293 294 definition document { 295 relation viewer: user 296 relation editor: user 297 relation owner: user 298 299 permission view = viewer - editor - owner 300 }`, 301 "document", 302 "view", 303 []string{"document#viewer", "document#view", "document#editor", "document#owner"}, 304 }, 305 { 306 "permission with arrow", 307 `definition user {} 308 309 definition organization { 310 relation admin: user 311 } 312 313 definition document { 314 relation org: organization 315 relation viewer: user 316 relation owner: user 317 318 permission view = viewer + owner + org->admin 319 }`, 320 "document", 321 "view", 322 []string{"document#viewer", "document#owner", "document#org", "document#view", "organization#admin"}, 323 }, 324 { 325 "permission with subrelation", 326 `definition user {} 327 328 definition group { 329 relation direct_member: user 330 relation admin: user 331 332 permission member = direct_member + admin 333 } 334 335 definition document { 336 relation viewer: user | group#member 337 338 permission view = viewer 339 }`, 340 341 "document", 342 "view", 343 344 []string{"document#viewer", "document#view", "group#direct_member", "group#admin", "group#member"}, 345 }, 346 { 347 "permission with unused relation", 348 `definition user {} 349 350 definition resource { 351 relation viewer: user 352 relation editor: user 353 relation owner: user 354 relation unused: user 355 356 permission view = viewer + editor + owner 357 }`, 358 359 "resource", 360 "view", 361 362 []string{"resource#viewer", "resource#editor", "resource#owner", "resource#view"}, 363 }, 364 { 365 "permission with multiple arrows", 366 `definition user {} 367 368 definition organization { 369 relation admin: user 370 relation banned: user 371 relation member: user 372 373 permission can_admin = admin - banned 374 } 375 376 definition group { 377 relation admin: user 378 relation direct_member: user | group#member 379 relation org: organization 380 381 permission member = direct_member + admin + org->can_admin 382 } 383 384 definition document { 385 relation viewer: user | group#member 386 relation owner: user 387 relation org: organization 388 389 permission view = viewer + owner + org->member 390 }`, 391 392 "document", 393 "view", 394 395 []string{ 396 "document#viewer", "document#owner", "document#org", "document#view", 397 "group#admin", "group#direct_member", "group#member", "organization#admin", 398 "organization#member", "organization#can_admin", "group#org", "organization#banned", 399 }, 400 }, 401 { 402 "permission with multiple arrows but only one used", 403 `definition user {} 404 405 definition organization { 406 relation admin: user 407 relation banned: user 408 relation member: user 409 410 permission can_admin = admin - banned 411 } 412 413 definition group { 414 relation admin: user 415 relation direct_member: user | group#member 416 relation org: organization 417 418 permission member = direct_member + admin + org->can_admin 419 } 420 421 definition document { 422 relation viewer: user | group#member 423 relation owner: user 424 relation org: organization 425 426 permission view = viewer + owner 427 }`, 428 429 "document", 430 "view", 431 432 []string{ 433 "document#viewer", "document#owner", "document#view", 434 "group#admin", "group#direct_member", "group#member", "organization#admin", 435 "organization#can_admin", "group#org", "organization#banned", 436 }, 437 }, 438 { 439 "permission with multiple items but only one path used", 440 `definition user {} 441 442 definition organization { 443 relation admin: user 444 relation banned: user 445 relation member: user 446 447 permission can_admin = admin - banned 448 } 449 450 definition group { 451 relation admin: user 452 relation direct_member: user | group#member 453 relation org: organization 454 455 permission member = direct_member + admin + org->can_admin 456 } 457 458 definition document { 459 relation viewer: user 460 relation owner: user 461 relation org: organization 462 463 permission view = viewer + owner 464 }`, 465 466 "document", 467 "view", 468 469 []string{ 470 "document#viewer", "document#owner", "document#view", 471 }, 472 }, 473 { 474 "permission with many indirect relations", 475 `definition user {} 476 477 definition first { 478 relation member: second#member 479 } 480 481 definition second { 482 relation member: third#member 483 } 484 485 definition third { 486 relation member: user 487 } 488 489 definition document { 490 relation viewer: user | first#member 491 permission view = viewer 492 }`, 493 494 "document", 495 "view", 496 497 []string{ 498 "document#viewer", "document#view", "first#member", "second#member", "third#member", 499 }, 500 }, 501 } 502 503 for _, tc := range tcs { 504 tc := tc 505 t.Run(tc.name, func(t *testing.T) { 506 require := require.New(t) 507 508 ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC) 509 require.NoError(err) 510 511 ctx := datastoremw.ContextWithDatastore(context.Background(), ds) 512 513 compiled, err := compiler.Compile(compiler.InputSchema{ 514 Source: input.Source("schema"), 515 SchemaString: tc.schema, 516 }, compiler.AllowUnprefixedObjectType()) 517 require.NoError(err) 518 519 // Write the schema. 520 _, err = ds.ReadWriteTx(context.Background(), func(ctx context.Context, tx datastore.ReadWriteTransaction) error { 521 for _, nsDef := range compiled.ObjectDefinitions { 522 if err := tx.WriteNamespaces(ctx, nsDef); err != nil { 523 return err 524 } 525 } 526 527 return nil 528 }) 529 require.NoError(err) 530 531 lastRevision, err := ds.HeadRevision(context.Background()) 532 require.NoError(err) 533 534 reader := ds.SnapshotReader(lastRevision) 535 536 _, vts, err := ReadNamespaceAndTypes(ctx, tc.resourceType, reader) 537 require.NoError(err) 538 539 rg := ReachabilityGraphFor(vts) 540 541 relations, err := rg.RelationsEncounteredForResource(ctx, &core.RelationReference{ 542 Namespace: tc.resourceType, 543 Relation: tc.permission, 544 }) 545 require.NoError(err) 546 547 relationStrs := make([]string, 0, len(relations)) 548 for _, relation := range relations { 549 relationStrs = append(relationStrs, tuple.StringRR(relation)) 550 } 551 552 sort.Strings(relationStrs) 553 sort.Strings(tc.expectedRelations) 554 555 require.Equal(tc.expectedRelations, relationStrs) 556 }) 557 } 558 } 559 560 func TestReachabilityGraph(t *testing.T) { 561 testCases := []struct { 562 name string 563 schema string 564 resourceType *core.RelationReference 565 subjectType *core.RelationReference 566 expectedFullEntrypointRelations []rrtStruct 567 expectedOptimizedEntrypointRelations []rrtStruct 568 }{ 569 { 570 "single relation", 571 `definition user {} 572 573 definition document { 574 relation viewer: user 575 }`, 576 rr("document", "viewer"), 577 rr("user", "..."), 578 []rrtStruct{rrt("document", "viewer", true)}, 579 []rrtStruct{rrt("document", "viewer", true)}, 580 }, 581 { 582 "simple permission", 583 `definition user {} 584 585 definition document { 586 relation viewer: user 587 permission view = viewer + nil 588 }`, 589 rr("document", "view"), 590 rr("user", "..."), 591 []rrtStruct{rrt("document", "viewer", true)}, 592 []rrtStruct{rrt("document", "viewer", true)}, 593 }, 594 { 595 "permission with multiple relations", 596 `definition user {} 597 598 definition document { 599 relation viewer: user 600 relation editor: user 601 relation owner: user 602 permission view = viewer + editor + owner 603 }`, 604 rr("document", "view"), 605 rr("user", "..."), 606 []rrtStruct{ 607 rrt("document", "editor", true), 608 rrt("document", "owner", true), 609 rrt("document", "viewer", true), 610 }, 611 []rrtStruct{ 612 rrt("document", "editor", true), 613 rrt("document", "owner", true), 614 rrt("document", "viewer", true), 615 }, 616 }, 617 { 618 "permission with multiple relations under intersection", 619 `definition user {} 620 621 definition document { 622 relation viewer: user 623 relation editor: user 624 relation owner: user 625 permission view = viewer & editor & owner 626 }`, 627 rr("document", "view"), 628 rr("user", "..."), 629 []rrtStruct{ 630 rrt("document", "editor", true), 631 rrt("document", "owner", true), 632 rrt("document", "viewer", true), 633 }, 634 []rrtStruct{ 635 rrt("document", "viewer", true), 636 }, 637 }, 638 { 639 "permission with multiple relations under exclusion", 640 `definition user {} 641 642 definition document { 643 relation viewer: user 644 relation editor: user 645 relation owner: user 646 permission view = viewer - editor - owner 647 }`, 648 rr("document", "view"), 649 rr("user", "..."), 650 []rrtStruct{ 651 rrt("document", "editor", true), 652 rrt("document", "owner", true), 653 rrt("document", "viewer", true), 654 }, 655 []rrtStruct{ 656 rrt("document", "viewer", true), 657 }, 658 }, 659 { 660 "permission with arrow", 661 `definition user {} 662 663 definition organization { 664 relation admin: user 665 } 666 667 definition document { 668 relation org: organization 669 relation viewer: user 670 relation owner: user 671 permission view = viewer + owner + org->admin 672 }`, 673 rr("document", "view"), 674 rr("user", "..."), 675 []rrtStruct{ 676 rrt("document", "owner", true), 677 rrt("document", "viewer", true), 678 rrt("organization", "admin", true), 679 }, 680 []rrtStruct{ 681 rrt("document", "owner", true), 682 rrt("document", "viewer", true), 683 rrt("organization", "admin", true), 684 }, 685 }, 686 { 687 "permission with multi-level arrows", 688 `definition user {} 689 690 definition organization { 691 relation admin: user 692 } 693 694 definition container { 695 relation parent: organization | container 696 permission admin = parent->admin 697 } 698 699 definition document { 700 relation container: container 701 relation viewer: user 702 relation owner: user 703 permission view = viewer + owner + container->admin 704 }`, 705 rr("document", "view"), 706 rr("user", "..."), 707 []rrtStruct{ 708 rrt("document", "owner", true), 709 rrt("document", "viewer", true), 710 rrt("organization", "admin", true), 711 }, 712 []rrtStruct{ 713 rrt("document", "owner", true), 714 rrt("document", "viewer", true), 715 rrt("organization", "admin", true), 716 }, 717 }, 718 { 719 "permission with multi-level arrows and container", 720 `definition user {} 721 722 definition organization { 723 relation admin: user 724 } 725 726 definition container { 727 relation parent: organization | container 728 relation localadmin: user 729 permission admin = parent->admin + localadmin 730 } 731 732 definition document { 733 relation container: container 734 relation viewer: user 735 relation owner: user 736 permission view = viewer + owner + container->admin 737 }`, 738 rr("document", "view"), 739 rr("user", "..."), 740 []rrtStruct{ 741 rrt("container", "localadmin", true), 742 rrt("document", "owner", true), 743 rrt("document", "viewer", true), 744 rrt("organization", "admin", true), 745 }, 746 []rrtStruct{ 747 rrt("container", "localadmin", true), 748 rrt("document", "owner", true), 749 rrt("document", "viewer", true), 750 rrt("organization", "admin", true), 751 }, 752 }, 753 { 754 "recursive relation", 755 `definition user {} 756 757 definition group { 758 relation direct_member: group#member | user 759 relation manager: group#member | user 760 permission member = direct_member + manager 761 } 762 763 definition document { 764 relation viewer: user | group#member 765 permission view = viewer 766 }`, 767 rr("document", "view"), 768 rr("user", "..."), 769 []rrtStruct{ 770 rrt("document", "viewer", true), 771 rrt("group", "direct_member", true), 772 rrt("group", "manager", true), 773 }, 774 []rrtStruct{ 775 rrt("document", "viewer", true), 776 rrt("group", "direct_member", true), 777 rrt("group", "manager", true), 778 }, 779 }, 780 { 781 "arrow under exclusion", 782 `definition user {} 783 784 definition organization { 785 relation admin: user 786 relation banned: user 787 } 788 789 definition document { 790 relation org: organization 791 relation viewer: user 792 permission view = (viewer - org->banned) + org->admin 793 }`, 794 rr("document", "view"), 795 rr("user", "..."), 796 []rrtStruct{ 797 rrt("document", "viewer", true), 798 rrt("organization", "admin", true), 799 rrt("organization", "banned", true), 800 }, 801 []rrtStruct{ 802 rrt("document", "viewer", true), 803 rrt("organization", "admin", true), 804 }, 805 }, 806 { 807 "multiple relations with different subject types", 808 `definition platform1user {} 809 definition platform2user {} 810 811 definition document { 812 relation viewer: platform1user | platform2user 813 relation editor: platform1user 814 relation owner: platform2user 815 816 permission view = viewer + editor + owner 817 }`, 818 rr("document", "view"), 819 rr("platform1user", "..."), 820 []rrtStruct{ 821 rrt("document", "editor", true), 822 rrt("document", "viewer", true), 823 }, 824 []rrtStruct{ 825 rrt("document", "editor", true), 826 rrt("document", "viewer", true), 827 }, 828 }, 829 { 830 "optimized reachability", 831 `definition user {} 832 833 definition organization { 834 relation admin: user 835 relation banned: user 836 } 837 838 definition document { 839 relation org: organization 840 relation viewer: user 841 relation anotherrel: user 842 relation thirdrel: user 843 relation fourthrel: user 844 permission view = ((((viewer - org->banned) & org->admin) + anotherrel) - thirdrel) + fourthrel 845 }`, 846 rr("document", "view"), 847 rr("user", "..."), 848 []rrtStruct{ 849 rrt("document", "anotherrel", true), 850 rrt("document", "fourthrel", true), 851 rrt("document", "thirdrel", true), 852 rrt("document", "viewer", true), 853 rrt("organization", "admin", true), 854 rrt("organization", "banned", true), 855 }, 856 []rrtStruct{ 857 rrt("document", "anotherrel", true), 858 rrt("document", "fourthrel", true), 859 rrt("document", "viewer", true), 860 }, 861 }, 862 { 863 "optimized reachability, within expression", 864 `definition user {} 865 866 definition organization { 867 relation admin: user 868 relation banned: user 869 } 870 871 definition document { 872 relation org: organization 873 relation viewer: user 874 relation anotherrel: user 875 relation thirdrel: user 876 relation fourthrel: user 877 permission view = ((((viewer - org->banned) & org->admin) + anotherrel) - thirdrel) + fourthrel 878 }`, 879 rr("document", "view"), 880 rr("document", "viewer"), 881 []rrtStruct{ 882 rrt("document", "view", false), 883 }, 884 []rrtStruct{ 885 rrt("document", "view", false), 886 }, 887 }, 888 { 889 "optimized reachability, within expression 2", 890 `definition user {} 891 892 definition organization { 893 relation admin: user 894 relation banned: user 895 } 896 897 definition document { 898 relation org: organization 899 relation viewer: user 900 relation anotherrel: user 901 relation thirdrel: user 902 relation fourthrel: user 903 permission view = ((((viewer - org->banned) & org->admin) + anotherrel) - thirdrel) + fourthrel 904 }`, 905 rr("document", "view"), 906 rr("organization", "admin"), 907 []rrtStruct{ 908 rrt("document", "view", false), 909 }, 910 []rrtStruct{}, 911 }, 912 { 913 "intermediate reachability", 914 `definition user {} 915 916 definition organization { 917 relation admin: user 918 } 919 920 definition container { 921 relation parent: organization | container 922 relation localadmin: user 923 permission admin = parent->admin + localadmin 924 permission anotherthing = localadmin 925 } 926 927 definition document { 928 relation container: container 929 relation viewer: user 930 relation owner: user 931 permission view = viewer + owner + container->admin + container->anotherthing 932 }`, 933 rr("document", "view"), 934 rr("container", "localadmin"), 935 []rrtStruct{ 936 rrt("container", "admin", true), 937 rrt("container", "anotherthing", true), 938 }, 939 []rrtStruct{ 940 rrt("container", "admin", true), 941 rrt("container", "anotherthing", true), 942 }, 943 }, 944 { 945 "intermediate reachability with intersection", 946 `definition user {} 947 948 definition organization { 949 relation admin: user 950 } 951 952 definition container { 953 relation parent: organization | container 954 relation localadmin: user 955 relation another: user 956 permission admin = parent->admin + localadmin 957 permission anotherthing = localadmin & another 958 } 959 960 definition document { 961 relation container: container 962 permission view = container->admin + container->anotherthing 963 }`, 964 rr("document", "view"), 965 rr("container", "localadmin"), 966 []rrtStruct{ 967 rrt("container", "admin", true), 968 rrt("container", "anotherthing", false), 969 }, 970 []rrtStruct{ 971 rrt("container", "admin", true), 972 rrt("container", "anotherthing", false), 973 }, 974 }, 975 { 976 "relation reused", 977 `definition user {} 978 979 definition document { 980 relation viewer: user 981 relation another: user 982 permission view = viewer + viewer 983 }`, 984 rr("document", "view"), 985 rr("document", "viewer"), 986 []rrtStruct{ 987 rrt("document", "view", true), 988 }, 989 []rrtStruct{ 990 rrt("document", "view", true), 991 }, 992 }, 993 { 994 "relation does not exist on one type of the arrow", 995 `definition user {} 996 997 definition team {} 998 999 definition organization { 1000 relation viewer: user 1001 } 1002 1003 definition document { 1004 relation parent: organization | team 1005 permission view = parent->viewer 1006 }`, 1007 rr("document", "view"), 1008 rr("user", "..."), 1009 []rrtStruct{ 1010 rrt("organization", "viewer", true), 1011 }, 1012 []rrtStruct{ 1013 rrt("organization", "viewer", true), 1014 }, 1015 }, 1016 } 1017 1018 for _, tc := range testCases { 1019 tc := tc 1020 t.Run(tc.name, func(t *testing.T) { 1021 require := require.New(t) 1022 1023 ds, err := memdb.NewMemdbDatastore(0, 0, memdb.DisableGC) 1024 require.NoError(err) 1025 1026 ctx := datastoremw.ContextWithDatastore(context.Background(), ds) 1027 1028 compiled, err := compiler.Compile(compiler.InputSchema{ 1029 Source: input.Source("schema"), 1030 SchemaString: tc.schema, 1031 }, compiler.AllowUnprefixedObjectType()) 1032 require.NoError(err) 1033 1034 lastRevision, err := ds.HeadRevision(context.Background()) 1035 require.NoError(err) 1036 1037 var rts *ValidatedNamespaceTypeSystem 1038 for _, nsDef := range compiled.ObjectDefinitions { 1039 reader := ds.SnapshotReader(lastRevision) 1040 ts, err := NewNamespaceTypeSystem(nsDef, 1041 ResolverForDatastoreReader(reader).WithPredefinedElements(PredefinedElements{ 1042 Namespaces: compiled.ObjectDefinitions, 1043 Caveats: compiled.CaveatDefinitions, 1044 })) 1045 require.NoError(err) 1046 1047 vts, terr := ts.Validate(ctx) 1048 require.NoError(terr) 1049 1050 if nsDef.Name == tc.resourceType.Namespace { 1051 rts = vts 1052 } 1053 } 1054 require.NotNil(rts) 1055 1056 foundEntrypoints, err := ReachabilityGraphFor(rts).AllEntrypointsForSubjectToResource(ctx, tc.subjectType, tc.resourceType) 1057 require.NoError(err) 1058 verifyEntrypoints(require, foundEntrypoints, tc.expectedFullEntrypointRelations) 1059 1060 foundOptEntrypoints, err := ReachabilityGraphFor(rts).OptimizedEntrypointsForSubjectToResource(ctx, tc.subjectType, tc.resourceType) 1061 require.NoError(err) 1062 verifyEntrypoints(require, foundOptEntrypoints, tc.expectedOptimizedEntrypointRelations) 1063 }) 1064 } 1065 } 1066 1067 func verifyEntrypoints(require *require.Assertions, foundEntrypoints []ReachabilityEntrypoint, expectedEntrypoints []rrtStruct) { 1068 expectedEntrypointRelations := make([]string, 0, len(expectedEntrypoints)) 1069 isDirectMap := map[string]bool{} 1070 for _, expected := range expectedEntrypoints { 1071 expectedEntrypointRelations = append(expectedEntrypointRelations, tuple.StringRR(expected.relationRef)) 1072 isDirectMap[tuple.StringRR(expected.relationRef)] = expected.isDirect 1073 } 1074 1075 foundRelations := make([]string, 0, len(foundEntrypoints)) 1076 for _, entrypoint := range foundEntrypoints { 1077 foundRelations = append(foundRelations, tuple.StringRR(entrypoint.ContainingRelationOrPermission())) 1078 if isDirect, ok := isDirectMap[tuple.StringRR(entrypoint.ContainingRelationOrPermission())]; ok { 1079 require.Equal(isDirect, entrypoint.IsDirectResult(), "found mismatch for whether a direct result for entrypoint for %s", entrypoint.parentRelation.Relation) 1080 } 1081 } 1082 1083 sort.Strings(expectedEntrypointRelations) 1084 sort.Strings(foundRelations) 1085 require.Equal(expectedEntrypointRelations, foundRelations) 1086 } 1087 1088 type rrtStruct struct { 1089 relationRef *core.RelationReference 1090 isDirect bool 1091 } 1092 1093 func rr(namespace, relation string) *core.RelationReference { 1094 return ns.RelationReference(namespace, relation) 1095 } 1096 1097 func rrt(namespace, relation string, isDirect bool) rrtStruct { 1098 return rrtStruct{ns.RelationReference(namespace, relation), isDirect} 1099 }