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

     1  /* eslint-disable qunit/require-expect */
     2  import { currentURL } 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 Allocations from 'nomad-ui/tests/pages/jobs/job/allocations';
     8  
     9  let job;
    10  let allocations;
    11  
    12  const makeSearchAllocations = (server) => {
    13    Array(10)
    14      .fill(null)
    15      .map((_, index) => {
    16        server.create('allocation', {
    17          id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`,
    18          shallow: true,
    19        });
    20      });
    21  };
    22  
    23  module('Acceptance | job allocations', function (hooks) {
    24    setupApplicationTest(hooks);
    25    setupMirage(hooks);
    26  
    27    hooks.beforeEach(function () {
    28      server.create('node');
    29  
    30      job = server.create('job', {
    31        noFailedPlacements: true,
    32        createAllocations: false,
    33      });
    34    });
    35  
    36    test('it passes an accessibility audit', async function (assert) {
    37      server.createList('allocation', Allocations.pageSize - 1, {
    38        shallow: true,
    39      });
    40      allocations = server.schema.allocations.where({ jobId: job.id }).models;
    41  
    42      await Allocations.visit({ id: job.id });
    43      await a11yAudit(assert);
    44    });
    45  
    46    test('lists all allocations for the job', async function (assert) {
    47      server.createList('allocation', Allocations.pageSize - 1, {
    48        shallow: true,
    49      });
    50      allocations = server.schema.allocations.where({ jobId: job.id }).models;
    51  
    52      await Allocations.visit({ id: job.id });
    53  
    54      assert.equal(
    55        Allocations.allocations.length,
    56        Allocations.pageSize - 1,
    57        'Allocations are shown in a table'
    58      );
    59  
    60      const sortedAllocations = allocations.sortBy('modifyIndex').reverse();
    61  
    62      Allocations.allocations.forEach((allocation, index) => {
    63        const shortId = sortedAllocations[index].id.split('-')[0];
    64        assert.equal(
    65          allocation.shortId,
    66          shortId,
    67          `Allocation ${index} is ${shortId}`
    68        );
    69      });
    70  
    71      assert.equal(document.title, `Job ${job.name} allocations - Nomad`);
    72    });
    73  
    74    test('allocations table is sortable', async function (assert) {
    75      server.createList('allocation', Allocations.pageSize - 1);
    76      allocations = server.schema.allocations.where({ jobId: job.id }).models;
    77  
    78      await Allocations.visit({ id: job.id });
    79      await Allocations.sortBy('taskGroupName');
    80  
    81      assert.equal(
    82        currentURL(),
    83        `/jobs/${job.id}/allocations?sort=taskGroupName`,
    84        'the URL persists the sort parameter'
    85      );
    86      const sortedAllocations = allocations.sortBy('taskGroup').reverse();
    87      Allocations.allocations.forEach((allocation, index) => {
    88        const shortId = sortedAllocations[index].id.split('-')[0];
    89        assert.equal(
    90          allocation.shortId,
    91          shortId,
    92          `Allocation ${index} is ${shortId} with task group ${sortedAllocations[index].taskGroup}`
    93        );
    94      });
    95    });
    96  
    97    test('allocations table is searchable', async function (assert) {
    98      makeSearchAllocations(server);
    99  
   100      allocations = server.schema.allocations.where({ jobId: job.id }).models;
   101  
   102      await Allocations.visit({ id: job.id });
   103      await Allocations.search('ffffff');
   104  
   105      assert.equal(
   106        Allocations.allocations.length,
   107        5,
   108        'List is filtered by search term'
   109      );
   110    });
   111  
   112    test('when a search yields no results, the search box remains', async function (assert) {
   113      makeSearchAllocations(server);
   114  
   115      allocations = server.schema.allocations.where({ jobId: job.id }).models;
   116  
   117      await Allocations.visit({ id: job.id });
   118      await Allocations.search('^nothing will ever match this long regex$');
   119  
   120      assert.equal(
   121        Allocations.emptyState.headline,
   122        'No Matches',
   123        'List is empty and the empty state is about search'
   124      );
   125  
   126      assert.ok(Allocations.hasSearchBox, 'Search box is still shown');
   127    });
   128  
   129    test('when the job for the allocations is not found, an error message is shown, but the URL persists', async function (assert) {
   130      await Allocations.visit({ id: 'not-a-real-job' });
   131  
   132      assert.equal(
   133        server.pretender.handledRequests
   134          .filter((request) => !request.url.includes('policy'))
   135          .findBy('status', 404).url,
   136        '/v1/job/not-a-real-job',
   137        'A request to the nonexistent job is made'
   138      );
   139      assert.equal(
   140        currentURL(),
   141        '/jobs/not-a-real-job/allocations',
   142        'The URL persists'
   143      );
   144      assert.ok(Allocations.error.isPresent, 'Error message is shown');
   145      assert.equal(
   146        Allocations.error.title,
   147        'Not Found',
   148        'Error message is for 404'
   149      );
   150    });
   151  
   152    testFacet('Status', {
   153      facet: Allocations.facets.status,
   154      paramName: 'status',
   155      expectedOptions: [
   156        'Pending',
   157        'Running',
   158        'Complete',
   159        'Failed',
   160        'Lost',
   161        'Unknown',
   162      ],
   163      async beforeEach() {
   164        ['pending', 'running', 'complete', 'failed', 'lost', 'unknown'].forEach(
   165          (s) => {
   166            server.createList('allocation', 5, { clientStatus: s });
   167          }
   168        );
   169        await Allocations.visit({ id: job.id });
   170      },
   171      filter: (alloc, selection) =>
   172        alloc.jobId == job.id && selection.includes(alloc.clientStatus),
   173    });
   174  
   175    testFacet('Client', {
   176      facet: Allocations.facets.client,
   177      paramName: 'client',
   178      expectedOptions(allocs) {
   179        return Array.from(
   180          new Set(
   181            allocs
   182              .filter((alloc) => alloc.jobId == job.id)
   183              .mapBy('nodeId')
   184              .map((id) => id.split('-')[0])
   185          )
   186        ).sort();
   187      },
   188      async beforeEach() {
   189        server.createList('node', 5);
   190        server.createList('allocation', 20);
   191  
   192        await Allocations.visit({ id: job.id });
   193      },
   194      filter: (alloc, selection) =>
   195        alloc.jobId == job.id && selection.includes(alloc.nodeId.split('-')[0]),
   196    });
   197  
   198    testFacet('Task Group', {
   199      facet: Allocations.facets.taskGroup,
   200      paramName: 'taskGroup',
   201      expectedOptions(allocs) {
   202        return Array.from(
   203          new Set(
   204            allocs.filter((alloc) => alloc.jobId == job.id).mapBy('taskGroup')
   205          )
   206        ).sort();
   207      },
   208      async beforeEach() {
   209        job = server.create('job', {
   210          type: 'service',
   211          status: 'running',
   212          groupsCount: 5,
   213        });
   214  
   215        await Allocations.visit({ id: job.id });
   216      },
   217      filter: (alloc, selection) =>
   218        alloc.jobId == job.id && selection.includes(alloc.taskGroup),
   219    });
   220  });
   221  
   222  function testFacet(
   223    label,
   224    { facet, paramName, beforeEach, filter, expectedOptions }
   225  ) {
   226    test(`facet ${label} | the ${label} facet has the correct options`, async function (assert) {
   227      await beforeEach();
   228      await facet.toggle();
   229  
   230      let expectation;
   231      if (typeof expectedOptions === 'function') {
   232        expectation = expectedOptions(server.db.allocations);
   233      } else {
   234        expectation = expectedOptions;
   235      }
   236  
   237      assert.deepEqual(
   238        facet.options.map((option) => option.label.trim()),
   239        expectation,
   240        'Options for facet are as expected'
   241      );
   242    });
   243  
   244    test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function (assert) {
   245      let option;
   246  
   247      await beforeEach();
   248  
   249      await facet.toggle();
   250      option = facet.options.objectAt(0);
   251      await option.toggle();
   252  
   253      const selection = [option.key];
   254      const expectedAllocs = server.db.allocations
   255        .filter((alloc) => filter(alloc, selection))
   256        .sortBy('modifyIndex')
   257        .reverse();
   258  
   259      Allocations.allocations.forEach((alloc, index) => {
   260        assert.equal(
   261          alloc.id,
   262          expectedAllocs[index].id,
   263          `Allocation at ${index} is ${expectedAllocs[index].id}`
   264        );
   265      });
   266    });
   267  
   268    test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function (assert) {
   269      const selection = [];
   270  
   271      await beforeEach();
   272      await facet.toggle();
   273  
   274      const option1 = facet.options.objectAt(0);
   275      const option2 = facet.options.objectAt(1);
   276      await option1.toggle();
   277      selection.push(option1.key);
   278      await option2.toggle();
   279      selection.push(option2.key);
   280  
   281      const expectedAllocs = server.db.allocations
   282        .filter((alloc) => filter(alloc, selection))
   283        .sortBy('modifyIndex')
   284        .reverse();
   285  
   286      Allocations.allocations.forEach((alloc, index) => {
   287        assert.equal(
   288          alloc.id,
   289          expectedAllocs[index].id,
   290          `Allocation at ${index} is ${expectedAllocs[index].id}`
   291        );
   292      });
   293    });
   294  
   295    test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) {
   296      const selection = [];
   297  
   298      await beforeEach();
   299      await facet.toggle();
   300  
   301      const option1 = facet.options.objectAt(0);
   302      const option2 = facet.options.objectAt(1);
   303      await option1.toggle();
   304      selection.push(option1.key);
   305      await option2.toggle();
   306      selection.push(option2.key);
   307  
   308      assert.equal(
   309        currentURL(),
   310        `/jobs/${job.id}/allocations?${paramName}=${encodeURIComponent(
   311          JSON.stringify(selection)
   312        )}`,
   313        'URL has the correct query param key and value'
   314      );
   315    });
   316  }