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