github.com/hernad/nomad@v1.6.112/ui/tests/acceptance/job-clients-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 } 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 Clients from 'nomad-ui/tests/pages/jobs/job/clients'; 13 import setPolicy from 'nomad-ui/tests/utils/set-policy'; 14 15 let job; 16 let clients; 17 18 const makeSearchableClients = (server, job) => { 19 Array(10) 20 .fill(null) 21 .map((_, index) => { 22 const node = server.create('node', { 23 id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`, 24 datacenter: 'dc1', 25 status: 'ready', 26 }); 27 server.create('allocation', { jobId: job.id, nodeId: node.id }); 28 }); 29 }; 30 31 module('Acceptance | job clients', function (hooks) { 32 setupApplicationTest(hooks); 33 setupMirage(hooks); 34 35 hooks.beforeEach(function () { 36 setPolicy({ 37 id: 'node-read', 38 name: 'node-read', 39 rulesJSON: { 40 Node: { 41 Policy: 'read', 42 }, 43 }, 44 }); 45 46 server.createList('node-pool', 5); 47 clients = server.createList('node', 12, { 48 datacenter: 'dc1', 49 status: 'ready', 50 }); 51 // Job with 1 task group. 52 job = server.create('job', { 53 status: 'running', 54 datacenters: ['dc1'], 55 type: 'sysbatch', 56 resourceSpec: ['M: 256, C: 500'], 57 createAllocations: false, 58 }); 59 clients.forEach((c) => { 60 server.create('allocation', { jobId: job.id, nodeId: c.id }); 61 }); 62 63 // Create clients without allocations to have some 'not scheduled' job status. 64 clients = clients.concat( 65 server.createList('node', 3, { 66 datacenter: 'dc1', 67 status: 'ready', 68 }) 69 ); 70 }); 71 72 test('it passes an accessibility audit', async function (assert) { 73 await Clients.visit({ id: job.id }); 74 await a11yAudit(assert); 75 }); 76 77 test('lists all clients for the job', async function (assert) { 78 await Clients.visit({ id: job.id }); 79 assert.equal(Clients.clients.length, 15, 'Clients are shown in a table'); 80 81 const clientIDs = clients.sortBy('id').map((c) => c.id); 82 const clientsInTable = Clients.clients.map((c) => c.id).sort(); 83 assert.deepEqual(clientsInTable, clientIDs); 84 85 assert.equal(document.title, `Job ${job.name} clients - Nomad`); 86 }); 87 88 test('dates have tooltip', async function (assert) { 89 await Clients.visit({ id: job.id }); 90 91 Clients.clients.forEach((clientRow, index) => { 92 const jobStatus = Clients.clientFor(clientRow.id).status; 93 94 ['createTime', 'modifyTime'].forEach((col) => { 95 if (jobStatus === 'not scheduled') { 96 /* eslint-disable-next-line qunit/no-conditional-assertions */ 97 assert.equal( 98 clientRow[col].text, 99 '-', 100 `row ${index} doesn't have ${col} tooltip` 101 ); 102 /* eslint-disable-next-line qunit/no-early-return */ 103 return; 104 } 105 106 const hasTooltip = clientRow[col].tooltip.isPresent; 107 const tooltipText = clientRow[col].tooltip.text; 108 assert.true(hasTooltip, `row ${index} has ${col} tooltip`); 109 assert.ok( 110 tooltipText, 111 `row ${index} has ${col} tooltip content ${tooltipText}` 112 ); 113 }); 114 }); 115 }); 116 117 test('clients table is sortable', async function (assert) { 118 await Clients.visit({ id: job.id }); 119 await Clients.sortBy('node.name'); 120 121 assert.equal( 122 currentURL(), 123 `/jobs/${job.id}/clients?desc=true&sort=node.name`, 124 'the URL persists the sort parameter' 125 ); 126 127 const sortedClients = clients.sortBy('name').reverse(); 128 Clients.clients.forEach((client, index) => { 129 const shortId = sortedClients[index].id.split('-')[0]; 130 assert.equal( 131 client.shortId, 132 shortId, 133 `Client ${index} is ${shortId} with name ${sortedClients[index].name}` 134 ); 135 }); 136 }); 137 138 test('clients table is searchable', async function (assert) { 139 makeSearchableClients(server, job); 140 141 await Clients.visit({ id: job.id }); 142 await Clients.search('ffffff'); 143 144 assert.equal(Clients.clients.length, 5, 'List is filtered by search term'); 145 }); 146 147 test('when a search yields no results, the search box remains', async function (assert) { 148 makeSearchableClients(server, job); 149 150 await Clients.visit({ id: job.id }); 151 await Clients.search('^nothing will ever match this long regex$'); 152 153 assert.equal( 154 Clients.emptyState.headline, 155 'No Matches', 156 'List is empty and the empty state is about search' 157 ); 158 159 assert.ok(Clients.hasSearchBox, 'Search box is still shown'); 160 }); 161 162 test('when the job for the clients is not found, an error message is shown, but the URL persists', async function (assert) { 163 await Clients.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/clients', 175 'The URL persists' 176 ); 177 assert.ok(Clients.error.isPresent, 'Error message is shown'); 178 assert.equal(Clients.error.title, 'Not Found', 'Error message is for 404'); 179 }); 180 181 test('clicking row goes to client details', async function (assert) { 182 const client = clients[0]; 183 184 await Clients.visit({ id: job.id }); 185 await Clients.clientFor(client.id).click(); 186 assert.equal(currentURL(), `/clients/${client.id}`); 187 188 await Clients.visit({ id: job.id }); 189 await Clients.clientFor(client.id).visit(); 190 assert.equal(currentURL(), `/clients/${client.id}`); 191 192 await Clients.visit({ id: job.id }); 193 await Clients.clientFor(client.id).visitRow(); 194 assert.equal(currentURL(), `/clients/${client.id}`); 195 }); 196 197 testFacet('Job Status', { 198 facet: Clients.facets.jobStatus, 199 paramName: 'jobStatus', 200 expectedOptions: [ 201 'Queued', 202 'Not Scheduled', 203 'Starting', 204 'Running', 205 'Complete', 206 'Degraded', 207 'Failed', 208 'Lost', 209 'Unknown', 210 ], 211 async beforeEach() { 212 await Clients.visit({ id: job.id }); 213 }, 214 }); 215 216 function testFacet(label, { facet, paramName, beforeEach, expectedOptions }) { 217 test(`the ${label} facet has the correct options`, async function (assert) { 218 await beforeEach(); 219 await facet.toggle(); 220 221 let expectation; 222 if (typeof expectedOptions === 'function') { 223 expectation = expectedOptions(); 224 } else { 225 expectation = expectedOptions; 226 } 227 228 assert.deepEqual( 229 facet.options.map((option) => option.label.trim()), 230 expectation, 231 `Options for facet ${paramName} are as expected` 232 ); 233 }); 234 235 // TODO: add facet tests for actual list filtering 236 } 237 });