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  });