github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datasets/subjectset_test.go (about) 1 package datasets 2 3 import ( 4 "fmt" 5 "math" 6 "testing" 7 8 "github.com/stretchr/testify/require" 9 10 "github.com/authzed/spicedb/internal/caveats" 11 "github.com/authzed/spicedb/internal/testutil" 12 v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" 13 ) 14 15 var ( 16 caveatexpr = caveats.CaveatExprForTesting 17 sub = testutil.FoundSubject 18 wc = testutil.Wildcard 19 csub = testutil.CaveatedFoundSubject 20 cwc = testutil.CaveatedWildcard 21 wrap = testutil.WrapFoundSubject 22 ) 23 24 func TestSubjectSetAdd(t *testing.T) { 25 tcs := []struct { 26 name string 27 existing []*v1.FoundSubject 28 toAdd *v1.FoundSubject 29 expectedSet []*v1.FoundSubject 30 }{ 31 { 32 "basic add", 33 []*v1.FoundSubject{sub("foo"), sub("bar")}, 34 sub("baz"), 35 []*v1.FoundSubject{sub("foo"), sub("bar"), sub("baz")}, 36 }, 37 { 38 "basic repeated add", 39 []*v1.FoundSubject{sub("foo"), sub("bar")}, 40 sub("bar"), 41 []*v1.FoundSubject{sub("foo"), sub("bar")}, 42 }, 43 { 44 "add of an empty wildcard", 45 []*v1.FoundSubject{sub("foo"), sub("bar")}, 46 wc(), 47 []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, 48 }, 49 { 50 "add of a wildcard with exclusions", 51 []*v1.FoundSubject{sub("foo"), sub("bar")}, 52 wc("1", "2"), 53 []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, 54 }, 55 { 56 "add of a wildcard to a wildcard", 57 []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, 58 wc(), 59 []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, 60 }, 61 { 62 "add of a wildcard with exclusions to a bare wildcard", 63 []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, 64 wc("1", "2"), 65 []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, 66 }, 67 { 68 "add of a bare wildcard to a wildcard with exclusions", 69 []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, 70 wc(), 71 []*v1.FoundSubject{sub("foo"), sub("bar"), wc()}, 72 }, 73 { 74 "add of a wildcard with exclusions to a wildcard with exclusions", 75 []*v1.FoundSubject{sub("foo"), sub("bar"), wc("1", "2")}, 76 wc("2", "3"), 77 []*v1.FoundSubject{sub("foo"), sub("bar"), wc("2")}, 78 }, 79 { 80 "add of a subject to a wildcard with exclusions that does not have that subject", 81 []*v1.FoundSubject{wc("1", "2")}, 82 sub("3"), 83 []*v1.FoundSubject{wc("1", "2"), sub("3")}, 84 }, 85 { 86 "add of a subject to a wildcard with exclusions that has that subject", 87 []*v1.FoundSubject{wc("1", "2")}, 88 sub("2"), 89 []*v1.FoundSubject{wc("1"), sub("2")}, 90 }, 91 { 92 "add of a subject to a wildcard with exclusions that has that subject", 93 []*v1.FoundSubject{sub("2")}, 94 wc("1", "2"), 95 []*v1.FoundSubject{wc("1"), sub("2")}, 96 }, 97 { 98 "add of a subject to a bare wildcard", 99 []*v1.FoundSubject{wc()}, 100 sub("1"), 101 []*v1.FoundSubject{wc(), sub("1")}, 102 }, 103 { 104 "add of two wildcards", 105 []*v1.FoundSubject{wc("1")}, 106 wc("2"), 107 []*v1.FoundSubject{wc()}, 108 }, 109 { 110 "add of two wildcards with same restrictions", 111 []*v1.FoundSubject{wc("1")}, 112 wc("1", "2"), 113 []*v1.FoundSubject{wc("1")}, 114 }, 115 116 // Tests with caveats. 117 { 118 "basic add of caveated to non-caveated", 119 []*v1.FoundSubject{ 120 csub("foo", caveatexpr("testcaveat")), 121 sub("bar"), 122 }, 123 sub("foo"), 124 []*v1.FoundSubject{sub("bar"), sub("foo")}, 125 }, 126 { 127 "basic add of non-caveated to caveated", 128 []*v1.FoundSubject{ 129 sub("foo"), 130 sub("bar"), 131 }, 132 csub("foo", caveatexpr("testcaveat")), 133 []*v1.FoundSubject{sub("bar"), sub("foo")}, 134 }, 135 { 136 "basic add of caveated to caveated", 137 []*v1.FoundSubject{ 138 csub("foo", caveatexpr("testcaveat")), 139 sub("bar"), 140 }, 141 csub("foo", caveatexpr("anothercaveat")), 142 []*v1.FoundSubject{ 143 sub("bar"), 144 csub("foo", caveatOr(caveatexpr("testcaveat"), caveatexpr("anothercaveat"))), 145 }, 146 }, 147 { 148 "add of caveated wildcard to non-caveated", 149 []*v1.FoundSubject{ 150 cwc(caveatexpr("testcaveat")), 151 sub("bar"), 152 }, 153 wc(), 154 []*v1.FoundSubject{sub("bar"), wc()}, 155 }, 156 { 157 "add of caveated wildcard to caveated", 158 []*v1.FoundSubject{ 159 cwc(caveatexpr("testcaveat")), 160 sub("bar"), 161 }, 162 cwc(caveatexpr("anothercaveat")), 163 []*v1.FoundSubject{ 164 sub("bar"), 165 cwc(caveatOr(caveatexpr("testcaveat"), caveatexpr("anothercaveat"))), 166 }, 167 }, 168 { 169 "add of wildcard to a caveated sub", 170 []*v1.FoundSubject{ 171 csub("bar", caveatexpr("testcaveat")), 172 }, 173 wc(), 174 []*v1.FoundSubject{ 175 csub("bar", caveatexpr("testcaveat")), 176 wc(), 177 }, 178 }, 179 { 180 "add caveated sub to wildcard with non-matching caveated exclusion", 181 []*v1.FoundSubject{ 182 cwc(nil, csub("bar", caveatexpr("testcaveat"))), 183 }, 184 csub("foo", caveatexpr("anothercaveat")), 185 []*v1.FoundSubject{ 186 cwc(nil, csub("bar", caveatexpr("testcaveat"))), 187 csub("foo", caveatexpr("anothercaveat")), 188 }, 189 }, 190 { 191 "add caveated sub to wildcard with matching caveated exclusion", 192 []*v1.FoundSubject{ 193 cwc(nil, csub("foo", caveatexpr("testcaveat"))), 194 }, 195 csub("foo", caveatexpr("anothercaveat")), 196 []*v1.FoundSubject{ 197 // The caveat of the exclusion is now the combination of the caveats from the original 198 // wildcard and the concrete which was added, as the exclusion applies when the exclusion 199 // caveat is true and the concrete's caveat is false. 200 cwc(nil, csub("foo", 201 caveatAnd( 202 caveatexpr("testcaveat"), 203 caveatInvert(caveatexpr("anothercaveat")), 204 ), 205 )), 206 csub("foo", caveatexpr("anothercaveat")), 207 }, 208 }, 209 { 210 "add caveated sub to caveated wildcard with matching caveated exclusion", 211 []*v1.FoundSubject{ 212 cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("testcaveat"))), 213 }, 214 csub("foo", caveatexpr("anothercaveat")), 215 []*v1.FoundSubject{ 216 // The caveat of the exclusion is now the combination of the caveats from the original 217 // wildcard and the concrete which was added, as the exclusion applies when the exclusion 218 // caveat is true and the concrete's caveat is false. 219 cwc(caveatexpr("wildcardcaveat"), csub("foo", 220 caveatAnd( 221 caveatexpr("testcaveat"), 222 caveatInvert(caveatexpr("anothercaveat")), 223 ), 224 )), 225 csub("foo", caveatexpr("anothercaveat")), 226 }, 227 }, 228 } 229 230 for _, tc := range tcs { 231 tc := tc 232 233 t.Run(tc.name, func(t *testing.T) { 234 existingSet := NewSubjectSet() 235 for _, existing := range tc.existing { 236 err := existingSet.Add(existing) 237 require.NoError(t, err) 238 } 239 240 err := existingSet.Add(tc.toAdd) 241 require.NoError(t, err) 242 243 expectedSet := tc.expectedSet 244 computedSet := existingSet.AsSlice() 245 testutil.RequireEquivalentSets(t, expectedSet, computedSet) 246 247 require.Equal(t, len(expectedSet), existingSet.SubjectCount()) 248 if existingSet.HasWildcard() { 249 require.Equal(t, len(expectedSet), existingSet.ConcreteSubjectCount()+1) 250 } else { 251 require.Equal(t, len(expectedSet), existingSet.ConcreteSubjectCount()) 252 } 253 }) 254 } 255 } 256 257 func TestSubjectSetSubtract(t *testing.T) { 258 tcs := []struct { 259 name string 260 existing []*v1.FoundSubject 261 toSubtract *v1.FoundSubject 262 expectedSet []*v1.FoundSubject 263 }{ 264 { 265 "basic subtract, no overlap", 266 []*v1.FoundSubject{sub("foo"), sub("bar")}, 267 sub("baz"), 268 []*v1.FoundSubject{sub("foo"), sub("bar")}, 269 }, 270 { 271 "basic subtract, with overlap", 272 []*v1.FoundSubject{sub("foo"), sub("bar")}, 273 sub("bar"), 274 []*v1.FoundSubject{sub("foo")}, 275 }, 276 { 277 "basic subtract from bare wildcard", 278 []*v1.FoundSubject{sub("foo"), wc()}, 279 sub("bar"), 280 []*v1.FoundSubject{sub("foo"), wc("bar")}, 281 }, 282 { 283 "subtract from bare wildcard and set", 284 []*v1.FoundSubject{sub("bar"), wc()}, 285 sub("bar"), 286 []*v1.FoundSubject{wc("bar")}, 287 }, 288 { 289 "subtract from wildcard", 290 []*v1.FoundSubject{sub("bar"), wc("bar")}, 291 sub("bar"), 292 []*v1.FoundSubject{wc("bar")}, 293 }, 294 { 295 "subtract from wildcard with existing exclusions", 296 []*v1.FoundSubject{sub("bar"), wc("hiya")}, 297 sub("bar"), 298 []*v1.FoundSubject{wc("hiya", "bar")}, 299 }, 300 { 301 "subtract bare wildcard from set", 302 []*v1.FoundSubject{sub("bar"), sub("foo")}, 303 wc(), 304 []*v1.FoundSubject{}, 305 }, 306 { 307 "subtract wildcard with no matching exclusions from set", 308 []*v1.FoundSubject{sub("bar"), sub("foo")}, 309 wc("baz"), 310 []*v1.FoundSubject{}, 311 }, 312 { 313 "subtract wildcard with matching exclusions from set", 314 []*v1.FoundSubject{sub("bar"), sub("foo")}, 315 wc("bar"), 316 []*v1.FoundSubject{sub("bar")}, 317 }, 318 { 319 "subtract wildcard from another wildcard, both with the same exclusions", 320 []*v1.FoundSubject{wc("sarah")}, 321 wc("sarah"), 322 []*v1.FoundSubject{}, 323 }, 324 { 325 "subtract wildcard from another wildcard, with different exclusions", 326 []*v1.FoundSubject{wc("tom"), sub("foo"), sub("bar")}, 327 wc("sarah"), 328 []*v1.FoundSubject{sub("sarah")}, 329 }, 330 { 331 "subtract wildcard from another wildcard, with more exclusions", 332 []*v1.FoundSubject{wc("sarah")}, 333 wc("sarah", "tom"), 334 []*v1.FoundSubject{sub("tom")}, 335 }, 336 337 // Tests with caveats. 338 { 339 "subtract non-caveated from caveated", 340 []*v1.FoundSubject{csub("foo", caveatexpr("somecaveat"))}, 341 sub("foo"), 342 []*v1.FoundSubject{}, 343 }, 344 { 345 "subtract caveated from non-caveated", 346 []*v1.FoundSubject{sub("foo")}, 347 csub("foo", caveatexpr("somecaveat")), 348 []*v1.FoundSubject{ 349 // The subject should only appear now when the caveat is false. 350 csub("foo", caveatInvert(caveatexpr("somecaveat"))), 351 }, 352 }, 353 { 354 "subtract caveated from caveated", 355 []*v1.FoundSubject{ 356 csub("foo", caveatexpr("startingcaveat")), 357 }, 358 csub("foo", caveatexpr("somecaveat")), 359 []*v1.FoundSubject{ 360 // The subject should only appear when its caveat is true, and the subtracted caveat 361 // is false. 362 csub("foo", caveatAnd(caveatexpr("startingcaveat"), caveatInvert(caveatexpr("somecaveat")))), 363 }, 364 }, 365 { 366 "subtract caveated bare wildcard from non-caveated", 367 []*v1.FoundSubject{sub("foo")}, 368 cwc(caveatexpr("somecaveat")), 369 []*v1.FoundSubject{ 370 // The subject should only appear now when the caveat is false. 371 csub("foo", caveatInvert(caveatexpr("somecaveat"))), 372 }, 373 }, 374 { 375 "subtract bare wildcard from caveated", 376 []*v1.FoundSubject{csub("foo", caveatexpr("something"))}, 377 wc(), 378 []*v1.FoundSubject{}, 379 }, 380 { 381 "subtract caveated bare wildcard from caveated", 382 []*v1.FoundSubject{csub("foo", caveatexpr("something"))}, 383 cwc(caveatexpr("somecaveat")), 384 []*v1.FoundSubject{ 385 // The subject should only appear now when its caveat is true and the wildcard's caveat 386 // is false. 387 csub("foo", caveatAnd(caveatexpr("something"), caveatInvert(caveatexpr("somecaveat")))), 388 }, 389 }, 390 { 391 "subtract wildcard with caveated exception from non-caveated", 392 []*v1.FoundSubject{sub("foo")}, 393 cwc(nil, csub("foo", caveatexpr("somecaveat"))), 394 []*v1.FoundSubject{ 395 // The subject should only appear now when somecaveat is true, making the exclusion present 396 // on the wildcard, and thus preventing the wildcard from removing the concrete subject. 397 csub("foo", caveatexpr("somecaveat")), 398 }, 399 }, 400 { 401 "subtract wildcard with caveated exception from caveated", 402 []*v1.FoundSubject{csub("foo", caveatexpr("somecaveat"))}, 403 cwc(nil, csub("foo", caveatexpr("anothercaveat"))), 404 []*v1.FoundSubject{ 405 // The subject should only appear now when both caveats are true. 406 csub("foo", caveatAnd(caveatexpr("somecaveat"), caveatexpr("anothercaveat"))), 407 }, 408 }, 409 { 410 "subtract caveated wildcard with caveated exception from caveated", 411 []*v1.FoundSubject{csub("foo", caveatexpr("somecaveat"))}, 412 cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("anothercaveat"))), 413 []*v1.FoundSubject{ 414 // The subject should appear when: 415 // somecaveat && (!wildcardcaveat || anothercaveat) 416 csub("foo", caveatAnd(caveatexpr("somecaveat"), caveatOr(caveatInvert(caveatexpr("wildcardcaveat")), caveatexpr("anothercaveat")))), 417 }, 418 }, 419 { 420 "subtract caveated concrete from non-caveated wildcard", 421 []*v1.FoundSubject{wc()}, 422 csub("foo", caveatexpr("somecaveat")), 423 []*v1.FoundSubject{ 424 // The subject should be a caveated exception on the wildcard. 425 cwc(nil, csub("foo", caveatexpr("somecaveat"))), 426 }, 427 }, 428 { 429 "subtract caveated concrete from caveated wildcard", 430 []*v1.FoundSubject{cwc(caveatexpr("wildcardcaveat"))}, 431 csub("foo", caveatexpr("somecaveat")), 432 []*v1.FoundSubject{ 433 // The subject should be a caveated exception on the wildcard. 434 cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("somecaveat"))), 435 }, 436 }, 437 { 438 "subtract caveated concrete from wildcard with exclusion", 439 []*v1.FoundSubject{ 440 wc("foo"), 441 }, 442 csub("foo", caveatexpr("somecaveat")), 443 []*v1.FoundSubject{ 444 // No change, as `foo` is always removed. 445 wc("foo"), 446 }, 447 }, 448 { 449 "subtract caveated concrete from caveated wildcard with exclusion", 450 []*v1.FoundSubject{ 451 cwc(caveatexpr("wildcardcaveat"), sub("foo")), 452 }, 453 csub("foo", caveatexpr("somecaveat")), 454 []*v1.FoundSubject{ 455 // No change, as `foo` is always removed. 456 cwc(caveatexpr("wildcardcaveat"), sub("foo")), 457 }, 458 }, 459 { 460 "subtract caveated concrete from caveated wildcard with caveated exclusion", 461 []*v1.FoundSubject{ 462 cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatexpr("exclusioncaveat"))), 463 }, 464 csub("foo", caveatexpr("somecaveat")), 465 []*v1.FoundSubject{ 466 // The subject should be excluded now if *either* caveat is true. 467 cwc(caveatexpr("wildcardcaveat"), csub("foo", caveatOr(caveatexpr("exclusioncaveat"), caveatexpr("somecaveat")))), 468 }, 469 }, 470 { 471 "subtract non-caveated bare wildcard from caveated wildcard", 472 []*v1.FoundSubject{ 473 cwc(caveatexpr("wildcardcaveat")), 474 }, 475 wc(), 476 []*v1.FoundSubject{}, 477 }, 478 { 479 "subtract caveated bare wildcard from non-caveated wildcard", 480 []*v1.FoundSubject{ 481 wc(), 482 }, 483 cwc(caveatexpr("wildcardcaveat")), 484 []*v1.FoundSubject{ 485 // The new wildcard is caveated on the inversion of the caveat. 486 cwc(caveatInvert(caveatexpr("wildcardcaveat"))), 487 }, 488 }, 489 { 490 "subtract non-caveated bare wildcard from caveated wildcard with exclusions", 491 []*v1.FoundSubject{ 492 cwc(caveatexpr("wildcardcaveat"), sub("foo")), 493 }, 494 wc(), 495 []*v1.FoundSubject{}, 496 }, 497 { 498 "subtract caveated bare wildcard from non-caveated wildcard with exclusions", 499 []*v1.FoundSubject{ 500 wc("foo"), 501 }, 502 cwc(caveatexpr("wildcardcaveat")), 503 []*v1.FoundSubject{ 504 // The new wildcard is caveated on the inversion of the caveat. 505 cwc(caveatInvert(caveatexpr("wildcardcaveat")), sub("foo")), 506 }, 507 }, 508 { 509 "subtract caveated wildcard with caveated exclusion from caveated wildcard with caveated exclusion", 510 []*v1.FoundSubject{ 511 cwc(caveatexpr("wildcardcaveat1"), csub("foo", caveatexpr("exclusion1"))), 512 }, 513 cwc(caveatexpr("wildcardcaveat2"), csub("foo", caveatexpr("exclusion2"))), 514 []*v1.FoundSubject{ 515 cwc( 516 // The wildcard itself only appears if caveat1 is true and caveat2 is *false*. 517 caveatAnd(caveatexpr("wildcardcaveat1"), caveatInvert(caveatexpr("wildcardcaveat2"))), 518 519 // The exclusion still only applies if exclusion1 is true, because if it is false, 520 // it doesn't matter that the subject was excluded from the subtraction. 521 csub("foo", caveatexpr("exclusion1")), 522 ), 523 524 // A concrete of foo is produced when all of these are true: 525 // 1) the first wildcard is present 526 // 2) the second wildcard is present 527 // 3) the exclusion on the first wildcard is false, meaning the concrete is present within 528 // the wildcard 529 // 4) the exclusion within the second is true, meaning that the concrete was not present 530 // 531 // thus causing the expression to become `{*} - {* - {foo}}`, and therefore producing 532 // `foo` as a concrete. 533 csub("foo", 534 caveatAnd( 535 caveatAnd( 536 caveatAnd( 537 caveatexpr("wildcardcaveat1"), 538 caveatexpr("wildcardcaveat2"), 539 ), 540 caveatInvert(caveatexpr("exclusion1")), 541 ), 542 caveatexpr("exclusion2"), 543 ), 544 ), 545 }, 546 }, 547 { 548 "subtract caveated wildcard with caveated exclusion from caveated wildcard with different caveated exclusion", 549 []*v1.FoundSubject{ 550 cwc(caveatexpr("wildcardcaveat1"), csub("foo", caveatexpr("exclusion1"))), 551 }, 552 cwc(caveatexpr("wildcardcaveat2"), csub("bar", caveatexpr("exclusion2"))), 553 []*v1.FoundSubject{ 554 cwc( 555 // The wildcard itself only appears if caveat1 is true and caveat2 is *false*. 556 caveatAnd(caveatexpr("wildcardcaveat1"), caveatInvert(caveatexpr("wildcardcaveat2"))), 557 558 // The foo exclusion remains the same. 559 csub("foo", caveatexpr("exclusion1")), 560 ), 561 562 // Because `bar` is excluded in the *subtraction*, it can appear as a *concrete* subject 563 // in the scenario where the first wildcard is applied, the second wildcard is applied 564 // and its own exclusion is true. 565 // Therefore, bar must be *concretely* in the set if: 566 // wildcard1 && wildcard2 && exclusion2 567 csub("bar", 568 caveatAnd( 569 caveatAnd( 570 caveatexpr("wildcardcaveat1"), 571 caveatexpr("wildcardcaveat2"), 572 ), 573 caveatexpr("exclusion2"), 574 ), 575 ), 576 }, 577 }, 578 { 579 "concretes with wildcard with partially matching exclusions subtracted", 580 []*v1.FoundSubject{ 581 sub("foo"), 582 sub("bar"), 583 }, 584 cwc(caveatexpr("caveat"), sub("foo")), 585 []*v1.FoundSubject{ 586 sub("foo"), 587 csub("bar", caveatInvert(caveatexpr("caveat"))), 588 }, 589 }, 590 { 591 "subtract caveated from caveated (same caveat)", 592 []*v1.FoundSubject{ 593 csub("foo", caveatexpr("somecaveat")), 594 }, 595 csub("foo", caveatexpr("somecaveat")), 596 []*v1.FoundSubject{ 597 // Since no expression simplification is occurring, subtracting a caveated concrete 598 // subject from another with the *same* caveat, results in an expression that &&'s 599 // together the expression and its inversion. In practice, this will simplify down to 600 // nothing, but that happens at a different layer. 601 csub("foo", caveatAnd(caveatexpr("somecaveat"), caveatInvert(caveatexpr("somecaveat")))), 602 }, 603 }, 604 } 605 606 for _, tc := range tcs { 607 tc := tc 608 609 t.Run(tc.name, func(t *testing.T) { 610 existingSet := NewSubjectSet() 611 for _, existing := range tc.existing { 612 existingSet.MustAdd(existing) 613 } 614 615 existingSet.Subtract(tc.toSubtract) 616 617 expectedSet := tc.expectedSet 618 computedSet := existingSet.AsSlice() 619 testutil.RequireEquivalentSets(t, expectedSet, computedSet) 620 }) 621 } 622 } 623 624 func TestSubjectSetIntersection(t *testing.T) { 625 tcs := []struct { 626 name string 627 existing []*v1.FoundSubject 628 toIntersect []*v1.FoundSubject 629 expectedSet []*v1.FoundSubject 630 expectedInvertedSet []*v1.FoundSubject 631 }{ 632 { 633 "basic intersection, full overlap", 634 []*v1.FoundSubject{sub("foo"), sub("bar")}, 635 []*v1.FoundSubject{sub("foo"), sub("bar")}, 636 []*v1.FoundSubject{sub("foo"), sub("bar")}, 637 nil, 638 }, 639 { 640 "basic intersection, partial overlap", 641 []*v1.FoundSubject{sub("foo"), sub("bar")}, 642 []*v1.FoundSubject{sub("foo")}, 643 []*v1.FoundSubject{sub("foo")}, 644 nil, 645 }, 646 { 647 "basic intersection, no overlap", 648 []*v1.FoundSubject{sub("foo"), sub("bar")}, 649 []*v1.FoundSubject{sub("baz")}, 650 []*v1.FoundSubject{}, 651 nil, 652 }, 653 { 654 "intersection between bare wildcard and concrete", 655 []*v1.FoundSubject{sub("foo")}, 656 []*v1.FoundSubject{wc()}, 657 []*v1.FoundSubject{sub("foo")}, 658 nil, 659 }, 660 { 661 "intersection between wildcard with exclusions and concrete", 662 []*v1.FoundSubject{sub("foo")}, 663 []*v1.FoundSubject{wc("tom")}, 664 []*v1.FoundSubject{sub("foo")}, 665 nil, 666 }, 667 { 668 "intersection between wildcard with matching exclusions and concrete", 669 []*v1.FoundSubject{sub("foo")}, 670 []*v1.FoundSubject{wc("foo")}, 671 []*v1.FoundSubject{}, 672 nil, 673 }, 674 { 675 "intersection between bare wildcards", 676 []*v1.FoundSubject{wc()}, 677 []*v1.FoundSubject{wc()}, 678 []*v1.FoundSubject{wc()}, 679 nil, 680 }, 681 { 682 "intersection between bare wildcard and one with exclusions", 683 []*v1.FoundSubject{wc()}, 684 []*v1.FoundSubject{wc("1", "2")}, 685 []*v1.FoundSubject{wc("1", "2")}, 686 nil, 687 }, 688 { 689 "intersection between wildcards", 690 []*v1.FoundSubject{wc("2", "3")}, 691 []*v1.FoundSubject{wc("1", "2")}, 692 []*v1.FoundSubject{wc("1", "2", "3")}, 693 nil, 694 }, 695 { 696 "intersection wildcard with exclusions and concrete", 697 []*v1.FoundSubject{wc("2", "3")}, 698 []*v1.FoundSubject{sub("4")}, 699 []*v1.FoundSubject{sub("4")}, 700 nil, 701 }, 702 { 703 "intersection wildcard with matching exclusions and concrete", 704 []*v1.FoundSubject{wc("2", "3", "4")}, 705 []*v1.FoundSubject{sub("4")}, 706 []*v1.FoundSubject{}, 707 nil, 708 }, 709 { 710 "intersection of wildcards and two concrete types", 711 []*v1.FoundSubject{wc(), sub("1")}, 712 []*v1.FoundSubject{wc(), sub("2")}, 713 []*v1.FoundSubject{wc(), sub("1"), sub("2")}, 714 nil, 715 }, 716 717 // Tests with caveats. 718 { 719 "intersection of non-caveated concrete and caveated concrete", 720 []*v1.FoundSubject{sub("1")}, 721 []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, 722 723 // Resulting subject is caveated on the caveat. 724 []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, 725 nil, 726 }, 727 { 728 "intersection of caveated concrete and non-caveated concrete", 729 []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, 730 []*v1.FoundSubject{sub("1")}, 731 732 // Resulting subject is caveated on the caveat. 733 []*v1.FoundSubject{csub("1", caveatexpr("caveat"))}, 734 nil, 735 }, 736 { 737 "intersection of caveated concrete and caveated concrete", 738 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 739 []*v1.FoundSubject{csub("1", caveatexpr("caveat2"))}, 740 741 // Resulting subject is caveated both caveats. 742 []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("caveat2")))}, 743 744 // Inverted result has the caveats reversed in the `&&` expression. 745 []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat2"), caveatexpr("caveat1")))}, 746 }, 747 { 748 "intersection of caveated concrete and non-caveated bare wildcard", 749 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 750 []*v1.FoundSubject{wc()}, 751 752 // Resulting subject is caveated and concrete. 753 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 754 nil, 755 }, 756 { 757 "intersection of non-caveated bare wildcard and caveated concrete", 758 []*v1.FoundSubject{wc()}, 759 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 760 761 // Resulting subject is caveated and concrete. 762 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 763 nil, 764 }, 765 { 766 "intersection of caveated concrete and caveated bare wildcard", 767 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 768 []*v1.FoundSubject{cwc(caveatexpr("caveat2"))}, 769 770 // Resulting subject is caveated from both and concrete. 771 []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("caveat2")))}, 772 nil, 773 }, 774 { 775 "intersection of caveated bare wildcard and caveated concrete", 776 []*v1.FoundSubject{cwc(caveatexpr("caveat2"))}, 777 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 778 779 // Resulting subject is caveated from both and concrete. 780 []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("caveat2")))}, 781 nil, 782 }, 783 { 784 "intersection of caveated concrete and non-caveated wildcard with non-matching exclusion", 785 []*v1.FoundSubject{wc("2")}, 786 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 787 788 // Resulting subject is caveated and concrete. 789 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 790 nil, 791 }, 792 { 793 "intersection of caveated concrete and non-caveated wildcard with matching exclusion", 794 []*v1.FoundSubject{wc("1")}, 795 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 796 797 // Empty since the subject was in the exclusion set. 798 []*v1.FoundSubject{}, 799 nil, 800 }, 801 { 802 "intersection of caveated concrete and caveated wildcard with non-matching exclusion", 803 []*v1.FoundSubject{cwc(caveatexpr("wcaveat"), sub("2"))}, 804 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 805 806 // Since the wildcard is caveated and has a non-matching exclusion, the caveat is added to 807 // the subject. 808 []*v1.FoundSubject{csub("1", caveatAnd(caveatexpr("caveat1"), caveatexpr("wcaveat")))}, 809 nil, 810 }, 811 { 812 "intersection of caveated concrete and caveated wildcard with matching exclusion", 813 []*v1.FoundSubject{cwc(caveatexpr("wcaveat"), sub("1"))}, 814 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 815 816 // Since the wildcard is caveated but has a matching exclusion, the result is empty. 817 []*v1.FoundSubject{}, 818 nil, 819 }, 820 { 821 "intersection of caveated concrete and caveated wildcard with matching caveated exclusion", 822 []*v1.FoundSubject{cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("ecaveat")))}, 823 []*v1.FoundSubject{csub("1", caveatexpr("caveat1"))}, 824 825 []*v1.FoundSubject{ 826 // The concrete is included if its own caveat is true, the wildcard's caveat is true 827 // and the exclusion's caveat is false. 828 csub("1", 829 caveatAnd( 830 caveatexpr("caveat1"), 831 caveatAnd( 832 caveatexpr("wcaveat"), 833 caveatInvert(caveatexpr("ecaveat")), 834 ), 835 ), 836 ), 837 }, 838 nil, 839 }, 840 { 841 "intersection of caveated concrete and wildcard", 842 []*v1.FoundSubject{csub("1", caveatexpr("first"))}, 843 []*v1.FoundSubject{wc()}, 844 845 []*v1.FoundSubject{ 846 csub("1", 847 caveatexpr("first"), 848 ), 849 }, 850 nil, 851 }, 852 { 853 "intersection of caveated concrete and wildcards", 854 []*v1.FoundSubject{wc(), csub("1", caveatexpr("first"))}, 855 []*v1.FoundSubject{wc(), csub("1", caveatexpr("second"))}, 856 857 []*v1.FoundSubject{ 858 // Wildcard is included because it is in both sets. 859 wc(), 860 861 // The subject is caveated to appear if either first or second is true, or both are true. 862 // This is because of the interaction with the wildcards in each set: 863 // 864 // - If first is true and second is false, we get {*, 1} ∩ {*} => {*, 1} 865 // - If first is false and second is true, we get {*} ∩ {*, 1} => {*, 1} 866 // - If both are true, then the subject appears, but that is just a remnant of the join 867 // on the concrete subject itself (since the set does not simplify expressions) 868 csub("1", 869 caveatOr( 870 caveatOr( 871 caveatAnd( 872 caveatexpr("first"), 873 caveatexpr("second"), 874 ), 875 caveatexpr("first"), 876 ), 877 caveatexpr("second"), 878 ), 879 ), 880 }, 881 []*v1.FoundSubject{ 882 wc(), 883 csub("1", 884 caveatOr( 885 caveatOr( 886 caveatAnd( 887 caveatexpr("second"), 888 caveatexpr("first"), 889 ), 890 caveatexpr("second"), 891 ), 892 caveatexpr("first"), 893 ), 894 ), 895 }, 896 }, 897 { 898 "intersection of caveated concrete and caveated wildcard", 899 []*v1.FoundSubject{csub("1", caveatexpr("first"))}, 900 []*v1.FoundSubject{ 901 cwc(caveatexpr("wcaveat")), 902 csub("1", caveatexpr("second")), 903 }, 904 []*v1.FoundSubject{ 905 csub("1", 906 caveatOr( 907 caveatAnd( 908 caveatexpr("first"), 909 caveatexpr("second"), 910 ), 911 caveatAnd( 912 caveatexpr("first"), 913 caveatexpr("wcaveat"), 914 ), 915 ), 916 ), 917 }, 918 []*v1.FoundSubject{ 919 csub("1", 920 caveatOr( 921 caveatAnd( 922 caveatexpr("second"), 923 caveatexpr("first"), 924 ), 925 caveatAnd( 926 caveatexpr("first"), 927 caveatexpr("wcaveat"), 928 ), 929 ), 930 ), 931 }, 932 }, 933 { 934 "intersection of caveated concrete and wildcard with wildcard", 935 []*v1.FoundSubject{csub("1", caveatexpr("caveat1")), wc()}, 936 []*v1.FoundSubject{wc()}, 937 []*v1.FoundSubject{wc(), csub("1", caveatexpr("caveat1"))}, 938 nil, 939 }, 940 } 941 942 for _, tc := range tcs { 943 tc := tc 944 945 t.Run(tc.name, func(t *testing.T) { 946 existingSet := NewSubjectSet() 947 for _, existing := range tc.existing { 948 existingSet.MustAdd(existing) 949 } 950 951 toIntersect := NewSubjectSet() 952 for _, toAdd := range tc.toIntersect { 953 toIntersect.MustAdd(toAdd) 954 } 955 956 existingSet.MustIntersectionDifference(toIntersect) 957 958 expectedSet := tc.expectedSet 959 computedSet := existingSet.AsSlice() 960 testutil.RequireEquivalentSets(t, expectedSet, computedSet) 961 962 // Run the intersection inverted, which should always result in the same results. 963 t.Run("inverted", func(t *testing.T) { 964 existingSet := NewSubjectSet() 965 for _, existing := range tc.existing { 966 existingSet.MustAdd(existing) 967 } 968 969 toIntersect := NewSubjectSet() 970 for _, toAdd := range tc.toIntersect { 971 toIntersect.MustAdd(toAdd) 972 } 973 974 toIntersect.MustIntersectionDifference(existingSet) 975 976 // The inverted set is necessary for some caveated sets, because their expressions may 977 // have references in reversed locations. 978 expectedSet := tc.expectedSet 979 if tc.expectedInvertedSet != nil { 980 expectedSet = tc.expectedInvertedSet 981 } 982 983 computedSet := toIntersect.AsSlice() 984 testutil.RequireEquivalentSets(t, expectedSet, computedSet) 985 }) 986 }) 987 } 988 } 989 990 func TestMultipleOperations(t *testing.T) { 991 tcs := []struct { 992 name string 993 runOps func(set SubjectSet) 994 expectedSet []*v1.FoundSubject 995 }{ 996 { 997 "basic adds", 998 func(set SubjectSet) { 999 set.MustAdd(sub("1")) 1000 set.MustAdd(sub("2")) 1001 set.MustAdd(sub("3")) 1002 1003 set.MustAdd(sub("1")) 1004 set.MustAdd(sub("2")) 1005 set.MustAdd(sub("3")) 1006 }, 1007 []*v1.FoundSubject{sub("1"), sub("2"), sub("3")}, 1008 }, 1009 { 1010 "add and remove", 1011 func(set SubjectSet) { 1012 set.MustAdd(sub("1")) 1013 set.MustAdd(sub("2")) 1014 set.MustAdd(sub("3")) 1015 1016 set.Subtract(sub("1")) 1017 }, 1018 []*v1.FoundSubject{sub("2"), sub("3")}, 1019 }, 1020 { 1021 "add and intersect", 1022 func(set SubjectSet) { 1023 set.MustAdd(sub("1")) 1024 set.MustAdd(sub("2")) 1025 set.MustAdd(sub("3")) 1026 1027 other := NewSubjectSet() 1028 other.MustAdd(sub("2")) 1029 1030 set.MustIntersectionDifference(other) 1031 }, 1032 []*v1.FoundSubject{sub("2")}, 1033 }, 1034 { 1035 "caveated adds", 1036 func(set SubjectSet) { 1037 set.MustAdd(sub("1")) 1038 set.MustAdd(sub("2")) 1039 set.MustAdd(sub("3")) 1040 1041 set.MustAdd(csub("1", caveatexpr("first"))) 1042 set.MustAdd(csub("1", caveatexpr("second"))) 1043 }, 1044 []*v1.FoundSubject{sub("1"), sub("2"), sub("3")}, 1045 }, 1046 { 1047 "all caveated adds", 1048 func(set SubjectSet) { 1049 set.MustAdd(csub("1", caveatexpr("first"))) 1050 set.MustAdd(csub("1", caveatexpr("second"))) 1051 }, 1052 []*v1.FoundSubject{csub("1", caveatOr(caveatexpr("first"), caveatexpr("second")))}, 1053 }, 1054 { 1055 "caveated adds and caveated sub", 1056 func(set SubjectSet) { 1057 set.MustAdd(csub("1", caveatexpr("first"))) 1058 set.MustAdd(csub("1", caveatexpr("second"))) 1059 set.Subtract(csub("1", caveatexpr("third"))) 1060 }, 1061 []*v1.FoundSubject{ 1062 csub("1", 1063 caveatAnd( 1064 caveatOr(caveatexpr("first"), caveatexpr("second")), 1065 caveatInvert(caveatexpr("third")), 1066 ), 1067 ), 1068 }, 1069 }, 1070 { 1071 "caveated adds, sub and add", 1072 func(set SubjectSet) { 1073 set.MustAdd(csub("1", caveatexpr("first"))) 1074 set.MustAdd(csub("1", caveatexpr("second"))) 1075 set.Subtract(csub("1", caveatexpr("third"))) 1076 set.MustAdd(csub("1", caveatexpr("fourth"))) 1077 }, 1078 []*v1.FoundSubject{ 1079 csub("1", 1080 caveatOr( 1081 caveatAnd( 1082 caveatOr(caveatexpr("first"), caveatexpr("second")), 1083 caveatInvert(caveatexpr("third")), 1084 ), 1085 caveatexpr("fourth"), 1086 ), 1087 ), 1088 }, 1089 }, 1090 { 1091 "caveated adds, sub and concrete add", 1092 func(set SubjectSet) { 1093 set.MustAdd(csub("1", caveatexpr("first"))) 1094 set.MustAdd(csub("1", caveatexpr("second"))) 1095 set.Subtract(csub("1", caveatexpr("third"))) 1096 set.MustAdd(sub("1")) 1097 }, 1098 []*v1.FoundSubject{ 1099 sub("1"), 1100 }, 1101 }, 1102 { 1103 "add concrete, add wildcard, sub concrete", 1104 func(set SubjectSet) { 1105 set.MustAdd(sub("1")) 1106 set.MustAdd(wc()) 1107 set.Subtract(sub("1")) 1108 set.Subtract(sub("1")) 1109 }, 1110 []*v1.FoundSubject{wc("1")}, 1111 }, 1112 { 1113 "add concrete, add wildcard, sub concrete, add concrete", 1114 func(set SubjectSet) { 1115 set.MustAdd(sub("1")) 1116 set.MustAdd(wc()) 1117 set.Subtract(sub("1")) 1118 set.MustAdd(sub("1")) 1119 }, 1120 []*v1.FoundSubject{wc(), sub("1")}, 1121 }, 1122 { 1123 "caveated concrete subtracted from wildcard and then concrete added back", 1124 func(set SubjectSet) { 1125 set.MustAdd(sub("1")) 1126 set.MustAdd(wc()) 1127 set.Subtract(csub("1", caveatexpr("first"))) 1128 set.MustAdd(sub("1")) 1129 }, 1130 []*v1.FoundSubject{wc(), sub("1")}, 1131 }, 1132 { 1133 "concrete subtracted from wildcard and then caveated added back", 1134 func(set SubjectSet) { 1135 set.MustAdd(sub("1")) 1136 set.MustAdd(wc()) 1137 set.Subtract(sub("1")) 1138 set.MustAdd(csub("1", caveatexpr("first"))) 1139 }, 1140 []*v1.FoundSubject{ 1141 cwc(nil, csub("1", caveatInvert(caveatexpr("first")))), 1142 csub("1", caveatexpr("first")), 1143 }, 1144 }, 1145 { 1146 "multiple concrete operations", 1147 func(set SubjectSet) { 1148 // Start with two sets of concretes and a wildcard. 1149 // Example: permission view = viewer + editor 1150 // ^ {*} ^ {1,2,3,4} 1151 set.MustAdd(sub("1")) 1152 set.MustAdd(sub("2")) 1153 set.MustAdd(sub("3")) 1154 set.MustAdd(sub("4")) 1155 set.MustAdd(wc()) 1156 1157 // Subtract out banned users. 1158 // Example: permission view_but_not_banned = view - banned 1159 // ^ {3,6,7} 1160 set.Subtract(sub("3")) 1161 set.Subtract(sub("6")) 1162 set.Subtract(sub("7")) 1163 1164 // Intersect with another set. 1165 // Example: permission result = view_but_not_banned & another_set 1166 // ^ {1,3,*} 1167 other := NewSubjectSet() 1168 other.MustAdd(sub("1")) 1169 other.MustAdd(sub("3")) 1170 other.MustAdd(wc()) 1171 1172 set.MustIntersectionDifference(other) 1173 1174 // Remaining 1175 // `1` from `view` and `another_set` 1176 // `2` from `view` and via * in `another_set` 1177 // `4` from `view` and via * in `another_set` 1178 // `*` with the `banned`` exclusions 1179 }, 1180 []*v1.FoundSubject{ 1181 sub("1"), 1182 sub("2"), 1183 sub("4"), 1184 wc("3", "6", "7"), 1185 }, 1186 }, 1187 { 1188 "multiple operations with caveats", 1189 func(set SubjectSet) { 1190 // Start with two sets of concretes and a wildcard. 1191 // Example: permission view = viewer + editor 1192 // ^ {1} ^ {2,3,4} 1193 set.MustAdd(csub("1", caveatexpr("first"))) 1194 set.MustAdd(sub("2")) 1195 set.MustAdd(sub("3")) 1196 set.MustAdd(sub("4")) 1197 1198 // Subtract out banned users. 1199 // Example: permission view_but_not_banned = view - banned 1200 // ^ {3,6,7} 1201 set.Subtract(csub("3", caveatexpr("banned"))) 1202 set.Subtract(sub("6")) 1203 set.Subtract(sub("7")) 1204 1205 // Intersect with another set. 1206 // Example: permission result = view_but_not_banned & another_set 1207 // ^ {1,3,*} 1208 other := NewSubjectSet() 1209 other.MustAdd(sub("1")) 1210 other.MustAdd(csub("3", caveatexpr("second"))) 1211 1212 set.MustIntersectionDifference(other) 1213 }, 1214 []*v1.FoundSubject{ 1215 csub("1", caveatexpr("first")), 1216 csub("3", caveatAnd( 1217 caveatInvert(caveatexpr("banned")), 1218 caveatexpr("second"), 1219 )), 1220 }, 1221 }, 1222 { 1223 "multiple operations with caveats and wildcards", 1224 func(set SubjectSet) { 1225 // Start with two sets of concretes and a wildcard. 1226 // Example: permission view = viewer + editor 1227 // ^ {*} ^ {1,2,3,4} 1228 set.MustAdd(csub("1", caveatexpr("first"))) 1229 set.MustAdd(sub("2")) 1230 set.MustAdd(sub("3")) 1231 set.MustAdd(sub("4")) 1232 set.MustAdd(wc()) 1233 1234 // Subtract out banned users. 1235 // Example: permission view_but_not_banned = view - banned 1236 // ^ {3,6,7} 1237 set.Subtract(csub("3", caveatexpr("banned"))) 1238 set.Subtract(sub("6")) 1239 set.Subtract(sub("7")) 1240 1241 // Intersect with another set. 1242 // Example: permission result = view_but_not_banned & another_set 1243 // ^ {1,3,*} 1244 other := NewSubjectSet() 1245 other.MustAdd(sub("1")) 1246 other.MustAdd(csub("3", caveatexpr("second"))) 1247 other.MustAdd(wc()) 1248 1249 set.MustIntersectionDifference(other) 1250 }, 1251 []*v1.FoundSubject{ 1252 // `1` is included without caveat because it is non-caveated in other and there is a 1253 // wildcard in the original set. 1254 sub("1"), 1255 1256 // `3` inclusion expression: 1257 // ({*, 3} - {3[banned]}) ∩ ({*, 3[second]}) 1258 // therefore: 1259 // `3` is included if banned is false (otherwise it gets removed in the first set). 1260 // 1261 // NOTE: the remaining expressions are cruft generated to cover other cases, but because 1262 // there is no expression simplification, it is not collapsing due to just `!banned` 1263 csub("3", 1264 caveatOr( 1265 caveatOr( 1266 caveatAnd( 1267 caveatInvert(caveatexpr("banned")), 1268 caveatexpr("second"), 1269 ), 1270 caveatInvert(caveatexpr("banned")), 1271 ), 1272 caveatAnd( 1273 caveatexpr("second"), 1274 caveatInvert(caveatexpr("banned")), 1275 ), 1276 ), 1277 ), 1278 sub("2"), 1279 sub("4"), 1280 cwc(nil, csub("3", caveatexpr("banned")), sub("6"), sub("7")), 1281 }, 1282 }, 1283 { 1284 "subtraction followed by intersection without wildcard", 1285 func(set SubjectSet) { 1286 set.MustAdd(sub("3")) 1287 set.MustAdd(wc()) 1288 1289 set.Subtract(csub("3", caveatexpr("banned"))) 1290 1291 other := NewSubjectSet() 1292 other.MustAdd(csub("3", caveatexpr("second"))) 1293 1294 set.MustIntersectionDifference(other) 1295 }, 1296 []*v1.FoundSubject{ 1297 // `3` inclusion expression: 1298 // ({*, 3} - {3[banned]}) ∩ ({3[second]}) 1299 // therefore: 1300 // `3` is included if banned is false and second is true. 1301 csub("3", 1302 caveatOr( 1303 caveatAnd( 1304 caveatInvert(caveatexpr("banned")), 1305 caveatexpr("second"), 1306 ), 1307 caveatAnd( 1308 caveatexpr("second"), 1309 caveatInvert(caveatexpr("banned")), 1310 ), 1311 ), 1312 ), 1313 }, 1314 }, 1315 } 1316 1317 for _, tc := range tcs { 1318 tc := tc 1319 1320 t.Run(tc.name, func(t *testing.T) { 1321 set := NewSubjectSet() 1322 tc.runOps(set) 1323 1324 expectedSet := tc.expectedSet 1325 computedSet := set.AsSlice() 1326 testutil.RequireEquivalentSets(t, expectedSet, computedSet) 1327 }) 1328 } 1329 } 1330 1331 func TestSubtractAll(t *testing.T) { 1332 tcs := []struct { 1333 name string 1334 startingSubjects []*v1.FoundSubject 1335 toSubtract []*v1.FoundSubject 1336 expected []*v1.FoundSubject 1337 }{ 1338 { 1339 "basic mult-subtraction", 1340 []*v1.FoundSubject{ 1341 sub("1"), sub("2"), sub("3"), 1342 }, 1343 []*v1.FoundSubject{sub("1"), sub("3")}, 1344 []*v1.FoundSubject{sub("2")}, 1345 }, 1346 { 1347 "wildcad subtraction", 1348 []*v1.FoundSubject{ 1349 sub("1"), sub("2"), sub("3"), 1350 }, 1351 []*v1.FoundSubject{wc()}, 1352 []*v1.FoundSubject{}, 1353 }, 1354 { 1355 "wildcad with exclusions subtraction", 1356 []*v1.FoundSubject{ 1357 sub("1"), sub("2"), sub("3"), 1358 }, 1359 []*v1.FoundSubject{wc("1"), sub("3")}, 1360 []*v1.FoundSubject{sub("1")}, 1361 }, 1362 } 1363 1364 for _, tc := range tcs { 1365 tc := tc 1366 1367 t.Run(tc.name, func(t *testing.T) { 1368 set := NewSubjectSet() 1369 1370 for _, starting := range tc.startingSubjects { 1371 set.MustAdd(starting) 1372 } 1373 1374 toRemove := NewSubjectSet() 1375 for _, toSubtract := range tc.toSubtract { 1376 toRemove.MustAdd(toSubtract) 1377 } 1378 1379 set.SubtractAll(toRemove) 1380 1381 expectedSet := tc.expected 1382 computedSet := set.AsSlice() 1383 testutil.RequireEquivalentSets(t, expectedSet, computedSet) 1384 }) 1385 } 1386 } 1387 1388 func TestSubjectSetClone(t *testing.T) { 1389 ss := NewSubjectSet() 1390 require.True(t, ss.IsEmpty()) 1391 1392 ss.MustAdd(sub("first")) 1393 ss.MustAdd(sub("second")) 1394 ss.MustAdd(csub("third", caveatexpr("somecaveat"))) 1395 ss.MustAdd(wc("one", "two")) 1396 require.False(t, ss.IsEmpty()) 1397 1398 existingSubjects := ss.AsSlice() 1399 1400 // Clone the set. 1401 cloned := ss.Clone() 1402 require.False(t, cloned.IsEmpty()) 1403 1404 clonedSubjects := cloned.AsSlice() 1405 testutil.RequireEquivalentSets(t, existingSubjects, clonedSubjects) 1406 1407 // Modify the existing set and ensure the cloned is not changed. 1408 ss.Subtract(sub("first")) 1409 ss.Subtract(wc()) 1410 1411 clonedSubjects = cloned.AsSlice() 1412 updatedExistingSubjects := ss.AsSlice() 1413 1414 testutil.RequireEquivalentSets(t, existingSubjects, clonedSubjects) 1415 require.NotEqual(t, len(updatedExistingSubjects), len(clonedSubjects)) 1416 } 1417 1418 func TestSubjectSetGet(t *testing.T) { 1419 ss := NewSubjectSet() 1420 require.True(t, ss.IsEmpty()) 1421 1422 ss.MustAdd(sub("first")) 1423 ss.MustAdd(sub("second")) 1424 ss.MustAdd(csub("third", caveatexpr("somecaveat"))) 1425 ss.MustAdd(wc("one", "two")) 1426 require.False(t, ss.IsEmpty()) 1427 1428 found, ok := ss.Get("first") 1429 require.True(t, ok) 1430 require.Equal(t, "first", found.SubjectId) 1431 require.Nil(t, found.CaveatExpression) 1432 1433 found, ok = ss.Get("second") 1434 require.True(t, ok) 1435 require.Equal(t, "second", found.SubjectId) 1436 require.Nil(t, found.CaveatExpression) 1437 1438 found, ok = ss.Get("third") 1439 require.True(t, ok) 1440 require.Equal(t, "third", found.SubjectId) 1441 require.NotNil(t, found.CaveatExpression) 1442 1443 _, ok = ss.Get("fourth") 1444 require.False(t, ok) 1445 1446 found, ok = ss.Get("*") 1447 require.True(t, ok) 1448 require.Equal(t, "*", found.SubjectId) 1449 require.Nil(t, found.CaveatExpression) 1450 require.Equal(t, 2, len(found.ExcludedSubjects)) 1451 } 1452 1453 var testSets = [][]*v1.FoundSubject{ 1454 {sub("foo"), sub("bar")}, 1455 {sub("foo")}, 1456 {sub("baz")}, 1457 {wc()}, 1458 {wc("tom")}, 1459 {wc("1", "2")}, 1460 {wc("1", "2", "3")}, 1461 {wc("2", "3")}, 1462 {sub("1")}, 1463 {wc(), sub("1"), sub("2")}, 1464 {wc(), sub("2")}, 1465 {wc(), sub("1")}, 1466 {csub("1", caveatexpr("caveat"))}, 1467 {csub("1", caveatexpr("caveat2"))}, 1468 {cwc(caveatexpr("caveat2"))}, 1469 {cwc(caveatexpr("wcaveat"), sub("1"))}, 1470 } 1471 1472 func TestUnionCommutativity(t *testing.T) { 1473 for _, pair := range allSubsets(testSets, 2) { 1474 pair := pair 1475 t.Run(fmt.Sprintf("%v", pair), func(t *testing.T) { 1476 left1, left2 := NewSubjectSet(), NewSubjectSet() 1477 for _, l := range pair[0] { 1478 left1.MustAdd(l) 1479 left2.MustAdd(l) 1480 } 1481 right1, right2 := NewSubjectSet(), NewSubjectSet() 1482 for _, r := range pair[1] { 1483 right1.MustAdd(r) 1484 right2.MustAdd(r) 1485 } 1486 // left union right 1487 left1.MustUnionWithSet(right1) 1488 1489 // right union left 1490 right2.MustUnionWithSet(left2) 1491 1492 mergedLeft := left1.AsSlice() 1493 mergedRight := right2.AsSlice() 1494 testutil.RequireEquivalentSets(t, mergedLeft, mergedRight) 1495 }) 1496 } 1497 } 1498 1499 func TestUnionAssociativity(t *testing.T) { 1500 for _, triple := range allSubsets(testSets, 3) { 1501 triple := triple 1502 t.Run(fmt.Sprintf("%s U %s U %s", testutil.FormatSubjects(triple[0]), testutil.FormatSubjects(triple[1]), testutil.FormatSubjects(triple[2])), func(t *testing.T) { 1503 // A U (B U C) == (A U B) U C 1504 1505 A1, A2 := NewSubjectSet(), NewSubjectSet() 1506 for _, l := range triple[0] { 1507 A1.MustAdd(l) 1508 A2.MustAdd(l) 1509 } 1510 B1, B2 := NewSubjectSet(), NewSubjectSet() 1511 for _, l := range triple[1] { 1512 B1.MustAdd(l) 1513 B2.MustAdd(l) 1514 } 1515 C1, C2 := NewSubjectSet(), NewSubjectSet() 1516 for _, l := range triple[2] { 1517 C1.MustAdd(l) 1518 C2.MustAdd(l) 1519 } 1520 1521 // A U (B U C) 1522 B1.MustUnionWithSet(C1) 1523 A1.MustUnionWithSet(B1) 1524 1525 // (A U B) U C 1526 A2.MustUnionWithSet(B2) 1527 A2.MustUnionWithSet(C2) 1528 1529 mergedLeft := A1.AsSlice() 1530 mergedRight := A2.AsSlice() 1531 testutil.RequireEquivalentSets(t, mergedLeft, mergedRight) 1532 }) 1533 } 1534 } 1535 1536 func TestIntersectionCommutativity(t *testing.T) { 1537 for _, pair := range allSubsets(testSets, 2) { 1538 pair := pair 1539 t.Run(fmt.Sprintf("%v", pair), func(t *testing.T) { 1540 left1, left2 := NewSubjectSet(), NewSubjectSet() 1541 for _, l := range pair[0] { 1542 left1.MustAdd(l) 1543 left2.MustAdd(l) 1544 } 1545 right1, right2 := NewSubjectSet(), NewSubjectSet() 1546 for _, r := range pair[1] { 1547 right1.MustAdd(r) 1548 right2.MustAdd(r) 1549 } 1550 // left intersect right 1551 left1.MustIntersectionDifference(right1) 1552 // right intersects left 1553 right2.MustIntersectionDifference(left2) 1554 1555 mergedLeft := left1.AsSlice() 1556 mergedRight := right2.AsSlice() 1557 testutil.RequireEquivalentSets(t, mergedLeft, mergedRight) 1558 }) 1559 } 1560 } 1561 1562 func TestIntersectionAssociativity(t *testing.T) { 1563 for _, triple := range allSubsets(testSets, 3) { 1564 triple := triple 1565 t.Run(fmt.Sprintf("%s ∩ %s ∩ %s", testutil.FormatSubjects(triple[0]), testutil.FormatSubjects(triple[1]), testutil.FormatSubjects(triple[2])), func(t *testing.T) { 1566 // A ∩ (B ∩ C) == (A ∩ B) ∩ C 1567 1568 A1, A2 := NewSubjectSet(), NewSubjectSet() 1569 for _, l := range triple[0] { 1570 A1.MustAdd(l) 1571 A2.MustAdd(l) 1572 } 1573 B1, B2 := NewSubjectSet(), NewSubjectSet() 1574 for _, l := range triple[1] { 1575 B1.MustAdd(l) 1576 B2.MustAdd(l) 1577 } 1578 C1, C2 := NewSubjectSet(), NewSubjectSet() 1579 for _, l := range triple[2] { 1580 C1.MustAdd(l) 1581 C2.MustAdd(l) 1582 } 1583 1584 // A ∩ (B ∩ C) 1585 B1.MustIntersectionDifference(C1) 1586 A1.MustIntersectionDifference(B1) 1587 1588 // (A ∩ B) ∩ C 1589 A2.MustIntersectionDifference(B2) 1590 A2.MustIntersectionDifference(C2) 1591 1592 mergedLeft := A1.AsSlice() 1593 mergedRight := A2.AsSlice() 1594 testutil.RequireEquivalentSets(t, mergedLeft, mergedRight) 1595 }) 1596 } 1597 } 1598 1599 func TestIdempotentUnion(t *testing.T) { 1600 for _, set := range testSets { 1601 set := set 1602 t.Run(fmt.Sprintf("%v", set), func(t *testing.T) { 1603 // A U A == A 1604 A1, A2 := NewSubjectSet(), NewSubjectSet() 1605 for _, l := range set { 1606 A1.MustAdd(l) 1607 A2.MustAdd(l) 1608 } 1609 A1.MustUnionWithSet(A2) 1610 1611 mergedLeft := A1.AsSlice() 1612 mergedRight := A2.AsSlice() 1613 testutil.RequireEquivalentSets(t, mergedLeft, mergedRight) 1614 }) 1615 } 1616 } 1617 1618 func TestIdempotentIntersection(t *testing.T) { 1619 for _, set := range testSets { 1620 set := set 1621 t.Run(fmt.Sprintf("%v", set), func(t *testing.T) { 1622 // A ∩ A == A 1623 A1, A2 := NewSubjectSet(), NewSubjectSet() 1624 for _, l := range set { 1625 A1.MustAdd(l) 1626 A2.MustAdd(l) 1627 } 1628 A1.MustIntersectionDifference(A2) 1629 1630 mergedLeft := A1.AsSlice() 1631 mergedRight := A2.AsSlice() 1632 testutil.RequireEquivalentSets(t, mergedLeft, mergedRight) 1633 }) 1634 } 1635 } 1636 1637 func TestUnionWildcardWithWildcard(t *testing.T) { 1638 tcs := []struct { 1639 existing *v1.FoundSubject 1640 toUnion *v1.FoundSubject 1641 expected *v1.FoundSubject 1642 expectedInverse *v1.FoundSubject 1643 }{ 1644 { 1645 nil, 1646 wc(), 1647 1648 // nil U {*} => {*} 1649 wc(), 1650 wc(), 1651 }, 1652 { 1653 wc(), 1654 wc(), 1655 1656 // {*} U {*} => {*} 1657 wc(), 1658 wc(), 1659 }, 1660 { 1661 wc("1"), 1662 wc(), 1663 1664 // {* - {1}} U {*} => {*} 1665 wc(), 1666 wc(), 1667 }, 1668 { 1669 wc("1"), 1670 wc("1"), 1671 1672 // {* - {1}} U {* - {1}} => {* - {1}} 1673 wc("1"), 1674 wc("1"), 1675 }, 1676 { 1677 wc("1", "2"), 1678 wc("1"), 1679 1680 // {* - {1, 2}} U {* - {1}} => {* - {1}} 1681 wc("1"), 1682 wc("1"), 1683 }, 1684 { 1685 cwc(caveatexpr("first")), 1686 wc(), 1687 1688 // {*[first]} U {*} => {*} 1689 wc(), 1690 wc(), 1691 }, 1692 { 1693 cwc(caveatexpr("first")), 1694 cwc(caveatexpr("second")), 1695 1696 // {*[first]} U {*[second]} => {*[first || second]} 1697 cwc(caveatOr(caveatexpr("first"), caveatexpr("second"))), 1698 cwc(caveatOr(caveatexpr("second"), caveatexpr("first"))), 1699 }, 1700 { 1701 wc("1"), 1702 cwc(nil, csub("1", caveatexpr("first"))), 1703 1704 // Expected 1705 // The subject is only excluded if the caveat is true 1706 cwc(nil, csub("1", caveatexpr("first"))), 1707 cwc(nil, csub("1", caveatexpr("first"))), 1708 }, 1709 { 1710 cwc(nil, csub("1", caveatexpr("second"))), 1711 cwc(nil, csub("1", caveatexpr("first"))), 1712 1713 // Expected 1714 // The subject is excluded if both its caveats are true 1715 cwc(nil, 1716 csub("1", 1717 caveatAnd( 1718 caveatexpr("second"), 1719 caveatexpr("first"), 1720 ), 1721 ), 1722 ), 1723 cwc(nil, 1724 csub("1", 1725 caveatAnd( 1726 caveatexpr("first"), 1727 caveatexpr("second"), 1728 ), 1729 ), 1730 ), 1731 }, 1732 } 1733 1734 for _, tc := range tcs { 1735 tc := tc 1736 1737 t.Run(fmt.Sprintf("%s U %s", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toUnion)), func(t *testing.T) { 1738 existing := wrap(tc.existing) 1739 produced, err := unionWildcardWithWildcard(existing, tc.toUnion, subjectSetConstructor) 1740 require.NoError(t, err) 1741 testutil.RequireExpectedSubject(t, tc.expected, produced) 1742 1743 toUnion := wrap(tc.toUnion) 1744 produced2, err := unionWildcardWithWildcard(toUnion, tc.existing, subjectSetConstructor) 1745 require.NoError(t, err) 1746 testutil.RequireExpectedSubject(t, tc.expectedInverse, produced2) 1747 }) 1748 } 1749 } 1750 1751 func TestUnionWildcardWithConcrete(t *testing.T) { 1752 tcs := []struct { 1753 existing *v1.FoundSubject 1754 toUnion *v1.FoundSubject 1755 expected *v1.FoundSubject 1756 }{ 1757 { 1758 nil, 1759 sub("1"), 1760 1761 // nil U {1} => nil 1762 nil, 1763 }, 1764 { 1765 wc(), 1766 sub("1"), 1767 1768 // {*} U {1} => {*} 1769 wc(), 1770 }, 1771 { 1772 wc("1"), 1773 sub("1"), 1774 1775 // {* - {1}} U {1} => {*} 1776 wc(), 1777 }, 1778 { 1779 wc("1", "2"), 1780 sub("1"), 1781 1782 // {* - {1, 2}} U {1} => {* - {2}} 1783 wc("2"), 1784 }, 1785 { 1786 cwc(nil, csub("1", caveatexpr("first"))), 1787 sub("1"), 1788 1789 // {* - {1[first]}} U {1} => {*} 1790 wc(), 1791 }, 1792 { 1793 cwc(nil, csub("2", caveatexpr("first"))), 1794 sub("1"), 1795 1796 // {* - {2[first]}} U {1} => {* - {2[first]}} 1797 cwc(nil, csub("2", caveatexpr("first"))), 1798 }, 1799 { 1800 cwc(nil, 1801 csub("1", caveatexpr("first")), 1802 csub("2", caveatexpr("second")), 1803 ), 1804 sub("1"), 1805 1806 // {* - {1[first], 2[second]}} U {1} => {* - {2[second]}} 1807 cwc(nil, csub("2", caveatexpr("second"))), 1808 }, 1809 { 1810 cwc(nil, 1811 csub("1", caveatexpr("first")), 1812 csub("2", caveatexpr("second")), 1813 ), 1814 csub("1", caveatexpr("third")), 1815 1816 // {* - {1[first], 2[second]}} U {1[third]} => {* - {1[first && !third], 2[second]}} 1817 cwc(nil, 1818 csub("1", 1819 caveatAnd( 1820 caveatexpr("first"), 1821 caveatInvert(caveatexpr("third")), 1822 ), 1823 ), 1824 csub("2", caveatexpr("second")), 1825 ), 1826 }, 1827 { 1828 cwc(caveatexpr("first")), 1829 sub("1"), 1830 1831 // {*}[first] U {1} => {*}[first] 1832 cwc(caveatexpr("first")), 1833 }, 1834 { 1835 cwc(caveatexpr("wcaveat"), 1836 csub("1", caveatexpr("first")), 1837 csub("2", caveatexpr("second")), 1838 ), 1839 csub("1", caveatexpr("third")), 1840 1841 // {* - {1[first], 2[second]}}[wcaveat] U {1[third]} => {* - {1[first && !third], 2[second]}}[wcaveat] 1842 cwc(caveatexpr("wcaveat"), 1843 csub("1", 1844 caveatAnd( 1845 caveatexpr("first"), 1846 caveatInvert(caveatexpr("third")), 1847 ), 1848 ), 1849 csub("2", caveatexpr("second")), 1850 ), 1851 }, 1852 } 1853 1854 for _, tc := range tcs { 1855 tc := tc 1856 1857 t.Run(fmt.Sprintf("%s U %s", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toUnion)), func(t *testing.T) { 1858 existing := wrap(tc.existing) 1859 produced := unionWildcardWithConcrete(existing, tc.toUnion, subjectSetConstructor) 1860 testutil.RequireExpectedSubject(t, tc.expected, produced) 1861 }) 1862 } 1863 } 1864 1865 func TestUnionConcreteWithConcrete(t *testing.T) { 1866 tcs := []struct { 1867 existing *v1.FoundSubject 1868 toUnion *v1.FoundSubject 1869 expected *v1.FoundSubject 1870 expectedInverted *v1.FoundSubject 1871 }{ 1872 { 1873 nil, 1874 nil, 1875 1876 // nil U nil => nil 1877 nil, 1878 nil, 1879 }, 1880 { 1881 sub("1"), 1882 nil, 1883 1884 // {1} U nil => {1} 1885 sub("1"), 1886 sub("1"), 1887 }, 1888 { 1889 sub("1"), 1890 sub("1"), 1891 1892 // {1} U {1} => {1} 1893 sub("1"), 1894 sub("1"), 1895 }, 1896 { 1897 csub("1", caveatexpr("first")), 1898 sub("1"), 1899 1900 // {1}[first] U {1} => {1} 1901 sub("1"), 1902 sub("1"), 1903 }, 1904 { 1905 csub("1", caveatexpr("first")), 1906 csub("1", caveatexpr("second")), 1907 1908 // {1}[first] U {1}[second] => {1}[first || second] 1909 csub("1", caveatOr(caveatexpr("first"), caveatexpr("second"))), 1910 csub("1", caveatOr(caveatexpr("second"), caveatexpr("first"))), 1911 }, 1912 } 1913 1914 for _, tc := range tcs { 1915 tc := tc 1916 1917 t.Run(fmt.Sprintf("%s U %s", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toUnion)), func(t *testing.T) { 1918 existing := wrap(tc.existing) 1919 toUnion := wrap(tc.toUnion) 1920 1921 produced := unionConcreteWithConcrete(existing, toUnion, subjectSetConstructor) 1922 testutil.RequireExpectedSubject(t, tc.expected, produced) 1923 1924 produced2 := unionConcreteWithConcrete(toUnion, existing, subjectSetConstructor) 1925 testutil.RequireExpectedSubject(t, tc.expectedInverted, produced2) 1926 }) 1927 } 1928 } 1929 1930 func TestSubtractWildcardFromWildcard(t *testing.T) { 1931 tcs := []struct { 1932 existing *v1.FoundSubject 1933 toSubtract *v1.FoundSubject 1934 expected *v1.FoundSubject 1935 expectedConcretes []*v1.FoundSubject 1936 }{ 1937 { 1938 nil, 1939 wc(), 1940 1941 // nil - {*} => nil 1942 nil, 1943 nil, 1944 }, 1945 { 1946 wc(), 1947 wc(), 1948 1949 // {*} - {*} => nil 1950 nil, 1951 nil, 1952 }, 1953 { 1954 wc("1", "2"), 1955 wc(), 1956 1957 // {* - {1, 2}} - {*} => nil 1958 nil, 1959 nil, 1960 }, 1961 { 1962 wc("1", "2"), 1963 wc("2", "3"), 1964 1965 // {* - {1, 2}} - {* - {2, 3}} => {3} 1966 nil, 1967 []*v1.FoundSubject{sub("3")}, 1968 }, 1969 { 1970 wc(), 1971 wc("1", "2"), 1972 1973 // {*} - {* - {1, 2}} => {1, 2} 1974 nil, 1975 []*v1.FoundSubject{sub("1"), sub("2")}, 1976 }, 1977 { 1978 cwc(caveatexpr("first")), 1979 wc(), 1980 1981 // {*}[first] - {*} => nil 1982 nil, 1983 nil, 1984 }, 1985 { 1986 wc(), 1987 cwc(caveatexpr("first")), 1988 1989 // {*} - {*}[first] => {*}[!first] 1990 cwc(caveatInvert(caveatexpr("first"))), 1991 []*v1.FoundSubject{}, 1992 }, 1993 { 1994 wc(), 1995 cwc(nil, csub("1", caveatexpr("first"))), 1996 1997 // {*} - {* - {1}[first]} => {1}[first] 1998 nil, 1999 []*v1.FoundSubject{csub("1", caveatexpr("first"))}, 2000 }, 2001 { 2002 cwc(nil, csub("1", caveatexpr("first"))), 2003 cwc(nil, csub("1", caveatexpr("second"))), 2004 2005 // {* - {1}[first]} - {* - {1}[second]} => {1}[!first && second] 2006 nil, 2007 []*v1.FoundSubject{ 2008 csub("1", 2009 caveatAnd( 2010 caveatInvert(caveatexpr("first")), 2011 caveatexpr("second"), 2012 ), 2013 ), 2014 }, 2015 }, 2016 { 2017 cwc(caveatexpr("wcaveat1"), csub("1", caveatexpr("first"))), 2018 cwc(caveatexpr("wcaveat2"), csub("1", caveatexpr("second"))), 2019 2020 // {* - {1}[first]}[wcaveat1] - 2021 // {* - {1}[second]}[wcaveat2] => 2022 // 2023 // The wildcard itself exists if its caveat is true and the caveat on the second wildcard 2024 // is false: 2025 // {* - {1}[first]}[wcaveat1 && !wcaveat2] 2026 // 2027 // The concrete is only produced when the first wildcard is present, the exclusion is 2028 // not, the second wildcard is present, and its exclusion is true: 2029 // {1}[wcaveat1 && !first && wcaveat2 && second] 2030 cwc( 2031 caveatAnd( 2032 caveatexpr("wcaveat1"), 2033 caveatInvert(caveatexpr("wcaveat2")), 2034 ), 2035 2036 // Note that the exclusion does not rely on the second caveat, because if the first caveat 2037 // is true, then the value is excluded regardless of the second caveat's value. 2038 csub("1", caveatexpr("first")), 2039 ), 2040 []*v1.FoundSubject{ 2041 csub("1", 2042 caveatAnd( 2043 caveatAnd( 2044 caveatAnd( 2045 caveatexpr("wcaveat1"), 2046 caveatexpr("wcaveat2"), 2047 ), 2048 caveatInvert(caveatexpr("first")), 2049 ), 2050 caveatexpr("second"), 2051 ), 2052 ), 2053 }, 2054 }, 2055 } 2056 2057 for _, tc := range tcs { 2058 tc := tc 2059 2060 t.Run(fmt.Sprintf("%s - %s", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toSubtract)), func(t *testing.T) { 2061 existing := wrap(tc.existing) 2062 2063 produced, concrete := subtractWildcardFromWildcard(existing, tc.toSubtract, subjectSetConstructor) 2064 testutil.RequireExpectedSubject(t, tc.expected, produced) 2065 testutil.RequireEquivalentSets(t, tc.expectedConcretes, concrete) 2066 }) 2067 } 2068 } 2069 2070 func TestSubtractWildcardFromConcrete(t *testing.T) { 2071 tcs := []struct { 2072 existing *v1.FoundSubject 2073 toSubtract *v1.FoundSubject 2074 expected *v1.FoundSubject 2075 }{ 2076 { 2077 sub("1"), 2078 wc(), 2079 2080 // {1} - {*} => nil 2081 nil, 2082 }, 2083 { 2084 sub("1"), 2085 wc("2"), 2086 2087 // {1} - {* - {2}} => nil 2088 nil, 2089 }, 2090 { 2091 sub("1"), 2092 wc("1", "2", "3"), 2093 2094 // {1} - {* - {1, 2, 3}} => {1} 2095 sub("1"), 2096 }, 2097 { 2098 csub("1", caveatexpr("first")), 2099 wc(), 2100 2101 // {1}[first] - {*} => nil 2102 nil, 2103 }, 2104 { 2105 csub("1", caveatexpr("first")), 2106 cwc(caveatexpr("second")), 2107 2108 // {1}[first] - {*}[second] => {1[first && !second]} 2109 csub("1", 2110 caveatAnd( 2111 caveatexpr("first"), 2112 caveatInvert(caveatexpr("second")), 2113 ), 2114 ), 2115 }, 2116 { 2117 sub("1"), 2118 cwc(nil, csub("1", caveatexpr("first"))), 2119 2120 // {1} - {* - {1[first]}} => {1}[first] 2121 csub("1", 2122 caveatexpr("first"), 2123 ), 2124 }, 2125 { 2126 csub("1", caveatexpr("previous")), 2127 cwc(nil, csub("1", caveatexpr("exclusion"))), 2128 2129 // {1}[previous] - {* - {1[exclusion]}} => {1}[previous && exclusion] 2130 csub("1", 2131 caveatAnd( 2132 caveatexpr("previous"), 2133 caveatexpr("exclusion"), 2134 ), 2135 ), 2136 }, 2137 { 2138 csub("1", caveatexpr("previous")), 2139 cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("exclusion"))), 2140 2141 // {1}[previous] - {* - {1[exclusion]}}[wcaveat] => {1}[previous && (!wcaveat || exclusion)]] 2142 csub("1", 2143 caveatAnd( 2144 caveatexpr("previous"), 2145 caveatOr( 2146 caveatInvert(caveatexpr("wcaveat")), 2147 caveatexpr("exclusion"), 2148 ), 2149 ), 2150 ), 2151 }, 2152 { 2153 csub("1", caveatexpr("previous")), 2154 cwc(caveatexpr("wcaveat"), csub("2", caveatexpr("exclusion"))), 2155 2156 // {1}[previous] - {* - {2[exclusion]}}[wcaveat] => {1}[previous && !wcaveat)]] 2157 csub("1", 2158 caveatAnd( 2159 caveatexpr("previous"), 2160 caveatInvert(caveatexpr("wcaveat")), 2161 ), 2162 ), 2163 }, 2164 } 2165 2166 for _, tc := range tcs { 2167 tc := tc 2168 2169 t.Run(fmt.Sprintf("%v - %v", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toSubtract)), func(t *testing.T) { 2170 produced := subtractWildcardFromConcrete(tc.existing, tc.toSubtract, subjectSetConstructor) 2171 testutil.RequireExpectedSubject(t, tc.expected, produced) 2172 }) 2173 } 2174 } 2175 2176 func TestSubtractConcreteFromConcrete(t *testing.T) { 2177 tcs := []struct { 2178 existing *v1.FoundSubject 2179 toSubtract *v1.FoundSubject 2180 expected *v1.FoundSubject 2181 }{ 2182 { 2183 sub("1"), 2184 sub("1"), 2185 2186 // {1} - {1} => nil 2187 nil, 2188 }, 2189 { 2190 csub("1", caveatexpr("first")), 2191 sub("1"), 2192 2193 // {1[first]} - {1} => nil 2194 nil, 2195 }, 2196 { 2197 sub("1"), 2198 csub("1", caveatexpr("first")), 2199 2200 // {1} - {1[first]} => {1[!first]} 2201 csub("1", caveatInvert(caveatexpr("first"))), 2202 }, 2203 { 2204 csub("1", caveatexpr("first")), 2205 csub("1", caveatexpr("second")), 2206 2207 // {1[first]} - {1[second]} => {1[first && !second]} 2208 csub("1", 2209 caveatAnd( 2210 caveatexpr("first"), 2211 caveatInvert(caveatexpr("second")), 2212 ), 2213 ), 2214 }, 2215 } 2216 2217 for _, tc := range tcs { 2218 tc := tc 2219 2220 t.Run(fmt.Sprintf("%s - %s", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toSubtract)), func(t *testing.T) { 2221 produced := subtractConcreteFromConcrete(tc.existing, tc.toSubtract, subjectSetConstructor) 2222 testutil.RequireExpectedSubject(t, tc.expected, produced) 2223 }) 2224 } 2225 } 2226 2227 func TestSubtractConcreteFromWildcard(t *testing.T) { 2228 tcs := []struct { 2229 existing *v1.FoundSubject 2230 toSubtract *v1.FoundSubject 2231 expected *v1.FoundSubject 2232 }{ 2233 { 2234 wc(), 2235 sub("1"), 2236 2237 // {*} - {1} => {* - {1}} 2238 wc("1"), 2239 }, 2240 { 2241 wc("1"), 2242 sub("1"), 2243 2244 // {* - {1}} - {1} => {* - {1}} 2245 wc("1"), 2246 }, 2247 { 2248 wc("1", "2"), 2249 sub("1"), 2250 2251 // {* - {1, 2}} - {1} => {* - {1, 2}} 2252 wc("1", "2"), 2253 }, 2254 { 2255 cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), 2256 sub("1"), 2257 2258 // {* - {1, 2}}[wcaveat] - {1} => {* - {1, 2}}[wcaveat] 2259 cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), 2260 }, 2261 { 2262 cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("first")), sub("2")), 2263 sub("1"), 2264 2265 // {* - {1[first], 2}}[wcaveat] - {1} => {* - {1, 2}}[wcaveat] 2266 cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), 2267 }, 2268 { 2269 cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), 2270 csub("1", caveatexpr("second")), 2271 2272 // {* - {1, 2}}[wcaveat] - {1}[first] => {* - {1, 2}}[wcaveat] 2273 cwc(caveatexpr("wcaveat"), sub("1"), sub("2")), 2274 }, 2275 { 2276 cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("first")), sub("2")), 2277 csub("1", caveatexpr("second")), 2278 2279 // {* - {1[first], 2}}[wcaveat] - {1}[second] => {* - {1[first || second], 2}}[wcaveat] 2280 cwc( 2281 caveatexpr("wcaveat"), 2282 csub("1", 2283 caveatOr( 2284 caveatexpr("first"), 2285 caveatexpr("second"), 2286 ), 2287 ), 2288 sub("2")), 2289 }, 2290 } 2291 2292 for _, tc := range tcs { 2293 tc := tc 2294 2295 t.Run(fmt.Sprintf("%s - %s", testutil.FormatSubject(tc.existing), testutil.FormatSubject(tc.toSubtract)), func(t *testing.T) { 2296 produced := subtractConcreteFromWildcard(tc.existing, tc.toSubtract, subjectSetConstructor) 2297 testutil.RequireExpectedSubject(t, tc.expected, produced) 2298 }) 2299 } 2300 } 2301 2302 func TestIntersectConcreteWithConcrete(t *testing.T) { 2303 tcs := []struct { 2304 first *v1.FoundSubject 2305 second *v1.FoundSubject 2306 expected *v1.FoundSubject 2307 }{ 2308 { 2309 sub("1"), 2310 nil, 2311 2312 // {1} ∩ {} => nil 2313 nil, 2314 }, 2315 { 2316 sub("1"), 2317 sub("1"), 2318 2319 // {1} ∩ {1} => {1} 2320 sub("1"), 2321 }, 2322 { 2323 csub("1", caveatexpr("first")), 2324 sub("1"), 2325 2326 // {1[first]} ∩ {1} => {1[first]} 2327 csub("1", caveatexpr("first")), 2328 }, 2329 { 2330 sub("1"), 2331 csub("1", caveatexpr("first")), 2332 2333 // {1} ∩ {1[first]} => {1[first]} 2334 csub("1", caveatexpr("first")), 2335 }, 2336 { 2337 csub("1", caveatexpr("first")), 2338 csub("1", caveatexpr("second")), 2339 2340 // {1[first]} ∩ {1[second]} => {1[first && second]} 2341 csub("1", 2342 caveatAnd( 2343 caveatexpr("first"), 2344 caveatexpr("second"), 2345 ), 2346 ), 2347 }, 2348 } 2349 2350 for _, tc := range tcs { 2351 tc := tc 2352 2353 t.Run(fmt.Sprintf("%s ∩ %s", testutil.FormatSubject(tc.first), testutil.FormatSubject(tc.second)), func(t *testing.T) { 2354 second := wrap(tc.second) 2355 2356 produced := intersectConcreteWithConcrete(tc.first, second, subjectSetConstructor) 2357 testutil.RequireExpectedSubject(t, tc.expected, produced) 2358 }) 2359 } 2360 } 2361 2362 func TestIntersectWildcardWithWildcard(t *testing.T) { 2363 tcs := []struct { 2364 first *v1.FoundSubject 2365 second *v1.FoundSubject 2366 2367 expected *v1.FoundSubject 2368 expectedInverted *v1.FoundSubject 2369 }{ 2370 { 2371 nil, 2372 nil, 2373 2374 // nil ∩ nil => nil 2375 nil, 2376 nil, 2377 }, 2378 { 2379 wc(), 2380 nil, 2381 2382 // {*} ∩ nil => nil 2383 nil, 2384 nil, 2385 }, 2386 { 2387 wc(), 2388 wc(), 2389 2390 // {*} ∩ {*} => {*} 2391 wc(), 2392 wc(), 2393 }, 2394 { 2395 wc("1"), 2396 wc(), 2397 2398 // {* - {1}} ∩ {*} => {* - {1}} 2399 wc("1"), 2400 wc("1"), 2401 }, 2402 { 2403 wc("1", "2"), 2404 wc("2", "3"), 2405 2406 // {* - {1,2}} ∩ {* - {2,3}} => {* - {1,2,3}} 2407 wc("1", "2", "3"), 2408 wc("1", "2", "3"), 2409 }, 2410 { 2411 cwc(caveatexpr("first")), 2412 cwc(caveatexpr("second")), 2413 2414 // {*}[first] ∩ {*}[second] => {*}[first && second] 2415 cwc( 2416 caveatAnd( 2417 caveatexpr("first"), 2418 caveatexpr("second"), 2419 ), 2420 ), 2421 cwc( 2422 caveatAnd( 2423 caveatexpr("second"), 2424 caveatexpr("first"), 2425 ), 2426 ), 2427 }, 2428 { 2429 cwc( 2430 caveatexpr("first"), 2431 csub("1", caveatexpr("ex1cav")), 2432 csub("2", caveatexpr("ex2cav")), 2433 ), 2434 cwc( 2435 caveatexpr("second"), 2436 csub("2", caveatexpr("ex3cav")), 2437 csub("3", caveatexpr("ex4cav")), 2438 ), 2439 2440 // {* - {1[ex1cav], 2[ex2cav]}}[first] ∩ {* - {2[ex3cav], 3[ex4cav]}}[second] => {* - {1[ex1cav], 2[ex2cav || ex3cav], 3[ex4cav]}}[first && second] 2441 cwc( 2442 caveatAnd( 2443 caveatexpr("first"), 2444 caveatexpr("second"), 2445 ), 2446 csub("1", caveatexpr("ex1cav")), 2447 csub("2", caveatOr(caveatexpr("ex2cav"), caveatexpr("ex3cav"))), 2448 csub("3", caveatexpr("ex4cav")), 2449 ), 2450 cwc( 2451 caveatAnd( 2452 caveatexpr("second"), 2453 caveatexpr("first"), 2454 ), 2455 csub("1", caveatexpr("ex1cav")), 2456 csub("2", caveatOr(caveatexpr("ex3cav"), caveatexpr("ex2cav"))), 2457 csub("3", caveatexpr("ex4cav")), 2458 ), 2459 }, 2460 } 2461 2462 for _, tc := range tcs { 2463 tc := tc 2464 2465 t.Run(fmt.Sprintf("%s ∩ %s", testutil.FormatSubject(tc.first), testutil.FormatSubject(tc.second)), func(t *testing.T) { 2466 first := wrap(tc.first) 2467 second := wrap(tc.second) 2468 2469 produced, err := intersectWildcardWithWildcard(first, second, subjectSetConstructor) 2470 require.NoError(t, err) 2471 testutil.RequireExpectedSubject(t, tc.expected, produced) 2472 2473 produced2, err := intersectWildcardWithWildcard(second, first, subjectSetConstructor) 2474 require.NoError(t, err) 2475 testutil.RequireExpectedSubject(t, tc.expectedInverted, produced2) 2476 }) 2477 } 2478 } 2479 2480 func TestIntersectConcreteWithWildcard(t *testing.T) { 2481 tcs := []struct { 2482 concrete *v1.FoundSubject 2483 wildcard *v1.FoundSubject 2484 2485 expected *v1.FoundSubject 2486 }{ 2487 { 2488 sub("1"), 2489 nil, 2490 2491 // 1 ∩ nil => nil 2492 nil, 2493 }, 2494 { 2495 sub("1"), 2496 wc(), 2497 2498 // 1 ∩ {*} => {1} 2499 sub("1"), 2500 }, 2501 { 2502 sub("1"), 2503 wc("1"), 2504 2505 // 1 ∩ {* - {1}} => nil 2506 nil, 2507 }, 2508 { 2509 sub("1"), 2510 wc("2"), 2511 2512 // 1 ∩ {* - {2}} => {1} 2513 sub("1"), 2514 }, 2515 { 2516 sub("1"), 2517 cwc(caveatexpr("wcaveat")), 2518 2519 // 1 ∩ {*}[wcaveat] => {1}[wcaveat] 2520 csub("1", caveatexpr("wcaveat")), 2521 }, 2522 { 2523 sub("42"), 2524 cwc(nil, csub("42", caveatexpr("first"))), 2525 2526 // 42 ∩ {* - 42[first]} => {42}[!first] 2527 csub("42", 2528 caveatInvert(caveatexpr("first")), 2529 ), 2530 }, 2531 { 2532 sub("1"), 2533 cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("first"))), 2534 2535 // 1 ∩ {* - 1[first]}[wcaveat] => {1}[wcaveat && !first] 2536 csub("1", 2537 caveatAnd( 2538 caveatexpr("wcaveat"), 2539 caveatInvert(caveatexpr("first")), 2540 ), 2541 ), 2542 }, 2543 { 2544 csub("1", caveatexpr("first")), 2545 cwc(nil, csub("1", caveatexpr("second"))), 2546 2547 // 1[first] ∩ {* - 1[second]} => {1}[first && !second] 2548 csub("1", 2549 caveatAnd( 2550 caveatexpr("first"), 2551 caveatInvert(caveatexpr("second")), 2552 ), 2553 ), 2554 }, 2555 { 2556 csub("1", caveatexpr("first")), 2557 cwc(caveatexpr("wcaveat"), csub("1", caveatexpr("second"))), 2558 2559 // 1[first] ∩ {* - 1[second]}[wcaveat] => {1}[first && !second && wcaveat] 2560 csub("1", 2561 caveatAnd( 2562 caveatexpr("first"), 2563 caveatAnd( 2564 caveatexpr("wcaveat"), 2565 caveatInvert(caveatexpr("second")), 2566 ), 2567 ), 2568 ), 2569 }, 2570 { 2571 csub("1", caveatexpr("first")), 2572 cwc( 2573 caveatexpr("wcaveat"), 2574 csub("1", caveatexpr("second")), 2575 csub("2", caveatexpr("third")), 2576 ), 2577 2578 // 1[first] ∩ {* - {1[second], 2[third]}}[wcaveat] => {1}[first && !second && wcaveat] 2579 csub("1", 2580 caveatAnd( 2581 caveatexpr("first"), 2582 caveatAnd( 2583 caveatexpr("wcaveat"), 2584 caveatInvert(caveatexpr("second")), 2585 ), 2586 ), 2587 ), 2588 }, 2589 } 2590 2591 for _, tc := range tcs { 2592 tc := tc 2593 2594 t.Run(fmt.Sprintf("%s ∩ %s", testutil.FormatSubject(tc.concrete), testutil.FormatSubject(tc.wildcard)), func(t *testing.T) { 2595 wildcard := wrap(tc.wildcard) 2596 2597 produced, err := intersectConcreteWithWildcard(tc.concrete, wildcard, subjectSetConstructor) 2598 require.NoError(t, err) 2599 testutil.RequireExpectedSubject(t, tc.expected, produced) 2600 }) 2601 } 2602 } 2603 2604 // allSubsets returns a list of all subsets of length n 2605 // it counts in binary and "activates" input funcs that match 1s in the binary representation 2606 // it doesn't check for overflow so don't go crazy 2607 func allSubsets[T any](objs []T, n int) [][]T { 2608 maxInt := uint64(math.Exp2(float64(len(objs)))) - 1 2609 all := make([][]T, 0) 2610 2611 for i := uint64(0); i < maxInt; i++ { 2612 set := make([]T, 0, n) 2613 for digit := uint64(0); digit < uint64(len(objs)); digit++ { 2614 mask := uint64(1) << digit 2615 if mask&i != 0 { 2616 set = append(set, objs[digit]) 2617 } 2618 if len(set) > n { 2619 break 2620 } 2621 } 2622 if len(set) == n { 2623 all = append(all, set) 2624 } 2625 } 2626 return all 2627 }