github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/v1/expreflection_test.go (about) 1 package v1 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 8 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 9 "github.com/ettle/strcase" 10 "github.com/stretchr/testify/require" 11 12 "github.com/authzed/spicedb/pkg/datastore/revisionparsing" 13 "github.com/authzed/spicedb/pkg/diff" 14 "github.com/authzed/spicedb/pkg/genutil/mapz" 15 "github.com/authzed/spicedb/pkg/schemadsl/compiler" 16 "github.com/authzed/spicedb/pkg/schemadsl/input" 17 "github.com/authzed/spicedb/pkg/testutil" 18 ) 19 20 func TestConvertDiff(t *testing.T) { 21 tcs := []struct { 22 name string 23 existingSchema string 24 comparisonSchema string 25 expectedResponse *v1.ExperimentalDiffSchemaResponse 26 }{ 27 { 28 "no diff", 29 `definition user {}`, 30 `definition user {}`, 31 &v1.ExperimentalDiffSchemaResponse{ 32 Diffs: []*v1.ExpSchemaDiff{}, 33 }, 34 }, 35 { 36 "add namespace", 37 ``, 38 `definition user {}`, 39 &v1.ExperimentalDiffSchemaResponse{ 40 Diffs: []*v1.ExpSchemaDiff{ 41 { 42 Diff: &v1.ExpSchemaDiff_DefinitionAdded{ 43 DefinitionAdded: &v1.ExpDefinition{ 44 Name: "user", 45 Comment: "", 46 }, 47 }, 48 }, 49 }, 50 }, 51 }, 52 { 53 "remove namespace", 54 `definition user {}`, 55 ``, 56 &v1.ExperimentalDiffSchemaResponse{ 57 Diffs: []*v1.ExpSchemaDiff{ 58 { 59 Diff: &v1.ExpSchemaDiff_DefinitionRemoved{ 60 DefinitionRemoved: &v1.ExpDefinition{ 61 Name: "user", 62 Comment: "", 63 }, 64 }, 65 }, 66 }, 67 }, 68 }, 69 { 70 "change namespace comment", 71 `definition user {}`, 72 `// user has a comment 73 definition user {}`, 74 &v1.ExperimentalDiffSchemaResponse{ 75 Diffs: []*v1.ExpSchemaDiff{ 76 { 77 Diff: &v1.ExpSchemaDiff_DefinitionDocCommentChanged{ 78 DefinitionDocCommentChanged: &v1.ExpDefinition{ 79 Name: "user", 80 Comment: "// user has a comment", 81 }, 82 }, 83 }, 84 }, 85 }, 86 }, 87 { 88 "add caveat", 89 ``, 90 `caveat someCaveat(someparam int) { someparam < 42 }`, 91 &v1.ExperimentalDiffSchemaResponse{ 92 Diffs: []*v1.ExpSchemaDiff{ 93 { 94 Diff: &v1.ExpSchemaDiff_CaveatAdded{ 95 CaveatAdded: &v1.ExpCaveat{ 96 Name: "someCaveat", 97 Comment: "", 98 Expression: "someparam < 42", 99 Parameters: []*v1.ExpCaveatParameter{ 100 { 101 Name: "someparam", 102 Type: "int", 103 ParentCaveatName: "someCaveat", 104 }, 105 }, 106 }, 107 }, 108 }, 109 }, 110 }, 111 }, 112 { 113 "remove caveat", 114 `caveat someCaveat(someparam int) { someparam < 42 }`, 115 ``, 116 &v1.ExperimentalDiffSchemaResponse{ 117 Diffs: []*v1.ExpSchemaDiff{ 118 { 119 Diff: &v1.ExpSchemaDiff_CaveatRemoved{ 120 CaveatRemoved: &v1.ExpCaveat{ 121 Name: "someCaveat", 122 Comment: "", 123 Expression: "someparam < 42", 124 Parameters: []*v1.ExpCaveatParameter{ 125 { 126 Name: "someparam", 127 Type: "int", 128 ParentCaveatName: "someCaveat", 129 }, 130 }, 131 }, 132 }, 133 }, 134 }, 135 }, 136 }, 137 { 138 "change caveat comment", 139 `// someCaveat has a comment 140 caveat someCaveat(someparam int) { someparam < 42 }`, 141 `// someCaveat has b comment 142 caveat someCaveat(someparam int) { someparam < 42 }`, 143 &v1.ExperimentalDiffSchemaResponse{ 144 Diffs: []*v1.ExpSchemaDiff{ 145 { 146 Diff: &v1.ExpSchemaDiff_CaveatDocCommentChanged{ 147 CaveatDocCommentChanged: &v1.ExpCaveat{ 148 Name: "someCaveat", 149 Comment: "// someCaveat has b comment", 150 Expression: "someparam < 42", 151 Parameters: []*v1.ExpCaveatParameter{ 152 { 153 Name: "someparam", 154 Type: "int", 155 ParentCaveatName: "someCaveat", 156 }, 157 }, 158 }, 159 }, 160 }, 161 }, 162 }, 163 }, 164 { 165 "added relation", 166 `definition user {}`, 167 `definition user { relation somerel: user; }`, 168 &v1.ExperimentalDiffSchemaResponse{ 169 Diffs: []*v1.ExpSchemaDiff{ 170 { 171 Diff: &v1.ExpSchemaDiff_RelationAdded{ 172 RelationAdded: &v1.ExpRelation{ 173 Name: "somerel", 174 Comment: "", 175 ParentDefinitionName: "user", 176 SubjectTypes: []*v1.ExpTypeReference{ 177 { 178 SubjectDefinitionName: "user", 179 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 180 }, 181 }, 182 }, 183 }, 184 }, 185 }, 186 }, 187 }, 188 { 189 "removed relation", 190 `definition user { relation somerel: user; }`, 191 `definition user {}`, 192 &v1.ExperimentalDiffSchemaResponse{ 193 Diffs: []*v1.ExpSchemaDiff{ 194 { 195 Diff: &v1.ExpSchemaDiff_RelationRemoved{ 196 RelationRemoved: &v1.ExpRelation{ 197 Name: "somerel", 198 Comment: "", 199 ParentDefinitionName: "user", 200 SubjectTypes: []*v1.ExpTypeReference{ 201 { 202 SubjectDefinitionName: "user", 203 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 204 }, 205 }, 206 }, 207 }, 208 }, 209 }, 210 }, 211 }, 212 { 213 "relation type added", 214 `definition user {} 215 216 definition anon {} 217 218 definition resource { 219 relation viewer: anon 220 } 221 `, 222 `definition user {} 223 224 definition anon {} 225 226 definition resource { 227 relation viewer: user | anon 228 } 229 `, 230 &v1.ExperimentalDiffSchemaResponse{ 231 Diffs: []*v1.ExpSchemaDiff{ 232 { 233 Diff: &v1.ExpSchemaDiff_RelationSubjectTypeAdded{ 234 RelationSubjectTypeAdded: &v1.ExpRelationSubjectTypeChange{ 235 Relation: &v1.ExpRelation{ 236 Name: "viewer", 237 Comment: "", 238 ParentDefinitionName: "resource", 239 SubjectTypes: []*v1.ExpTypeReference{ 240 { 241 SubjectDefinitionName: "user", 242 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 243 }, 244 { 245 SubjectDefinitionName: "anon", 246 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 247 }, 248 }, 249 }, 250 ChangedSubjectType: &v1.ExpTypeReference{ 251 SubjectDefinitionName: "user", 252 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 253 }, 254 }, 255 }, 256 }, 257 }, 258 }, 259 }, 260 { 261 "relation type removed", 262 `definition user {} 263 264 definition anon {} 265 266 definition resource { 267 relation viewer: anon | user 268 } 269 `, 270 `definition user {} 271 272 definition anon {} 273 274 definition resource { 275 relation viewer: user 276 } 277 `, 278 &v1.ExperimentalDiffSchemaResponse{ 279 Diffs: []*v1.ExpSchemaDiff{ 280 { 281 Diff: &v1.ExpSchemaDiff_RelationSubjectTypeRemoved{ 282 RelationSubjectTypeRemoved: &v1.ExpRelationSubjectTypeChange{ 283 Relation: &v1.ExpRelation{ 284 Name: "viewer", 285 Comment: "", 286 ParentDefinitionName: "resource", 287 SubjectTypes: []*v1.ExpTypeReference{ 288 { 289 SubjectDefinitionName: "user", 290 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 291 }, 292 }, 293 }, 294 ChangedSubjectType: &v1.ExpTypeReference{ 295 SubjectDefinitionName: "anon", 296 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 297 }, 298 }, 299 }, 300 }, 301 }, 302 }, 303 }, 304 { 305 "relation comment changed", 306 `definition user {} 307 308 definition resource { 309 relation viewer: user 310 }`, 311 `definition user {} 312 313 definition resource { 314 // viewer has a comment 315 relation viewer: user 316 }`, 317 &v1.ExperimentalDiffSchemaResponse{ 318 Diffs: []*v1.ExpSchemaDiff{ 319 { 320 Diff: &v1.ExpSchemaDiff_RelationDocCommentChanged{ 321 RelationDocCommentChanged: &v1.ExpRelation{ 322 Name: "viewer", 323 Comment: "// viewer has a comment", 324 ParentDefinitionName: "resource", 325 SubjectTypes: []*v1.ExpTypeReference{ 326 { 327 SubjectDefinitionName: "user", 328 Typeref: &v1.ExpTypeReference_IsTerminalSubject{}, 329 }, 330 }, 331 }, 332 }, 333 }, 334 }, 335 }, 336 }, 337 { 338 "added permission", 339 `definition user {} 340 341 definition resource { 342 } 343 `, 344 `definition user {} 345 346 definition resource { 347 permission foo = nil 348 }`, 349 &v1.ExperimentalDiffSchemaResponse{ 350 Diffs: []*v1.ExpSchemaDiff{ 351 { 352 Diff: &v1.ExpSchemaDiff_PermissionAdded{ 353 PermissionAdded: &v1.ExpPermission{ 354 Name: "foo", 355 Comment: "", 356 ParentDefinitionName: "resource", 357 }, 358 }, 359 }, 360 }, 361 }, 362 }, 363 { 364 "removed permission", 365 `definition user {} 366 367 definition resource { 368 permission foo = nil 369 }`, 370 `definition user {} 371 372 definition resource { 373 }`, 374 &v1.ExperimentalDiffSchemaResponse{ 375 Diffs: []*v1.ExpSchemaDiff{ 376 { 377 Diff: &v1.ExpSchemaDiff_PermissionRemoved{ 378 PermissionRemoved: &v1.ExpPermission{ 379 Name: "foo", 380 Comment: "", 381 ParentDefinitionName: "resource", 382 }, 383 }, 384 }, 385 }, 386 }, 387 }, 388 { 389 "permission comment changed", 390 `definition user {} 391 392 definition resource { 393 // foo has a comment 394 permission foo = nil 395 }`, 396 `definition user {} 397 398 definition resource { 399 // foo has a new comment 400 permission foo = nil 401 }`, 402 &v1.ExperimentalDiffSchemaResponse{ 403 Diffs: []*v1.ExpSchemaDiff{ 404 { 405 Diff: &v1.ExpSchemaDiff_PermissionDocCommentChanged{ 406 PermissionDocCommentChanged: &v1.ExpPermission{ 407 Name: "foo", 408 Comment: "// foo has a new comment", 409 ParentDefinitionName: "resource", 410 }, 411 }, 412 }, 413 }, 414 }, 415 }, 416 { 417 "permission expression changed", 418 `definition resource { 419 permission foo = nil 420 }`, 421 `definition resource { 422 permission foo = foo 423 }`, 424 &v1.ExperimentalDiffSchemaResponse{ 425 Diffs: []*v1.ExpSchemaDiff{ 426 { 427 Diff: &v1.ExpSchemaDiff_PermissionExprChanged{ 428 PermissionExprChanged: &v1.ExpPermission{ 429 Name: "foo", 430 Comment: "", 431 ParentDefinitionName: "resource", 432 }, 433 }, 434 }, 435 }, 436 }, 437 }, 438 { 439 "caveat parameter added", 440 `caveat someCaveat(someparam int) { someparam < 42 }`, 441 `caveat someCaveat(someparam int, someparam2 string) { someparam < 42 }`, 442 &v1.ExperimentalDiffSchemaResponse{ 443 Diffs: []*v1.ExpSchemaDiff{ 444 { 445 Diff: &v1.ExpSchemaDiff_CaveatParameterAdded{ 446 CaveatParameterAdded: &v1.ExpCaveatParameter{ 447 Name: "someparam2", 448 Type: "string", 449 ParentCaveatName: "someCaveat", 450 }, 451 }, 452 }, 453 }, 454 }, 455 }, 456 { 457 "caveat parameter removed", 458 `caveat someCaveat(someparam int, someparam2 string) { someparam < 42 }`, 459 `caveat someCaveat(someparam int) { someparam < 42 }`, 460 &v1.ExperimentalDiffSchemaResponse{ 461 Diffs: []*v1.ExpSchemaDiff{ 462 { 463 Diff: &v1.ExpSchemaDiff_CaveatParameterRemoved{ 464 CaveatParameterRemoved: &v1.ExpCaveatParameter{ 465 Name: "someparam2", 466 Type: "string", 467 ParentCaveatName: "someCaveat", 468 }, 469 }, 470 }, 471 }, 472 }, 473 }, 474 { 475 "caveat parameter type changed", 476 `caveat someCaveat(someparam int) { someparam < 42 }`, 477 `caveat someCaveat(someparam uint) { someparam < 42 }`, 478 &v1.ExperimentalDiffSchemaResponse{ 479 Diffs: []*v1.ExpSchemaDiff{ 480 { 481 Diff: &v1.ExpSchemaDiff_CaveatParameterTypeChanged{ 482 CaveatParameterTypeChanged: &v1.ExpCaveatParameterTypeChange{ 483 Parameter: &v1.ExpCaveatParameter{ 484 Name: "someparam", 485 Type: "uint", 486 ParentCaveatName: "someCaveat", 487 }, 488 PreviousType: "int", 489 }, 490 }, 491 }, 492 }, 493 }, 494 }, 495 { 496 "caveat expression changes", 497 `caveat someCaveat(someparam int) { someparam < 42 }`, 498 `caveat someCaveat(someparam int) { someparam < 43 }`, 499 &v1.ExperimentalDiffSchemaResponse{ 500 Diffs: []*v1.ExpSchemaDiff{ 501 { 502 Diff: &v1.ExpSchemaDiff_CaveatExprChanged{ 503 CaveatExprChanged: &v1.ExpCaveat{ 504 Name: "someCaveat", 505 Comment: "", 506 Expression: "someparam < 43", 507 Parameters: []*v1.ExpCaveatParameter{ 508 { 509 Name: "someparam", 510 Type: "int", 511 ParentCaveatName: "someCaveat", 512 }, 513 }, 514 }, 515 }, 516 }, 517 }, 518 }, 519 }, 520 } 521 522 encounteredDiffTypes := mapz.NewSet[string]() 523 casesRun := 0 524 525 for _, tc := range tcs { 526 t.Run(tc.name, func(t *testing.T) { 527 casesRun++ 528 529 existingSchema, err := compiler.Compile(compiler.InputSchema{ 530 Source: input.Source("schema"), 531 SchemaString: tc.existingSchema, 532 }, compiler.AllowUnprefixedObjectType()) 533 require.NoError(t, err) 534 535 comparisonSchema, err := compiler.Compile(compiler.InputSchema{ 536 Source: input.Source("schema"), 537 SchemaString: tc.comparisonSchema, 538 }, compiler.AllowUnprefixedObjectType()) 539 require.NoError(t, err) 540 541 es := diff.NewDiffableSchemaFromCompiledSchema(existingSchema) 542 cs := diff.NewDiffableSchemaFromCompiledSchema(comparisonSchema) 543 544 diff, err := diff.DiffSchemas(es, cs) 545 require.NoError(t, err) 546 547 resp, err := convertDiff( 548 diff, 549 &es, 550 &cs, 551 revisionparsing.MustParseRevisionForTest("1"), 552 ) 553 if err != nil { 554 t.Fatalf("unexpected error: %v", err) 555 } 556 require.NotNil(t, resp.ReadAt) 557 resp.ReadAt = nil 558 559 testutil.RequireProtoEqual(t, tc.expectedResponse, resp, "got mismatch") 560 561 for _, diff := range resp.Diffs { 562 name := reflect.TypeOf(diff.GetDiff()).String() 563 encounteredDiffTypes.Add(strings.ToLower(strings.Split(name, "_")[1])) 564 } 565 }) 566 } 567 568 if casesRun == len(tcs) { 569 msg := &v1.ExpSchemaDiff{} 570 571 allDiffTypes := mapz.NewSet[string]() 572 fields := msg.ProtoReflect().Descriptor().Oneofs().ByName("diff").Fields() 573 for i := 0; i < fields.Len(); i++ { 574 allDiffTypes.Add(strings.ToLower(strcase.ToCamel(string(fields.Get(i).Name())))) 575 } 576 577 require.Empty(t, allDiffTypes.Subtract(encounteredDiffTypes).AsSlice()) 578 } 579 } 580 581 type filterCheck func(sf *schemaFilters) bool 582 583 func TestSchemaFiltering(t *testing.T) { 584 tcs := []struct { 585 name string 586 filters []*v1.ExpSchemaFilter 587 checkers []filterCheck 588 }{ 589 { 590 "no filters", 591 []*v1.ExpSchemaFilter{}, 592 []filterCheck{ 593 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 594 func(sf *schemaFilters) bool { return sf.HasCaveats() }, 595 func(sf *schemaFilters) bool { return sf.HasNamespace("foo") }, 596 func(sf *schemaFilters) bool { return sf.HasCaveat("foo") }, 597 func(sf *schemaFilters) bool { return sf.HasRelation("document", "viewer") }, 598 func(sf *schemaFilters) bool { return sf.HasPermission("document", "view") }, 599 }, 600 }, 601 { 602 "namespace filter", 603 []*v1.ExpSchemaFilter{ 604 { 605 OptionalDefinitionNameFilter: "doc", 606 }, 607 }, 608 []filterCheck{ 609 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 610 func(sf *schemaFilters) bool { return !sf.HasCaveats() }, 611 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 612 func(sf *schemaFilters) bool { return !sf.HasNamespace("foo") }, 613 func(sf *schemaFilters) bool { return sf.HasRelation("document", "viewer") }, 614 func(sf *schemaFilters) bool { return sf.HasPermission("document", "view") }, 615 }, 616 }, 617 { 618 "caveat filter", 619 []*v1.ExpSchemaFilter{ 620 { 621 OptionalCaveatNameFilter: "somec", 622 }, 623 }, 624 []filterCheck{ 625 func(sf *schemaFilters) bool { return !sf.HasNamespaces() }, 626 func(sf *schemaFilters) bool { return sf.HasCaveats() }, 627 func(sf *schemaFilters) bool { return sf.HasCaveat("somecaveat") }, 628 func(sf *schemaFilters) bool { return !sf.HasCaveat("foo") }, 629 }, 630 }, 631 { 632 "multiple namespace filters", 633 []*v1.ExpSchemaFilter{ 634 { 635 OptionalDefinitionNameFilter: "doc", 636 }, 637 { 638 OptionalDefinitionNameFilter: "user", 639 }, 640 }, 641 []filterCheck{ 642 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 643 func(sf *schemaFilters) bool { return !sf.HasCaveats() }, 644 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 645 func(sf *schemaFilters) bool { return sf.HasNamespace("user") }, 646 func(sf *schemaFilters) bool { return !sf.HasNamespace("foo") }, 647 func(sf *schemaFilters) bool { return sf.HasRelation("document", "viewer") }, 648 func(sf *schemaFilters) bool { return sf.HasPermission("document", "view") }, 649 func(sf *schemaFilters) bool { return sf.HasRelation("user", "viewer") }, 650 func(sf *schemaFilters) bool { return sf.HasPermission("user", "view") }, 651 }, 652 }, 653 { 654 "multiple caveat filters", 655 []*v1.ExpSchemaFilter{ 656 { 657 OptionalCaveatNameFilter: "somec", 658 }, 659 { 660 OptionalCaveatNameFilter: "somec2", 661 }, 662 }, 663 []filterCheck{ 664 func(sf *schemaFilters) bool { return !sf.HasNamespaces() }, 665 func(sf *schemaFilters) bool { return sf.HasCaveats() }, 666 func(sf *schemaFilters) bool { return sf.HasCaveat("somecaveat") }, 667 func(sf *schemaFilters) bool { return sf.HasCaveat("somecaveat2") }, 668 func(sf *schemaFilters) bool { return !sf.HasCaveat("foo") }, 669 }, 670 }, 671 { 672 "namespace and caveat filters", 673 []*v1.ExpSchemaFilter{ 674 { 675 OptionalDefinitionNameFilter: "doc", 676 }, 677 { 678 OptionalCaveatNameFilter: "somec", 679 }, 680 }, 681 []filterCheck{ 682 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 683 func(sf *schemaFilters) bool { return sf.HasCaveats() }, 684 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 685 func(sf *schemaFilters) bool { return sf.HasCaveat("somecaveat") }, 686 func(sf *schemaFilters) bool { return !sf.HasNamespace("foo") }, 687 func(sf *schemaFilters) bool { return !sf.HasCaveat("foo") }, 688 }, 689 }, 690 { 691 "relation filter", 692 []*v1.ExpSchemaFilter{ 693 { 694 OptionalDefinitionNameFilter: "doc", 695 OptionalRelationNameFilter: "v", 696 }, 697 }, 698 []filterCheck{ 699 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 700 func(sf *schemaFilters) bool { return !sf.HasCaveats() }, 701 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 702 func(sf *schemaFilters) bool { return sf.HasRelation("document", "viewer") }, 703 func(sf *schemaFilters) bool { return !sf.HasRelation("document", "foo") }, 704 }, 705 }, 706 { 707 "permission filter", 708 []*v1.ExpSchemaFilter{ 709 { 710 OptionalDefinitionNameFilter: "doc", 711 OptionalPermissionNameFilter: "v", 712 }, 713 }, 714 []filterCheck{ 715 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 716 func(sf *schemaFilters) bool { return !sf.HasCaveats() }, 717 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 718 func(sf *schemaFilters) bool { return sf.HasPermission("document", "view") }, 719 func(sf *schemaFilters) bool { return !sf.HasPermission("document", "foo") }, 720 }, 721 }, 722 { 723 "permission and relation filter", 724 []*v1.ExpSchemaFilter{ 725 { 726 OptionalDefinitionNameFilter: "doc", 727 OptionalPermissionNameFilter: "r", 728 }, 729 { 730 OptionalDefinitionNameFilter: "doc", 731 OptionalRelationNameFilter: "v", 732 }, 733 }, 734 []filterCheck{ 735 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 736 func(sf *schemaFilters) bool { return !sf.HasCaveats() }, 737 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 738 func(sf *schemaFilters) bool { return sf.HasRelation("document", "viewer") }, 739 func(sf *schemaFilters) bool { return sf.HasPermission("document", "read") }, 740 func(sf *schemaFilters) bool { return !sf.HasRelation("document", "foo") }, 741 func(sf *schemaFilters) bool { return !sf.HasPermission("document", "foo") }, 742 }, 743 }, 744 { 745 "permission and relation filter over different definitions", 746 []*v1.ExpSchemaFilter{ 747 { 748 OptionalDefinitionNameFilter: "doc", 749 OptionalPermissionNameFilter: "r", 750 }, 751 { 752 OptionalDefinitionNameFilter: "user", 753 OptionalRelationNameFilter: "v", 754 }, 755 }, 756 []filterCheck{ 757 func(sf *schemaFilters) bool { return sf.HasNamespaces() }, 758 func(sf *schemaFilters) bool { return !sf.HasCaveats() }, 759 func(sf *schemaFilters) bool { return sf.HasNamespace("document") }, 760 func(sf *schemaFilters) bool { return sf.HasNamespace("user") }, 761 func(sf *schemaFilters) bool { return sf.HasRelation("user", "viewer") }, 762 func(sf *schemaFilters) bool { return sf.HasPermission("document", "read") }, 763 func(sf *schemaFilters) bool { return !sf.HasRelation("document", "viewer") }, 764 func(sf *schemaFilters) bool { return !sf.HasPermission("user", "read") }, 765 }, 766 }, 767 } 768 769 for _, tc := range tcs { 770 tc := tc 771 t.Run(tc.name, func(t *testing.T) { 772 sf, err := newSchemaFilters(tc.filters) 773 require.NoError(t, err) 774 775 for index, check := range tc.checkers { 776 require.True(t, check(sf), "check failed: #%d", index) 777 } 778 }) 779 } 780 } 781 782 func TestNewSchemaFilters(t *testing.T) { 783 tcs := []struct { 784 name string 785 filters []*v1.ExpSchemaFilter 786 err string 787 }{ 788 { 789 "no filters", 790 []*v1.ExpSchemaFilter{}, 791 "", 792 }, 793 { 794 "namespace filter", 795 []*v1.ExpSchemaFilter{ 796 { 797 OptionalDefinitionNameFilter: "doc", 798 }, 799 }, 800 "", 801 }, 802 { 803 "caveat filter", 804 []*v1.ExpSchemaFilter{ 805 { 806 OptionalCaveatNameFilter: "somec", 807 }, 808 }, 809 "", 810 }, 811 { 812 "relation filter", 813 []*v1.ExpSchemaFilter{ 814 { 815 OptionalDefinitionNameFilter: "doc", 816 OptionalRelationNameFilter: "v", 817 }, 818 }, 819 "", 820 }, 821 { 822 "permission filter", 823 []*v1.ExpSchemaFilter{ 824 { 825 OptionalDefinitionNameFilter: "doc", 826 OptionalPermissionNameFilter: "v", 827 }, 828 }, 829 "", 830 }, 831 { 832 "permission and relation filter", 833 []*v1.ExpSchemaFilter{ 834 { 835 OptionalDefinitionNameFilter: "doc", 836 OptionalPermissionNameFilter: "r", 837 }, 838 { 839 OptionalDefinitionNameFilter: "doc", 840 OptionalRelationNameFilter: "v", 841 }, 842 }, 843 "", 844 }, 845 { 846 "relation filter without definition", 847 []*v1.ExpSchemaFilter{ 848 { 849 OptionalRelationNameFilter: "v", 850 }, 851 }, 852 "relation name match requires definition name match", 853 }, 854 { 855 "permission filter without definition", 856 []*v1.ExpSchemaFilter{ 857 { 858 OptionalPermissionNameFilter: "v", 859 }, 860 }, 861 "permission name match requires definition name match", 862 }, 863 { 864 "filter with both definition and caveat", 865 []*v1.ExpSchemaFilter{ 866 { 867 OptionalDefinitionNameFilter: "doc", 868 OptionalCaveatNameFilter: "somec", 869 }, 870 }, 871 "cannot filter by both definition and caveat name", 872 }, 873 { 874 "filter with both relation and permission", 875 []*v1.ExpSchemaFilter{ 876 { 877 OptionalDefinitionNameFilter: "doc", 878 OptionalRelationNameFilter: "v", 879 OptionalPermissionNameFilter: "r", 880 }, 881 }, 882 "cannot filter by both relation and permission name", 883 }, 884 } 885 886 for _, tc := range tcs { 887 tc := tc 888 t.Run(tc.name, func(t *testing.T) { 889 _, err := newSchemaFilters(tc.filters) 890 if tc.err == "" { 891 require.NoError(t, err) 892 } else { 893 require.ErrorContains(t, err, tc.err) 894 } 895 }) 896 } 897 }