github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/acceptance/clients-list-test.js (about)

     1  import { currentURL, settled } from '@ember/test-helpers';
     2  import { module, test } from 'qunit';
     3  import { setupApplicationTest } from 'ember-qunit';
     4  import { setupMirage } from 'ember-cli-mirage/test-support';
     5  import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
     6  import pageSizeSelect from './behaviors/page-size-select';
     7  import ClientsList from 'nomad-ui/tests/pages/clients/list';
     8  
     9  module('Acceptance | clients list', function(hooks) {
    10    setupApplicationTest(hooks);
    11    setupMirage(hooks);
    12  
    13    hooks.beforeEach(function() {
    14      window.localStorage.clear();
    15    });
    16  
    17    test('it passes an accessibility audit', async function(assert) {
    18      const nodesCount = ClientsList.pageSize + 1;
    19  
    20      server.createList('node', nodesCount);
    21      server.createList('agent', 1);
    22  
    23      await ClientsList.visit();
    24      await a11yAudit(assert);
    25    });
    26  
    27    test('/clients should list one page of clients', async function(assert) {
    28      // Make sure to make more nodes than 1 page to assert that pagination is working
    29      const nodesCount = ClientsList.pageSize + 1;
    30  
    31      server.createList('node', nodesCount);
    32      server.createList('agent', 1);
    33  
    34      await ClientsList.visit();
    35  
    36      assert.equal(ClientsList.nodes.length, ClientsList.pageSize);
    37      assert.ok(ClientsList.hasPagination, 'Pagination found on the page');
    38  
    39      const sortedNodes = server.db.nodes.sortBy('modifyIndex').reverse();
    40  
    41      ClientsList.nodes.forEach((node, index) => {
    42        assert.equal(node.id, sortedNodes[index].id.split('-')[0], 'Clients are ordered');
    43      });
    44  
    45      assert.equal(document.title, 'Clients - Nomad');
    46    });
    47  
    48    test('each client record should show high-level info of the client', async function(assert) {
    49      const node = server.create('node', 'draining', {
    50        status: 'ready',
    51      });
    52  
    53      server.createList('agent', 1);
    54  
    55      await ClientsList.visit();
    56  
    57      const nodeRow = ClientsList.nodes.objectAt(0);
    58      const allocations = server.db.allocations.where({ nodeId: node.id });
    59  
    60      assert.equal(nodeRow.id, node.id.split('-')[0], 'ID');
    61      assert.equal(nodeRow.name, node.name, 'Name');
    62      assert.equal(
    63        nodeRow.compositeStatus.text,
    64        'draining',
    65        'Combined status, draining, and eligbility'
    66      );
    67      assert.equal(nodeRow.address, node.httpAddr);
    68      assert.equal(nodeRow.datacenter, node.datacenter, 'Datacenter');
    69      assert.equal(nodeRow.allocations, allocations.length, '# Allocations');
    70    });
    71  
    72    test('each client record should show running allocations', async function(assert) {
    73      server.createList('agent', 1);
    74  
    75      const node = server.create('node', {
    76        modifyIndex: 4,
    77        status: 'ready',
    78        schedulingEligibility: 'eligible',
    79        drain: false,
    80      });
    81  
    82      server.create('job', { createAllocations: false });
    83  
    84      const running = server.createList('allocation', 2, { clientStatus: 'running' });
    85      server.createList('allocation', 3, { clientStatus: 'pending' });
    86      server.createList('allocation', 10, { clientStatus: 'complete' });
    87  
    88      await ClientsList.visit();
    89  
    90      const nodeRow = ClientsList.nodes.objectAt(0);
    91  
    92      assert.equal(nodeRow.id, node.id.split('-')[0], 'ID');
    93      assert.equal(
    94        nodeRow.compositeStatus.text,
    95        'ready',
    96        'Combined status, draining, and eligbility'
    97      );
    98      assert.equal(nodeRow.allocations, running.length, '# Allocations');
    99    });
   100  
   101    test('client status, draining, and eligibility are collapsed into one column that stays sorted', async function(assert) {
   102      server.createList('agent', 1);
   103  
   104      server.create('node', {
   105        modifyIndex: 5,
   106        status: 'ready',
   107        schedulingEligibility: 'eligible',
   108        drain: false,
   109      });
   110      server.create('node', {
   111        modifyIndex: 4,
   112        status: 'initializing',
   113        schedulingEligibility: 'eligible',
   114        drain: false,
   115      });
   116      server.create('node', {
   117        modifyIndex: 3,
   118        status: 'down',
   119        schedulingEligibility: 'eligible',
   120        drain: false,
   121      });
   122      server.create('node', {
   123        modifyIndex: 2,
   124        status: 'down',
   125        schedulingEligibility: 'ineligible',
   126        drain: false,
   127      });
   128      server.create('node', {
   129        modifyIndex: 1,
   130        status: 'ready',
   131        schedulingEligibility: 'ineligible',
   132        drain: false,
   133      });
   134      server.create('node', 'draining', {
   135        modifyIndex: 0,
   136        status: 'ready',
   137      });
   138  
   139      await ClientsList.visit();
   140  
   141      ClientsList.nodes[0].compositeStatus.as(readyClient => {
   142        assert.equal(readyClient.text, 'ready');
   143        assert.ok(readyClient.isUnformatted, 'expected no status class');
   144        assert.equal(readyClient.tooltip, 'ready / not draining / eligible');
   145      });
   146  
   147      assert.equal(ClientsList.nodes[1].compositeStatus.text, 'initializing');
   148      assert.equal(ClientsList.nodes[2].compositeStatus.text, 'down');
   149      assert.equal(ClientsList.nodes[2].compositeStatus.text, 'down', 'down takes priority over ineligible');
   150  
   151      assert.equal(ClientsList.nodes[4].compositeStatus.text, 'ineligible');
   152      assert.ok(ClientsList.nodes[4].compositeStatus.isWarning, 'expected warning class');
   153  
   154      assert.equal(ClientsList.nodes[5].compositeStatus.text, 'draining');
   155      assert.ok(ClientsList.nodes[5].compositeStatus.isInfo, 'expected info class');
   156  
   157      await ClientsList.sortBy('compositeStatus');
   158  
   159      assert.deepEqual(ClientsList.nodes.mapBy('compositeStatus.text'), [
   160        'ready',
   161        'initializing',
   162        'ineligible',
   163        'draining',
   164        'down',
   165        'down',
   166      ]);
   167  
   168      // Simulate a client state change arriving through polling
   169      let readyClient = this.owner
   170        .lookup('service:store')
   171        .peekAll('node')
   172        .findBy('modifyIndex', 5);
   173      readyClient.set('schedulingEligibility', 'ineligible');
   174  
   175      await settled();
   176  
   177      assert.deepEqual(ClientsList.nodes.mapBy('compositeStatus.text'), [
   178        'initializing',
   179        'ineligible',
   180        'ineligible',
   181        'draining',
   182        'down',
   183        'down',
   184      ]);
   185    });
   186  
   187    test('each client should link to the client detail page', async function(assert) {
   188      server.createList('node', 1);
   189      server.createList('agent', 1);
   190  
   191      const node = server.db.nodes[0];
   192  
   193      await ClientsList.visit();
   194      await ClientsList.nodes.objectAt(0).clickRow();
   195  
   196      assert.equal(currentURL(), `/clients/${node.id}`);
   197    });
   198  
   199    test('when there are no clients, there is an empty message', async function(assert) {
   200      server.createList('agent', 1);
   201  
   202      await ClientsList.visit();
   203  
   204      assert.ok(ClientsList.isEmpty);
   205      assert.equal(ClientsList.empty.headline, 'No Clients');
   206    });
   207  
   208    test('when there are clients, but no matches for a search term, there is an empty message', async function(assert) {
   209      server.createList('agent', 1);
   210      server.create('node', { name: 'node' });
   211  
   212      await ClientsList.visit();
   213  
   214      await ClientsList.search('client');
   215      assert.ok(ClientsList.isEmpty);
   216      assert.equal(ClientsList.empty.headline, 'No Matches');
   217    });
   218  
   219    test('when accessing clients is forbidden, show a message with a link to the tokens page', async function(assert) {
   220      server.create('agent');
   221      server.create('node', { name: 'node' });
   222      server.pretender.get('/v1/nodes', () => [403, {}, null]);
   223  
   224      await ClientsList.visit();
   225  
   226      assert.equal(ClientsList.error.title, 'Not Authorized');
   227  
   228      await ClientsList.error.seekHelp();
   229  
   230      assert.equal(currentURL(), '/settings/tokens');
   231    });
   232  
   233    pageSizeSelect({
   234      resourceName: 'client',
   235      pageObject: ClientsList,
   236      pageObjectList: ClientsList.nodes,
   237      async setup() {
   238        server.createList('node', ClientsList.pageSize);
   239        server.createList('agent', 1);
   240        await ClientsList.visit();
   241      },
   242    });
   243  
   244    testFacet('Class', {
   245      facet: ClientsList.facets.class,
   246      paramName: 'class',
   247      expectedOptions(nodes) {
   248        return Array.from(new Set(nodes.mapBy('nodeClass'))).sort();
   249      },
   250      async beforeEach() {
   251        server.create('agent');
   252        server.createList('node', 2, { nodeClass: 'nc-one' });
   253        server.createList('node', 2, { nodeClass: 'nc-two' });
   254        server.createList('node', 2, { nodeClass: 'nc-three' });
   255        await ClientsList.visit();
   256      },
   257      filter: (node, selection) => selection.includes(node.nodeClass),
   258    });
   259  
   260    testFacet('State', {
   261      facet: ClientsList.facets.state,
   262      paramName: 'state',
   263      expectedOptions: ['Initializing', 'Ready', 'Down', 'Ineligible', 'Draining'],
   264      async beforeEach() {
   265        server.create('agent');
   266  
   267        server.createList('node', 2, { status: 'initializing' });
   268        server.createList('node', 2, { status: 'ready' });
   269        server.createList('node', 2, { status: 'down' });
   270  
   271        server.createList('node', 2, { schedulingEligibility: 'eligible', drain: false });
   272        server.createList('node', 2, { schedulingEligibility: 'ineligible', drain: false });
   273        server.createList('node', 2, { schedulingEligibility: 'ineligible', drain: true });
   274  
   275        await ClientsList.visit();
   276      },
   277      filter: (node, selection) => {
   278        if (selection.includes('draining') && !node.drain) return false;
   279        if (selection.includes('ineligible') && node.schedulingEligibility === 'eligible')
   280          return false;
   281  
   282        return selection.includes(node.status);
   283      },
   284    });
   285  
   286    testFacet('Datacenters', {
   287      facet: ClientsList.facets.datacenter,
   288      paramName: 'dc',
   289      expectedOptions(nodes) {
   290        return Array.from(new Set(nodes.mapBy('datacenter'))).sort();
   291      },
   292      async beforeEach() {
   293        server.create('agent');
   294        server.createList('node', 2, { datacenter: 'pdx-1' });
   295        server.createList('node', 2, { datacenter: 'nyc-1' });
   296        server.createList('node', 2, { datacenter: 'ams-1' });
   297        await ClientsList.visit();
   298      },
   299      filter: (node, selection) => selection.includes(node.datacenter),
   300    });
   301  
   302    testFacet('Volumes', {
   303      facet: ClientsList.facets.volume,
   304      paramName: 'volume',
   305      expectedOptions(nodes) {
   306        const flatten = (acc, val) => acc.concat(Object.keys(val));
   307        return Array.from(new Set(nodes.mapBy('hostVolumes').reduce(flatten, [])));
   308      },
   309      async beforeEach() {
   310        server.create('agent');
   311        server.createList('node', 2, { hostVolumes: { One: { Name: 'One' } } });
   312        server.createList('node', 2, { hostVolumes: { One: { Name: 'One' }, Two: { Name: 'Two' } } });
   313        server.createList('node', 2, { hostVolumes: { Two: { Name: 'Two' } } });
   314        await ClientsList.visit();
   315      },
   316      filter: (node, selection) =>
   317        Object.keys(node.hostVolumes).find(volume => selection.includes(volume)),
   318    });
   319  
   320    test('when the facet selections result in no matches, the empty state states why', async function(assert) {
   321      server.create('agent');
   322      server.createList('node', 2, { status: 'ready' });
   323  
   324      await ClientsList.visit();
   325  
   326      await ClientsList.facets.state.toggle();
   327      await ClientsList.facets.state.options.objectAt(0).toggle();
   328      assert.ok(ClientsList.isEmpty, 'There is an empty message');
   329      assert.equal(ClientsList.empty.headline, 'No Matches', 'The message is appropriate');
   330    });
   331  
   332    test('the clients list is immediately filtered based on query params', async function(assert) {
   333      server.create('agent');
   334      server.create('node', { nodeClass: 'omg-large' });
   335      server.create('node', { nodeClass: 'wtf-tiny' });
   336  
   337      await ClientsList.visit({ class: JSON.stringify(['wtf-tiny']) });
   338  
   339      assert.equal(ClientsList.nodes.length, 1, 'Only one client shown due to query param');
   340    });
   341  
   342    function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) {
   343      test(`the ${label} facet has the correct options`, async function(assert) {
   344        await beforeEach();
   345        await facet.toggle();
   346  
   347        let expectation;
   348        if (typeof expectedOptions === 'function') {
   349          expectation = expectedOptions(server.db.nodes);
   350        } else {
   351          expectation = expectedOptions;
   352        }
   353  
   354        assert.deepEqual(
   355          facet.options.map(option => option.label.trim()),
   356          expectation,
   357          'Options for facet are as expected'
   358        );
   359      });
   360  
   361      test(`the ${label} facet filters the nodes list by ${label}`, async function(assert) {
   362        let option;
   363  
   364        await beforeEach();
   365  
   366        await facet.toggle();
   367        option = facet.options.objectAt(0);
   368        await option.toggle();
   369  
   370        const selection = [option.key];
   371        const expectedNodes = server.db.nodes
   372          .filter(node => filter(node, selection))
   373          .sortBy('modifyIndex')
   374          .reverse();
   375  
   376        ClientsList.nodes.forEach((node, index) => {
   377          assert.equal(
   378            node.id,
   379            expectedNodes[index].id.split('-')[0],
   380            `Node at ${index} is ${expectedNodes[index].id}`
   381          );
   382        });
   383      });
   384  
   385      test(`selecting multiple options in the ${label} facet results in a broader search`, async function(assert) {
   386        const selection = [];
   387  
   388        await beforeEach();
   389        await facet.toggle();
   390  
   391        const option1 = facet.options.objectAt(0);
   392        const option2 = facet.options.objectAt(1);
   393        await option1.toggle();
   394        selection.push(option1.key);
   395        await option2.toggle();
   396        selection.push(option2.key);
   397  
   398        const expectedNodes = server.db.nodes
   399          .filter(node => filter(node, selection))
   400          .sortBy('modifyIndex')
   401          .reverse();
   402  
   403        ClientsList.nodes.forEach((node, index) => {
   404          assert.equal(
   405            node.id,
   406            expectedNodes[index].id.split('-')[0],
   407            `Node at ${index} is ${expectedNodes[index].id}`
   408          );
   409        });
   410      });
   411  
   412      test(`selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) {
   413        const selection = [];
   414  
   415        await beforeEach();
   416        await facet.toggle();
   417  
   418        const option1 = facet.options.objectAt(0);
   419        const option2 = facet.options.objectAt(1);
   420        await option1.toggle();
   421        selection.push(option1.key);
   422        await option2.toggle();
   423        selection.push(option2.key);
   424  
   425        assert.equal(
   426          currentURL(),
   427          `/clients?${paramName}=${encodeURIComponent(JSON.stringify(selection))}`,
   428          'URL has the correct query param key and value'
   429        );
   430      });
   431    }
   432  });