github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/unit/abilities/variable-test.js (about) 1 /* eslint-disable ember/avoid-leaking-state-in-ember-objects */ 2 import { module, test } from 'qunit'; 3 import { setupTest } from 'ember-qunit'; 4 import Service from '@ember/service'; 5 import setupAbility from 'nomad-ui/tests/helpers/setup-ability'; 6 7 module('Unit | Ability | variable', function (hooks) { 8 setupTest(hooks); 9 setupAbility('variable')(hooks); 10 hooks.beforeEach(function () { 11 const mockSystem = Service.extend({ 12 features: [], 13 }); 14 15 this.owner.register('service:system', mockSystem); 16 }); 17 18 module('#list', function () { 19 test('it does not permit listing variables by default', function (assert) { 20 const mockToken = Service.extend({ 21 aclEnabled: true, 22 }); 23 24 this.owner.register('service:token', mockToken); 25 26 assert.notOk(this.ability.canList); 27 }); 28 29 test('it does not permit listing variables when token type is client', function (assert) { 30 const mockToken = Service.extend({ 31 aclEnabled: true, 32 selfToken: { type: 'client' }, 33 }); 34 35 this.owner.register('service:token', mockToken); 36 37 assert.notOk(this.ability.canList); 38 }); 39 40 test('it permits listing variables when token type is management', function (assert) { 41 const mockToken = Service.extend({ 42 aclEnabled: true, 43 selfToken: { type: 'management' }, 44 }); 45 46 this.owner.register('service:token', mockToken); 47 48 assert.ok(this.ability.canList); 49 }); 50 51 test('it permits listing variables when token has Variables with list capabilities in its rules', function (assert) { 52 const mockToken = Service.extend({ 53 aclEnabled: true, 54 selfToken: { type: 'client' }, 55 selfTokenPolicies: [ 56 { 57 rulesJSON: { 58 Namespaces: [ 59 { 60 Name: 'default', 61 Capabilities: [], 62 Variables: { 63 Paths: [{ Capabilities: ['list'], PathSpec: '*' }], 64 }, 65 }, 66 ], 67 }, 68 }, 69 ], 70 }); 71 72 this.owner.register('service:token', mockToken); 73 74 assert.ok(this.ability.canList); 75 }); 76 77 test('it does not permit listing variables when token has Variables alone in its rules', function (assert) { 78 const mockToken = Service.extend({ 79 aclEnabled: true, 80 selfToken: { type: 'client' }, 81 selfTokenPolicies: [ 82 { 83 rulesJSON: { 84 Namespaces: [ 85 { 86 Name: 'default', 87 Capabilities: [], 88 Variables: {}, 89 }, 90 ], 91 }, 92 }, 93 ], 94 }); 95 96 this.owner.register('service:token', mockToken); 97 98 assert.notOk(this.ability.canList); 99 }); 100 101 test('it does not permit listing variables when token has a null Variables block', function (assert) { 102 const mockToken = Service.extend({ 103 aclEnabled: true, 104 selfToken: { type: 'client' }, 105 selfTokenPolicies: [ 106 { 107 rulesJSON: { 108 Namespaces: [ 109 { 110 Name: 'default', 111 Capabilities: [], 112 Variables: null, 113 }, 114 ], 115 }, 116 }, 117 ], 118 }); 119 120 this.owner.register('service:token', mockToken); 121 122 assert.notOk(this.ability.canList); 123 }); 124 125 test('it does not permit listing variables when token has a Variables block where paths are without capabilities', function (assert) { 126 const mockToken = Service.extend({ 127 aclEnabled: true, 128 selfToken: { type: 'client' }, 129 selfTokenPolicies: [ 130 { 131 rulesJSON: { 132 Namespaces: [ 133 { 134 Name: 'default', 135 Capabilities: [], 136 Variables: { 137 Paths: [ 138 { Capabilities: [], PathSpec: '*' }, 139 { Capabilities: [], PathSpec: 'foo' }, 140 { Capabilities: [], PathSpec: 'foo/bar' }, 141 ], 142 }, 143 }, 144 ], 145 }, 146 }, 147 ], 148 }); 149 150 this.owner.register('service:token', mockToken); 151 152 assert.notOk(this.ability.canList); 153 }); 154 155 test('it does not permit listing variables when token has no Variables block', function (assert) { 156 const mockToken = Service.extend({ 157 aclEnabled: true, 158 selfToken: { type: 'client' }, 159 selfTokenPolicies: [ 160 { 161 rulesJSON: { 162 Namespaces: [ 163 { 164 Name: 'default', 165 Capabilities: [], 166 }, 167 ], 168 }, 169 }, 170 ], 171 }); 172 173 this.owner.register('service:token', mockToken); 174 175 assert.notOk(this.ability.canList); 176 }); 177 178 test('it permits listing variables when token multiple namespaces, only one of which having a Variables block', function (assert) { 179 const mockToken = Service.extend({ 180 aclEnabled: true, 181 selfToken: { type: 'client' }, 182 selfTokenPolicies: [ 183 { 184 rulesJSON: { 185 Namespaces: [ 186 { 187 Name: 'default', 188 Capabilities: [], 189 Variables: null, 190 }, 191 { 192 Name: 'nonsense', 193 Capabilities: [], 194 Variables: { 195 Paths: [{ Capabilities: [], PathSpec: '*' }], 196 }, 197 }, 198 { 199 Name: 'shenanigans', 200 Capabilities: [], 201 Variables: { 202 Paths: [ 203 { Capabilities: ['list'], PathSpec: 'foo/bar/baz' }, 204 ], 205 }, 206 }, 207 ], 208 }, 209 }, 210 ], 211 }); 212 213 this.owner.register('service:token', mockToken); 214 215 assert.ok(this.ability.canList); 216 }); 217 }); 218 219 module('#create', function () { 220 test('it does not permit creating variables by default', function (assert) { 221 const mockToken = Service.extend({ 222 aclEnabled: true, 223 }); 224 225 this.owner.register('service:token', mockToken); 226 227 assert.notOk(this.ability.canWrite); 228 }); 229 230 test('it permits creating variables when token type is management', function (assert) { 231 const mockToken = Service.extend({ 232 aclEnabled: true, 233 selfToken: { type: 'management' }, 234 }); 235 236 this.owner.register('service:token', mockToken); 237 238 assert.ok(this.ability.canWrite); 239 }); 240 241 test('it permits creating variables when acl is disabled', function (assert) { 242 const mockToken = Service.extend({ 243 aclEnabled: false, 244 selfToken: { type: 'client' }, 245 }); 246 247 this.owner.register('service:token', mockToken); 248 249 assert.ok(this.ability.canWrite); 250 }); 251 252 test('it permits creating variables when token has Variables with write capabilities in its rules', function (assert) { 253 const mockToken = Service.extend({ 254 aclEnabled: true, 255 selfToken: { type: 'client' }, 256 selfTokenPolicies: [ 257 { 258 rulesJSON: { 259 Namespaces: [ 260 { 261 Name: 'default', 262 Capabilities: [], 263 Variables: { 264 Paths: [{ Capabilities: ['write'], PathSpec: '*' }], 265 }, 266 }, 267 ], 268 }, 269 }, 270 ], 271 }); 272 273 this.owner.register('service:token', mockToken); 274 275 assert.ok(this.ability.canWrite); 276 }); 277 278 test('it handles namespace matching', function (assert) { 279 const mockToken = Service.extend({ 280 aclEnabled: true, 281 selfToken: { type: 'client' }, 282 selfTokenPolicies: [ 283 { 284 rulesJSON: { 285 Namespaces: [ 286 { 287 Name: 'default', 288 Capabilities: [], 289 Variables: { 290 Paths: [{ Capabilities: ['list'], PathSpec: 'foo/bar' }], 291 }, 292 }, 293 { 294 Name: 'pablo', 295 Capabilities: [], 296 Variables: { 297 Paths: [{ Capabilities: ['write'], PathSpec: 'foo/bar' }], 298 }, 299 }, 300 ], 301 }, 302 }, 303 ], 304 }); 305 306 this.owner.register('service:token', mockToken); 307 this.ability.path = 'foo/bar'; 308 this.ability.namespace = 'pablo'; 309 310 assert.ok(this.ability.canWrite); 311 }); 312 }); 313 314 module('#destroy', function () { 315 test('it does not permit destroying variables by default', function (assert) { 316 const mockToken = Service.extend({ 317 aclEnabled: true, 318 }); 319 320 this.owner.register('service:token', mockToken); 321 322 assert.notOk(this.ability.canDestroy); 323 }); 324 325 test('it permits destroying variables when token type is management', function (assert) { 326 const mockToken = Service.extend({ 327 aclEnabled: true, 328 selfToken: { type: 'management' }, 329 }); 330 331 this.owner.register('service:token', mockToken); 332 333 assert.ok(this.ability.canDestroy); 334 }); 335 336 test('it permits destroying variables when acl is disabled', function (assert) { 337 const mockToken = Service.extend({ 338 aclEnabled: false, 339 selfToken: { type: 'client' }, 340 }); 341 342 this.owner.register('service:token', mockToken); 343 344 assert.ok(this.ability.canDestroy); 345 }); 346 347 test('it permits destroying variables when token has Variables with write capabilities in its rules', function (assert) { 348 const mockToken = Service.extend({ 349 aclEnabled: true, 350 selfToken: { type: 'client' }, 351 selfTokenPolicies: [ 352 { 353 rulesJSON: { 354 Namespaces: [ 355 { 356 Name: 'default', 357 Capabilities: [], 358 Variables: { 359 Paths: [{ Capabilities: ['destroy'], PathSpec: '*' }], 360 }, 361 }, 362 ], 363 }, 364 }, 365 ], 366 }); 367 368 this.owner.register('service:token', mockToken); 369 370 assert.ok(this.ability.canDestroy); 371 }); 372 373 test('it handles namespace matching', function (assert) { 374 const mockToken = Service.extend({ 375 aclEnabled: true, 376 selfToken: { type: 'client' }, 377 selfTokenPolicies: [ 378 { 379 rulesJSON: { 380 Namespaces: [ 381 { 382 Name: 'default', 383 Capabilities: [], 384 Variables: { 385 Paths: [{ Capabilities: ['list'], PathSpec: 'foo/bar' }], 386 }, 387 }, 388 { 389 Name: 'pablo', 390 Capabilities: [], 391 Variables: { 392 Paths: [{ Capabilities: ['destroy'], PathSpec: 'foo/bar' }], 393 }, 394 }, 395 ], 396 }, 397 }, 398 ], 399 }); 400 401 this.owner.register('service:token', mockToken); 402 this.ability.path = 'foo/bar'; 403 this.ability.namespace = 'pablo'; 404 405 assert.ok(this.ability.canDestroy); 406 }); 407 }); 408 409 module('#read', function () { 410 test('it does not permit reading variables by default', function (assert) { 411 const mockToken = Service.extend({ 412 aclEnabled: true, 413 }); 414 415 this.owner.register('service:token', mockToken); 416 417 assert.notOk(this.ability.canRead); 418 }); 419 420 test('it permits reading variables when token type is management', function (assert) { 421 const mockToken = Service.extend({ 422 aclEnabled: true, 423 selfToken: { type: 'management' }, 424 }); 425 426 this.owner.register('service:token', mockToken); 427 428 assert.ok(this.ability.canRead); 429 }); 430 431 test('it permits reading variables when acl is disabled', function (assert) { 432 const mockToken = Service.extend({ 433 aclEnabled: false, 434 selfToken: { type: 'client' }, 435 }); 436 437 this.owner.register('service:token', mockToken); 438 439 assert.ok(this.ability.canRead); 440 }); 441 442 test('it permits reading variables when token has Variables with read capabilities in its rules', function (assert) { 443 const mockToken = Service.extend({ 444 aclEnabled: true, 445 selfToken: { type: 'client' }, 446 selfTokenPolicies: [ 447 { 448 rulesJSON: { 449 Namespaces: [ 450 { 451 Name: 'default', 452 Capabilities: [], 453 Variables: { 454 Paths: [{ Capabilities: ['read'], PathSpec: '*' }], 455 }, 456 }, 457 ], 458 }, 459 }, 460 ], 461 }); 462 463 this.owner.register('service:token', mockToken); 464 465 assert.ok(this.ability.canRead); 466 }); 467 468 test('it handles namespace matching', function (assert) { 469 const mockToken = Service.extend({ 470 aclEnabled: true, 471 selfToken: { type: 'client' }, 472 selfTokenPolicies: [ 473 { 474 rulesJSON: { 475 Namespaces: [ 476 { 477 Name: 'default', 478 Capabilities: [], 479 Variables: { 480 Paths: [{ Capabilities: ['list'], PathSpec: 'foo/bar' }], 481 }, 482 }, 483 { 484 Name: 'pablo', 485 Capabilities: [], 486 Variables: { 487 Paths: [{ Capabilities: ['read'], PathSpec: 'foo/bar' }], 488 }, 489 }, 490 ], 491 }, 492 }, 493 ], 494 }); 495 496 this.owner.register('service:token', mockToken); 497 this.ability.path = 'foo/bar'; 498 this.ability.namespace = 'pablo'; 499 500 assert.ok(this.ability.canRead); 501 }); 502 }); 503 504 module('#_nearestMatchingPath', function () { 505 test('returns capabilities for an exact path match', function (assert) { 506 const mockToken = Service.extend({ 507 aclEnabled: true, 508 selfToken: { type: 'client' }, 509 selfTokenPolicies: [ 510 { 511 rulesJSON: { 512 Namespaces: [ 513 { 514 Name: 'default', 515 Capabilities: [], 516 Variables: { 517 Paths: [{ Capabilities: ['write'], PathSpec: 'foo' }], 518 }, 519 }, 520 ], 521 }, 522 }, 523 ], 524 }); 525 526 this.owner.register('service:token', mockToken); 527 const path = 'foo'; 528 529 const nearestMatchingPath = this.ability._nearestMatchingPath(path); 530 531 assert.equal( 532 nearestMatchingPath, 533 'foo', 534 'It should return the exact path match.' 535 ); 536 }); 537 538 test('returns capabilities for the nearest fuzzy match if no exact match', function (assert) { 539 const mockToken = Service.extend({ 540 aclEnabled: true, 541 selfToken: { type: 'client' }, 542 selfTokenPolicies: [ 543 { 544 rulesJSON: { 545 Namespaces: [ 546 { 547 Name: 'default', 548 Capabilities: [], 549 Variables: { 550 Paths: [ 551 { Capabilities: ['write'], PathSpec: 'foo/*' }, 552 { Capabilities: ['write'], PathSpec: 'foo/bar/*' }, 553 ], 554 }, 555 }, 556 ], 557 }, 558 }, 559 ], 560 }); 561 562 this.owner.register('service:token', mockToken); 563 const path = 'foo/bar/baz'; 564 565 const nearestMatchingPath = this.ability._nearestMatchingPath(path); 566 567 assert.equal( 568 nearestMatchingPath, 569 'foo/bar/*', 570 'It should return the nearest fuzzy matching path.' 571 ); 572 }); 573 574 test('handles wildcard prefix matches', function (assert) { 575 const mockToken = Service.extend({ 576 aclEnabled: true, 577 selfToken: { type: 'client' }, 578 selfTokenPolicies: [ 579 { 580 rulesJSON: { 581 Namespaces: [ 582 { 583 Name: 'default', 584 Capabilities: [], 585 Variables: { 586 Paths: [{ Capabilities: ['write'], PathSpec: 'foo/*' }], 587 }, 588 }, 589 ], 590 }, 591 }, 592 ], 593 }); 594 595 this.owner.register('service:token', mockToken); 596 const path = 'foo/bar/baz'; 597 598 const nearestMatchingPath = this.ability._nearestMatchingPath(path); 599 600 assert.equal( 601 nearestMatchingPath, 602 'foo/*', 603 'It should handle wildcard glob.' 604 ); 605 }); 606 607 test('handles wildcard suffix matches', function (assert) { 608 const mockToken = Service.extend({ 609 aclEnabled: true, 610 selfToken: { type: 'client' }, 611 selfTokenPolicies: [ 612 { 613 rulesJSON: { 614 Namespaces: [ 615 { 616 Name: 'default', 617 Capabilities: [], 618 Variables: { 619 Paths: [ 620 { Capabilities: ['write'], PathSpec: '*/bar' }, 621 { Capabilities: ['write'], PathSpec: '*/bar/baz' }, 622 ], 623 }, 624 }, 625 ], 626 }, 627 }, 628 ], 629 }); 630 631 this.owner.register('service:token', mockToken); 632 const path = 'foo/bar/baz'; 633 634 const nearestMatchingPath = this.ability._nearestMatchingPath(path); 635 636 assert.equal( 637 nearestMatchingPath, 638 '*/bar/baz', 639 'It should return the nearest ancestor matching path.' 640 ); 641 }); 642 643 test('prioritizes wildcard suffix matches over wildcard prefix matches', function (assert) { 644 const mockToken = Service.extend({ 645 aclEnabled: true, 646 selfToken: { type: 'client' }, 647 selfTokenPolicies: [ 648 { 649 rulesJSON: { 650 Namespaces: [ 651 { 652 Name: 'default', 653 Capabilities: [], 654 Variables: { 655 Paths: [ 656 { Capabilities: ['write'], PathSpec: '*/bar' }, 657 { Capabilities: ['write'], PathSpec: 'foo/*' }, 658 ], 659 }, 660 }, 661 ], 662 }, 663 }, 664 ], 665 }); 666 667 this.owner.register('service:token', mockToken); 668 const path = 'foo/bar/baz'; 669 670 const nearestMatchingPath = this.ability._nearestMatchingPath(path); 671 672 assert.equal( 673 nearestMatchingPath, 674 'foo/*', 675 'It should prioritize suffix glob wildcard of prefix glob wildcard.' 676 ); 677 }); 678 679 test('defaults to the glob path if there is no exact match or wildcard matches', function (assert) { 680 const mockToken = Service.extend({ 681 aclEnabled: true, 682 selfToken: { type: 'client' }, 683 selfTokenPolicies: [ 684 { 685 rulesJSON: { 686 Namespaces: [ 687 { 688 Name: 'default', 689 Capabilities: [], 690 Variables: { 691 'Path "*"': { 692 Capabilities: ['write'], 693 }, 694 'Path "foo"': { 695 Capabilities: ['write'], 696 }, 697 }, 698 }, 699 ], 700 }, 701 }, 702 ], 703 }); 704 705 this.owner.register('service:token', mockToken); 706 const path = 'foo/bar/baz'; 707 708 const nearestMatchingPath = this.ability._nearestMatchingPath(path); 709 710 assert.equal( 711 nearestMatchingPath, 712 '*', 713 'It should default to glob wildcard if no matches.' 714 ); 715 }); 716 }); 717 718 module('#_doesMatchPattern', function () { 719 const edgeCaseTest = 'this is a ϗѾ test'; 720 721 module('base cases', function () { 722 test('it handles an empty pattern', function (assert) { 723 // arrange 724 const pattern = ''; 725 const emptyPath = ''; 726 const nonEmptyPath = 'a'; 727 728 // act 729 const matchingResult = this.ability._doesMatchPattern( 730 pattern, 731 emptyPath 732 ); 733 const nonMatchingResult = this.ability._doesMatchPattern( 734 pattern, 735 nonEmptyPath 736 ); 737 738 // assert 739 assert.ok(matchingResult, 'Empty pattern should match empty path'); 740 assert.notOk( 741 nonMatchingResult, 742 'Empty pattern should not match non-empty path' 743 ); 744 }); 745 746 test('it handles an empty path', function (assert) { 747 // arrange 748 const emptyPath = ''; 749 const emptyPattern = ''; 750 const nonEmptyPattern = 'a'; 751 752 // act 753 const matchingResult = this.ability._doesMatchPattern( 754 emptyPattern, 755 emptyPath 756 ); 757 const nonMatchingResult = this.ability._doesMatchPattern( 758 nonEmptyPattern, 759 emptyPath 760 ); 761 762 // assert 763 assert.ok(matchingResult, 'Empty path should match empty pattern'); 764 assert.notOk( 765 nonMatchingResult, 766 'Empty path should not match non-empty pattern' 767 ); 768 }); 769 770 test('it handles a pattern without a glob', function (assert) { 771 // arrange 772 const path = '/foo'; 773 const matchingPattern = '/foo'; 774 const nonMatchingPattern = '/bar'; 775 776 // act 777 const matchingResult = this.ability._doesMatchPattern( 778 matchingPattern, 779 path 780 ); 781 const nonMatchingResult = this.ability._doesMatchPattern( 782 nonMatchingPattern, 783 path 784 ); 785 786 // assert 787 assert.ok(matchingResult, 'Matches path correctly.'); 788 assert.notOk(nonMatchingResult, 'Does not match non-matching path.'); 789 }); 790 791 test('it handles a pattern that is a lone glob', function (assert) { 792 // arrange 793 const path = '/foo'; 794 const glob = '*'; 795 796 // act 797 const matchingResult = this.ability._doesMatchPattern(glob, path); 798 799 // assert 800 assert.ok(matchingResult, 'Matches glob.'); 801 }); 802 803 test('it matches on leading glob', function (assert) { 804 // arrange 805 const pattern = '*bar'; 806 const matchingPath = 'footbar'; 807 const nonMatchingPath = 'rockthecasba'; 808 809 // act 810 const matchingResult = this.ability._doesMatchPattern( 811 pattern, 812 matchingPath 813 ); 814 const nonMatchingResult = this.ability._doesMatchPattern( 815 pattern, 816 nonMatchingPath 817 ); 818 819 // assert 820 assert.ok( 821 matchingResult, 822 'Correctly matches when leading glob and matching path.' 823 ); 824 assert.notOk( 825 nonMatchingResult, 826 'Does not match when leading glob and non-matching path.' 827 ); 828 }); 829 830 test('it matches on trailing glob', function (assert) { 831 // arrange 832 const pattern = 'foo*'; 833 const matchingPath = 'footbar'; 834 const nonMatchingPath = 'bar'; 835 836 // act 837 const matchingResult = this.ability._doesMatchPattern( 838 pattern, 839 matchingPath 840 ); 841 const nonMatchingResult = this.ability._doesMatchPattern( 842 pattern, 843 nonMatchingPath 844 ); 845 846 // assert 847 assert.ok(matchingResult, 'Correctly matches on trailing glob.'); 848 assert.notOk( 849 nonMatchingResult, 850 'Does not match on trailing glob if pattern does not match.' 851 ); 852 }); 853 854 test('it matches when glob is in middle', function (assert) { 855 // arrange 856 const pattern = 'foo*bar'; 857 const matchingPath = 'footbar'; 858 const nonMatchingPath = 'footba'; 859 860 // act 861 const matchingResult = this.ability._doesMatchPattern( 862 pattern, 863 matchingPath 864 ); 865 const nonMatchingResult = this.ability._doesMatchPattern( 866 pattern, 867 nonMatchingPath 868 ); 869 870 // assert 871 assert.ok( 872 matchingResult, 873 'Correctly matches on glob in middle of path.' 874 ); 875 assert.notOk( 876 nonMatchingResult, 877 'Does not match on glob in middle of path if not full pattern match.' 878 ); 879 }); 880 }); 881 882 module('matching edge cases', function () { 883 test('it matches when string is between globs', function (assert) { 884 // arrange 885 const pattern = '*is *'; 886 887 // act 888 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 889 890 // assert 891 assert.ok(result); 892 }); 893 894 test('it handles many non-consective globs', function (assert) { 895 // arrange 896 const pattern = '*is*a*'; 897 898 // act 899 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 900 901 // assert 902 assert.ok(result); 903 }); 904 905 test('it handles double globs', function (assert) { 906 // arrange 907 const pattern = '**test**'; 908 909 // act 910 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 911 912 // assert 913 assert.ok(result); 914 }); 915 916 test('it handles many consecutive globs', function (assert) { 917 // arrange 918 const pattern = '**is**a***test*'; 919 920 // act 921 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 922 923 // assert 924 assert.ok(result); 925 }); 926 927 test('it handles white space between globs', function (assert) { 928 // arrange 929 const pattern = '* *'; 930 931 // act 932 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 933 934 // assert 935 assert.ok(result); 936 }); 937 938 test('it handles a pattern of only globs', function (assert) { 939 // arrange 940 const pattern = '**********'; 941 942 // act 943 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 944 945 // assert 946 assert.ok(result); 947 }); 948 949 test('it handles unicode characters', function (assert) { 950 // arrange 951 const pattern = `*Ѿ*`; 952 953 // act 954 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 955 956 // assert 957 assert.ok(result); 958 }); 959 960 test('it handles mixed ASCII codes', function (assert) { 961 // arrange 962 const pattern = `*is a ϗѾ *`; 963 964 // act 965 const result = this.ability._doesMatchPattern(pattern, edgeCaseTest); 966 967 // assert 968 assert.ok(result); 969 }); 970 }); 971 972 module('non-matching edge cases', function () { 973 const failingCases = [ 974 { 975 case: 'test*', 976 message: 'Implicit substring match', 977 }, 978 { 979 case: '*is', 980 message: 'Parial match', 981 }, 982 { 983 case: '*no*', 984 message: 'Globs without match between them', 985 }, 986 { 987 case: ' ', 988 message: 'Plain white space', 989 }, 990 { 991 case: '* ', 992 message: 'Trailing white space', 993 }, 994 { 995 case: ' *', 996 message: 'Leading white space', 997 }, 998 { 999 case: '*ʤ*', 1000 message: 'Non-matching unicode', 1001 }, 1002 { 1003 case: 'this*this is a test', 1004 message: 'Repeated prefix', 1005 }, 1006 ]; 1007 1008 failingCases.forEach(({ case: failingPattern, message }) => { 1009 test('should fail the specified cases', function (assert) { 1010 const result = this.ability._doesMatchPattern( 1011 failingPattern, 1012 edgeCaseTest 1013 ); 1014 assert.notOk(result, `${message} should not match.`); 1015 }); 1016 }); 1017 }); 1018 }); 1019 1020 module('#_computeLengthDiff', function () { 1021 test('should return the difference in length between a path and a pattern', function (assert) { 1022 // arrange 1023 const path = 'foo'; 1024 const pattern = 'bar'; 1025 1026 // act 1027 const result = this.ability._computeLengthDiff(pattern, path); 1028 1029 // assert 1030 assert.equal( 1031 result, 1032 0, 1033 'it returns the difference in length between path and pattern' 1034 ); 1035 }); 1036 1037 test('should factor the number of globs into consideration', function (assert) { 1038 // arrange 1039 const pattern = 'foo*'; 1040 const path = 'bark'; 1041 1042 // act 1043 const result = this.ability._computeLengthDiff(pattern, path); 1044 1045 // assert 1046 assert.equal( 1047 result, 1048 1, 1049 'it adds the number of globs in the pattern to the difference' 1050 ); 1051 }); 1052 }); 1053 1054 module('#_smallestDifference', function () { 1055 test('returns the smallest difference in the list', function (assert) { 1056 // arrange 1057 const path = 'foo/bar'; 1058 const matchingPath = 'foo/*'; 1059 const matches = ['*/baz', '*', matchingPath]; 1060 1061 // act 1062 const result = this.ability._smallestDifference(matches, path); 1063 1064 // assert 1065 assert.equal( 1066 result, 1067 matchingPath, 1068 'It should return the smallest difference path.' 1069 ); 1070 }); 1071 }); 1072 1073 module('#allPaths', function () { 1074 test('it filters by namespace and shows all matching paths on the namespace', function (assert) { 1075 const mockToken = Service.extend({ 1076 aclEnabled: true, 1077 selfToken: { type: 'client' }, 1078 selfTokenPolicies: [ 1079 { 1080 rulesJSON: { 1081 Namespaces: [ 1082 { 1083 Name: 'default', 1084 Capabilities: [], 1085 Variables: { 1086 Paths: [{ Capabilities: ['write'], PathSpec: 'foo' }], 1087 }, 1088 }, 1089 { 1090 Name: 'bar', 1091 Capabilities: [], 1092 Variables: { 1093 Paths: [ 1094 { Capabilities: ['read', 'write'], PathSpec: 'foo' }, 1095 ], 1096 }, 1097 }, 1098 ], 1099 }, 1100 }, 1101 ], 1102 }); 1103 1104 this.owner.register('service:token', mockToken); 1105 this.ability.namespace = 'bar'; 1106 1107 const allPaths = this.ability.allPaths; 1108 1109 assert.deepEqual( 1110 allPaths, 1111 [ 1112 { 1113 capabilities: ['read', 'write'], 1114 name: 'foo', 1115 }, 1116 ], 1117 'It should return the exact path match.' 1118 ); 1119 }); 1120 1121 test('it matches on default if no namespace is selected', function (assert) { 1122 const mockToken = Service.extend({ 1123 aclEnabled: true, 1124 selfToken: { type: 'client' }, 1125 selfTokenPolicies: [ 1126 { 1127 rulesJSON: { 1128 Namespaces: [ 1129 { 1130 Name: 'default', 1131 Capabilities: [], 1132 Variables: { 1133 Paths: [{ Capabilities: ['write'], PathSpec: 'foo' }], 1134 }, 1135 }, 1136 { 1137 Name: 'bar', 1138 Capabilities: [], 1139 Variables: { 1140 Paths: [ 1141 { Capabilities: ['read', 'write'], PathSpec: 'foo' }, 1142 ], 1143 }, 1144 }, 1145 ], 1146 }, 1147 }, 1148 ], 1149 }); 1150 1151 this.owner.register('service:token', mockToken); 1152 this.ability.namespace = undefined; 1153 1154 const allPaths = this.ability.allPaths; 1155 1156 assert.deepEqual( 1157 allPaths, 1158 [ 1159 { 1160 capabilities: ['write'], 1161 name: 'foo', 1162 }, 1163 ], 1164 'It should return the exact path match.' 1165 ); 1166 }); 1167 1168 test('it handles globs in namespaces', function (assert) { 1169 const mockToken = Service.extend({ 1170 aclEnabled: true, 1171 selfToken: { type: 'client' }, 1172 selfTokenPolicies: [ 1173 { 1174 rulesJSON: { 1175 Namespaces: [ 1176 { 1177 Name: '*', 1178 Capabilities: ['list-jobs', 'alloc-exec', 'read-logs'], 1179 Variables: { 1180 Paths: [ 1181 { 1182 Capabilities: ['list'], 1183 PathSpec: '*', 1184 }, 1185 ], 1186 }, 1187 }, 1188 { 1189 Name: 'namespace-1', 1190 Capabilities: ['list-jobs', 'alloc-exec', 'read-logs'], 1191 Variables: { 1192 Paths: [ 1193 { 1194 Capabilities: ['list', 'read', 'destroy', 'create'], 1195 PathSpec: '*', 1196 }, 1197 ], 1198 }, 1199 }, 1200 { 1201 Name: 'namespace-2', 1202 Capabilities: ['list-jobs', 'alloc-exec', 'read-logs'], 1203 Variables: { 1204 Paths: [ 1205 { 1206 Capabilities: ['list', 'read', 'destroy', 'create'], 1207 PathSpec: 'blue/*', 1208 }, 1209 { 1210 Capabilities: ['list', 'read', 'create'], 1211 PathSpec: 'nomad/jobs/*', 1212 }, 1213 ], 1214 }, 1215 }, 1216 ], 1217 }, 1218 }, 1219 ], 1220 }); 1221 1222 this.owner.register('service:token', mockToken); 1223 this.ability.namespace = 'pablo'; 1224 1225 const allPaths = this.ability.allPaths; 1226 1227 assert.deepEqual( 1228 allPaths, 1229 [ 1230 { 1231 capabilities: ['list'], 1232 name: '*', 1233 }, 1234 ], 1235 'It should return the glob matching namespace match.' 1236 ); 1237 }); 1238 }); 1239 });