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

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