open-nomad/ui/tests/acceptance/job-clients-test.js

237 lines
6.8 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
/* eslint-disable qunit/require-expect */
import { currentURL } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
import Clients from 'nomad-ui/tests/pages/jobs/job/clients';
import setPolicy from 'nomad-ui/tests/utils/set-policy';
let job;
let clients;
const makeSearchableClients = (server, job) => {
Array(10)
.fill(null)
.map((_, index) => {
const node = server.create('node', {
id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`,
datacenter: 'dc1',
status: 'ready',
});
server.create('allocation', { jobId: job.id, nodeId: node.id });
});
};
module('Acceptance | job clients', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
hooks.beforeEach(function () {
setPolicy({
id: 'node-read',
name: 'node-read',
rulesJSON: {
Node: {
Policy: 'read',
},
},
});
clients = server.createList('node', 12, {
datacenter: 'dc1',
status: 'ready',
});
// Job with 1 task group.
job = server.create('job', {
status: 'running',
datacenters: ['dc1'],
type: 'sysbatch',
resourceSpec: ['M: 256, C: 500'],
createAllocations: false,
});
clients.forEach((c) => {
server.create('allocation', { jobId: job.id, nodeId: c.id });
});
// Create clients without allocations to have some 'not scheduled' job status.
clients = clients.concat(
server.createList('node', 3, {
datacenter: 'dc1',
status: 'ready',
})
);
});
test('it passes an accessibility audit', async function (assert) {
await Clients.visit({ id: job.id });
await a11yAudit(assert);
});
test('lists all clients for the job', async function (assert) {
await Clients.visit({ id: job.id });
assert.equal(Clients.clients.length, 15, 'Clients are shown in a table');
const clientIDs = clients.sortBy('id').map((c) => c.id);
const clientsInTable = Clients.clients.map((c) => c.id).sort();
assert.deepEqual(clientsInTable, clientIDs);
assert.equal(document.title, `Job ${job.name} clients - Nomad`);
});
test('dates have tooltip', async function (assert) {
await Clients.visit({ id: job.id });
Clients.clients.forEach((clientRow, index) => {
const jobStatus = Clients.clientFor(clientRow.id).status;
['createTime', 'modifyTime'].forEach((col) => {
if (jobStatus === 'not scheduled') {
/* eslint-disable-next-line qunit/no-conditional-assertions */
assert.equal(
clientRow[col].text,
'-',
`row ${index} doesn't have ${col} tooltip`
);
/* eslint-disable-next-line qunit/no-early-return */
return;
}
const hasTooltip = clientRow[col].tooltip.isPresent;
const tooltipText = clientRow[col].tooltip.text;
assert.true(hasTooltip, `row ${index} has ${col} tooltip`);
assert.ok(
tooltipText,
`row ${index} has ${col} tooltip content ${tooltipText}`
);
});
});
});
test('clients table is sortable', async function (assert) {
await Clients.visit({ id: job.id });
await Clients.sortBy('node.name');
assert.equal(
currentURL(),
`/jobs/${job.id}/clients?desc=true&sort=node.name`,
'the URL persists the sort parameter'
);
const sortedClients = clients.sortBy('name').reverse();
Clients.clients.forEach((client, index) => {
const shortId = sortedClients[index].id.split('-')[0];
assert.equal(
client.shortId,
shortId,
`Client ${index} is ${shortId} with name ${sortedClients[index].name}`
);
});
});
test('clients table is searchable', async function (assert) {
makeSearchableClients(server, job);
await Clients.visit({ id: job.id });
await Clients.search('ffffff');
assert.equal(Clients.clients.length, 5, 'List is filtered by search term');
});
test('when a search yields no results, the search box remains', async function (assert) {
makeSearchableClients(server, job);
await Clients.visit({ id: job.id });
await Clients.search('^nothing will ever match this long regex$');
assert.equal(
Clients.emptyState.headline,
'No Matches',
'List is empty and the empty state is about search'
);
assert.ok(Clients.hasSearchBox, 'Search box is still shown');
});
test('when the job for the clients is not found, an error message is shown, but the URL persists', async function (assert) {
await Clients.visit({ id: 'not-a-real-job' });
assert.equal(
server.pretender.handledRequests
.filter((request) => !request.url.includes('policy'))
.findBy('status', 404).url,
'/v1/job/not-a-real-job',
'A request to the nonexistent job is made'
);
assert.equal(
currentURL(),
'/jobs/not-a-real-job/clients',
'The URL persists'
);
assert.ok(Clients.error.isPresent, 'Error message is shown');
assert.equal(Clients.error.title, 'Not Found', 'Error message is for 404');
});
test('clicking row goes to client details', async function (assert) {
const client = clients[0];
await Clients.visit({ id: job.id });
await Clients.clientFor(client.id).click();
assert.equal(currentURL(), `/clients/${client.id}`);
await Clients.visit({ id: job.id });
await Clients.clientFor(client.id).visit();
assert.equal(currentURL(), `/clients/${client.id}`);
await Clients.visit({ id: job.id });
await Clients.clientFor(client.id).visitRow();
assert.equal(currentURL(), `/clients/${client.id}`);
});
testFacet('Job Status', {
facet: Clients.facets.jobStatus,
paramName: 'jobStatus',
expectedOptions: [
'Queued',
'Not Scheduled',
'Starting',
'Running',
'Complete',
'Degraded',
'Failed',
'Lost',
'Unknown',
],
async beforeEach() {
await Clients.visit({ id: job.id });
},
});
function testFacet(label, { facet, paramName, beforeEach, expectedOptions }) {
test(`the ${label} facet has the correct options`, async function (assert) {
await beforeEach();
await facet.toggle();
let expectation;
if (typeof expectedOptions === 'function') {
expectation = expectedOptions();
} else {
expectation = expectedOptions;
}
assert.deepEqual(
facet.options.map((option) => option.label.trim()),
expectation,
`Options for facet ${paramName} are as expected`
);
});
// TODO: add facet tests for actual list filtering
}
});