github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/variables-test.js (about)

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