github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/development/wasm/operations_test.go (about) 1 //go:build wasm 2 // +build wasm 3 4 package main 5 6 import ( 7 "testing" 8 9 "github.com/stretchr/testify/require" 10 "google.golang.org/protobuf/types/known/structpb" 11 12 core "github.com/authzed/spicedb/pkg/proto/core/v1" 13 devinterface "github.com/authzed/spicedb/pkg/proto/developer/v1" 14 "github.com/authzed/spicedb/pkg/testutil" 15 "github.com/authzed/spicedb/pkg/tuple" 16 ) 17 18 type editCheckResult struct { 19 Relationship *core.RelationTuple 20 IsMember bool 21 Error *devinterface.DeveloperError 22 IsConditional bool 23 } 24 25 func TestCheckOperation(t *testing.T) { 26 type testCase struct { 27 name string 28 schema string 29 relationships []*core.RelationTuple 30 checkRelationship *core.RelationTuple 31 caveatContext map[string]any 32 expectedError *devinterface.DeveloperError 33 expectedResult *editCheckResult 34 } 35 36 tests := []testCase{ 37 { 38 "invalid keyword", 39 `def foo { 40 relation bar: 41 }`, 42 []*core.RelationTuple{}, 43 tuple.MustParse("somenamespace:someobj#anotherrel@user:foo"), 44 nil, 45 &devinterface.DeveloperError{ 46 Message: "Unexpected token at root level: TokenTypeIdentifier", 47 Kind: devinterface.DeveloperError_SCHEMA_ISSUE, 48 Source: devinterface.DeveloperError_SCHEMA, 49 Line: 1, 50 Column: 1, 51 Context: "def", 52 }, 53 nil, 54 }, 55 { 56 "invalid namespace", 57 `definition foo { 58 relation bar: 59 }`, 60 []*core.RelationTuple{}, 61 tuple.MustParse("somenamespace:someobj#anotherrel@user:foo"), 62 nil, 63 &devinterface.DeveloperError{ 64 Message: "Expected identifier, found token TokenTypeRightBrace", 65 Kind: devinterface.DeveloperError_SCHEMA_ISSUE, 66 Source: devinterface.DeveloperError_SCHEMA, 67 Line: 3, 68 Column: 4, 69 Context: "}", 70 }, 71 nil, 72 }, 73 { 74 "invalid namespace name", 75 `definition fo {}`, 76 []*core.RelationTuple{}, 77 tuple.MustParse("somenamespace:someobj#anotherrel@user:foo"), 78 nil, 79 &devinterface.DeveloperError{ 80 Message: "error in object definition fo: invalid NamespaceDefinition.Name: value does not match regex pattern \"^([a-z][a-z0-9_]{1,62}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$\"", 81 Kind: devinterface.DeveloperError_SCHEMA_ISSUE, 82 Source: devinterface.DeveloperError_SCHEMA, 83 Line: 1, 84 Column: 1, 85 }, 86 nil, 87 }, 88 { 89 "invalid shared name", 90 `definition user {} 91 92 definition resource { 93 relation writer: user 94 permission writer = writer 95 }`, 96 []*core.RelationTuple{}, 97 tuple.MustParse("somenamespace:someobj#anotherrel@user:foo"), 98 nil, 99 &devinterface.DeveloperError{ 100 Message: "found duplicate relation/permission name `writer` under definition `resource`", 101 Kind: devinterface.DeveloperError_SCHEMA_ISSUE, 102 Source: devinterface.DeveloperError_SCHEMA, 103 Line: 5, 104 Column: 6, 105 Context: "writer", 106 }, 107 nil, 108 }, 109 { 110 "invalid check", 111 ` 112 definition user {} 113 definition somenamespace { 114 relation somerel: user 115 } 116 `, 117 []*core.RelationTuple{ 118 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 119 }, 120 tuple.MustParse("somenamespace:someobj#anotherrel@user:foo"), 121 nil, 122 nil, 123 &editCheckResult{ 124 Relationship: tuple.MustParse("somenamespace:someobj#anotherrel@user:foo"), 125 Error: &devinterface.DeveloperError{ 126 Message: "relation/permission `anotherrel` not found under definition `somenamespace`", 127 Kind: devinterface.DeveloperError_UNKNOWN_RELATION, 128 Source: devinterface.DeveloperError_CHECK_WATCH, 129 Context: "somenamespace:someobj#anotherrel@user:foo", 130 }, 131 }, 132 }, 133 { 134 "valid check", 135 ` 136 definition user {} 137 definition somenamespace { 138 relation somerel: user 139 } 140 `, 141 []*core.RelationTuple{ 142 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 143 }, 144 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 145 nil, 146 nil, 147 &editCheckResult{ 148 Relationship: tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 149 IsMember: true, 150 }, 151 }, 152 { 153 "valid negative check", 154 ` 155 definition user {} 156 definition somenamespace { 157 relation somerel: user 158 } 159 `, 160 []*core.RelationTuple{ 161 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 162 }, 163 tuple.MustParse("somenamespace:someobj#somerel@user:bar"), 164 nil, 165 nil, 166 &editCheckResult{ 167 Relationship: tuple.MustParse("somenamespace:someobj#somerel@user:bar"), 168 IsMember: false, 169 }, 170 }, 171 { 172 "valid wildcard check", 173 ` 174 definition user {} 175 definition somenamespace { 176 relation somerel: user | user:* 177 } 178 `, 179 []*core.RelationTuple{ 180 tuple.MustParse("somenamespace:someobj#somerel@user:*"), 181 }, 182 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 183 nil, 184 nil, 185 &editCheckResult{ 186 Relationship: tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 187 IsMember: true, 188 }, 189 }, 190 { 191 "valid nil check", 192 ` 193 definition user {} 194 definition somenamespace { 195 permission empty = nil 196 } 197 `, 198 []*core.RelationTuple{}, 199 tuple.MustParse("somenamespace:someobj#empty@user:foo"), 200 nil, 201 nil, 202 &editCheckResult{ 203 Relationship: tuple.MustParse("somenamespace:someobj#empty@user:foo"), 204 IsMember: false, 205 }, 206 }, 207 { 208 "recursive check", 209 ` 210 definition user {} 211 definition document { 212 relation viewer: user | document#viewer 213 } 214 `, 215 []*core.RelationTuple{ 216 tuple.MustParse("document:someobj#viewer@document:someobj#viewer"), 217 }, 218 tuple.MustParse("document:someobj#viewer@user:foo"), 219 nil, 220 nil, 221 &editCheckResult{ 222 Relationship: tuple.MustParse("document:someobj#viewer@user:foo"), 223 Error: &devinterface.DeveloperError{ 224 Message: "max depth exceeded: this usually indicates a recursive or too deep data dependency", 225 Kind: devinterface.DeveloperError_MAXIMUM_RECURSION, 226 Source: devinterface.DeveloperError_CHECK_WATCH, 227 Context: "document:someobj#viewer@user:foo", 228 }, 229 }, 230 }, 231 { 232 "valid caveated check to negative", 233 ` 234 caveat somecaveat(somecondition int) { 235 somecondition == 42 236 } 237 238 definition user {} 239 definition somenamespace { 240 relation somerel: user with somecaveat 241 } 242 `, 243 []*core.RelationTuple{ 244 tuple.MustParse("somenamespace:someobj#somerel@user:foo[somecaveat]"), 245 }, 246 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 247 map[string]any{"somecondition": 41}, 248 nil, 249 &editCheckResult{ 250 Relationship: tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 251 IsMember: false, 252 }, 253 }, 254 { 255 "valid caveated check to positive", 256 ` 257 caveat somecaveat(somecondition int) { 258 somecondition == 42 259 } 260 261 definition user {} 262 definition somenamespace { 263 relation somerel: user with somecaveat 264 } 265 `, 266 []*core.RelationTuple{ 267 tuple.MustParse("somenamespace:someobj#somerel@user:foo[somecaveat]"), 268 }, 269 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 270 map[string]any{"somecondition": 42}, 271 nil, 272 &editCheckResult{ 273 Relationship: tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 274 IsMember: true, 275 }, 276 }, 277 { 278 "valid caveated check to conditional", 279 ` 280 caveat somecaveat(somecondition int) { 281 somecondition == 42 282 } 283 284 definition user {} 285 definition somenamespace { 286 relation somerel: user with somecaveat 287 } 288 `, 289 []*core.RelationTuple{ 290 tuple.MustParse("somenamespace:someobj#somerel@user:foo[somecaveat]"), 291 }, 292 tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 293 map[string]any{"anothercondition": 42}, 294 nil, 295 &editCheckResult{ 296 Relationship: tuple.MustParse("somenamespace:someobj#somerel@user:foo"), 297 IsMember: false, 298 IsConditional: true, 299 }, 300 }, 301 { 302 "invalid relationship subject type", 303 `definition user {} 304 305 definition resource { 306 relation viewer: user 307 permission view = viewer 308 }`, 309 []*core.RelationTuple{tuple.MustParse("resource:someobj#viewer@resource:foo")}, 310 tuple.MustParse("resource:someobj#view@user:foo"), 311 nil, 312 &devinterface.DeveloperError{ 313 Message: "subjects of type `resource` are not allowed on relation `resource#viewer`", 314 Kind: devinterface.DeveloperError_INVALID_SUBJECT_TYPE, 315 Source: devinterface.DeveloperError_RELATIONSHIP, 316 Context: "resource:someobj#viewer@resource:foo", 317 }, 318 nil, 319 }, 320 { 321 "invalid relationship with caveated subject type", 322 `definition user {} 323 324 caveat somecaveat(somecondition int) { 325 somecondition == 42 326 } 327 328 definition resource { 329 relation viewer: user with somecaveat 330 permission view = viewer 331 }`, 332 []*core.RelationTuple{tuple.MustParse("resource:someobj#viewer@user:foo")}, 333 tuple.MustParse("resource:someobj#view@user:foo"), 334 nil, 335 &devinterface.DeveloperError{ 336 Message: "subjects of type `user` are not allowed on relation `resource#viewer`", 337 Kind: devinterface.DeveloperError_INVALID_SUBJECT_TYPE, 338 Source: devinterface.DeveloperError_RELATIONSHIP, 339 Context: "resource:someobj#viewer@user:foo", 340 }, 341 nil, 342 }, 343 { 344 "valid extended ID", 345 ` 346 definition user {} 347 definition somenamespace { 348 permission empty = nil 349 } 350 `, 351 []*core.RelationTuple{}, 352 tuple.MustParse("somenamespace:--=base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==#empty@user:veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylong"), 353 nil, 354 nil, 355 &editCheckResult{ 356 Relationship: tuple.MustParse("somenamespace:--=base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==#empty@user:veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylong"), 357 IsMember: false, 358 }, 359 }, 360 } 361 362 for _, tc := range tests { 363 t.Run(tc.name, func(t *testing.T) { 364 var caveatContext *structpb.Struct 365 if len(tc.caveatContext) > 0 { 366 cc, err := structpb.NewStruct(tc.caveatContext) 367 require.NoError(t, err) 368 caveatContext = cc 369 } 370 371 response := run(t, &devinterface.DeveloperRequest{ 372 Context: &devinterface.RequestContext{ 373 Schema: tc.schema, 374 Relationships: tc.relationships, 375 }, 376 Operations: []*devinterface.Operation{ 377 { 378 CheckParameters: &devinterface.CheckOperationParameters{ 379 Resource: tc.checkRelationship.ResourceAndRelation, 380 Subject: tc.checkRelationship.Subject, 381 CaveatContext: caveatContext, 382 }, 383 }, 384 }, 385 }) 386 387 if tc.expectedError != nil { 388 require.NotNil(t, response.GetDeveloperErrors()) 389 require.Equal(t, 1, len(response.GetDeveloperErrors().InputErrors)) 390 testutil.RequireProtoEqual(t, tc.expectedError, response.GetDeveloperErrors().InputErrors[0], "found mismatching error") 391 } else { 392 require.Equal(t, "", response.GetInternalError()) 393 require.Nil(t, response.GetDeveloperErrors()) 394 395 checkResult := response.GetOperationsResults().Results[0].GetCheckResult() 396 require.NotNil(t, checkResult) 397 398 if tc.expectedResult.Error != nil { 399 testutil.RequireProtoEqual(t, tc.expectedResult.Error, checkResult.CheckError, "found mismatching error") 400 } else { 401 require.Nil(t, checkResult.CheckError) 402 require.Equal(t, tc.expectedResult.IsMember, checkResult.Membership == devinterface.CheckOperationsResult_MEMBER) 403 require.Equal(t, tc.expectedResult.IsConditional, checkResult.Membership == devinterface.CheckOperationsResult_CAVEATED_MEMBER) 404 } 405 } 406 }) 407 } 408 } 409 410 func TestFormatSchemaOperation(t *testing.T) { 411 require := require.New(t) 412 response := run(t, &devinterface.DeveloperRequest{ 413 Context: &devinterface.RequestContext{ 414 Schema: "/** hi there */definition foos {} definition bars{}", 415 }, 416 Operations: []*devinterface.Operation{ 417 { 418 FormatSchemaParameters: &devinterface.FormatSchemaParameters{}, 419 }, 420 }, 421 }) 422 423 formatResult := response.GetOperationsResults().Results[0].GetFormatSchemaResult() 424 require.Equal("/** hi there */\ndefinition foos {}\n\ndefinition bars {}", formatResult.FormattedSchema) 425 } 426 427 func TestRunAssertionsAndValidationOperations(t *testing.T) { 428 type testCase struct { 429 name string 430 schema string 431 relationships []*core.RelationTuple 432 validationYaml string 433 assertionsYaml string 434 expectedError *devinterface.DeveloperError 435 expectCheckTraces bool 436 expectedValidationYaml string 437 } 438 439 tests := []testCase{ 440 { 441 "valid namespace", 442 `definition somenamespace {}`, 443 []*core.RelationTuple{}, 444 "", 445 "", 446 nil, 447 false, 448 "{}\n", 449 }, 450 { 451 "invalid validation yaml", 452 `definition somenamespace {}`, 453 []*core.RelationTuple{}, 454 `asdkjhgasd`, 455 "", 456 &devinterface.DeveloperError{ 457 Message: "unexpected value `asdkjhg`", 458 Kind: devinterface.DeveloperError_PARSE_ERROR, 459 Source: devinterface.DeveloperError_VALIDATION_YAML, 460 Context: "asdkjhg", 461 Line: 1, 462 }, 463 false, 464 "", 465 }, 466 { 467 "invalid assertions yaml", 468 `definition somenamespace {}`, 469 []*core.RelationTuple{}, 470 "", 471 `asdhasjdkhjasd`, 472 &devinterface.DeveloperError{ 473 Message: "unexpected value `asdhasj`", 474 Kind: devinterface.DeveloperError_PARSE_ERROR, 475 Source: devinterface.DeveloperError_ASSERTION, 476 Context: "asdhasj", 477 Line: 1, 478 }, 479 false, 480 "", 481 }, 482 { 483 "assertions yaml with garbage", 484 `definition somenamespace {}`, 485 []*core.RelationTuple{}, 486 "", 487 `assertTrue: 488 - document:firstdoc#view@user:tom 489 - document:firstdoc#view@user:fred 490 - document:seconddoc#view@user:tom 491 assertFalse: garbage 492 - document:seconddoc#view@user:fred`, 493 &devinterface.DeveloperError{ 494 Message: "did not find expected key", 495 Kind: devinterface.DeveloperError_PARSE_ERROR, 496 Source: devinterface.DeveloperError_ASSERTION, 497 Line: 5, 498 }, 499 false, 500 "", 501 }, 502 { 503 "assertions yaml with indented garbage", 504 `definition somenamespace {}`, 505 []*core.RelationTuple{}, 506 "", 507 `assertTrue: 508 - document:firstdoc#view@user:tom 509 - document:firstdoc#view@user:fred 510 - document:seconddoc#view@user:tom 511 assertFalse: garbage 512 - document:seconddoc#view@user:fred`, 513 &devinterface.DeveloperError{ 514 Message: "unexpected value `garbage`", 515 Kind: devinterface.DeveloperError_PARSE_ERROR, 516 Source: devinterface.DeveloperError_ASSERTION, 517 Line: 5, 518 Column: 0, 519 Context: "garbage", 520 }, 521 false, 522 "", 523 }, 524 { 525 "invalid assertions true yaml", 526 `definition somenamespace {}`, 527 []*core.RelationTuple{}, 528 "", 529 `assertTrue: 530 - something`, 531 &devinterface.DeveloperError{ 532 Message: "error parsing relationship in assertion `something`", 533 Kind: devinterface.DeveloperError_PARSE_ERROR, 534 Source: devinterface.DeveloperError_ASSERTION, 535 Line: 2, 536 Column: 3, 537 Context: "something", 538 }, 539 false, 540 "", 541 }, 542 { 543 "assertion true failure", 544 ` 545 definition user {} 546 definition document { 547 relation viewer: user 548 } 549 `, 550 []*core.RelationTuple{tuple.MustParse("document:somedoc#viewer@user:jimmy")}, 551 "", 552 `assertTrue: 553 - document:somedoc#viewer@user:jake`, 554 &devinterface.DeveloperError{ 555 Message: "Expected relation or permission document:somedoc#viewer@user:jake to exist", 556 Kind: devinterface.DeveloperError_ASSERTION_FAILED, 557 Source: devinterface.DeveloperError_ASSERTION, 558 Context: "document:somedoc#viewer@user:jake", 559 Line: 2, 560 Column: 3, 561 }, 562 true, 563 "{}\n", 564 }, 565 { 566 "assertion false failure", 567 ` 568 definition user {} 569 definition document { 570 relation viewer: user 571 } 572 `, 573 []*core.RelationTuple{tuple.MustParse("document:somedoc#viewer@user:jimmy")}, 574 "", 575 `assertFalse: 576 - document:somedoc#viewer@user:jimmy`, 577 &devinterface.DeveloperError{ 578 Message: "Expected relation or permission document:somedoc#viewer@user:jimmy to not exist", 579 Kind: devinterface.DeveloperError_ASSERTION_FAILED, 580 Source: devinterface.DeveloperError_ASSERTION, 581 Context: "document:somedoc#viewer@user:jimmy", 582 Line: 2, 583 Column: 3, 584 }, 585 true, 586 "{}\n", 587 }, 588 { 589 "assertion invalid caveated relation", 590 ` 591 definition user {} 592 definition document {} 593 `, 594 []*core.RelationTuple{}, 595 "", 596 `assertFalse: 597 - document:somedoc#viewer@user:jimmy[somecaveat]`, 598 &devinterface.DeveloperError{ 599 Message: "cannot specify a caveat on an assertion: `document:somedoc#viewer@user:jimmy[somecaveat]`", 600 Kind: devinterface.DeveloperError_UNKNOWN_RELATION, 601 Source: devinterface.DeveloperError_ASSERTION, 602 Context: "document:somedoc#viewer@user:jimmy[somecaveat]", 603 Line: 2, 604 Column: 3, 605 }, 606 false, 607 "{}\n", 608 }, 609 { 610 "assertion invalid relation", 611 ` 612 definition user {} 613 definition document {} 614 `, 615 []*core.RelationTuple{}, 616 "", 617 `assertFalse: 618 - document:somedoc#viewer@user:jimmy`, 619 &devinterface.DeveloperError{ 620 Message: "relation/permission `viewer` not found under definition `document`", 621 Kind: devinterface.DeveloperError_UNKNOWN_RELATION, 622 Source: devinterface.DeveloperError_ASSERTION, 623 Context: "document:somedoc#viewer@user:jimmy", 624 Line: 2, 625 Column: 3, 626 }, 627 false, 628 "{}\n", 629 }, 630 { 631 "missing subject", 632 ` 633 definition user {} 634 definition document { 635 relation writer: user 636 relation viewer: user 637 permission view = viewer + writer 638 } 639 `, 640 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 641 `"document:somedoc#view":`, 642 `assertTrue: 643 - document:somedoc#view@user:jimmy`, 644 &devinterface.DeveloperError{ 645 Message: "For object and permission/relation `document:somedoc#view`, subject `user:jimmy` found but missing from specified", 646 Kind: devinterface.DeveloperError_EXTRA_RELATIONSHIP_FOUND, 647 Source: devinterface.DeveloperError_VALIDATION_YAML, 648 Context: "document:somedoc#view", 649 Line: 1, 650 Column: 1, 651 }, 652 false, 653 `document:somedoc#view: 654 - '[user:jimmy] is <document:somedoc#writer>' 655 `, 656 }, 657 { 658 "extra subject", 659 ` 660 definition user {} 661 definition document { 662 relation writer: user 663 relation viewer: user 664 permission view = viewer + writer 665 } 666 `, 667 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 668 `"document:somedoc#view": 669 - "[user:jimmy] is <document:somedoc#writer>" 670 - "[user:jake] is <document:somedoc#viewer>"`, 671 `assertTrue: 672 - document:somedoc#view@user:jimmy`, 673 &devinterface.DeveloperError{ 674 Message: "For object and permission/relation `document:somedoc#view`, missing expected subject `user:jake`", 675 Kind: devinterface.DeveloperError_MISSING_EXPECTED_RELATIONSHIP, 676 Source: devinterface.DeveloperError_VALIDATION_YAML, 677 Context: "[user:jake] is <document:somedoc#viewer>", 678 Line: 3, 679 Column: 3, 680 }, 681 false, 682 `document:somedoc#view: 683 - '[user:jimmy] is <document:somedoc#writer>' 684 `, 685 }, 686 { 687 "parse error in validation", 688 ` 689 definition user {} 690 definition document { 691 relation writer: user 692 relation viewer: user 693 permission view = viewer + writer 694 } 695 `, 696 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 697 `"document:somedoc#view": 698 - "[user] is <document:somedoc#writer>"`, 699 `assertTrue: 700 - document:somedoc#view@user:jimmy`, 701 &devinterface.DeveloperError{ 702 Message: "invalid subject: `user`", 703 Kind: devinterface.DeveloperError_PARSE_ERROR, 704 Source: devinterface.DeveloperError_VALIDATION_YAML, 705 Context: "user", 706 Line: 2, 707 Column: 3, 708 }, 709 false, 710 ``, 711 }, 712 { 713 "parse error in validation relationships", 714 ` 715 definition user {} 716 definition document { 717 relation writer: user 718 relation viewer: user 719 permission view = viewer + writer 720 } 721 `, 722 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 723 `"document:somedoc#view": 724 - "[user:jimmy] is <document:som>"`, 725 `assertTrue: 726 - document:somedoc#view@user:jimmy`, 727 &devinterface.DeveloperError{ 728 Message: "invalid resource and relation: `document:som`", 729 Kind: devinterface.DeveloperError_PARSE_ERROR, 730 Source: devinterface.DeveloperError_VALIDATION_YAML, 731 Context: "document:som", 732 Line: 2, 733 Column: 3, 734 }, 735 false, 736 ``, 737 }, 738 { 739 "different relations", 740 ` 741 definition user {} 742 definition document { 743 relation writer: user 744 relation viewer: user 745 permission view = viewer + writer 746 } 747 `, 748 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 749 `"document:somedoc#view": 750 - "[user:jimmy] is <document:somedoc#viewer>"`, 751 `assertTrue: 752 - document:somedoc#view@user:jimmy`, 753 &devinterface.DeveloperError{ 754 Message: "For object and permission/relation `document:somedoc#view`, found different relationships for subject `user:jimmy`: Specified: `<document:somedoc#viewer>`, Computed: `<document:somedoc#writer>`", 755 Kind: devinterface.DeveloperError_MISSING_EXPECTED_RELATIONSHIP, 756 Source: devinterface.DeveloperError_VALIDATION_YAML, 757 Context: `[user:jimmy] is <document:somedoc#viewer>`, 758 Line: 2, 759 Column: 3, 760 }, 761 false, 762 `document:somedoc#view: 763 - '[user:jimmy] is <document:somedoc#writer>' 764 `, 765 }, 766 { 767 "full valid", 768 ` 769 definition user {} 770 771 caveat testcaveat(somecondition int) { 772 somecondition == 42 773 } 774 775 definition document { 776 relation writer: user 777 relation viewer: user | user with testcaveat 778 permission view = viewer + writer 779 } 780 `, 781 []*core.RelationTuple{ 782 tuple.MustParse("document:somedoc#writer@user:jimmy"), 783 tuple.MustParse("document:somedoc#viewer@user:jake"), 784 tuple.MustParse("document:somedoc#viewer@user:sarah[testcaveat]"), 785 tuple.MustParse(`document:somedoc#viewer@user:tom[testcaveat:{"somecondition": 42}]`), 786 tuple.MustParse(`document:somedoc#viewer@user:fred[testcaveat:{"somecondition": 53}]`), 787 }, 788 `"document:somedoc#view": 789 - '[user:fred[...]] is <document:somedoc#viewer>' 790 - '[user:jake] is <document:somedoc#viewer>' 791 - '[user:jimmy] is <document:somedoc#writer>' 792 - '[user:sarah[...]] is <document:somedoc#viewer>' 793 - '[user:tom[...]] is <document:somedoc#viewer>' 794 `, 795 `assertTrue: 796 - document:somedoc#writer@user:jimmy 797 - document:somedoc#view@user:jimmy 798 - document:somedoc#viewer@user:jake 799 - document:somedoc#viewer@user:tom 800 - 'document:somedoc#viewer@user:sarah with {"somecondition": "42"}' 801 assertCaveated: 802 - document:somedoc#viewer@user:sarah 803 assertFalse: 804 - document:somedoc#writer@user:sarah 805 - document:somedoc#writer@user:jake 806 - document:somedoc#viewer@user:fred 807 - 'document:somedoc#viewer@user:sarah with {"somecondition": "45"}' 808 `, 809 nil, 810 false, 811 `document:somedoc#view: 812 - '[user:fred[...]] is <document:somedoc#viewer>' 813 - '[user:jake] is <document:somedoc#viewer>' 814 - '[user:jimmy] is <document:somedoc#writer>' 815 - '[user:sarah[...]] is <document:somedoc#viewer>' 816 - '[user:tom[...]] is <document:somedoc#viewer>' 817 `, 818 }, 819 { 820 "multipath", 821 ` 822 definition user {} 823 definition document { 824 relation writer: user 825 relation viewer: user 826 permission view = viewer + writer 827 } 828 `, 829 []*core.RelationTuple{ 830 tuple.MustParse("document:somedoc#writer@user:jimmy"), 831 tuple.MustParse("document:somedoc#viewer@user:jimmy"), 832 }, 833 `"document:somedoc#view": 834 - "[user:jimmy] is <document:somedoc#writer>/<document:somedoc#viewer>"`, 835 `assertTrue: 836 - document:somedoc#writer@user:jimmy 837 `, 838 nil, 839 false, 840 `document:somedoc#view: 841 - '[user:jimmy] is <document:somedoc#viewer>/<document:somedoc#writer>' 842 `, 843 }, 844 { 845 "multipath missing relationship", 846 ` 847 definition user {} 848 definition document { 849 relation writer: user 850 relation viewer: user 851 permission view = viewer + writer 852 } 853 `, 854 []*core.RelationTuple{ 855 tuple.MustParse("document:somedoc#writer@user:jimmy"), 856 tuple.MustParse("document:somedoc#viewer@user:jimmy"), 857 }, 858 `"document:somedoc#view": 859 - "[user:jimmy] is <document:somedoc#writer>"`, 860 `assertTrue: 861 - document:somedoc#writer@user:jimmy 862 `, 863 &devinterface.DeveloperError{ 864 Message: "For object and permission/relation `document:somedoc#view`, found different relationships for subject `user:jimmy`: Specified: `<document:somedoc#writer>`, Computed: `<document:somedoc#viewer>/<document:somedoc#writer>`", 865 Kind: devinterface.DeveloperError_MISSING_EXPECTED_RELATIONSHIP, 866 Source: devinterface.DeveloperError_VALIDATION_YAML, 867 Context: `[user:jimmy] is <document:somedoc#writer>`, 868 Line: 2, 869 Column: 3, 870 }, 871 false, 872 `document:somedoc#view: 873 - '[user:jimmy] is <document:somedoc#viewer>/<document:somedoc#writer>' 874 `, 875 }, 876 { 877 "invalid namespace on tuple", 878 ` 879 definition user {} 880 `, 881 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 882 ``, 883 ``, 884 &devinterface.DeveloperError{ 885 Message: "object definition `document` not found", 886 Kind: devinterface.DeveloperError_UNKNOWN_OBJECT_TYPE, 887 Source: devinterface.DeveloperError_RELATIONSHIP, 888 Context: `document:somedoc#writer@user:jimmy`, 889 }, 890 false, 891 ``, 892 }, 893 { 894 "invalid relation on tuple", 895 ` 896 definition user {} 897 definition document {} 898 `, 899 []*core.RelationTuple{tuple.MustParse("document:somedoc#writer@user:jimmy")}, 900 ``, 901 ``, 902 &devinterface.DeveloperError{ 903 Message: "relation/permission `writer` not found under definition `document`", 904 Kind: devinterface.DeveloperError_UNKNOWN_RELATION, 905 Source: devinterface.DeveloperError_RELATIONSHIP, 906 Context: `document:somedoc#writer@user:jimmy`, 907 }, 908 false, 909 ``, 910 }, 911 { 912 "wildcard relationship", 913 ` 914 definition user {} 915 definition document { 916 relation writer: user 917 relation viewer: user | user:* 918 permission view = viewer + writer 919 } 920 `, 921 []*core.RelationTuple{ 922 tuple.MustParse("document:somedoc#writer@user:jimmy"), 923 tuple.MustParse("document:somedoc#viewer@user:*"), 924 }, 925 `"document:somedoc#view": 926 - "[user:*] is <document:somedoc#viewer>" 927 - "[user:jimmy] is <document:somedoc#viewer>/<document:somedoc#writer>"`, 928 `assertTrue: 929 - document:somedoc#writer@user:jimmy 930 - document:somedoc#viewer@user:jimmy 931 - document:somedoc#viewer@user:somegal 932 assertFalse: 933 - document:somedoc#writer@user:somegal`, 934 nil, 935 false, 936 `document:somedoc#view: 937 - '[user:*] is <document:somedoc#viewer>' 938 - '[user:jimmy] is <document:somedoc#writer>' 939 `, 940 }, 941 { 942 "wildcard exclusion", 943 ` 944 definition user {} 945 definition document { 946 relation banned: user 947 relation viewer: user | user:* 948 permission view = viewer - banned 949 } 950 `, 951 []*core.RelationTuple{ 952 tuple.MustParse("document:somedoc#banned@user:jimmy"), 953 tuple.MustParse("document:somedoc#viewer@user:*"), 954 }, 955 `"document:somedoc#view": 956 - "[user:* - {user:jimmy}] is <document:somedoc#viewer>"`, 957 `assertTrue: 958 - document:somedoc#view@user:somegal 959 assertFalse: 960 - document:somedoc#view@user:jimmy`, 961 nil, 962 false, 963 `document:somedoc#view: 964 - '[user:* - {user:jimmy}] is <document:somedoc#viewer>' 965 `, 966 }, 967 { 968 "wildcard exclusion under intersection", 969 ` 970 definition user {} 971 definition document { 972 relation banned: user 973 relation viewer: user | user:* 974 relation other: user 975 permission view = (viewer - banned) & (viewer - other) 976 } 977 `, 978 []*core.RelationTuple{ 979 tuple.MustParse("document:somedoc#other@user:sarah"), 980 tuple.MustParse("document:somedoc#banned@user:jimmy"), 981 tuple.MustParse("document:somedoc#viewer@user:*"), 982 }, 983 `"document:somedoc#view": 984 - "[user:* - {user:jimmy}] is <document:somedoc#viewer>"`, 985 `assertTrue: 986 - document:somedoc#view@user:somegal 987 assertFalse: 988 - document:somedoc#view@user:jimmy 989 - document:somedoc#view@user:sarah`, 990 nil, 991 false, 992 `document:somedoc#view: 993 - '[user:* - {user:jimmy, user:sarah}] is <document:somedoc#viewer>' 994 `, 995 }, 996 { 997 "nil handling", 998 ` 999 definition user {} 1000 definition document { 1001 relation viewer: user 1002 permission view = viewer 1003 permission empty = nil 1004 } 1005 `, 1006 []*core.RelationTuple{ 1007 tuple.MustParse("document:somedoc#viewer@user:jill"), 1008 tuple.MustParse("document:somedoc#viewer@user:tom"), 1009 }, 1010 `"document:somedoc#view": 1011 - "[user:jill] is <document:somedoc#viewer>" 1012 - "[user:tom] is <document:somedoc#viewer>" 1013 "document:somedoc#empty": []`, 1014 `assertTrue: 1015 - document:somedoc#view@user:jill 1016 - document:somedoc#view@user:tom 1017 assertFalse: 1018 - document:somedoc#empty@user:jill 1019 - document:somedoc#empty@user:tom`, 1020 nil, 1021 false, 1022 "document:somedoc#empty: []\ndocument:somedoc#view:\n- '[user:jill] is <document:somedoc#viewer>'\n- '[user:tom] is <document:somedoc#viewer>'\n", 1023 }, 1024 { 1025 "no expected subject or relation", 1026 ` 1027 definition user {} 1028 definition document { 1029 relation viewer: user 1030 permission view = viewer 1031 } 1032 `, 1033 []*core.RelationTuple{ 1034 tuple.MustParse("document:somedoc#viewer@user:jill"), 1035 tuple.MustParse("document:somedoc#viewer@user:tom"), 1036 }, 1037 `"document:somedoc#view": 1038 - "is <document:somedoc#viewer>" 1039 - "[user:tom] is "`, 1040 `assertTrue: 1041 - document:somedoc#view@user:jill 1042 - document:somedoc#view@user:tom`, 1043 &devinterface.DeveloperError{ 1044 Message: "For object and permission/relation `document:somedoc#view`, no expected subject specified in `is <document:somedoc#viewer>`", 1045 Kind: devinterface.DeveloperError_MISSING_EXPECTED_RELATIONSHIP, 1046 Source: devinterface.DeveloperError_VALIDATION_YAML, 1047 Context: `is <document:somedoc#viewer>`, 1048 Line: 2, 1049 Column: 3, 1050 }, 1051 false, 1052 "document:somedoc#view:\n- '[user:jill] is <document:somedoc#viewer>'\n- '[user:tom] is <document:somedoc#viewer>'\n", 1053 }, 1054 1055 { 1056 "expected relations containing uncaveated subject that should be caveated", 1057 ` 1058 definition user {} 1059 1060 caveat testcaveat(somecondition int) { 1061 somecondition == 42 1062 } 1063 1064 definition document { 1065 relation viewer: user with testcaveat 1066 permission view = viewer 1067 } 1068 `, 1069 []*core.RelationTuple{ 1070 tuple.MustParse("document:somedoc#viewer@user:sarah[testcaveat]"), 1071 }, 1072 `"document:somedoc#view": 1073 - '[user:sarah] is <document:somedoc#viewer>' 1074 `, 1075 `assertTrue: 1076 - 'document:somedoc#viewer@user:sarah with {"somecondition": "42"}' 1077 assertCaveated: 1078 - document:somedoc#viewer@user:sarah 1079 assertFalse: 1080 - 'document:somedoc#viewer@user:sarah with {"somecondition": "45"}' 1081 `, 1082 &devinterface.DeveloperError{ 1083 Message: "For object and permission/relation `document:somedoc#view`, found caveat mismatch", 1084 Line: 2, 1085 Column: 3, 1086 Source: devinterface.DeveloperError_VALIDATION_YAML, 1087 Kind: devinterface.DeveloperError_MISSING_EXPECTED_RELATIONSHIP, 1088 Context: "[user:sarah] is <document:somedoc#viewer>", 1089 }, 1090 false, 1091 `document:somedoc#view: 1092 - '[user:sarah[...]] is <document:somedoc#viewer>' 1093 `, 1094 }, 1095 } 1096 1097 for _, tc := range tests { 1098 t.Run(tc.name, func(t *testing.T) { 1099 require := require.New(t) 1100 1101 response := run(t, &devinterface.DeveloperRequest{ 1102 Context: &devinterface.RequestContext{ 1103 Schema: tc.schema, 1104 Relationships: tc.relationships, 1105 }, 1106 Operations: []*devinterface.Operation{ 1107 { 1108 AssertionsParameters: &devinterface.RunAssertionsParameters{ 1109 AssertionsYaml: tc.assertionsYaml, 1110 }, 1111 }, 1112 { 1113 ValidationParameters: &devinterface.RunValidationParameters{ 1114 ValidationYaml: tc.validationYaml, 1115 }, 1116 }, 1117 }, 1118 }) 1119 1120 if tc.expectedError != nil { 1121 errors := []*devinterface.DeveloperError{} 1122 1123 if response.GetDeveloperErrors() != nil { 1124 errors = append(errors, response.GetDeveloperErrors().InputErrors...) 1125 } else { 1126 require.NotNil(response.GetOperationsResults(), "found nil results: %v", response) 1127 require.GreaterOrEqual(len(response.GetOperationsResults().Results), 2, "found insufficient results") 1128 1129 if response.GetOperationsResults().Results[0].GetAssertionsResult().InputError != nil { 1130 errors = append(errors, response.GetOperationsResults().Results[0].GetAssertionsResult().InputError) 1131 } 1132 1133 if response.GetOperationsResults().Results[1].GetValidationResult().InputError != nil { 1134 errors = append(errors, response.GetOperationsResults().Results[1].GetValidationResult().InputError) 1135 } 1136 1137 if len(response.GetOperationsResults().Results[0].GetAssertionsResult().ValidationErrors) > 0 { 1138 errors = append(errors, response.GetOperationsResults().Results[0].GetAssertionsResult().ValidationErrors...) 1139 } 1140 1141 if len(response.GetOperationsResults().Results[1].GetValidationResult().ValidationErrors) > 0 { 1142 errors = append(errors, response.GetOperationsResults().Results[1].GetValidationResult().ValidationErrors...) 1143 } 1144 } 1145 1146 if tc.expectCheckTraces { 1147 require.NotNil(t, errors[0].CheckDebugInformation) 1148 require.NotNil(t, errors[0].CheckResolvedDebugInformation) 1149 1150 // Unset these values to avoid the need to specify above 1151 // in the test data. This is necessary because the debug 1152 // information contains the revision timestamp, which changes 1153 // on every call. 1154 errors[0].CheckDebugInformation = nil 1155 errors[0].CheckResolvedDebugInformation = nil 1156 } 1157 1158 testutil.RequireProtoEqual(t, tc.expectedError, errors[0], "mismatch on errors") 1159 } else { 1160 require.Equal(0, len(response.GetOperationsResults().Results[0].GetAssertionsResult().ValidationErrors), "Failed assertion", response.GetOperationsResults().Results[0].GetAssertionsResult().ValidationErrors) 1161 } 1162 1163 if tc.expectedValidationYaml != "" && response.GetOperationsResults() != nil { 1164 require.Equal(tc.expectedValidationYaml, response.GetOperationsResults().Results[1].GetValidationResult().UpdatedValidationYaml) 1165 } 1166 }) 1167 } 1168 }