github.com/hernad/nomad@v1.6.112/ui/tests/acceptance/volumes-list-test.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  /* eslint-disable qunit/require-expect */
     7  import { currentURL, visit } from '@ember/test-helpers';
     8  import { module, test } from 'qunit';
     9  import { setupApplicationTest } from 'ember-qunit';
    10  import { setupMirage } from 'ember-cli-mirage/test-support';
    11  import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
    12  import pageSizeSelect from './behaviors/page-size-select';
    13  import VolumesList from 'nomad-ui/tests/pages/storage/volumes/list';
    14  import percySnapshot from '@percy/ember';
    15  import faker from 'nomad-ui/mirage/faker';
    16  
    17  const assignWriteAlloc = (volume, alloc) => {
    18    volume.writeAllocs.add(alloc);
    19    volume.allocations.add(alloc);
    20    volume.save();
    21  };
    22  
    23  const assignReadAlloc = (volume, alloc) => {
    24    volume.readAllocs.add(alloc);
    25    volume.allocations.add(alloc);
    26    volume.save();
    27  };
    28  
    29  module('Acceptance | volumes list', function (hooks) {
    30    setupApplicationTest(hooks);
    31    setupMirage(hooks);
    32  
    33    hooks.beforeEach(function () {
    34      faker.seed(1);
    35      server.create('node-pool');
    36      server.create('node');
    37      server.create('csi-plugin', { createVolumes: false });
    38      window.localStorage.clear();
    39    });
    40  
    41    test('it passes an accessibility audit', async function (assert) {
    42      await VolumesList.visit();
    43      await a11yAudit(assert);
    44    });
    45  
    46    test('visiting /csi redirects to /csi/volumes', async function (assert) {
    47      await visit('/csi');
    48  
    49      assert.equal(currentURL(), '/csi/volumes');
    50    });
    51  
    52    test('visiting /csi/volumes', async function (assert) {
    53      await VolumesList.visit();
    54  
    55      assert.equal(currentURL(), '/csi/volumes');
    56      assert.equal(document.title, 'CSI Volumes - Nomad');
    57    });
    58  
    59    test('/csi/volumes should list the first page of volumes sorted by name', async function (assert) {
    60      const volumeCount = VolumesList.pageSize + 1;
    61      server.createList('csi-volume', volumeCount);
    62  
    63      await VolumesList.visit();
    64  
    65      await percySnapshot(assert);
    66  
    67      const sortedVolumes = server.db.csiVolumes.sortBy('id');
    68      assert.equal(VolumesList.volumes.length, VolumesList.pageSize);
    69      VolumesList.volumes.forEach((volume, index) => {
    70        assert.equal(volume.name, sortedVolumes[index].id, 'Volumes are ordered');
    71      });
    72    });
    73  
    74    test('each volume row should contain information about the volume', async function (assert) {
    75      const volume = server.create('csi-volume');
    76      const readAllocs = server.createList('allocation', 2, { shallow: true });
    77      const writeAllocs = server.createList('allocation', 3, { shallow: true });
    78      readAllocs.forEach((alloc) => assignReadAlloc(volume, alloc));
    79      writeAllocs.forEach((alloc) => assignWriteAlloc(volume, alloc));
    80  
    81      await VolumesList.visit();
    82  
    83      const volumeRow = VolumesList.volumes.objectAt(0);
    84  
    85      let controllerHealthStr = 'Node Only';
    86      if (volume.controllerRequired || volume.controllersExpected > 0) {
    87        const healthy = volume.controllersHealthy;
    88        const expected = volume.controllersExpected;
    89        const isHealthy = healthy > 0;
    90        controllerHealthStr = `${
    91          isHealthy ? 'Healthy' : 'Unhealthy'
    92        } ( ${healthy} / ${expected} )`;
    93      }
    94  
    95      const nodeHealthStr = volume.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy';
    96  
    97      assert.equal(volumeRow.name, volume.id);
    98      assert.notOk(volumeRow.hasNamespace);
    99      assert.equal(
   100        volumeRow.schedulable,
   101        volume.schedulable ? 'Schedulable' : 'Unschedulable'
   102      );
   103      assert.equal(volumeRow.controllerHealth, controllerHealthStr);
   104      assert.equal(
   105        volumeRow.nodeHealth,
   106        `${nodeHealthStr} ( ${volume.nodesHealthy} / ${volume.nodesExpected} )`
   107      );
   108      assert.equal(volumeRow.provider, volume.provider);
   109      assert.equal(volumeRow.allocations, readAllocs.length + writeAllocs.length);
   110    });
   111  
   112    test('each volume row should link to the corresponding volume', async function (assert) {
   113      const [, secondNamespace] = server.createList('namespace', 2);
   114      const volume = server.create('csi-volume', {
   115        namespaceId: secondNamespace.id,
   116      });
   117  
   118      await VolumesList.visit({ namespace: '*' });
   119  
   120      await VolumesList.volumes.objectAt(0).clickName();
   121      assert.equal(
   122        currentURL(),
   123        `/csi/volumes/${volume.id}@${secondNamespace.id}`
   124      );
   125  
   126      await VolumesList.visit({ namespace: '*' });
   127      assert.equal(currentURL(), '/csi/volumes?namespace=*');
   128  
   129      await VolumesList.volumes.objectAt(0).clickRow();
   130      assert.equal(
   131        currentURL(),
   132        `/csi/volumes/${volume.id}@${secondNamespace.id}`
   133      );
   134    });
   135  
   136    test('when there are no volumes, there is an empty message', async function (assert) {
   137      await VolumesList.visit();
   138  
   139      await percySnapshot(assert);
   140  
   141      assert.ok(VolumesList.isEmpty);
   142      assert.equal(VolumesList.emptyState.headline, 'No Volumes');
   143    });
   144  
   145    test('when there are volumes, but no matches for a search, there is an empty message', async function (assert) {
   146      server.create('csi-volume', { id: 'cat 1' });
   147      server.create('csi-volume', { id: 'cat 2' });
   148  
   149      await VolumesList.visit();
   150  
   151      await VolumesList.search('dog');
   152      assert.ok(VolumesList.isEmpty);
   153      assert.equal(VolumesList.emptyState.headline, 'No Matches');
   154    });
   155  
   156    test('searching resets the current page', async function (assert) {
   157      server.createList('csi-volume', VolumesList.pageSize + 1);
   158  
   159      await VolumesList.visit();
   160      await VolumesList.nextPage();
   161  
   162      assert.equal(currentURL(), '/csi/volumes?page=2');
   163  
   164      await VolumesList.search('foobar');
   165  
   166      assert.equal(currentURL(), '/csi/volumes?search=foobar');
   167    });
   168  
   169    test('when the cluster has namespaces, each volume row includes the volume namespace', async function (assert) {
   170      server.createList('namespace', 2);
   171      const volume = server.create('csi-volume');
   172  
   173      await VolumesList.visit({ namespace: '*' });
   174  
   175      const volumeRow = VolumesList.volumes.objectAt(0);
   176      assert.equal(volumeRow.namespace, volume.namespaceId);
   177    });
   178  
   179    test('when the namespace query param is set, only matching volumes are shown and the namespace value is forwarded to app state', async function (assert) {
   180      server.createList('namespace', 2);
   181      const volume1 = server.create('csi-volume', {
   182        namespaceId: server.db.namespaces[0].id,
   183      });
   184      const volume2 = server.create('csi-volume', {
   185        namespaceId: server.db.namespaces[1].id,
   186      });
   187  
   188      await VolumesList.visit();
   189      assert.equal(VolumesList.volumes.length, 2);
   190  
   191      const firstNamespace = server.db.namespaces[0];
   192      await VolumesList.visit({ namespace: firstNamespace.id });
   193      assert.equal(VolumesList.volumes.length, 1);
   194      assert.equal(VolumesList.volumes.objectAt(0).name, volume1.id);
   195  
   196      const secondNamespace = server.db.namespaces[1];
   197      await VolumesList.visit({ namespace: secondNamespace.id });
   198  
   199      assert.equal(VolumesList.volumes.length, 1);
   200      assert.equal(VolumesList.volumes.objectAt(0).name, volume2.id);
   201    });
   202  
   203    test('when accessing volumes is forbidden, a message is shown with a link to the tokens page', async function (assert) {
   204      server.pretender.get('/v1/volumes', () => [403, {}, null]);
   205  
   206      await VolumesList.visit();
   207      assert.equal(VolumesList.error.title, 'Not Authorized');
   208  
   209      await VolumesList.error.seekHelp();
   210      assert.equal(currentURL(), '/settings/tokens');
   211    });
   212  
   213    pageSizeSelect({
   214      resourceName: 'volume',
   215      pageObject: VolumesList,
   216      pageObjectList: VolumesList.volumes,
   217      async setup() {
   218        server.createList('csi-volume', VolumesList.pageSize);
   219        await VolumesList.visit();
   220      },
   221    });
   222  
   223    testSingleSelectFacet('Namespace', {
   224      facet: VolumesList.facets.namespace,
   225      paramName: 'namespace',
   226      expectedOptions: ['All (*)', 'default', 'namespace-2'],
   227      optionToSelect: 'namespace-2',
   228      async beforeEach() {
   229        server.create('namespace', { id: 'default' });
   230        server.create('namespace', { id: 'namespace-2' });
   231        server.createList('csi-volume', 2, { namespaceId: 'default' });
   232        server.createList('csi-volume', 2, { namespaceId: 'namespace-2' });
   233        await VolumesList.visit();
   234      },
   235      filter(volume, selection) {
   236        return volume.namespaceId === selection;
   237      },
   238    });
   239  
   240    function testSingleSelectFacet(
   241      label,
   242      { facet, paramName, beforeEach, filter, expectedOptions, optionToSelect }
   243    ) {
   244      test(`the ${label} facet has the correct options`, async function (assert) {
   245        await beforeEach();
   246        await facet.toggle();
   247  
   248        let expectation;
   249        if (typeof expectedOptions === 'function') {
   250          expectation = expectedOptions(server.db.jobs);
   251        } else {
   252          expectation = expectedOptions;
   253        }
   254  
   255        assert.deepEqual(
   256          facet.options.map((option) => option.label.trim()),
   257          expectation,
   258          'Options for facet are as expected'
   259        );
   260      });
   261  
   262      test(`the ${label} facet filters the volumes list by ${label}`, async function (assert) {
   263        await beforeEach();
   264        await facet.toggle();
   265  
   266        const option = facet.options.findOneBy('label', optionToSelect);
   267        const selection = option.key;
   268        await option.select();
   269  
   270        const expectedVolumes = server.db.csiVolumes
   271          .filter((volume) => filter(volume, selection))
   272          .sortBy('id');
   273  
   274        VolumesList.volumes.forEach((volume, index) => {
   275          assert.equal(
   276            volume.name,
   277            expectedVolumes[index].name,
   278            `Volume at ${index} is ${expectedVolumes[index].name}`
   279          );
   280        });
   281      });
   282  
   283      test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function (assert) {
   284        await beforeEach();
   285        await facet.toggle();
   286  
   287        const option = facet.options.objectAt(1);
   288        const selection = option.key;
   289        await option.select();
   290  
   291        assert.ok(
   292          currentURL().includes(`${paramName}=${selection}`),
   293          'URL has the correct query param key and value'
   294        );
   295      });
   296    }
   297  });