github.com/hernad/nomad@v1.6.112/ui/tests/acceptance/variables-test.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 import { 7 currentRouteName, 8 currentURL, 9 click, 10 find, 11 findAll, 12 typeIn, 13 visit, 14 } from '@ember/test-helpers'; 15 import { setupMirage } from 'ember-cli-mirage/test-support'; 16 import { 17 selectChoose, 18 clickTrigger, 19 } from 'ember-power-select/test-support/helpers'; 20 import { setupApplicationTest } from 'ember-qunit'; 21 import { module, test } from 'qunit'; 22 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 23 import { allScenarios } from '../../mirage/scenarios/default'; 24 import cleanWhitespace from '../utils/clean-whitespace'; 25 import percySnapshot from '@percy/ember'; 26 import faker from 'nomad-ui/mirage/faker'; 27 28 import Variables from 'nomad-ui/tests/pages/variables'; 29 import Layout from 'nomad-ui/tests/pages/layout'; 30 31 const VARIABLE_TOKEN_ID = '53cur3-v4r14bl35'; 32 const LIMITED_VARIABLE_TOKEN_ID = 'f3w3r-53cur3-v4r14bl35'; 33 34 module('Acceptance | variables', function (hooks) { 35 setupApplicationTest(hooks); 36 setupMirage(hooks); 37 hooks.beforeEach(async function () { 38 faker.seed(1); 39 server.createList('variable', 3); 40 }); 41 42 test('it redirects to jobs and hides the gutter link when the token lacks permissions', async function (assert) { 43 await Variables.visit(); 44 assert.equal(currentURL(), '/jobs'); 45 assert.ok(Layout.gutter.variables.isHidden); 46 }); 47 48 test('it allows access for management level tokens', async function (assert) { 49 allScenarios.variableTestCluster(server); 50 window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId; 51 await Variables.visit(); 52 assert.equal(currentURL(), '/variables'); 53 assert.ok(Layout.gutter.variables.isVisible, 'Menu section is visible'); 54 }); 55 56 test('it allows access for list-variables allowed ACL rules', async function (assert) { 57 assert.expect(2); 58 allScenarios.variableTestCluster(server); 59 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 60 window.localStorage.nomadTokenSecret = variablesToken.secretId; 61 62 await Variables.visit(); 63 assert.equal(currentURL(), '/variables'); 64 assert.ok(Layout.gutter.variables.isVisible); 65 await percySnapshot(assert); 66 }); 67 68 test('it correctly traverses to and deletes a variable', async function (assert) { 69 assert.expect(13); 70 allScenarios.variableTestCluster(server); 71 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 72 window.localStorage.nomadTokenSecret = variablesToken.secretId; 73 server.db.variables.update({ namespace: 'default' }); 74 const policy = server.db.policies.find('Variable Maker'); 75 policy.rulesJSON.Namespaces[0].Variables.Paths.find( 76 (path) => path.PathSpec === '*' 77 ).Capabilities = ['list', 'read', 'destroy']; 78 79 await Variables.visit(); 80 assert.equal(currentURL(), '/variables'); 81 assert.ok(Layout.gutter.variables.isVisible); 82 83 let abcLink = [...findAll('[data-test-folder-row]')].filter((a) => 84 a.textContent.includes('a/b/c') 85 )[0]; 86 87 await click(abcLink); 88 89 assert.equal( 90 currentURL(), 91 '/variables/path/a/b/c', 92 'correctly traverses to a deeply nested path' 93 ); 94 assert.equal( 95 findAll('[data-test-folder-row]').length, 96 2, 97 'correctly shows 2 sub-folders' 98 ); 99 assert.equal( 100 findAll('[data-test-file-row]').length, 101 2, 102 'correctly shows 2 files' 103 ); 104 let fooLink = [...findAll('[data-test-file-row]')].filter((a) => 105 a.textContent.includes('foo0') 106 )[0]; 107 108 assert.ok(fooLink, 'foo0 file is present'); 109 110 await percySnapshot(assert); 111 112 await click(fooLink); 113 assert.ok( 114 currentURL().includes('/variables/var/a/b/c/foo0'), 115 'correctly traverses to a deeply nested variable file' 116 ); 117 const deleteButton = find('[data-test-delete-button] button'); 118 assert.dom(deleteButton).exists('delete button is present'); 119 120 await percySnapshot('deeply nested variable'); 121 122 await click(deleteButton); 123 assert 124 .dom('[data-test-confirmation-message]') 125 .exists('confirmation message is present'); 126 127 await click(find('[data-test-confirm-button]')); 128 assert.equal( 129 currentURL(), 130 '/variables/path/a/b/c', 131 'correctly returns to the parent path page after deletion' 132 ); 133 134 assert.equal( 135 findAll('[data-test-folder-row]').length, 136 2, 137 'still correctly shows 2 sub-folders' 138 ); 139 assert.equal( 140 findAll('[data-test-file-row]').length, 141 1, 142 'now correctly shows 1 file' 143 ); 144 145 fooLink = [...findAll('[data-test-file-row]')].filter((a) => 146 a.textContent.includes('foo0') 147 )[0]; 148 149 assert.notOk(fooLink, 'foo0 file is no longer present'); 150 }); 151 152 test('variables prefixed with nomad/jobs/ correctly link to entities', async function (assert) { 153 assert.expect(29); 154 allScenarios.variableTestCluster(server); 155 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 156 157 const variableLinkedJob = server.db.jobs[0]; 158 const variableLinkedGroup = server.db.taskGroups.findBy({ 159 jobId: variableLinkedJob.id, 160 }); 161 const variableLinkedTask = server.db.tasks.findBy({ 162 taskGroupId: variableLinkedGroup.id, 163 }); 164 const variableLinkedTaskAlloc = server.db.allocations 165 .filterBy('taskGroup', variableLinkedGroup.name) 166 ?.find((alloc) => alloc.taskStateIds.length); 167 168 window.localStorage.nomadTokenSecret = variablesToken.secretId; 169 170 // Non-job variable 171 await Variables.visit(); 172 assert.equal(currentURL(), '/variables'); 173 assert.ok(Layout.gutter.variables.isVisible); 174 175 let nonJobLink = [...findAll('[data-test-file-row]')].filter((a) => 176 a.textContent.includes('just some arbitrary file') 177 )[0]; 178 179 assert.ok(nonJobLink, 'non-job file is present'); 180 181 await click(nonJobLink); 182 assert.ok( 183 currentURL().includes('/variables/var/just some arbitrary file'), 184 'correctly traverses to a non-job file' 185 ); 186 let relatedEntitiesBox = find('.related-entities'); 187 assert 188 .dom(relatedEntitiesBox) 189 .doesNotExist('Related Entities box is not present'); 190 191 // Job variable 192 await Variables.visit(); 193 let jobsDirectoryLink = [...findAll('[data-test-folder-row]')].filter((a) => 194 a.textContent.includes('jobs') 195 )[0]; 196 197 await click(jobsDirectoryLink); 198 199 assert.equal( 200 currentURL(), 201 '/variables/path/nomad/jobs', 202 'correctly traverses to the jobs directory' 203 ); 204 let jobFileLink = find('[data-test-file-row]'); 205 206 assert.ok(jobFileLink, 'A job file is present'); 207 208 await click(jobFileLink); 209 assert.ok( 210 currentURL().startsWith('/variables/var/nomad/jobs/'), 211 'correctly traverses to a job file' 212 ); 213 relatedEntitiesBox = find('.related-entities'); 214 assert.dom(relatedEntitiesBox).exists('Related Entities box is present'); 215 assert.ok( 216 cleanWhitespace(relatedEntitiesBox.textContent).includes( 217 'This variable is accessible by job' 218 ), 219 'Related Entities box is job-oriented' 220 ); 221 222 await percySnapshot('related entities box for job variable'); 223 224 let relatedJobLink = find('.related-entities a'); 225 await click(relatedJobLink); 226 assert 227 .dom('[data-test-job-stat="variables"]') 228 .exists('Link from Job to Variable exists'); 229 let jobVariableLink = find('[data-test-job-stat="variables"] a'); 230 await click(jobVariableLink); 231 assert.ok( 232 currentURL().startsWith( 233 `/variables/var/nomad/jobs/${variableLinkedJob.id}` 234 ), 235 'correctly traverses from job to variable' 236 ); 237 238 // Group Variable 239 await Variables.visit(); 240 jobsDirectoryLink = [...findAll('[data-test-folder-row]')].filter((a) => 241 a.textContent.includes('jobs') 242 )[0]; 243 await click(jobsDirectoryLink); 244 let groupDirectoryLink = [...findAll('[data-test-folder-row]')][0]; 245 await click(groupDirectoryLink); 246 let groupFileLink = find('[data-test-file-row]'); 247 assert.ok(groupFileLink, 'A group file is present'); 248 await click(groupFileLink); 249 relatedEntitiesBox = find('.related-entities'); 250 assert.dom(relatedEntitiesBox).exists('Related Entities box is present'); 251 assert.ok( 252 cleanWhitespace(relatedEntitiesBox.textContent).includes( 253 'This variable is accessible by group' 254 ), 255 'Related Entities box is group-oriented' 256 ); 257 258 await percySnapshot('related entities box for group variable'); 259 260 let relatedGroupLink = find('.related-entities a'); 261 await click(relatedGroupLink); 262 assert 263 .dom('[data-test-task-group-stat="variables"]') 264 .exists('Link from Group to Variable exists'); 265 let groupVariableLink = find('[data-test-task-group-stat="variables"] a'); 266 await click(groupVariableLink); 267 assert.ok( 268 currentURL().startsWith( 269 `/variables/var/nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}` 270 ), 271 'correctly traverses from group to variable' 272 ); 273 274 // Task Variable 275 await Variables.visit(); 276 jobsDirectoryLink = [...findAll('[data-test-folder-row]')].filter((a) => 277 a.textContent.includes('jobs') 278 )[0]; 279 await click(jobsDirectoryLink); 280 groupDirectoryLink = [...findAll('[data-test-folder-row]')][0]; 281 await click(groupDirectoryLink); 282 let taskDirectoryLink = [...findAll('[data-test-folder-row]')][0]; 283 await click(taskDirectoryLink); 284 let taskFileLink = find('[data-test-file-row]'); 285 assert.ok(taskFileLink, 'A task file is present'); 286 await click(taskFileLink); 287 relatedEntitiesBox = find('.related-entities'); 288 assert.dom(relatedEntitiesBox).exists('Related Entities box is present'); 289 assert.ok( 290 cleanWhitespace(relatedEntitiesBox.textContent).includes( 291 'This variable is accessible by task' 292 ), 293 'Related Entities box is task-oriented' 294 ); 295 296 await percySnapshot('related entities box for task variable'); 297 298 let relatedTaskLink = find('.related-entities a'); 299 await click(relatedTaskLink); 300 // Gotta go the long way and click into the alloc/then task from here; but we know this one by virtue of stable test env. 301 await visit( 302 `/allocations/${variableLinkedTaskAlloc.id}/${variableLinkedTask.name}` 303 ); 304 assert 305 .dom('[data-test-task-stat="variables"]') 306 .exists('Link from Task to Variable exists'); 307 let taskVariableLink = find('[data-test-task-stat="variables"] a'); 308 await click(taskVariableLink); 309 assert.ok( 310 currentURL().startsWith( 311 `/variables/var/nomad/jobs/${variableLinkedJob.id}/${variableLinkedGroup.name}/${variableLinkedTask.name}` 312 ), 313 'correctly traverses from task to variable' 314 ); 315 316 // A non-variable-having job 317 await visit(`/jobs/${server.db.jobs[1].id}`); 318 assert 319 .dom('[data-test-task-stat="variables"]') 320 .doesNotExist('Link from Variable-less Job to Variable does not exist'); 321 322 // Related Entities during the Variable creation process 323 await Variables.visitNew(); 324 assert 325 .dom('.related-entities.notification') 326 .doesNotExist('Related Entities notification is not present by default'); 327 await typeIn('[data-test-path-input]', 'foo/bar'); 328 assert 329 .dom('.related-entities.notification') 330 .doesNotExist( 331 'Related Entities notification is not present when path is generic' 332 ); 333 document.querySelector('[data-test-path-input]').value = ''; // clear path input 334 await typeIn('[data-test-path-input]', 'nomad/jobs/abc'); 335 assert 336 .dom('.related-entities.notification') 337 .exists( 338 'Related Entities notification is present when path is job-oriented' 339 ); 340 assert 341 .dom('.related-entities.notification') 342 .containsText( 343 'This variable will be accessible by job', 344 'Related Entities notification is job-oriented' 345 ); 346 await typeIn('[data-test-path-input]', '/def'); 347 assert 348 .dom('.related-entities.notification') 349 .containsText( 350 'This variable will be accessible by group', 351 'Related Entities notification is group-oriented' 352 ); 353 await typeIn('[data-test-path-input]', '/ghi'); 354 assert 355 .dom('.related-entities.notification') 356 .containsText( 357 'This variable will be accessible by task', 358 'Related Entities notification is task-oriented' 359 ); 360 }); 361 362 test('it does not allow you to save if you lack Items', async function (assert) { 363 assert.expect(5); 364 allScenarios.variableTestCluster(server); 365 window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId; 366 await Variables.visitNew(); 367 assert.equal(currentURL(), '/variables/new'); 368 await typeIn('.path-input', 'foo/bar'); 369 await click('button[type="submit"]'); 370 assert.dom('.flash-message.alert-critical').exists(); 371 await click('.flash-message.alert-critical .hds-dismiss-button'); 372 assert.dom('.flash-message.alert-critical').doesNotExist(); 373 374 await typeIn('.key-value label:nth-child(1) input', 'myKey'); 375 await typeIn('.key-value label:nth-child(2) input', 'superSecret'); 376 377 await percySnapshot(assert); 378 379 await click('button[type="submit"]'); 380 381 assert.dom('.flash-message.alert-success').exists(); 382 assert.ok( 383 currentURL().includes('/variables/var/foo'), 384 'drops you back off to the parent page' 385 ); 386 }); 387 388 test('it passes an accessibility audit', async function (assert) { 389 assert.expect(1); 390 allScenarios.variableTestCluster(server); 391 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 392 window.localStorage.nomadTokenSecret = variablesToken.secretId; 393 await Variables.visit(); 394 await a11yAudit(assert); 395 }); 396 397 module('create flow', function () { 398 test('allows a user with correct permissions to create a variable', async function (assert) { 399 // Arrange Test Set-up 400 allScenarios.variableTestCluster(server); 401 server.createList('variable', 3); 402 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 403 window.localStorage.nomadTokenSecret = variablesToken.secretId; 404 await Variables.visit(); 405 // End Test Set-up 406 407 assert 408 .dom('[data-test-create-var]') 409 .exists('It should display an enabled button to create a variable'); 410 await click('[data-test-create-var]'); 411 412 assert.equal(currentRouteName(), 'variables.new'); 413 414 await typeIn('[data-test-path-input]', 'foo/bar'); 415 await clickTrigger('[data-test-variable-namespace-filter]'); 416 417 assert.dom('.dropdown-options').exists('Namespace can be edited.'); 418 assert 419 .dom('[data-test-variable-namespace-filter]') 420 .containsText( 421 'default', 422 'The first alphabetically sorted namespace should be selected as the default option.' 423 ); 424 425 await selectChoose( 426 '[data-test-variable-namespace-filter]', 427 'namespace-1' 428 ); 429 await typeIn('[data-test-var-key]', 'kiki'); 430 await typeIn('[data-test-var-value]', 'do you love me'); 431 await click('[data-test-submit-var]'); 432 433 assert.equal( 434 currentRouteName(), 435 'variables.variable.index', 436 'Navigates user back to variables list page after creating variable.' 437 ); 438 assert 439 .dom('.flash-message.alert.alert-success') 440 .exists('Shows a success toast notification on creation.'); 441 assert 442 .dom('[data-test-var=kiki]') 443 .exists('The new variable key should appear in the list.'); 444 445 // Reset Token 446 window.localStorage.nomadTokenSecret = null; 447 }); 448 449 test('prevents users from creating a variable without proper permissions', async function (assert) { 450 // Arrange Test Set-up 451 allScenarios.variableTestCluster(server); 452 server.createList('variable', 3); 453 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 454 window.localStorage.nomadTokenSecret = variablesToken.secretId; 455 const policy = server.db.policies.find('Variable Maker'); 456 policy.rulesJSON.Namespaces[0].Variables.Paths.find( 457 (path) => path.PathSpec === '*' 458 ).Capabilities = ['list']; 459 await Variables.visit(); 460 // End Test Set-up 461 462 assert 463 .dom('[data-test-disabled-create-var]') 464 .exists( 465 'It should display an disabled button to create a variable on the main listings page' 466 ); 467 468 // Reset Token 469 window.localStorage.nomadTokenSecret = null; 470 }); 471 472 test('allows creating a variable that starts with nomad/jobs/', async function (assert) { 473 // Arrange Test Set-up 474 allScenarios.variableTestCluster(server); 475 server.createList('variable', 3); 476 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 477 window.localStorage.nomadTokenSecret = variablesToken.secretId; 478 await Variables.visitNew(); 479 // End Test Set-up 480 481 await typeIn('[data-test-path-input]', 'nomad/jobs/foo/bar'); 482 await typeIn('[data-test-var-key]', 'my-test-key'); 483 await typeIn('[data-test-var-value]', 'my_test_value'); 484 await click('[data-test-submit-var]'); 485 486 assert.equal( 487 currentRouteName(), 488 'variables.variable.index', 489 'Navigates user back to variables list page after creating variable.' 490 ); 491 assert 492 .dom('.flash-message.alert.alert-success') 493 .exists('Shows a success toast notification on creation.'); 494 495 // Reset Token 496 window.localStorage.nomadTokenSecret = null; 497 }); 498 499 test('disallows creating a variable that starts with nomad/<something-other-than-jobs>/', async function (assert) { 500 // Arrange Test Set-up 501 allScenarios.variableTestCluster(server); 502 server.createList('variable', 3); 503 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 504 window.localStorage.nomadTokenSecret = variablesToken.secretId; 505 await Variables.visitNew(); 506 // End Test Set-up 507 508 await typeIn('[data-test-path-input]', 'nomad/foo/'); 509 await typeIn('[data-test-var-key]', 'my-test-key'); 510 await typeIn('[data-test-var-value]', 'my_test_value'); 511 assert 512 .dom('[data-test-submit-var]') 513 .isDisabled( 514 'Cannot submit a variable that begins with nomad/<not-jobs>/' 515 ); 516 517 document.querySelector('[data-test-path-input]').value = ''; // clear current input 518 await typeIn('[data-test-path-input]', 'nomad/jobs/'); 519 assert 520 .dom('[data-test-submit-var]') 521 .isNotDisabled('Can submit a variable that begins with nomad/jobs/'); 522 523 document.querySelector('[data-test-path-input]').value = ''; // clear current input 524 await typeIn('[data-test-path-input]', 'nomad/another-foo/'); 525 assert 526 .dom('[data-test-submit-var]') 527 .isDisabled('Disabled state re-evaluated when path input changes'); 528 529 document.querySelector('[data-test-path-input]').value = ''; // clear current input 530 await typeIn('[data-test-path-input]', 'nomad/jobs/job-templates/'); 531 assert 532 .dom('[data-test-submit-var]') 533 .isNotDisabled( 534 'Can submit a variable that begins with nomad/job-templates/' 535 ); 536 537 // Reset Token 538 window.localStorage.nomadTokenSecret = null; 539 }); 540 541 test('shows a custom editor when editing a job template variable', async function (assert) { 542 // Arrange Test Set-up 543 allScenarios.variableTestCluster(server); 544 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 545 window.localStorage.nomadTokenSecret = variablesToken.secretId; 546 await Variables.visitNew(); 547 // End Test Set-up 548 549 assert 550 .dom('.related-entities-hint') 551 .exists('Shows a hint about related entities by default'); 552 assert.dom('.CodeMirror').doesNotExist(); 553 await typeIn('[data-test-path-input]', 'nomad/job-templates/hello-world'); 554 assert 555 .dom('.related-entities-hint') 556 .doesNotExist('Hides the hint when editing a job template variable'); 557 assert 558 .dom('.job-template-hint') 559 .exists('Shows a hint about job templates'); 560 assert 561 .dom('.CodeMirror') 562 .exists('Shows a custom editor for job templates'); 563 564 document.querySelector('[data-test-path-input]').value = ''; // clear current input 565 await typeIn('[data-test-path-input]', 'hello-world-non-template'); 566 assert 567 .dom('.related-entities-hint') 568 .exists('Shows a hint about related entities by default'); 569 assert.dom('.CodeMirror').doesNotExist(); 570 // Reset Token 571 window.localStorage.nomadTokenSecret = null; 572 }); 573 }); 574 575 module('edit flow', function () { 576 test('allows a user with correct permissions to edit a variable', async function (assert) { 577 assert.expect(8); 578 // Arrange Test Set-up 579 allScenarios.variableTestCluster(server); 580 server.createList('variable', 3); 581 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 582 window.localStorage.nomadTokenSecret = variablesToken.secretId; 583 const policy = server.db.policies.find('Variable Maker'); 584 policy.rulesJSON.Namespaces[0].Variables.Paths.find( 585 (path) => path.PathSpec === '*' 586 ).Capabilities = ['list', 'read', 'write']; 587 server.db.variables.update({ namespace: 'default' }); 588 await Variables.visit(); 589 await click('[data-test-file-row]'); 590 // End Test Set-up 591 592 assert.equal(currentRouteName(), 'variables.variable.index'); 593 assert 594 .dom('[data-test-edit-button]') 595 .exists('The edit button is enabled in the view.'); 596 await click('[data-test-edit-button]'); 597 assert.equal( 598 currentRouteName(), 599 'variables.variable.edit', 600 'Clicking the button navigates you to editing view.' 601 ); 602 603 await percySnapshot(assert); 604 605 assert.dom('[data-test-path-input]').isDisabled('Path cannot be edited'); 606 await clickTrigger('[data-test-variable-namespace-filter]'); 607 assert 608 .dom('.dropdown-options') 609 .doesNotExist('Namespace cannot be edited.'); 610 611 document.querySelector('[data-test-var-key]').value = ''; // clear current input 612 await typeIn('[data-test-var-key]', 'kiki'); 613 await typeIn('[data-test-var-value]', 'do you love me'); 614 await click('[data-test-submit-var]'); 615 assert.equal( 616 currentRouteName(), 617 'variables.variable.index', 618 'Navigates user back to variables list page after creating variable.' 619 ); 620 assert 621 .dom('.flash-message.alert.alert-success') 622 .exists('Shows a success toast notification on edit.'); 623 assert 624 .dom('[data-test-var=kiki]') 625 .exists('The edited variable key should appear in the list.'); 626 627 // Reset Token 628 window.localStorage.nomadTokenSecret = null; 629 }); 630 631 test('prevents users from editing a variable without proper permissions', async function (assert) { 632 // Arrange Test Set-up 633 allScenarios.variableTestCluster(server); 634 server.createList('variable', 3); 635 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 636 window.localStorage.nomadTokenSecret = variablesToken.secretId; 637 const policy = server.db.policies.find('Variable Maker'); 638 policy.rulesJSON.Namespaces[0].Variables.Paths.find( 639 (path) => path.PathSpec === '*' 640 ).Capabilities = ['list', 'read']; 641 await Variables.visit(); 642 await click('[data-test-file-row]'); 643 // End Test Set-up 644 645 assert.equal(currentRouteName(), 'variables.variable.index'); 646 assert 647 .dom('[data-test-edit-button]') 648 .doesNotExist('The edit button is hidden in the view.'); 649 650 // Reset Token 651 window.localStorage.nomadTokenSecret = null; 652 }); 653 test('handles conflicts on save', async function (assert) { 654 // Arrange Test Set-up 655 allScenarios.variableTestCluster(server); 656 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 657 window.localStorage.nomadTokenSecret = variablesToken.secretId; 658 // End Test Set-up 659 660 await Variables.visitConflicting(); 661 await click('button[type="submit"]'); 662 663 assert 664 .dom('.notification.conflict') 665 .exists('Notification alerting user of conflict is present'); 666 667 document.querySelector('[data-test-var-key]').value = ''; // clear current input 668 await typeIn('[data-test-var-key]', 'buddy'); 669 await typeIn('[data-test-var-value]', 'pal'); 670 await click('[data-test-submit-var]'); 671 672 await click('button[data-test-overwrite-button]'); 673 assert.equal( 674 currentURL(), 675 '/variables/var/Auto-conflicting Variable@default', 676 'Selecting overwrite forces a save and redirects' 677 ); 678 679 assert 680 .dom('.flash-message.alert.alert-success') 681 .exists('Shows a success toast notification on edit.'); 682 683 assert 684 .dom('[data-test-var=buddy]') 685 .exists('The edited variable key should appear in the list.'); 686 687 // Reset Token 688 window.localStorage.nomadTokenSecret = null; 689 }); 690 691 test('warns you if you try to leave with an unsaved form', async function (assert) { 692 // Arrange Test Set-up 693 allScenarios.variableTestCluster(server); 694 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 695 window.localStorage.nomadTokenSecret = variablesToken.secretId; 696 697 const originalWindowConfirm = window.confirm; 698 let confirmFired = false; 699 let leave = true; 700 window.confirm = function () { 701 confirmFired = true; 702 return leave; 703 }; 704 // End Test Set-up 705 706 await Variables.visitConflicting(); 707 document.querySelector('[data-test-var-key]').value = ''; // clear current input 708 await typeIn('[data-test-var-key]', 'buddy'); 709 await typeIn('[data-test-var-value]', 'pal'); 710 await click('[data-test-gutter-link="jobs"]'); 711 assert.ok(confirmFired, 'Confirm fired when leaving with unsaved form'); 712 assert.equal( 713 currentURL(), 714 '/jobs?namespace=*', 715 'Opted to leave, ended up on desired page' 716 ); 717 718 // Reset checks 719 confirmFired = false; 720 leave = false; 721 722 await Variables.visitConflicting(); 723 document.querySelector('[data-test-var-key]').value = ''; // clear current input 724 await typeIn('[data-test-var-key]', 'buddy'); 725 await typeIn('[data-test-var-value]', 'pal'); 726 await click('[data-test-gutter-link="jobs"]'); 727 assert.ok(confirmFired, 'Confirm fired when leaving with unsaved form'); 728 assert.equal( 729 currentURL(), 730 '/variables/var/Auto-conflicting%20Variable@default/edit', 731 'Opted to stay, did not leave page' 732 ); 733 734 // Reset checks 735 confirmFired = false; 736 737 await Variables.visitConflicting(); 738 document.querySelector('[data-test-var-key]').value = ''; // clear current input 739 await typeIn('[data-test-var-key]', 'buddy'); 740 await typeIn('[data-test-var-value]', 'pal'); 741 await click('[data-test-json-toggle]'); 742 assert.notOk( 743 confirmFired, 744 'Confirm did not fire when only transitioning queryParams' 745 ); 746 assert.equal( 747 currentURL(), 748 '/variables/var/Auto-conflicting%20Variable@default/edit?view=json', 749 'Stayed on page, queryParams changed' 750 ); 751 752 // Reset Token 753 window.localStorage.nomadTokenSecret = null; 754 // Restore the original window.confirm implementation 755 window.confirm = originalWindowConfirm; 756 }); 757 }); 758 759 module('delete flow', function () { 760 test('allows a user with correct permissions to delete a variable', async function (assert) { 761 // Arrange Test Set-up 762 allScenarios.variableTestCluster(server); 763 server.createList('variable', 3); 764 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 765 window.localStorage.nomadTokenSecret = variablesToken.secretId; 766 const policy = server.db.policies.find('Variable Maker'); 767 policy.rulesJSON.Namespaces[0].Variables.Paths.find( 768 (path) => path.PathSpec === '*' 769 ).Capabilities = ['list', 'read', 'destroy']; 770 server.db.variables.update({ namespace: 'default' }); 771 await Variables.visit(); 772 await click('[data-test-file-row]'); 773 // End Test Set-up 774 assert.equal(currentRouteName(), 'variables.variable.index'); 775 assert 776 .dom('[data-test-delete-button]') 777 .exists('The delete button is enabled in the view.'); 778 await click('[data-test-idle-button]'); 779 780 assert 781 .dom('[data-test-confirmation-message]') 782 .exists('Deleting a variable requires two-step confirmation.'); 783 784 await click('[data-test-confirm-button]'); 785 786 assert.equal( 787 currentRouteName(), 788 'variables.index', 789 'Navigates user back to variables list page after destroying a variable.' 790 ); 791 792 // Reset Token 793 window.localStorage.nomadTokenSecret = null; 794 }); 795 796 test('prevents users from delete a variable without proper permissions', async function (assert) { 797 // Arrange Test Set-up 798 allScenarios.variableTestCluster(server); 799 server.createList('variable', 3); 800 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 801 window.localStorage.nomadTokenSecret = variablesToken.secretId; 802 const policy = server.db.policies.find('Variable Maker'); 803 policy.rulesJSON.Namespaces[0].Variables.Paths.find( 804 (path) => path.PathSpec === '*' 805 ).Capabilities = ['list', 'read']; 806 await Variables.visit(); 807 await click('[data-test-file-row]'); 808 // End Test Set-up 809 810 assert.equal(currentRouteName(), 'variables.variable.index'); 811 assert 812 .dom('[data-test-delete-button]') 813 .doesNotExist('The delete button is hidden in the view.'); 814 815 // Reset Token 816 window.localStorage.nomadTokenSecret = null; 817 }); 818 }); 819 820 module('read flow', function () { 821 test('allows a user with correct permissions to read a variable', async function (assert) { 822 allScenarios.variableTestCluster(server); 823 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 824 window.localStorage.nomadTokenSecret = variablesToken.secretId; 825 await Variables.visit(); 826 827 assert 828 .dom('[data-test-file-row]:not(.inaccessible)') 829 .exists( 830 { count: 4 }, 831 'Shows 4 variable files, none of which are inaccessible' 832 ); 833 834 await click('[data-test-file-row]'); 835 assert.equal(currentRouteName(), 'variables.variable.index'); 836 837 // Reset Token 838 window.localStorage.nomadTokenSecret = null; 839 }); 840 841 test('prevents users from reading a variable without proper permissions', async function (assert) { 842 allScenarios.variableTestCluster(server); 843 const variablesToken = server.db.tokens.find(LIMITED_VARIABLE_TOKEN_ID); 844 window.localStorage.nomadTokenSecret = variablesToken.secretId; 845 await Variables.visit(); 846 847 assert 848 .dom('[data-test-file-row].inaccessible') 849 .exists( 850 { count: 4 }, 851 'Shows 4 variable files, all of which are inaccessible' 852 ); 853 854 // Reset Token 855 window.localStorage.nomadTokenSecret = null; 856 }); 857 }); 858 859 module('namespace filtering', function () { 860 test('allows a user to filter variables by namespace', async function (assert) { 861 assert.expect(3); 862 863 // Arrange 864 allScenarios.variableTestCluster(server); 865 server.createList('variable', 3); 866 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 867 window.localStorage.nomadTokenSecret = variablesToken.secretId; 868 await Variables.visit(); 869 870 assert 871 .dom('[data-test-variable-namespace-filter]') 872 .exists('Shows a dropdown of namespaces'); 873 874 // Assert Side Side Effect 875 server.get('/vars', function (_server, fakeRequest) { 876 assert.deepEqual( 877 fakeRequest.queryParams, 878 { 879 namespace: 'default', 880 }, 881 'It makes another server request using the options selected by the user' 882 ); 883 return []; 884 }); 885 886 // Act 887 await clickTrigger('[data-test-variable-namespace-filter]'); 888 await selectChoose('[data-test-variable-namespace-filter]', 'default'); 889 890 assert 891 .dom('[data-test-no-matching-variables-list-headline]') 892 .exists('Renders an empty list.'); 893 }); 894 895 test('does not show namespace filtering if the user only has access to one namespace', async function (assert) { 896 allScenarios.variableTestCluster(server); 897 server.createList('variable', 3); 898 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 899 window.localStorage.nomadTokenSecret = variablesToken.secretId; 900 const twoTokens = server.db.namespaces.slice(0, 2); 901 server.db.namespaces.remove(twoTokens); 902 await Variables.visit(); 903 904 assert.equal( 905 server.db.namespaces.length, 906 1, 907 'There should only be one namespace.' 908 ); 909 assert 910 .dom('[data-test-variable-namespace-filter]') 911 .doesNotExist('Does not show a dropdown of namespaces'); 912 }); 913 914 module('path route', function () { 915 test('allows a user to filter variables by namespace', async function (assert) { 916 assert.expect(4); 917 918 // Arrange 919 allScenarios.variableTestCluster(server); 920 server.createList('variable', 3); 921 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 922 window.localStorage.nomadTokenSecret = variablesToken.secretId; 923 await Variables.visit(); 924 await click('[data-test-folder-row]'); 925 926 assert.equal( 927 currentRouteName(), 928 'variables.path', 929 'It navigates a user to the path subroute' 930 ); 931 932 assert 933 .dom('[data-test-variable-namespace-filter]') 934 .exists('Shows a dropdown of namespaces'); 935 936 // Assert Side Side Effect 937 server.get('/vars', function (_server, fakeRequest) { 938 assert.deepEqual( 939 fakeRequest.queryParams, 940 { 941 namespace: 'default', 942 }, 943 'It makes another server request using the options selected by the user' 944 ); 945 return []; 946 }); 947 948 // Act 949 await clickTrigger('[data-test-variable-namespace-filter]'); 950 await selectChoose('[data-test-variable-namespace-filter]', 'default'); 951 952 assert 953 .dom('[data-test-no-matching-variables-list-headline]') 954 .exists('Renders an empty list.'); 955 }); 956 957 test('does not show namespace filtering if the user only has access to one namespace', async function (assert) { 958 allScenarios.variableTestCluster(server); 959 server.createList('variable', 3); 960 const variablesToken = server.db.tokens.find(VARIABLE_TOKEN_ID); 961 window.localStorage.nomadTokenSecret = variablesToken.secretId; 962 const twoTokens = server.db.namespaces.slice(0, 2); 963 server.db.namespaces.remove(twoTokens); 964 await Variables.visit(); 965 966 assert.equal( 967 server.db.namespaces.length, 968 1, 969 'There should only be one namespace.' 970 ); 971 972 await click('[data-test-folder-row]'); 973 974 assert.equal( 975 currentRouteName(), 976 'variables.path', 977 'It navigates a user to the path subroute' 978 ); 979 980 assert 981 .dom('[data-test-variable-namespace-filter]') 982 .doesNotExist('Does not show a dropdown of namespaces'); 983 }); 984 }); 985 }); 986 });