github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/tuple/tuple_test.go (about) 1 package tuple 2 3 import ( 4 "strings" 5 "testing" 6 7 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 8 "github.com/stretchr/testify/require" 9 "google.golang.org/protobuf/types/known/structpb" 10 11 core "github.com/authzed/spicedb/pkg/proto/core/v1" 12 "github.com/authzed/spicedb/pkg/testutil" 13 ) 14 15 func makeTuple(onr *core.ObjectAndRelation, subject *core.ObjectAndRelation) *core.RelationTuple { 16 return &core.RelationTuple{ 17 ResourceAndRelation: onr, 18 Subject: subject, 19 } 20 } 21 22 func rel(resType, resID, relation, subType, subID, subRel string) *v1.Relationship { 23 return &v1.Relationship{ 24 Resource: &v1.ObjectReference{ 25 ObjectType: resType, 26 ObjectId: resID, 27 }, 28 Relation: relation, 29 Subject: &v1.SubjectReference{ 30 Object: &v1.ObjectReference{ 31 ObjectType: subType, 32 ObjectId: subID, 33 }, 34 OptionalRelation: subRel, 35 }, 36 } 37 } 38 39 func crel(resType, resID, relation, subType, subID, subRel, caveatName string, caveatContext map[string]any) *v1.Relationship { 40 context, err := structpb.NewStruct(caveatContext) 41 if err != nil { 42 panic(err) 43 } 44 45 if len(context.Fields) == 0 { 46 context = nil 47 } 48 49 return &v1.Relationship{ 50 Resource: &v1.ObjectReference{ 51 ObjectType: resType, 52 ObjectId: resID, 53 }, 54 Relation: relation, 55 Subject: &v1.SubjectReference{ 56 Object: &v1.ObjectReference{ 57 ObjectType: subType, 58 ObjectId: subID, 59 }, 60 OptionalRelation: subRel, 61 }, 62 OptionalCaveat: &v1.ContextualizedCaveat{ 63 CaveatName: caveatName, 64 Context: context, 65 }, 66 } 67 } 68 69 var superLongID = strings.Repeat("f", 1024) 70 71 var testCases = []struct { 72 input string 73 expectedOutput string 74 tupleFormat *core.RelationTuple 75 relFormat *v1.Relationship 76 }{ 77 { 78 input: "testns:testobj#testrel@user:testusr", 79 expectedOutput: "testns:testobj#testrel@user:testusr", 80 tupleFormat: makeTuple( 81 ObjectAndRelation("testns", "testobj", "testrel"), 82 ObjectAndRelation("user", "testusr", "..."), 83 ), 84 relFormat: rel("testns", "testobj", "testrel", "user", "testusr", ""), 85 }, 86 { 87 input: "testns:testobj#testrel@user:testusr#...", 88 expectedOutput: "testns:testobj#testrel@user:testusr", 89 tupleFormat: makeTuple( 90 ObjectAndRelation("testns", "testobj", "testrel"), 91 ObjectAndRelation("user", "testusr", "..."), 92 ), 93 relFormat: rel("testns", "testobj", "testrel", "user", "testusr", ""), 94 }, 95 { 96 input: "tenant/testns:testobj#testrel@tenant/user:testusr", 97 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:testusr", 98 tupleFormat: makeTuple( 99 ObjectAndRelation("tenant/testns", "testobj", "testrel"), 100 ObjectAndRelation("tenant/user", "testusr", "..."), 101 ), 102 relFormat: rel("tenant/testns", "testobj", "testrel", "tenant/user", "testusr", ""), 103 }, 104 { 105 input: "tenant/testns:testobj#testrel@tenant/user:testusr#...", 106 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:testusr", 107 tupleFormat: makeTuple( 108 ObjectAndRelation("tenant/testns", "testobj", "testrel"), 109 ObjectAndRelation("tenant/user", "testusr", "..."), 110 ), 111 relFormat: rel("tenant/testns", "testobj", "testrel", "tenant/user", "testusr", ""), 112 }, 113 { 114 input: "tenant/testns:testobj#testrel@tenant/user:testusr#somerel", 115 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:testusr#somerel", 116 tupleFormat: makeTuple( 117 ObjectAndRelation("tenant/testns", "testobj", "testrel"), 118 ObjectAndRelation("tenant/user", "testusr", "somerel"), 119 ), 120 relFormat: rel("tenant/testns", "testobj", "testrel", "tenant/user", "testusr", "somerel"), 121 }, 122 { 123 input: "org/division/team/testns:testobj#testrel@org/division/identity_team/user:testusr#somerel", 124 expectedOutput: "org/division/team/testns:testobj#testrel@org/division/identity_team/user:testusr#somerel", 125 tupleFormat: makeTuple( 126 ObjectAndRelation("org/division/team/testns", "testobj", "testrel"), 127 ObjectAndRelation("org/division/identity_team/user", "testusr", "somerel"), 128 ), 129 relFormat: rel("org/division/team/testns", "testobj", "testrel", "org/division/identity_team/user", "testusr", "somerel"), 130 }, 131 { 132 input: "tenant/testns:testobj#testrel@tenant/user:testusr something", 133 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:testusr", 134 tupleFormat: nil, 135 relFormat: nil, 136 }, 137 { 138 input: "tenant/testns:testobj#testrel@tenant/user:testusr:", 139 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:testusr", 140 tupleFormat: nil, 141 relFormat: nil, 142 }, 143 { 144 input: "tenant/testns:testobj#testrel@tenant/user:testusr#", 145 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:testusr", 146 tupleFormat: nil, 147 relFormat: nil, 148 }, 149 { 150 input: "", 151 expectedOutput: "", 152 tupleFormat: nil, 153 relFormat: nil, 154 }, 155 { 156 input: "foos:bar#bazzy@groo:grar#...", 157 expectedOutput: "foos:bar#bazzy@groo:grar", 158 tupleFormat: makeTuple( 159 ObjectAndRelation("foos", "bar", "bazzy"), 160 ObjectAndRelation("groo", "grar", "..."), 161 ), 162 relFormat: rel("foos", "bar", "bazzy", "groo", "grar", ""), 163 }, 164 { 165 input: "tenant/testns:testobj#testrel@tenant/user:*#...", 166 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:*", 167 tupleFormat: makeTuple( 168 ObjectAndRelation("tenant/testns", "testobj", "testrel"), 169 ObjectAndRelation("tenant/user", "*", "..."), 170 ), 171 relFormat: rel("tenant/testns", "testobj", "testrel", "tenant/user", "*", ""), 172 }, 173 { 174 input: "tenant/testns:testobj#testrel@tenant/user:authn|foo", 175 expectedOutput: "tenant/testns:testobj#testrel@tenant/user:authn|foo", 176 tupleFormat: makeTuple( 177 ObjectAndRelation("tenant/testns", "testobj", "testrel"), 178 ObjectAndRelation("tenant/user", "authn|foo", "..."), 179 ), 180 relFormat: rel("tenant/testns", "testobj", "testrel", "tenant/user", "authn|foo", ""), 181 }, 182 { 183 input: "document:foo#viewer@user:tom[somecaveat]", 184 expectedOutput: "document:foo#viewer@user:tom[somecaveat]", 185 tupleFormat: MustWithCaveat( 186 makeTuple( 187 ObjectAndRelation("document", "foo", "viewer"), 188 ObjectAndRelation("user", "tom", "..."), 189 ), 190 "somecaveat", 191 ), 192 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "somecaveat", nil), 193 }, 194 { 195 input: "document:foo#viewer@user:tom[tenant/somecaveat]", 196 expectedOutput: "document:foo#viewer@user:tom[tenant/somecaveat]", 197 tupleFormat: MustWithCaveat( 198 makeTuple( 199 ObjectAndRelation("document", "foo", "viewer"), 200 ObjectAndRelation("user", "tom", "..."), 201 ), 202 "tenant/somecaveat", 203 ), 204 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "tenant/somecaveat", nil), 205 }, 206 { 207 input: "document:foo#viewer@user:tom[tenant/division/somecaveat]", 208 expectedOutput: "document:foo#viewer@user:tom[tenant/division/somecaveat]", 209 tupleFormat: MustWithCaveat( 210 makeTuple( 211 ObjectAndRelation("document", "foo", "viewer"), 212 ObjectAndRelation("user", "tom", "..."), 213 ), 214 "tenant/division/somecaveat", 215 ), 216 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "tenant/division/somecaveat", nil), 217 }, 218 { 219 input: "document:foo#viewer@user:tom[somecaveat", 220 expectedOutput: "", 221 tupleFormat: nil, 222 relFormat: nil, 223 }, 224 { 225 input: "document:foo#viewer@user:tom[]", 226 expectedOutput: "", 227 tupleFormat: nil, 228 relFormat: nil, 229 }, 230 { 231 input: `document:foo#viewer@user:tom[somecaveat:{"hi": "there"}]`, 232 expectedOutput: `document:foo#viewer@user:tom[somecaveat:{"hi":"there"}]`, 233 tupleFormat: MustWithCaveat( 234 makeTuple( 235 ObjectAndRelation("document", "foo", "viewer"), 236 ObjectAndRelation("user", "tom", "..."), 237 ), 238 "somecaveat", 239 map[string]any{ 240 "hi": "there", 241 }, 242 ), 243 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "somecaveat", map[string]any{"hi": "there"}), 244 }, 245 { 246 input: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo": 123}}]`, 247 expectedOutput: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo":123}}]`, 248 tupleFormat: MustWithCaveat( 249 makeTuple( 250 ObjectAndRelation("document", "foo", "viewer"), 251 ObjectAndRelation("user", "tom", "..."), 252 ), 253 "somecaveat", 254 map[string]any{ 255 "hi": map[string]any{ 256 "yo": 123, 257 }, 258 }, 259 ), 260 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "somecaveat", map[string]any{ 261 "hi": map[string]any{ 262 "yo": 123, 263 }, 264 }), 265 }, 266 { 267 input: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo":{"hey":true}}}]`, 268 expectedOutput: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo":{"hey":true}}}]`, 269 tupleFormat: MustWithCaveat( 270 makeTuple( 271 ObjectAndRelation("document", "foo", "viewer"), 272 ObjectAndRelation("user", "tom", "..."), 273 ), 274 "somecaveat", 275 map[string]any{ 276 "hi": map[string]any{ 277 "yo": map[string]any{ 278 "hey": true, 279 }, 280 }, 281 }, 282 ), 283 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "somecaveat", map[string]any{ 284 "hi": map[string]any{ 285 "yo": map[string]any{ 286 "hey": true, 287 }, 288 }, 289 }), 290 }, 291 { 292 input: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo":{"hey":[1,2,3]}}}]`, 293 expectedOutput: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo":{"hey":[1,2,3]}}}]`, 294 tupleFormat: MustWithCaveat( 295 makeTuple( 296 ObjectAndRelation("document", "foo", "viewer"), 297 ObjectAndRelation("user", "tom", "..."), 298 ), 299 "somecaveat", 300 map[string]any{ 301 "hi": map[string]any{ 302 "yo": map[string]any{ 303 "hey": []any{1, 2, 3}, 304 }, 305 }, 306 }, 307 ), 308 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "somecaveat", map[string]any{ 309 "hi": map[string]any{ 310 "yo": map[string]any{ 311 "hey": []any{1, 2, 3}, 312 }, 313 }, 314 }), 315 }, 316 { 317 input: `document:foo#viewer@user:tom[somecaveat:{"hi":{"yo":"hey":true}}}]`, 318 expectedOutput: "", 319 tupleFormat: nil, 320 relFormat: nil, 321 }, 322 { 323 input: "testns:" + superLongID + "#testrel@user:testusr", 324 expectedOutput: "testns:" + superLongID + "#testrel@user:testusr", 325 tupleFormat: makeTuple( 326 ObjectAndRelation("testns", superLongID, "testrel"), 327 ObjectAndRelation("user", "testusr", "..."), 328 ), 329 relFormat: rel("testns", superLongID, "testrel", "user", "testusr", ""), 330 }, 331 { 332 input: "testns:foo#testrel@user:" + superLongID, 333 expectedOutput: "testns:foo#testrel@user:" + superLongID, 334 tupleFormat: makeTuple( 335 ObjectAndRelation("testns", "foo", "testrel"), 336 ObjectAndRelation("user", superLongID, "..."), 337 ), 338 relFormat: rel("testns", "foo", "testrel", "user", superLongID, ""), 339 }, 340 { 341 input: "testns:foo#testrel@user:" + superLongID + "more", 342 expectedOutput: "", 343 tupleFormat: nil, 344 relFormat: nil, 345 }, 346 { 347 input: "testns:-base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==#testrel@user:-base65YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", 348 expectedOutput: "testns:-base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==#testrel@user:-base65YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", 349 tupleFormat: makeTuple( 350 ObjectAndRelation("testns", "-base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", "testrel"), 351 ObjectAndRelation("user", "-base65YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", "..."), 352 ), 353 relFormat: rel("testns", "-base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", "testrel", "user", "-base65YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==", ""), 354 }, 355 { 356 input: `document:foo#viewer@user:tom[somecaveat:{"hi":"a@example.com"}]`, 357 expectedOutput: `document:foo#viewer@user:tom[somecaveat:{"hi":"a@example.com"}]`, 358 tupleFormat: MustWithCaveat( 359 makeTuple( 360 ObjectAndRelation("document", "foo", "viewer"), 361 ObjectAndRelation("user", "tom", "..."), 362 ), 363 "somecaveat", 364 map[string]any{ 365 "hi": "a@example.com", 366 }, 367 ), 368 relFormat: crel("document", "foo", "viewer", "user", "tom", "", "somecaveat", map[string]any{ 369 "hi": "a@example.com", 370 }), 371 }, 372 } 373 374 func TestSerialize(t *testing.T) { 375 for _, tc := range testCases { 376 tc := tc 377 t.Run("tuple/"+tc.input, func(t *testing.T) { 378 if tc.tupleFormat == nil { 379 return 380 } 381 382 serialized := strings.Replace(MustString(tc.tupleFormat), " ", "", -1) 383 require.Equal(t, tc.expectedOutput, serialized) 384 385 withoutCaveat := StringWithoutCaveat(tc.tupleFormat) 386 require.Contains(t, tc.expectedOutput, withoutCaveat) 387 require.NotContains(t, withoutCaveat, "[") 388 }) 389 } 390 391 for _, tc := range testCases { 392 tc := tc 393 t.Run("relationship/"+tc.input, func(t *testing.T) { 394 if tc.relFormat == nil { 395 return 396 } 397 398 serialized := strings.Replace(MustRelString(tc.relFormat), " ", "", -1) 399 require.Equal(t, tc.expectedOutput, serialized) 400 401 withoutCaveat := StringRelationshipWithoutCaveat(tc.relFormat) 402 require.Contains(t, tc.expectedOutput, withoutCaveat) 403 require.NotContains(t, withoutCaveat, "[") 404 }) 405 } 406 } 407 408 func TestParse(t *testing.T) { 409 for _, tc := range testCases { 410 tc := tc 411 t.Run("tuple/"+tc.input, func(t *testing.T) { 412 testutil.RequireProtoEqual(t, tc.tupleFormat, Parse(tc.input), "found difference in parsed tuple") 413 }) 414 } 415 416 for _, tc := range testCases { 417 tc := tc 418 t.Run("relationship/"+tc.input, func(t *testing.T) { 419 testutil.RequireProtoEqual(t, tc.relFormat, ParseRel(tc.input), "found difference in parsed relationship") 420 }) 421 } 422 } 423 424 func TestConvert(t *testing.T) { 425 for _, tc := range testCases { 426 tc := tc 427 t.Run(tc.input, func(t *testing.T) { 428 require := require.New(t) 429 430 parsed := Parse(tc.input) 431 testutil.RequireProtoEqual(t, tc.tupleFormat, parsed, "found difference in parsed tuple") 432 if parsed == nil { 433 return 434 } 435 436 relationship := ToRelationship(parsed) 437 relString := strings.Replace(MustRelString(relationship), " ", "", -1) 438 require.Equal(tc.expectedOutput, relString) 439 440 backToTpl := FromRelationship[*v1.ObjectReference, *v1.SubjectReference, *v1.ContextualizedCaveat](relationship) 441 testutil.RequireProtoEqual(t, tc.tupleFormat, backToTpl, "found difference in converted tuple") 442 443 serialized := strings.Replace(MustString(backToTpl), " ", "", -1) 444 require.Equal(tc.expectedOutput, serialized) 445 }) 446 } 447 } 448 449 func TestValidate(t *testing.T) { 450 for _, tc := range testCases { 451 tc := tc 452 t.Run("validate/"+tc.input, func(t *testing.T) { 453 parsed := ParseRel(tc.input) 454 if parsed != nil { 455 require.NoError(t, ValidateResourceID(parsed.Resource.ObjectId)) 456 require.NoError(t, ValidateSubjectID(parsed.Subject.Object.ObjectId)) 457 } 458 }) 459 } 460 } 461 462 func TestCopyRelationTupleToRelationship(t *testing.T) { 463 testCases := []*core.RelationTuple{ 464 { 465 ResourceAndRelation: &core.ObjectAndRelation{ 466 Namespace: "abc", 467 ObjectId: "def", 468 Relation: "ghi", 469 }, 470 Subject: &core.ObjectAndRelation{ 471 Namespace: "jkl", 472 ObjectId: "mno", 473 Relation: "pqr", 474 }, 475 }, 476 { 477 ResourceAndRelation: &core.ObjectAndRelation{ 478 Namespace: "abc", 479 ObjectId: "def", 480 Relation: "ghi", 481 }, 482 Subject: &core.ObjectAndRelation{ 483 Namespace: "jkl", 484 ObjectId: "mno", 485 Relation: "...", 486 }, 487 }, 488 { 489 ResourceAndRelation: &core.ObjectAndRelation{ 490 Namespace: "abc", 491 ObjectId: "def", 492 Relation: "ghi", 493 }, 494 Subject: &core.ObjectAndRelation{ 495 Namespace: "jkl", 496 ObjectId: "mno", 497 Relation: "pqr", 498 }, 499 Caveat: &core.ContextualizedCaveat{ 500 CaveatName: "stu", 501 Context: &structpb.Struct{}, 502 }, 503 }, 504 { 505 ResourceAndRelation: &core.ObjectAndRelation{ 506 Namespace: "abc", 507 ObjectId: "def", 508 Relation: "ghi", 509 }, 510 Subject: &core.ObjectAndRelation{ 511 Namespace: "jkl", 512 ObjectId: "mno", 513 Relation: "pqr", 514 }, 515 Caveat: &core.ContextualizedCaveat{ 516 CaveatName: "stu", 517 Context: &structpb.Struct{ 518 Fields: map[string]*structpb.Value{ 519 "vwx": { 520 Kind: &structpb.Value_StringValue{ 521 StringValue: "yz", 522 }, 523 }, 524 }, 525 }, 526 }, 527 }, 528 } 529 530 for _, tc := range testCases { 531 t.Run(MustString(tc), func(t *testing.T) { 532 require := require.New(t) 533 534 dst := &v1.Relationship{ 535 Resource: &v1.ObjectReference{}, 536 Subject: &v1.SubjectReference{ 537 Object: &v1.ObjectReference{}, 538 }, 539 } 540 optionalCaveat := &v1.ContextualizedCaveat{} 541 542 CopyRelationTupleToRelationship(tc, dst, optionalCaveat) 543 544 expectedSubjectRelation := tc.Subject.Relation 545 if tc.Subject.Relation == "..." { 546 expectedSubjectRelation = "" 547 } 548 549 require.Equal(tc.ResourceAndRelation.Namespace, dst.Resource.ObjectType) 550 require.Equal(tc.ResourceAndRelation.ObjectId, dst.Resource.ObjectId) 551 require.Equal(tc.ResourceAndRelation.Relation, dst.Relation) 552 require.Equal(tc.Subject.Namespace, dst.Subject.Object.ObjectType) 553 require.Equal(tc.Subject.ObjectId, dst.Subject.Object.ObjectId) 554 require.Equal(expectedSubjectRelation, dst.Subject.OptionalRelation) 555 556 if tc.Caveat != nil { 557 require.Equal(tc.Caveat.CaveatName, dst.OptionalCaveat.CaveatName) 558 require.Equal(tc.Caveat.Context, dst.OptionalCaveat.Context) 559 } else { 560 require.Nil(dst.OptionalCaveat) 561 } 562 }) 563 } 564 } 565 566 func TestEqual(t *testing.T) { 567 equalTestCases := []*core.RelationTuple{ 568 makeTuple( 569 ObjectAndRelation("testns", "testobj", "testrel"), 570 ObjectAndRelation("user", "testusr", "..."), 571 ), 572 MustWithCaveat( 573 makeTuple( 574 ObjectAndRelation("testns", "testobj", "testrel"), 575 ObjectAndRelation("user", "testusr", "..."), 576 ), 577 "somecaveat", 578 map[string]any{ 579 "context": map[string]any{ 580 "deeply": map[string]any{ 581 "nested": true, 582 }, 583 }, 584 }, 585 ), 586 MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":\"there\"}]"), 587 MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":123}}]"), 588 MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":{\"hey\":true}}, \"hi2\":{\"yo2\":{\"hey2\":false}}}]"), 589 MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":{\"hey\":true}}, \"hi2\":{\"yo2\":{\"hey2\":[1,2,3]}}}]"), 590 } 591 592 for _, tc := range equalTestCases { 593 t.Run(MustString(tc), func(t *testing.T) { 594 require := require.New(t) 595 require.True(Equal(tc, tc.CloneVT())) 596 require.True(Equal(tc, MustParse(MustString(tc)))) 597 }) 598 } 599 600 notEqualTestCases := []struct { 601 name string 602 lhs *core.RelationTuple 603 rhs *core.RelationTuple 604 }{ 605 { 606 name: "Mismatch Resource Type", 607 lhs: makeTuple( 608 ObjectAndRelation("testns1", "testobj", "testrel"), 609 ObjectAndRelation("user", "testusr", "..."), 610 ), 611 rhs: makeTuple( 612 ObjectAndRelation("testns2", "testobj", "testrel"), 613 ObjectAndRelation("user", "testusr", "..."), 614 ), 615 }, 616 { 617 name: "Mismatch Resource ID", 618 lhs: makeTuple( 619 ObjectAndRelation("testns", "testobj1", "testrel"), 620 ObjectAndRelation("user", "testusr", "..."), 621 ), 622 rhs: makeTuple( 623 ObjectAndRelation("testns", "testobj2", "testrel"), 624 ObjectAndRelation("user", "testusr", "..."), 625 ), 626 }, 627 { 628 name: "Mismatch Resource Relationship", 629 lhs: makeTuple( 630 ObjectAndRelation("testns", "testobj", "testrel1"), 631 ObjectAndRelation("user", "testusr", "..."), 632 ), 633 rhs: makeTuple( 634 ObjectAndRelation("testns", "testobj", "testrel2"), 635 ObjectAndRelation("user", "testusr", "..."), 636 ), 637 }, 638 { 639 name: "Mismatch Subject Type", 640 lhs: makeTuple( 641 ObjectAndRelation("testns", "testobj", "testrel"), 642 ObjectAndRelation("user1", "testusr", "..."), 643 ), 644 rhs: makeTuple( 645 ObjectAndRelation("testns", "testobj", "testrel"), 646 ObjectAndRelation("user2", "testusr", "..."), 647 ), 648 }, 649 { 650 name: "Mismatch Subject ID", 651 lhs: makeTuple( 652 ObjectAndRelation("testns", "testobj", "testrel"), 653 ObjectAndRelation("user", "testusr1", "..."), 654 ), 655 rhs: makeTuple( 656 ObjectAndRelation("testns", "testobj", "testrel"), 657 ObjectAndRelation("user", "testusr2", "..."), 658 ), 659 }, 660 { 661 name: "Mismatch Subject Relationship", 662 lhs: makeTuple( 663 ObjectAndRelation("testns", "testobj", "testrel"), 664 ObjectAndRelation("user", "testusr", "testrel1"), 665 ), 666 rhs: makeTuple( 667 ObjectAndRelation("testns", "testobj", "testrel"), 668 ObjectAndRelation("user", "testusr", "testrel2"), 669 ), 670 }, 671 { 672 name: "Mismatch Caveat Name", 673 lhs: MustWithCaveat( 674 makeTuple( 675 ObjectAndRelation("testns", "testobj", "testrel"), 676 ObjectAndRelation("user", "testusr", "..."), 677 ), 678 "somecaveat1", 679 map[string]any{ 680 "context": map[string]any{ 681 "deeply": map[string]any{ 682 "nested": true, 683 }, 684 }, 685 }, 686 ), 687 rhs: MustWithCaveat( 688 makeTuple( 689 ObjectAndRelation("testns", "testobj", "testrel"), 690 ObjectAndRelation("user", "testusr", "..."), 691 ), 692 "somecaveat2", 693 map[string]any{ 694 "context": map[string]any{ 695 "deeply": map[string]any{ 696 "nested": true, 697 }, 698 }, 699 }, 700 ), 701 }, 702 { 703 name: "Mismatch Caveat Content", 704 lhs: MustWithCaveat( 705 makeTuple( 706 ObjectAndRelation("testns", "testobj", "testrel"), 707 ObjectAndRelation("user", "testusr", "..."), 708 ), 709 "somecaveat", 710 map[string]any{ 711 "context": map[string]any{ 712 "deeply": map[string]any{ 713 "nested": "1", 714 }, 715 }, 716 }, 717 ), 718 rhs: MustWithCaveat( 719 makeTuple( 720 ObjectAndRelation("testns", "testobj", "testrel"), 721 ObjectAndRelation("user", "testusr", "..."), 722 ), 723 "somecaveat", 724 map[string]any{ 725 "context": map[string]any{ 726 "deeply": map[string]any{ 727 "nested": "2", 728 }, 729 }, 730 }, 731 ), 732 }, 733 { 734 name: "missing caveat context via string", 735 lhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":\"there\"}]"), 736 rhs: MustParse("document:foo#viewer@user:tom[somecaveat]"), 737 }, 738 { 739 name: "mismatch caveat context via string", 740 lhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":\"there\"}]"), 741 rhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":\"there2\"}]"), 742 }, 743 { 744 name: "mismatch caveat name", 745 lhs: MustParse("document:foo#viewer@user:tom[somecaveat]"), 746 rhs: MustParse("document:foo#viewer@user:tom[somecaveat2]"), 747 }, 748 { 749 name: "mismatch caveat context, deeply nested", 750 lhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":123}}]"), 751 rhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":124}}]"), 752 }, 753 { 754 name: "mismatch caveat context, deeply nested with array", 755 lhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":[1,2,3]}}]"), 756 rhs: MustParse("document:foo#viewer@user:tom[somecaveat:{\"hi\":{\"yo\":[1,2,4]}}]"), 757 }, 758 } 759 760 for _, tc := range notEqualTestCases { 761 t.Run(tc.name, func(t *testing.T) { 762 require := require.New(t) 763 require.False(Equal(tc.lhs, tc.rhs)) 764 require.False(Equal(tc.rhs, tc.lhs)) 765 require.False(Equal(tc.lhs, MustParse(MustString(tc.rhs)))) 766 require.False(Equal(tc.rhs, MustParse(MustString(tc.lhs)))) 767 }) 768 } 769 }