github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/acceptance/task-detail-test.js (about) 1 import { currentURL } from '@ember/test-helpers'; 2 import { module, test } from 'qunit'; 3 import { setupApplicationTest } from 'ember-qunit'; 4 import { setupMirage } from 'ember-cli-mirage/test-support'; 5 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 6 import Task from 'nomad-ui/tests/pages/allocations/task/detail'; 7 import Layout from 'nomad-ui/tests/pages/layout'; 8 import moment from 'moment'; 9 10 let allocation; 11 let task; 12 13 module('Acceptance | task detail', function(hooks) { 14 setupApplicationTest(hooks); 15 setupMirage(hooks); 16 17 hooks.beforeEach(async function() { 18 server.create('agent'); 19 server.create('node'); 20 server.create('job', { createAllocations: false }); 21 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 22 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 23 24 await Task.visit({ id: allocation.id, name: task.name }); 25 }); 26 27 test('it passes an accessibility audit', async function(assert) { 28 await a11yAudit(assert); 29 }); 30 31 test('/allocation/:id/:task_name should name the task and list high-level task information', async function(assert) { 32 assert.ok(Task.title.text.includes(task.name), 'Task name'); 33 assert.ok(Task.state.includes(task.state), 'Task state'); 34 35 assert.ok( 36 Task.startedAt.includes(moment(task.startedAt).format("MMM DD, 'YY HH:mm:ss ZZ")), 37 'Task started at' 38 ); 39 40 const lifecycle = server.db.tasks.where({ name: task.name })[0].Lifecycle; 41 42 let lifecycleName = 'main'; 43 if (lifecycle && (lifecycle.Hook === 'prestart' || lifecycle.Hook === 'poststart')) { 44 lifecycleName = `${lifecycle.Hook}-${lifecycle.Sidecar ? 'sidecar' : 'ephemeral'}`; 45 } 46 if (lifecycle && lifecycle.Hook === 'poststop') { 47 lifecycleName = 'poststop'; 48 } 49 50 assert.equal(Task.lifecycle, lifecycleName); 51 52 assert.equal(document.title, `Task ${task.name} - Nomad`); 53 }); 54 55 test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { 56 const { jobId, taskGroup } = allocation; 57 const job = server.db.jobs.find(jobId); 58 59 const shortId = allocation.id.split('-')[0]; 60 61 assert.equal(Layout.breadcrumbFor('jobs.index').text, 'Jobs', 'Jobs is the first breadcrumb'); 62 assert.equal( 63 Layout.breadcrumbFor('jobs.job.index').text, 64 job.name, 65 'Job is the second breadcrumb' 66 ); 67 assert.equal( 68 Layout.breadcrumbFor('jobs.job.task-group').text, 69 taskGroup, 70 'Task Group is the third breadcrumb' 71 ); 72 assert.equal( 73 Layout.breadcrumbFor('allocations.allocation').text, 74 shortId, 75 'Allocation short id is the fourth breadcrumb' 76 ); 77 assert.equal( 78 Layout.breadcrumbFor('allocations.allocation.task').text, 79 task.name, 80 'Task name is the fifth breadcrumb' 81 ); 82 83 await Layout.breadcrumbFor('jobs.index').visit(); 84 assert.equal(currentURL(), '/jobs', 'Jobs breadcrumb links correctly'); 85 86 await Task.visit({ id: allocation.id, name: task.name }); 87 await Layout.breadcrumbFor('jobs.job.index').visit(); 88 assert.equal(currentURL(), `/jobs/${job.id}`, 'Job breadcrumb links correctly'); 89 90 await Task.visit({ id: allocation.id, name: task.name }); 91 await Layout.breadcrumbFor('jobs.job.task-group').visit(); 92 assert.equal( 93 currentURL(), 94 `/jobs/${job.id}/${taskGroup}`, 95 'Task Group breadcrumb links correctly' 96 ); 97 98 await Task.visit({ id: allocation.id, name: task.name }); 99 await Layout.breadcrumbFor('allocations.allocation').visit(); 100 assert.equal( 101 currentURL(), 102 `/allocations/${allocation.id}`, 103 'Allocations breadcrumb links correctly' 104 ); 105 }); 106 107 test('/allocation/:id/:task_name should include resource utilization graphs', async function(assert) { 108 assert.equal(Task.resourceCharts.length, 2, 'Two resource utilization graphs'); 109 assert.equal(Task.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU'); 110 assert.equal(Task.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory'); 111 }); 112 113 test('the events table lists all recent events', async function(assert) { 114 const events = server.db.taskEvents.where({ taskStateId: task.id }); 115 116 assert.equal(Task.events.length, events.length, `Lists ${events.length} events`); 117 }); 118 119 test('when a task has volumes, the volumes table is shown', async function(assert) { 120 const taskGroup = server.schema.taskGroups.where({ 121 jobId: allocation.jobId, 122 name: allocation.taskGroup, 123 }).models[0]; 124 125 const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); 126 127 assert.ok(Task.hasVolumes); 128 assert.equal(Task.volumes.length, jobTask.volumeMounts.length); 129 }); 130 131 test('when a task does not have volumes, the volumes table is not shown', async function(assert) { 132 const job = server.create('job', { createAllocations: false, noHostVolumes: true }); 133 allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' }); 134 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 135 136 await Task.visit({ id: allocation.id, name: task.name }); 137 assert.notOk(Task.hasVolumes); 138 }); 139 140 test('each volume in the volumes table shows information about the volume', async function(assert) { 141 const taskGroup = server.schema.taskGroups.where({ 142 jobId: allocation.jobId, 143 name: allocation.taskGroup, 144 }).models[0]; 145 146 const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); 147 const volume = jobTask.volumeMounts[0]; 148 149 Task.volumes[0].as(volumeRow => { 150 assert.equal(volumeRow.name, volume.Volume); 151 assert.equal(volumeRow.destination, volume.Destination); 152 assert.equal(volumeRow.permissions, volume.ReadOnly ? 'Read' : 'Read/Write'); 153 assert.equal(volumeRow.clientSource, taskGroup.volumes[volume.Volume].Source); 154 }); 155 }); 156 157 test('each recent event should list the time, type, and description of the event', async function(assert) { 158 const event = server.db.taskEvents.where({ taskStateId: task.id })[0]; 159 const recentEvent = Task.events.objectAt(Task.events.length - 1); 160 161 assert.equal( 162 recentEvent.time, 163 moment(event.time / 1000000).format("MMM DD, 'YY HH:mm:ss ZZ"), 164 'Event timestamp' 165 ); 166 assert.equal(recentEvent.type, event.type, 'Event type'); 167 assert.equal(recentEvent.message, event.displayMessage, 'Event message'); 168 }); 169 170 test('when the allocation is not found, the application errors', async function(assert) { 171 await Task.visit({ id: 'not-a-real-allocation', name: task.name }); 172 173 assert.equal( 174 server.pretender.handledRequests 175 .filter(request => !request.url.includes('policy')) 176 .findBy('status', 404).url, 177 '/v1/allocation/not-a-real-allocation', 178 'A request to the nonexistent allocation is made' 179 ); 180 assert.equal( 181 currentURL(), 182 `/allocations/not-a-real-allocation/${task.name}`, 183 'The URL persists' 184 ); 185 assert.ok(Task.error.isPresent, 'Error message is shown'); 186 assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); 187 }); 188 189 test('when the allocation is found but the task is not, the application errors', async function(assert) { 190 await Task.visit({ id: allocation.id, name: 'not-a-real-task-name' }); 191 192 assert.ok( 193 server.pretender.handledRequests 194 .filterBy('status', 200) 195 .mapBy('url') 196 .includes(`/v1/allocation/${allocation.id}`), 197 'A request to the allocation is made successfully' 198 ); 199 assert.equal( 200 currentURL(), 201 `/allocations/${allocation.id}/not-a-real-task-name`, 202 'The URL persists' 203 ); 204 assert.ok(Task.error.isPresent, 'Error message is shown'); 205 assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); 206 }); 207 208 test('task can be restarted', async function(assert) { 209 await Task.restart.idle(); 210 await Task.restart.confirm(); 211 212 const request = server.pretender.handledRequests.findBy('method', 'PUT'); 213 assert.equal( 214 request.url, 215 `/v1/client/allocation/${allocation.id}/restart`, 216 'Restart request is made for the allocation' 217 ); 218 219 assert.deepEqual( 220 JSON.parse(request.requestBody), 221 { TaskName: task.name }, 222 'Restart request is made for the correct task' 223 ); 224 }); 225 226 test('when task restart fails (403), an ACL permissions error message is shown', async function(assert) { 227 server.pretender.put('/v1/client/allocation/:id/restart', () => [403, {}, '']); 228 229 await Task.restart.idle(); 230 await Task.restart.confirm(); 231 232 assert.ok(Task.inlineError.isShown, 'Inline error is shown'); 233 assert.ok(Task.inlineError.title.includes('Could Not Restart Task'), 'Title is descriptive'); 234 assert.ok( 235 /ACL token.+?allocation lifecycle/.test(Task.inlineError.message), 236 'Message mentions ACLs and the appropriate permission' 237 ); 238 239 await Task.inlineError.dismiss(); 240 241 assert.notOk(Task.inlineError.isShown, 'Inline error is no longer shown'); 242 }); 243 244 test('when task restart fails (500), the error message from the API is piped through to the alert', async function(assert) { 245 const message = 'A plaintext error message'; 246 server.pretender.put('/v1/client/allocation/:id/restart', () => [500, {}, message]); 247 248 await Task.restart.idle(); 249 await Task.restart.confirm(); 250 251 assert.ok(Task.inlineError.isShown); 252 assert.ok(Task.inlineError.title.includes('Could Not Restart Task')); 253 assert.equal(Task.inlineError.message, message); 254 255 await Task.inlineError.dismiss(); 256 257 assert.notOk(Task.inlineError.isShown); 258 }); 259 260 test('exec button is present', async function(assert) { 261 assert.ok(Task.execButton.isPresent); 262 }); 263 }); 264 265 module('Acceptance | task detail (no addresses)', function(hooks) { 266 setupApplicationTest(hooks); 267 setupMirage(hooks); 268 269 hooks.beforeEach(async function() { 270 server.create('agent'); 271 server.create('node'); 272 server.create('job'); 273 allocation = server.create('allocation', 'withoutTaskWithPorts', { clientStatus: 'running' }); 274 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 275 276 await Task.visit({ id: allocation.id, name: task.name }); 277 }); 278 }); 279 280 module('Acceptance | task detail (different namespace)', function(hooks) { 281 setupApplicationTest(hooks); 282 setupMirage(hooks); 283 284 hooks.beforeEach(async function() { 285 server.create('agent'); 286 server.create('node'); 287 server.create('namespace'); 288 server.create('namespace', { id: 'other-namespace' }); 289 server.create('job', { createAllocations: false, namespaceId: 'other-namespace' }); 290 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 291 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 292 293 await Task.visit({ id: allocation.id, name: task.name }); 294 }); 295 296 test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { 297 const { jobId, taskGroup } = allocation; 298 const job = server.db.jobs.find(jobId); 299 300 await Layout.breadcrumbFor('jobs.index').visit(); 301 assert.equal( 302 currentURL(), 303 '/jobs?namespace=other-namespace', 304 'Jobs breadcrumb links correctly' 305 ); 306 307 await Task.visit({ id: allocation.id, name: task.name }); 308 await Layout.breadcrumbFor('jobs.job.index').visit(); 309 assert.equal( 310 currentURL(), 311 `/jobs/${job.id}?namespace=other-namespace`, 312 'Job breadcrumb links correctly' 313 ); 314 315 await Task.visit({ id: allocation.id, name: task.name }); 316 await Layout.breadcrumbFor('jobs.job.task-group').visit(); 317 assert.equal( 318 currentURL(), 319 `/jobs/${job.id}/${taskGroup}?namespace=other-namespace`, 320 'Task Group breadcrumb links correctly' 321 ); 322 323 await Task.visit({ id: allocation.id, name: task.name }); 324 await Layout.breadcrumbFor('allocations.allocation').visit(); 325 assert.equal( 326 currentURL(), 327 `/allocations/${allocation.id}`, 328 'Allocations breadcrumb links correctly' 329 ); 330 }); 331 }); 332 333 module('Acceptance | task detail (not running)', function(hooks) { 334 setupApplicationTest(hooks); 335 setupMirage(hooks); 336 337 hooks.beforeEach(async function() { 338 server.create('agent'); 339 server.create('node'); 340 server.create('namespace'); 341 server.create('namespace', { id: 'other-namespace' }); 342 server.create('job', { createAllocations: false, namespaceId: 'other-namespace' }); 343 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'complete' }); 344 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 345 346 await Task.visit({ id: allocation.id, name: task.name }); 347 }); 348 349 test('when the allocation for a task is not running, the resource utilization graphs are replaced by an empty message', async function(assert) { 350 assert.equal(Task.resourceCharts.length, 0, 'No resource charts'); 351 assert.equal(Task.resourceEmptyMessage, "Task isn't running", 'Empty message is appropriate'); 352 }); 353 354 test('exec button is absent', async function(assert) { 355 assert.notOk(Task.execButton.isPresent); 356 }); 357 }); 358 359 module('Acceptance | proxy task detail', function(hooks) { 360 setupApplicationTest(hooks); 361 setupMirage(hooks); 362 363 hooks.beforeEach(async function() { 364 server.create('agent'); 365 server.create('node'); 366 server.create('job', { createAllocations: false }); 367 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 368 369 const taskState = allocation.taskStates.models[0]; 370 const task = server.schema.tasks.findBy({ name: taskState.name }); 371 task.update('kind', 'connect-proxy:task'); 372 task.save(); 373 374 await Task.visit({ id: allocation.id, name: taskState.name }); 375 }); 376 377 test('a proxy tag is shown', async function(assert) { 378 assert.ok(Task.title.proxyTag.isPresent); 379 }); 380 });