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