github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/dispatch/keys/computed_test.go (about) 1 package keys 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 "google.golang.org/protobuf/types/known/structpb" 11 12 "github.com/authzed/spicedb/pkg/genutil/mapz" 13 core "github.com/authzed/spicedb/pkg/proto/core/v1" 14 v1 "github.com/authzed/spicedb/pkg/proto/dispatch/v1" 15 "github.com/authzed/spicedb/pkg/tuple" 16 ) 17 18 func TestKeyPrefixOverlap(t *testing.T) { 19 encountered := map[string]struct{}{} 20 for _, prefix := range cachePrefixes { 21 _, ok := encountered[string(prefix)] 22 require.False(t, ok) 23 encountered[string(prefix)] = struct{}{} 24 } 25 } 26 27 var ( 28 ONR = tuple.ObjectAndRelation 29 RR = tuple.RelationReference 30 ) 31 32 func TestStableCacheKeys(t *testing.T) { 33 tcs := []struct { 34 name string 35 createKey func() DispatchCacheKey 36 expected string 37 }{ 38 { 39 "basic check", 40 func() DispatchCacheKey { 41 return checkRequestToKey(&v1.DispatchCheckRequest{ 42 ResourceRelation: RR("document", "view"), 43 ResourceIds: []string{"foo", "bar"}, 44 Subject: ONR("user", "tom", "..."), 45 Metadata: &v1.ResolverMeta{ 46 AtRevision: "1234", 47 }, 48 }, computeBothHashes) 49 }, 50 "e09cbca18290f7afae01", 51 }, 52 { 53 "basic check with canonical ordering", 54 func() DispatchCacheKey { 55 return checkRequestToKey(&v1.DispatchCheckRequest{ 56 ResourceRelation: RR("document", "view"), 57 ResourceIds: []string{"bar", "foo"}, 58 Subject: ONR("user", "tom", "..."), 59 Metadata: &v1.ResolverMeta{ 60 AtRevision: "1234", 61 }, 62 }, computeBothHashes) 63 }, 64 "e09cbca18290f7afae01", 65 }, 66 { 67 "different check", 68 func() DispatchCacheKey { 69 return checkRequestToKey(&v1.DispatchCheckRequest{ 70 ResourceRelation: RR("document", "edit"), 71 ResourceIds: []string{"foo"}, 72 Subject: ONR("user", "sarah", "..."), 73 Metadata: &v1.ResolverMeta{ 74 AtRevision: "123456", 75 }, 76 }, computeBothHashes) 77 }, 78 "d586cee091f9e591c301", 79 }, 80 { 81 "canonical check", 82 func() DispatchCacheKey { 83 key, _ := checkRequestToKeyWithCanonical(&v1.DispatchCheckRequest{ 84 ResourceRelation: RR("document", "view"), 85 ResourceIds: []string{"foo", "bar"}, 86 Subject: ONR("user", "tom", "..."), 87 Metadata: &v1.ResolverMeta{ 88 AtRevision: "1234", 89 }, 90 }, "view") 91 return key 92 }, 93 "a1ebd1d6a7a8b18fff01", 94 }, 95 { 96 "expand", 97 func() DispatchCacheKey { 98 return expandRequestToKey(&v1.DispatchExpandRequest{ 99 ResourceAndRelation: ONR("document", "foo", "view"), 100 Metadata: &v1.ResolverMeta{ 101 AtRevision: "1234", 102 }, 103 }, computeBothHashes) 104 }, 105 "8afff68e91a7cbb3ef01", 106 }, 107 { 108 "expand different resource", 109 func() DispatchCacheKey { 110 return expandRequestToKey(&v1.DispatchExpandRequest{ 111 ResourceAndRelation: ONR("document", "foo2", "view"), 112 Metadata: &v1.ResolverMeta{ 113 AtRevision: "1234", 114 }, 115 }, computeBothHashes) 116 }, 117 "9dd1e0c9cba88edc6b", 118 }, 119 { 120 "expand different revision", 121 func() DispatchCacheKey { 122 return expandRequestToKey(&v1.DispatchExpandRequest{ 123 ResourceAndRelation: ONR("document", "foo2", "view"), 124 Metadata: &v1.ResolverMeta{ 125 AtRevision: "1235", 126 }, 127 }, computeBothHashes) 128 }, 129 "f1b396da87bdeae2bd01", 130 }, 131 { 132 "lookup resources", 133 func() DispatchCacheKey { 134 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 135 ObjectRelation: RR("document", "view"), 136 Subject: ONR("user", "mariah", "..."), 137 Metadata: &v1.ResolverMeta{ 138 AtRevision: "1234", 139 }, 140 }, computeBothHashes) 141 }, 142 "b4989ce7d2f695c251", 143 }, 144 { 145 "lookup resources with zero limit", 146 func() DispatchCacheKey { 147 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 148 ObjectRelation: RR("document", "view"), 149 Subject: ONR("user", "mariah", "..."), 150 Metadata: &v1.ResolverMeta{ 151 AtRevision: "1234", 152 }, 153 OptionalLimit: 0, 154 }, computeBothHashes) 155 }, 156 "b4989ce7d2f695c251", 157 }, 158 { 159 "lookup resources with non-zero limit", 160 func() DispatchCacheKey { 161 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 162 ObjectRelation: RR("document", "view"), 163 Subject: ONR("user", "mariah", "..."), 164 Metadata: &v1.ResolverMeta{ 165 AtRevision: "1234", 166 }, 167 OptionalLimit: 42, 168 }, computeBothHashes) 169 }, 170 "a1bcf8c7e581fb9be401", 171 }, 172 { 173 "lookup resources with nil context", 174 func() DispatchCacheKey { 175 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 176 ObjectRelation: RR("document", "view"), 177 Subject: ONR("user", "mariah", "..."), 178 Metadata: &v1.ResolverMeta{ 179 AtRevision: "1234", 180 }, 181 Context: nil, 182 }, computeBothHashes) 183 }, 184 "b4989ce7d2f695c251", 185 }, 186 { 187 "lookup resources with empty context", 188 func() DispatchCacheKey { 189 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 190 ObjectRelation: RR("document", "view"), 191 Subject: ONR("user", "mariah", "..."), 192 Metadata: &v1.ResolverMeta{ 193 AtRevision: "1234", 194 }, 195 Context: func() *structpb.Struct { 196 v, _ := structpb.NewStruct(map[string]any{}) 197 return v 198 }(), 199 }, computeBothHashes) 200 }, 201 "b4989ce7d2f695c251", 202 }, 203 { 204 "lookup resources with context", 205 func() DispatchCacheKey { 206 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 207 ObjectRelation: RR("document", "view"), 208 Subject: ONR("user", "mariah", "..."), 209 Metadata: &v1.ResolverMeta{ 210 AtRevision: "1234", 211 }, 212 Context: func() *structpb.Struct { 213 v, _ := structpb.NewStruct(map[string]any{ 214 "foo": 1, 215 "bar": true, 216 }) 217 return v 218 }(), 219 }, computeBothHashes) 220 }, 221 "ccffc5dde799b4879401", 222 }, 223 { 224 "lookup resources with different context", 225 func() DispatchCacheKey { 226 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 227 ObjectRelation: RR("document", "view"), 228 Subject: ONR("user", "mariah", "..."), 229 Metadata: &v1.ResolverMeta{ 230 AtRevision: "1234", 231 }, 232 Context: func() *structpb.Struct { 233 v, _ := structpb.NewStruct(map[string]any{ 234 "foo": 2, 235 "bar": true, 236 }) 237 return v 238 }(), 239 }, computeBothHashes) 240 }, 241 "dea2e0b3fbdcafdaca01", 242 }, 243 { 244 "lookup resources with escaped string", 245 func() DispatchCacheKey { 246 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 247 ObjectRelation: RR("document", "view"), 248 Subject: ONR("user", "mariah", "..."), 249 Metadata: &v1.ResolverMeta{ 250 AtRevision: "1234", 251 }, 252 Context: func() *structpb.Struct { 253 v, _ := structpb.NewStruct(map[string]any{ 254 "foo": "this is an `escaped` string\nhi", 255 }) 256 return v 257 }(), 258 }, computeBothHashes) 259 }, 260 "949b95adaabcaba6e001", 261 }, 262 { 263 "lookup resources with nested context", 264 func() DispatchCacheKey { 265 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 266 ObjectRelation: RR("document", "view"), 267 Subject: ONR("user", "mariah", "..."), 268 Metadata: &v1.ResolverMeta{ 269 AtRevision: "1234", 270 }, 271 Context: func() *structpb.Struct { 272 v, _ := structpb.NewStruct(map[string]any{ 273 "foo": 1, 274 "bar": map[string]any{ 275 "meh": "hiya", 276 "baz": "yo", 277 }, 278 }) 279 return v 280 }(), 281 }, computeBothHashes) 282 }, 283 "d19a9c9c82d885e13b", 284 }, 285 { 286 "lookup resources with empty cursor", 287 func() DispatchCacheKey { 288 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 289 ObjectRelation: RR("document", "view"), 290 Subject: ONR("user", "mariah", "..."), 291 Metadata: &v1.ResolverMeta{ 292 AtRevision: "1234", 293 }, 294 OptionalCursor: &v1.Cursor{}, 295 }, computeBothHashes) 296 }, 297 "b4989ce7d2f695c251", 298 }, 299 { 300 "lookup resources with non-empty cursor", 301 func() DispatchCacheKey { 302 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 303 ObjectRelation: RR("document", "view"), 304 Subject: ONR("user", "mariah", "..."), 305 Metadata: &v1.ResolverMeta{ 306 AtRevision: "1234", 307 }, 308 OptionalCursor: &v1.Cursor{ 309 Sections: []string{"foo"}, 310 }, 311 }, computeBothHashes) 312 }, 313 "d3899bc2cdb9a2d47f", 314 }, 315 { 316 "lookup resources with different cursor", 317 func() DispatchCacheKey { 318 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 319 ObjectRelation: RR("document", "view"), 320 Subject: ONR("user", "mariah", "..."), 321 Metadata: &v1.ResolverMeta{ 322 AtRevision: "1234", 323 }, 324 OptionalCursor: &v1.Cursor{ 325 Sections: []string{"foo", "bar"}, 326 }, 327 }, computeBothHashes) 328 }, 329 "f7c18ddf8abc8da3d701", 330 }, 331 { 332 "reachable resources", 333 func() DispatchCacheKey { 334 return reachableResourcesRequestToKey(&v1.DispatchReachableResourcesRequest{ 335 ResourceRelation: RR("document", "view"), 336 SubjectRelation: RR("user", "..."), 337 SubjectIds: []string{"mariah", "tom"}, 338 Metadata: &v1.ResolverMeta{ 339 AtRevision: "1234", 340 }, 341 }, computeBothHashes) 342 }, 343 "c0918ce6b3b0efcc3e", 344 }, 345 { 346 "reachable resources with limit", 347 func() DispatchCacheKey { 348 return reachableResourcesRequestToKey(&v1.DispatchReachableResourcesRequest{ 349 ResourceRelation: RR("document", "view"), 350 SubjectRelation: RR("user", "..."), 351 SubjectIds: []string{"mariah", "tom"}, 352 Metadata: &v1.ResolverMeta{ 353 AtRevision: "1234", 354 }, 355 OptionalLimit: 42, 356 }, computeBothHashes) 357 }, 358 "cab5fbaecddc9dbbd501", 359 }, 360 { 361 "reachable resources with cursor", 362 func() DispatchCacheKey { 363 return reachableResourcesRequestToKey(&v1.DispatchReachableResourcesRequest{ 364 ResourceRelation: RR("document", "view"), 365 SubjectRelation: RR("user", "..."), 366 SubjectIds: []string{"mariah", "tom"}, 367 Metadata: &v1.ResolverMeta{ 368 AtRevision: "1234", 369 }, 370 OptionalCursor: &v1.Cursor{ 371 Sections: []string{"foo"}, 372 }, 373 }, computeBothHashes) 374 }, 375 "9a82c4b1abe1cdff68", 376 }, 377 { 378 "reachable resources with different cursor", 379 func() DispatchCacheKey { 380 return reachableResourcesRequestToKey(&v1.DispatchReachableResourcesRequest{ 381 ResourceRelation: RR("document", "view"), 382 SubjectRelation: RR("user", "..."), 383 SubjectIds: []string{"mariah", "tom"}, 384 Metadata: &v1.ResolverMeta{ 385 AtRevision: "1234", 386 }, 387 OptionalCursor: &v1.Cursor{ 388 Sections: []string{"foo", "bar"}, 389 }, 390 }, computeBothHashes) 391 }, 392 "d1acd88b828fce96c701", 393 }, 394 { 395 "lookup subjects", 396 func() DispatchCacheKey { 397 return lookupSubjectsRequestToKey(&v1.DispatchLookupSubjectsRequest{ 398 ResourceRelation: RR("document", "view"), 399 SubjectRelation: RR("user", "..."), 400 ResourceIds: []string{"mariah", "tom"}, 401 Metadata: &v1.ResolverMeta{ 402 AtRevision: "1234", 403 }, 404 }, computeBothHashes) 405 }, 406 "c2b2d3fcb3aa94f5a801", 407 }, 408 { 409 "lookup subjects with default limit", 410 func() DispatchCacheKey { 411 return lookupSubjectsRequestToKey(&v1.DispatchLookupSubjectsRequest{ 412 ResourceRelation: RR("document", "view"), 413 SubjectRelation: RR("user", "..."), 414 ResourceIds: []string{"mariah", "tom"}, 415 Metadata: &v1.ResolverMeta{ 416 AtRevision: "1234", 417 }, 418 OptionalLimit: 0, 419 }, computeBothHashes) 420 }, 421 "c2b2d3fcb3aa94f5a801", 422 }, 423 { 424 "lookup subjects with different limit", 425 func() DispatchCacheKey { 426 return lookupSubjectsRequestToKey(&v1.DispatchLookupSubjectsRequest{ 427 ResourceRelation: RR("document", "view"), 428 SubjectRelation: RR("user", "..."), 429 ResourceIds: []string{"mariah", "tom"}, 430 Metadata: &v1.ResolverMeta{ 431 AtRevision: "1234", 432 }, 433 OptionalLimit: 10, 434 }, computeBothHashes) 435 }, 436 "ca98fbc58abac8983b", 437 }, 438 { 439 "lookup subjects with cursor", 440 func() DispatchCacheKey { 441 return lookupSubjectsRequestToKey(&v1.DispatchLookupSubjectsRequest{ 442 ResourceRelation: RR("document", "view"), 443 SubjectRelation: RR("user", "..."), 444 ResourceIds: []string{"mariah", "tom"}, 445 Metadata: &v1.ResolverMeta{ 446 AtRevision: "1234", 447 }, 448 OptionalCursor: &v1.Cursor{ 449 Sections: []string{"foo", "bar"}, 450 }, 451 }, computeBothHashes) 452 }, 453 "e7d38be4d395cfc3fc01", 454 }, 455 { 456 "lookup subjects with different cursor", 457 func() DispatchCacheKey { 458 return lookupSubjectsRequestToKey(&v1.DispatchLookupSubjectsRequest{ 459 ResourceRelation: RR("document", "view"), 460 SubjectRelation: RR("user", "..."), 461 ResourceIds: []string{"mariah", "tom"}, 462 Metadata: &v1.ResolverMeta{ 463 AtRevision: "1234", 464 }, 465 OptionalCursor: &v1.Cursor{ 466 Sections: []string{"foo", "baz"}, 467 }, 468 }, computeBothHashes) 469 }, 470 "fccbc38e9cdbcc8cf901", 471 }, 472 } 473 474 for _, tc := range tcs { 475 tc := tc 476 t.Run(tc.name, func(t *testing.T) { 477 key := tc.createKey() 478 require.Equal(t, tc.expected, hex.EncodeToString(key.StableSumAsBytes())) 479 }) 480 } 481 } 482 483 type generatorFunc func( 484 resourceIds []string, 485 subjectIds []string, 486 resourceRelation *core.RelationReference, 487 subjectRelation *core.RelationReference, 488 revision *v1.ResolverMeta, 489 ) (DispatchCacheKey, []string) 490 491 var generatorFuncs = map[string]generatorFunc{ 492 // Check. 493 string(checkViaRelationPrefix): func( 494 resourceIds []string, 495 subjectIds []string, 496 resourceRelation *core.RelationReference, 497 subjectRelation *core.RelationReference, 498 metadata *v1.ResolverMeta, 499 ) (DispatchCacheKey, []string) { 500 return checkRequestToKey(&v1.DispatchCheckRequest{ 501 ResourceRelation: resourceRelation, 502 ResourceIds: resourceIds, 503 Subject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), 504 Metadata: metadata, 505 }, computeBothHashes), []string{ 506 resourceRelation.Namespace, 507 resourceRelation.Relation, 508 subjectRelation.Namespace, 509 subjectIds[0], 510 subjectRelation.Relation, 511 } 512 }, 513 514 // Canonical Check. 515 string(checkViaCanonicalPrefix): func( 516 resourceIds []string, 517 subjectIds []string, 518 resourceRelation *core.RelationReference, 519 subjectRelation *core.RelationReference, 520 metadata *v1.ResolverMeta, 521 ) (DispatchCacheKey, []string) { 522 key, _ := checkRequestToKeyWithCanonical(&v1.DispatchCheckRequest{ 523 ResourceRelation: resourceRelation, 524 ResourceIds: resourceIds, 525 Subject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), 526 Metadata: metadata, 527 }, resourceRelation.Relation) 528 return key, append([]string{ 529 resourceRelation.Namespace, 530 resourceRelation.Relation, 531 subjectRelation.Namespace, 532 subjectIds[0], 533 subjectRelation.Relation, 534 }, resourceIds...) 535 }, 536 537 // Lookup Resources. 538 string(lookupPrefix): func( 539 resourceIds []string, 540 subjectIds []string, 541 resourceRelation *core.RelationReference, 542 subjectRelation *core.RelationReference, 543 metadata *v1.ResolverMeta, 544 ) (DispatchCacheKey, []string) { 545 return lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 546 ObjectRelation: resourceRelation, 547 Subject: ONR(subjectRelation.Namespace, subjectIds[0], subjectRelation.Relation), 548 Metadata: metadata, 549 }, computeBothHashes), []string{ 550 resourceRelation.Namespace, 551 resourceRelation.Relation, 552 subjectRelation.Namespace, 553 subjectIds[0], 554 subjectRelation.Relation, 555 } 556 }, 557 558 // Expand. 559 string(expandPrefix): func( 560 resourceIds []string, 561 subjectIds []string, 562 resourceRelation *core.RelationReference, 563 subjectRelation *core.RelationReference, 564 metadata *v1.ResolverMeta, 565 ) (DispatchCacheKey, []string) { 566 return expandRequestToKey(&v1.DispatchExpandRequest{ 567 ResourceAndRelation: ONR(resourceRelation.Namespace, resourceIds[0], resourceRelation.Relation), 568 Metadata: metadata, 569 }, computeBothHashes), []string{ 570 resourceRelation.Namespace, 571 resourceIds[0], 572 resourceRelation.Relation, 573 } 574 }, 575 576 // Reachable Resources. 577 string(reachableResourcesPrefix): func( 578 resourceIds []string, 579 subjectIds []string, 580 resourceRelation *core.RelationReference, 581 subjectRelation *core.RelationReference, 582 metadata *v1.ResolverMeta, 583 ) (DispatchCacheKey, []string) { 584 return reachableResourcesRequestToKey(&v1.DispatchReachableResourcesRequest{ 585 ResourceRelation: resourceRelation, 586 SubjectRelation: subjectRelation, 587 SubjectIds: subjectIds, 588 Metadata: metadata, 589 }, computeBothHashes), append([]string{ 590 resourceRelation.Namespace, 591 resourceRelation.Relation, 592 subjectRelation.Namespace, 593 subjectRelation.Relation, 594 }, subjectIds...) 595 }, 596 597 // Lookup Subjects. 598 string(lookupSubjectsPrefix): func( 599 resourceIds []string, 600 subjectIds []string, 601 resourceRelation *core.RelationReference, 602 subjectRelation *core.RelationReference, 603 metadata *v1.ResolverMeta, 604 ) (DispatchCacheKey, []string) { 605 return lookupSubjectsRequestToKey(&v1.DispatchLookupSubjectsRequest{ 606 ResourceRelation: resourceRelation, 607 SubjectRelation: subjectRelation, 608 ResourceIds: resourceIds, 609 Metadata: metadata, 610 }, computeBothHashes), append([]string{ 611 resourceRelation.Namespace, 612 resourceRelation.Relation, 613 subjectRelation.Namespace, 614 subjectRelation.Relation, 615 }, resourceIds...) 616 }, 617 } 618 619 func TestCacheKeyNoOverlap(t *testing.T) { 620 allResourceIds := [][]string{ 621 {"1"}, 622 {"1", "2"}, 623 {"1", "2", "3"}, 624 {"hi"}, 625 } 626 627 allSubjectIds := [][]string{ 628 {"tom"}, 629 {"mariah", "tom"}, 630 {"sarah", "mariah", "tom"}, 631 } 632 633 resourceRelations := []*core.RelationReference{ 634 RR("document", "view"), 635 RR("document", "viewer"), 636 RR("document", "edit"), 637 RR("folder", "view"), 638 } 639 640 subjectRelations := []*core.RelationReference{ 641 RR("user", "..."), 642 RR("user", "token"), 643 RR("folder", "parent"), 644 RR("group", "member"), 645 } 646 647 revisions := []string{"1234", "4567", "1235"} 648 649 dataCombinationSeen := mapz.NewSet[string]() 650 stableCacheKeysSeen := mapz.NewSet[string]() 651 unstableCacheKeysSeen := mapz.NewSet[uint64]() 652 653 // Ensure all key functions are generated. 654 require.Equal(t, len(generatorFuncs), len(cachePrefixes)) 655 656 for _, resourceIds := range allResourceIds { 657 resourceIds := resourceIds 658 t.Run(strings.Join(resourceIds, ","), func(t *testing.T) { 659 for _, subjectIds := range allSubjectIds { 660 subjectIds := subjectIds 661 t.Run(strings.Join(subjectIds, ","), func(t *testing.T) { 662 for _, resourceRelation := range resourceRelations { 663 resourceRelation := resourceRelation 664 t.Run(tuple.StringRR(resourceRelation), func(t *testing.T) { 665 for _, subjectRelation := range subjectRelations { 666 subjectRelation := subjectRelation 667 t.Run(tuple.StringRR(subjectRelation), func(t *testing.T) { 668 for _, revision := range revisions { 669 revision := revision 670 t.Run(revision, func(t *testing.T) { 671 metadata := &v1.ResolverMeta{ 672 AtRevision: revision, 673 } 674 675 for prefix, f := range generatorFuncs { 676 prefix := prefix 677 f := f 678 t.Run(prefix, func(t *testing.T) { 679 generated, usedData := f(resourceIds, subjectIds, resourceRelation, subjectRelation, metadata) 680 usedDataString := fmt.Sprintf("%s:%s", prefix, strings.Join(usedData, ",")) 681 if dataCombinationSeen.Add(usedDataString) { 682 require.True(t, stableCacheKeysSeen.Add(hex.EncodeToString((generated.StableSumAsBytes())))) 683 require.True(t, unstableCacheKeysSeen.Add(generated.processSpecificSum)) 684 } 685 }) 686 } 687 }) 688 } 689 }) 690 } 691 }) 692 } 693 }) 694 } 695 }) 696 } 697 } 698 699 func TestComputeOnlyStableHash(t *testing.T) { 700 result := checkRequestToKey(&v1.DispatchCheckRequest{ 701 ResourceRelation: RR("document", "view"), 702 ResourceIds: []string{"foo", "bar"}, 703 Subject: ONR("user", "tom", "..."), 704 Metadata: &v1.ResolverMeta{ 705 AtRevision: "1234", 706 }, 707 }, computeOnlyStableHash) 708 709 require.Equal(t, uint64(0), result.processSpecificSum) 710 } 711 712 func TestComputeContextHash(t *testing.T) { 713 result := lookupResourcesRequestToKey(&v1.DispatchLookupResourcesRequest{ 714 ObjectRelation: RR("document", "view"), 715 Subject: ONR("user", "mariah", "..."), 716 Metadata: &v1.ResolverMeta{ 717 AtRevision: "1234", 718 }, 719 Context: func() *structpb.Struct { 720 v, _ := structpb.NewStruct(map[string]any{ 721 "null": nil, 722 "list": []any{ 723 1, true, "3", 724 }, 725 "nested": map[string]any{ 726 "a": "hi", 727 "b": "hello", 728 "c": 123, 729 }, 730 }) 731 return v 732 }(), 733 }, computeBothHashes) 734 735 require.Equal(t, "a4eacff68ec68bca62", hex.EncodeToString(result.StableSumAsBytes())) 736 }