github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/graph/membershipset_test.go (about) 1 package graph 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/require" 7 "google.golang.org/protobuf/types/known/structpb" 8 9 "github.com/authzed/spicedb/internal/caveats" 10 core "github.com/authzed/spicedb/pkg/proto/core/v1" 11 "github.com/authzed/spicedb/pkg/tuple" 12 ) 13 14 var invert = caveats.Invert 15 16 func caveat(name string, context map[string]any) *core.CaveatExpression { 17 s, _ := structpb.NewStruct(context) 18 return wrapCaveat( 19 &core.ContextualizedCaveat{ 20 CaveatName: name, 21 Context: s, 22 }) 23 } 24 25 func TestMembershipSetAddDirectMember(t *testing.T) { 26 tcs := []struct { 27 name string 28 existingMembers map[string]*core.CaveatExpression 29 directMemberID string 30 directMemberCaveat *core.CaveatExpression 31 expectedMembers map[string]*core.CaveatExpression 32 hasDeterminedMember bool 33 }{ 34 { 35 "add determined member to empty set", 36 nil, 37 "somedoc", 38 nil, 39 map[string]*core.CaveatExpression{ 40 "somedoc": nil, 41 }, 42 true, 43 }, 44 { 45 "add caveated member to empty set", 46 nil, 47 "somedoc", 48 caveat("somecaveat", nil), 49 map[string]*core.CaveatExpression{ 50 "somedoc": caveat("somecaveat", nil), 51 }, 52 false, 53 }, 54 { 55 "add caveated member to set with other members", 56 map[string]*core.CaveatExpression{ 57 "somedoc": caveat("somecaveat", nil), 58 }, 59 "anotherdoc", 60 caveat("anothercaveat", nil), 61 map[string]*core.CaveatExpression{ 62 "somedoc": caveat("somecaveat", nil), 63 "anotherdoc": caveat("anothercaveat", nil), 64 }, 65 false, 66 }, 67 { 68 "add non-caveated member to caveated member", 69 map[string]*core.CaveatExpression{ 70 "somedoc": caveat("somecaveat", nil), 71 }, 72 "somedoc", 73 nil, 74 map[string]*core.CaveatExpression{ 75 "somedoc": nil, 76 }, 77 true, 78 }, 79 { 80 "add caveated member to non-caveated member", 81 map[string]*core.CaveatExpression{ 82 "somedoc": nil, 83 }, 84 "somedoc", 85 caveat("somecaveat", nil), 86 map[string]*core.CaveatExpression{ 87 "somedoc": nil, 88 }, 89 true, 90 }, 91 { 92 "add caveated member to caveated member", 93 map[string]*core.CaveatExpression{ 94 "somedoc": caveat("c1", nil), 95 }, 96 "somedoc", 97 caveat("c2", nil), 98 map[string]*core.CaveatExpression{ 99 "somedoc": caveatOr( 100 caveat("c1", nil), 101 caveat("c2", nil), 102 ), 103 }, 104 false, 105 }, 106 { 107 "add caveats with the same name, different args", 108 map[string]*core.CaveatExpression{ 109 "somedoc": caveat("c1", nil), 110 }, 111 "somedoc", 112 caveat("c1", map[string]any{ 113 "hi": "hello", 114 }), 115 map[string]*core.CaveatExpression{ 116 "somedoc": caveatOr( 117 caveat("c1", nil), 118 caveat("c1", map[string]any{ 119 "hi": "hello", 120 }), 121 ), 122 }, 123 false, 124 }, 125 } 126 127 for _, tc := range tcs { 128 tc := tc 129 t.Run(tc.name, func(t *testing.T) { 130 ms := membershipSetFromMap(tc.existingMembers) 131 ms.AddDirectMember(tc.directMemberID, unwrapCaveat(tc.directMemberCaveat)) 132 require.Equal(t, tc.expectedMembers, ms.membersByID) 133 require.Equal(t, tc.hasDeterminedMember, ms.HasDeterminedMember()) 134 require.False(t, ms.IsEmpty()) 135 }) 136 } 137 } 138 139 func TestMembershipSetAddMemberViaRelationship(t *testing.T) { 140 tcs := []struct { 141 name string 142 existingMembers map[string]*core.CaveatExpression 143 resourceID string 144 resourceCaveatExpression *core.CaveatExpression 145 parentRelationship *core.RelationTuple 146 expectedMembers map[string]*core.CaveatExpression 147 hasDeterminedMember bool 148 }{ 149 { 150 "add determined member to empty set", 151 nil, 152 "somedoc", 153 nil, 154 tuple.MustParse("document:foo#viewer@user:tom"), 155 map[string]*core.CaveatExpression{ 156 "somedoc": nil, 157 }, 158 true, 159 }, 160 { 161 "add caveated member to empty set", 162 nil, 163 "somedoc", 164 caveat("somecaveat", nil), 165 tuple.MustParse("document:foo#viewer@user:tom"), 166 map[string]*core.CaveatExpression{ 167 "somedoc": caveat("somecaveat", nil), 168 }, 169 false, 170 }, 171 { 172 "add determined member, via caveated relationship, to empty set", 173 nil, 174 "somedoc", 175 nil, 176 withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("somecaveat", nil)), 177 map[string]*core.CaveatExpression{ 178 "somedoc": caveat("somecaveat", nil), 179 }, 180 false, 181 }, 182 { 183 "add caveated member, via caveated relationship, to empty set", 184 nil, 185 "somedoc", 186 caveat("c1", nil), 187 withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("c2", nil)), 188 map[string]*core.CaveatExpression{ 189 "somedoc": caveatAnd( 190 caveat("c2", nil), 191 caveat("c1", nil), 192 ), 193 }, 194 false, 195 }, 196 { 197 "add caveated member, via caveated relationship, to determined set", 198 map[string]*core.CaveatExpression{ 199 "somedoc": nil, 200 }, 201 "somedoc", 202 caveat("c1", nil), 203 withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("c2", nil)), 204 map[string]*core.CaveatExpression{ 205 "somedoc": nil, 206 }, 207 true, 208 }, 209 { 210 "add caveated member, via caveated relationship, to caveated set", 211 map[string]*core.CaveatExpression{ 212 "somedoc": caveat("c0", nil), 213 }, 214 "somedoc", 215 caveat("c1", nil), 216 withCaveat(tuple.MustParse("document:foo#viewer@user:tom"), caveat("c2", nil)), 217 map[string]*core.CaveatExpression{ 218 "somedoc": caveatOr( 219 caveat("c0", nil), 220 caveatAnd( 221 caveat("c2", nil), 222 caveat("c1", nil), 223 ), 224 ), 225 }, 226 false, 227 }, 228 } 229 230 for _, tc := range tcs { 231 tc := tc 232 t.Run(tc.name, func(t *testing.T) { 233 ms := membershipSetFromMap(tc.existingMembers) 234 ms.AddMemberViaRelationship(tc.resourceID, tc.resourceCaveatExpression, tc.parentRelationship) 235 require.Equal(t, tc.expectedMembers, ms.membersByID) 236 require.Equal(t, tc.hasDeterminedMember, ms.HasDeterminedMember()) 237 }) 238 } 239 } 240 241 func TestMembershipSetUnionWith(t *testing.T) { 242 tcs := []struct { 243 name string 244 set1 map[string]*core.CaveatExpression 245 set2 map[string]*core.CaveatExpression 246 expected map[string]*core.CaveatExpression 247 hasDeterminedMember bool 248 isEmpty bool 249 }{ 250 { 251 "empty with empty", 252 nil, 253 nil, 254 map[string]*core.CaveatExpression{}, 255 false, 256 true, 257 }, 258 { 259 "set with empty", 260 map[string]*core.CaveatExpression{ 261 "somedoc": nil, 262 }, 263 nil, 264 map[string]*core.CaveatExpression{ 265 "somedoc": nil, 266 }, 267 true, 268 false, 269 }, 270 { 271 "empty with set", 272 nil, 273 map[string]*core.CaveatExpression{ 274 "somedoc": nil, 275 }, 276 map[string]*core.CaveatExpression{ 277 "somedoc": nil, 278 }, 279 true, 280 false, 281 }, 282 { 283 "non-overlapping", 284 map[string]*core.CaveatExpression{ 285 "somedoc": nil, 286 }, 287 map[string]*core.CaveatExpression{ 288 "anotherdoc": nil, 289 }, 290 map[string]*core.CaveatExpression{ 291 "somedoc": nil, 292 "anotherdoc": nil, 293 }, 294 true, 295 false, 296 }, 297 { 298 "non-overlapping with caveats", 299 map[string]*core.CaveatExpression{ 300 "somedoc": nil, 301 }, 302 map[string]*core.CaveatExpression{ 303 "anotherdoc": caveat("c1", nil), 304 }, 305 map[string]*core.CaveatExpression{ 306 "somedoc": nil, 307 "anotherdoc": caveat("c1", nil), 308 }, 309 true, 310 false, 311 }, 312 { 313 "overlapping without caveats", 314 map[string]*core.CaveatExpression{ 315 "somedoc": nil, 316 }, 317 map[string]*core.CaveatExpression{ 318 "somedoc": nil, 319 }, 320 map[string]*core.CaveatExpression{ 321 "somedoc": nil, 322 }, 323 true, 324 false, 325 }, 326 { 327 "overlapping with single caveat", 328 map[string]*core.CaveatExpression{ 329 "somedoc": nil, 330 }, 331 map[string]*core.CaveatExpression{ 332 "somedoc": caveat("c1", nil), 333 }, 334 map[string]*core.CaveatExpression{ 335 "somedoc": nil, 336 }, 337 true, 338 false, 339 }, 340 { 341 "overlapping with multiple caveats", 342 map[string]*core.CaveatExpression{ 343 "somedoc": caveat("c1", nil), 344 }, 345 map[string]*core.CaveatExpression{ 346 "somedoc": caveat("c2", nil), 347 }, 348 map[string]*core.CaveatExpression{ 349 "somedoc": caveatOr(caveat("c1", nil), caveat("c2", nil)), 350 }, 351 false, 352 false, 353 }, 354 { 355 "overlapping with multiple caveats and a determined member", 356 map[string]*core.CaveatExpression{ 357 "somedoc": caveat("c1", nil), 358 "anotherdoc": nil, 359 }, 360 map[string]*core.CaveatExpression{ 361 "somedoc": caveat("c2", nil), 362 }, 363 map[string]*core.CaveatExpression{ 364 "anotherdoc": nil, 365 "somedoc": caveatOr(caveat("c1", nil), caveat("c2", nil)), 366 }, 367 true, 368 false, 369 }, 370 } 371 372 for _, tc := range tcs { 373 tc := tc 374 t.Run(tc.name, func(t *testing.T) { 375 ms1 := membershipSetFromMap(tc.set1) 376 ms2 := membershipSetFromMap(tc.set2) 377 ms1.UnionWith(ms2.AsCheckResultsMap()) 378 require.Equal(t, tc.expected, ms1.membersByID) 379 require.Equal(t, tc.hasDeterminedMember, ms1.HasDeterminedMember()) 380 require.Equal(t, tc.isEmpty, ms1.IsEmpty()) 381 }) 382 } 383 } 384 385 func TestMembershipSetIntersectWith(t *testing.T) { 386 tcs := []struct { 387 name string 388 set1 map[string]*core.CaveatExpression 389 set2 map[string]*core.CaveatExpression 390 expected map[string]*core.CaveatExpression 391 hasDeterminedMember bool 392 isEmpty bool 393 }{ 394 { 395 "empty with empty", 396 nil, 397 nil, 398 map[string]*core.CaveatExpression{}, 399 false, 400 true, 401 }, 402 { 403 "set with empty", 404 map[string]*core.CaveatExpression{ 405 "somedoc": nil, 406 }, 407 nil, 408 map[string]*core.CaveatExpression{}, 409 false, 410 true, 411 }, 412 { 413 "empty with set", 414 nil, 415 map[string]*core.CaveatExpression{ 416 "somedoc": nil, 417 }, 418 map[string]*core.CaveatExpression{}, 419 false, 420 true, 421 }, 422 { 423 "basic set with set", 424 map[string]*core.CaveatExpression{ 425 "somedoc": nil, 426 }, 427 map[string]*core.CaveatExpression{ 428 "somedoc": nil, 429 }, 430 map[string]*core.CaveatExpression{ 431 "somedoc": nil, 432 }, 433 true, 434 false, 435 }, 436 { 437 "non-overlapping set with set", 438 map[string]*core.CaveatExpression{ 439 "somedoc": nil, 440 }, 441 map[string]*core.CaveatExpression{ 442 "anotherdoc": nil, 443 }, 444 map[string]*core.CaveatExpression{}, 445 false, 446 true, 447 }, 448 { 449 "partially overlapping set with set", 450 map[string]*core.CaveatExpression{ 451 "somedoc": nil, 452 "anotherdoc": nil, 453 }, 454 map[string]*core.CaveatExpression{ 455 "anotherdoc": nil, 456 }, 457 map[string]*core.CaveatExpression{ 458 "anotherdoc": nil, 459 }, 460 true, 461 false, 462 }, 463 { 464 "set with partially overlapping set", 465 map[string]*core.CaveatExpression{ 466 "anotherdoc": nil, 467 }, 468 map[string]*core.CaveatExpression{ 469 "somedoc": nil, 470 "anotherdoc": nil, 471 }, 472 map[string]*core.CaveatExpression{ 473 "anotherdoc": nil, 474 }, 475 true, 476 false, 477 }, 478 { 479 "partially overlapping sets with one caveat", 480 map[string]*core.CaveatExpression{ 481 "anotherdoc": nil, 482 }, 483 map[string]*core.CaveatExpression{ 484 "somedoc": nil, 485 "anotherdoc": caveat("c2", nil), 486 }, 487 map[string]*core.CaveatExpression{ 488 "anotherdoc": caveat("c2", nil), 489 }, 490 false, 491 false, 492 }, 493 { 494 "partially overlapping sets with one caveat (other side)", 495 map[string]*core.CaveatExpression{ 496 "anotherdoc": caveat("c1", nil), 497 }, 498 map[string]*core.CaveatExpression{ 499 "somedoc": nil, 500 "anotherdoc": nil, 501 }, 502 map[string]*core.CaveatExpression{ 503 "anotherdoc": caveat("c1", nil), 504 }, 505 false, 506 false, 507 }, 508 { 509 "partially overlapping sets with caveats", 510 map[string]*core.CaveatExpression{ 511 "anotherdoc": caveat("c1", nil), 512 }, 513 map[string]*core.CaveatExpression{ 514 "somedoc": nil, 515 "anotherdoc": caveat("c2", nil), 516 }, 517 map[string]*core.CaveatExpression{ 518 "anotherdoc": caveatAnd( 519 caveat("c1", nil), 520 caveat("c2", nil), 521 ), 522 }, 523 false, 524 false, 525 }, 526 { 527 "overlapping sets with caveats and a determined member", 528 map[string]*core.CaveatExpression{ 529 "somedoc": nil, 530 "thirddoc": nil, 531 "anotherdoc": caveat("c1", nil), 532 }, 533 map[string]*core.CaveatExpression{ 534 "somedoc": nil, 535 "anotherdoc": caveat("c2", nil), 536 }, 537 map[string]*core.CaveatExpression{ 538 "somedoc": nil, 539 "anotherdoc": caveatAnd( 540 caveat("c1", nil), 541 caveat("c2", nil), 542 ), 543 }, 544 true, 545 false, 546 }, 547 } 548 549 for _, tc := range tcs { 550 tc := tc 551 t.Run(tc.name, func(t *testing.T) { 552 ms1 := membershipSetFromMap(tc.set1) 553 ms2 := membershipSetFromMap(tc.set2) 554 ms1.IntersectWith(ms2.AsCheckResultsMap()) 555 require.Equal(t, tc.expected, ms1.membersByID) 556 require.Equal(t, tc.hasDeterminedMember, ms1.HasDeterminedMember()) 557 require.Equal(t, tc.isEmpty, ms1.IsEmpty()) 558 }) 559 } 560 } 561 562 func TestMembershipSetSubtract(t *testing.T) { 563 tcs := []struct { 564 name string 565 set1 map[string]*core.CaveatExpression 566 set2 map[string]*core.CaveatExpression 567 expected map[string]*core.CaveatExpression 568 hasDeterminedMember bool 569 isEmpty bool 570 }{ 571 { 572 "empty with empty", 573 nil, 574 nil, 575 map[string]*core.CaveatExpression{}, 576 false, 577 true, 578 }, 579 { 580 "empty with set", 581 nil, 582 map[string]*core.CaveatExpression{ 583 "somedoc": nil, 584 }, 585 map[string]*core.CaveatExpression{}, 586 false, 587 true, 588 }, 589 { 590 "set with empty", 591 map[string]*core.CaveatExpression{ 592 "somedoc": nil, 593 }, 594 nil, 595 map[string]*core.CaveatExpression{ 596 "somedoc": nil, 597 }, 598 true, 599 false, 600 }, 601 { 602 "non overlapping sets", 603 map[string]*core.CaveatExpression{ 604 "somedoc": nil, 605 }, 606 map[string]*core.CaveatExpression{ 607 "anotherdoc": nil, 608 }, 609 map[string]*core.CaveatExpression{ 610 "somedoc": nil, 611 }, 612 true, 613 false, 614 }, 615 { 616 "overlapping sets with no caveats", 617 map[string]*core.CaveatExpression{ 618 "somedoc": nil, 619 }, 620 map[string]*core.CaveatExpression{ 621 "somedoc": nil, 622 }, 623 map[string]*core.CaveatExpression{}, 624 false, 625 true, 626 }, 627 { 628 "overlapping sets with first having a caveat", 629 map[string]*core.CaveatExpression{ 630 "somedoc": caveat("c1", nil), 631 }, 632 map[string]*core.CaveatExpression{ 633 "somedoc": nil, 634 }, 635 map[string]*core.CaveatExpression{}, 636 false, 637 true, 638 }, 639 { 640 "overlapping sets with second having a caveat", 641 map[string]*core.CaveatExpression{ 642 "somedoc": nil, 643 }, 644 map[string]*core.CaveatExpression{ 645 "somedoc": caveat("c2", nil), 646 }, 647 map[string]*core.CaveatExpression{ 648 "somedoc": invert(caveat("c2", nil)), 649 }, 650 false, 651 false, 652 }, 653 { 654 "overlapping sets with both having caveats", 655 map[string]*core.CaveatExpression{ 656 "somedoc": caveat("c1", nil), 657 }, 658 map[string]*core.CaveatExpression{ 659 "somedoc": caveat("c2", nil), 660 }, 661 map[string]*core.CaveatExpression{ 662 "somedoc": caveatAnd( 663 caveat("c1", nil), 664 invert(caveat("c2", nil)), 665 ), 666 }, 667 false, 668 false, 669 }, 670 { 671 "overlapping sets with both having caveats and determined member", 672 map[string]*core.CaveatExpression{ 673 "somedoc": caveat("c1", nil), 674 "anotherdoc": nil, 675 }, 676 map[string]*core.CaveatExpression{ 677 "somedoc": caveat("c2", nil), 678 }, 679 map[string]*core.CaveatExpression{ 680 "anotherdoc": nil, 681 "somedoc": caveatAnd( 682 caveat("c1", nil), 683 invert(caveat("c2", nil)), 684 ), 685 }, 686 true, 687 false, 688 }, 689 { 690 "overlapping sets with both having caveats and determined members", 691 map[string]*core.CaveatExpression{ 692 "somedoc": caveat("c1", nil), 693 "anotherdoc": nil, 694 }, 695 map[string]*core.CaveatExpression{ 696 "somedoc": caveat("c2", nil), 697 "anotherdoc": nil, 698 }, 699 map[string]*core.CaveatExpression{ 700 "somedoc": caveatAnd( 701 caveat("c1", nil), 702 invert(caveat("c2", nil)), 703 ), 704 }, 705 false, 706 false, 707 }, 708 } 709 710 for _, tc := range tcs { 711 tc := tc 712 t.Run(tc.name, func(t *testing.T) { 713 ms1 := membershipSetFromMap(tc.set1) 714 ms2 := membershipSetFromMap(tc.set2) 715 ms1.Subtract(ms2.AsCheckResultsMap()) 716 require.Equal(t, tc.expected, ms1.membersByID) 717 require.Equal(t, tc.hasDeterminedMember, ms1.HasDeterminedMember()) 718 require.Equal(t, tc.isEmpty, ms1.IsEmpty()) 719 }) 720 } 721 } 722 723 func unwrapCaveat(ce *core.CaveatExpression) *core.ContextualizedCaveat { 724 if ce == nil { 725 return nil 726 } 727 return ce.GetCaveat() 728 } 729 730 func withCaveat(tple *core.RelationTuple, ce *core.CaveatExpression) *core.RelationTuple { 731 tple.Caveat = unwrapCaveat(ce) 732 return tple 733 }