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 }