github.com/hernad/nomad@v1.6.112/ui/tests/integration/components/job-page/service-test.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 import { assign } from '@ember/polyfills'; 7 import { module, test } from 'qunit'; 8 import { setupRenderingTest } from 'ember-qunit'; 9 import { click, find, render } from '@ember/test-helpers'; 10 import hbs from 'htmlbars-inline-precompile'; 11 import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; 12 import { 13 startJob, 14 stopJob, 15 purgeJob, 16 expectError, 17 expectDeleteRequest, 18 expectStartRequest, 19 expectPurgeRequest, 20 } from './helpers'; 21 import Job from 'nomad-ui/tests/pages/jobs/detail'; 22 import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; 23 import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; 24 25 module('Integration | Component | job-page/service', function (hooks) { 26 setupRenderingTest(hooks); 27 28 hooks.beforeEach(function () { 29 fragmentSerializerInitializer(this.owner); 30 window.localStorage.clear(); 31 this.store = this.owner.lookup('service:store'); 32 this.server = startMirage(); 33 this.server.create('namespace'); 34 this.server.create('node-pool'); 35 }); 36 37 hooks.afterEach(function () { 38 this.server.shutdown(); 39 window.localStorage.clear(); 40 }); 41 42 const commonTemplate = hbs` 43 <JobPage::Service 44 @job={{job}} 45 @sortProperty={{sortProperty}} 46 @sortDescending={{sortDescending}} 47 @currentPage={{currentPage}} 48 @gotoJob={{gotoJob}} 49 @statusMode={{statusMode}} 50 @setStatusMode={{setStatusMode}} 51 /> 52 `; 53 54 const commonProperties = (job) => ({ 55 job, 56 sortProperty: 'name', 57 sortDescending: true, 58 currentPage: 1, 59 gotoJob() {}, 60 statusMode: 'current', 61 setStatusMode() {}, 62 }); 63 64 const makeMirageJob = (server, props = {}) => 65 server.create( 66 'job', 67 assign( 68 { 69 type: 'service', 70 createAllocations: false, 71 status: 'running', 72 }, 73 props 74 ) 75 ); 76 77 test('Stopping a job sends a delete request for the job', async function (assert) { 78 assert.expect(1); 79 80 const mirageJob = makeMirageJob(this.server); 81 await this.store.findAll('job'); 82 83 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 84 85 this.setProperties(commonProperties(job)); 86 await render(commonTemplate); 87 88 await stopJob(); 89 expectDeleteRequest(assert, this.server, job); 90 }); 91 92 test('Stopping a job without proper permissions shows an error message', async function (assert) { 93 assert.expect(4); 94 95 this.server.pretender.delete('/v1/job/:id', () => [403, {}, '']); 96 97 const mirageJob = makeMirageJob(this.server); 98 await this.store.findAll('job'); 99 100 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 101 102 this.setProperties(commonProperties(job)); 103 await render(commonTemplate); 104 105 await stopJob(); 106 expectError(assert, 'Could Not Stop Job'); 107 108 await componentA11yAudit(this.element, assert); 109 }); 110 111 test('Starting a job sends a post request for the job using the current definition', async function (assert) { 112 assert.expect(2); 113 114 const mirageJob = makeMirageJob(this.server, { status: 'dead' }); 115 await this.store.findAll('job'); 116 117 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 118 119 this.setProperties(commonProperties(job)); 120 await render(commonTemplate); 121 122 await startJob(); 123 expectStartRequest(assert, this.server, job); 124 }); 125 126 test('Starting a job without proper permissions shows an error message', async function (assert) { 127 assert.expect(3); 128 129 this.server.pretender.post('/v1/job/:id', () => [403, {}, '']); 130 131 const mirageJob = makeMirageJob(this.server, { status: 'dead' }); 132 await this.store.findAll('job'); 133 134 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 135 136 this.setProperties(commonProperties(job)); 137 await render(commonTemplate); 138 139 await startJob(); 140 141 await expectError(assert, 'Could Not Start Job'); 142 }); 143 144 test('Purging a job sends a purge request for the job', async function (assert) { 145 assert.expect(1); 146 147 const mirageJob = makeMirageJob(this.server, { status: 'dead' }); 148 await this.store.findAll('job'); 149 150 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 151 152 this.setProperties(commonProperties(job)); 153 await render(commonTemplate); 154 155 await purgeJob(); 156 expectPurgeRequest(assert, this.server, job); 157 }); 158 159 test('Recent allocations shows allocations in the job context', async function (assert) { 160 assert.expect(3); 161 162 this.server.create('node'); 163 const mirageJob = makeMirageJob(this.server, { createAllocations: true }); 164 await this.store.findAll('job'); 165 166 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 167 168 this.setProperties(commonProperties(job)); 169 await render(commonTemplate); 170 171 const allocation = this.server.db.allocations 172 .sortBy('modifyIndex') 173 .reverse()[0]; 174 const allocationRow = Job.allocations.objectAt(0); 175 176 assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'ID'); 177 assert.equal( 178 allocationRow.taskGroup, 179 allocation.taskGroup, 180 'Task Group name' 181 ); 182 183 await componentA11yAudit(this.element, assert); 184 }); 185 186 test('Recent allocations caps out at five', async function (assert) { 187 this.server.create('node'); 188 const mirageJob = makeMirageJob(this.server); 189 this.server.createList('allocation', 10); 190 191 await this.store.findAll('job'); 192 193 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 194 195 this.setProperties(commonProperties(job)); 196 await render(commonTemplate); 197 198 assert.equal(Job.allocations.length, 5, 'Capped at 5 allocations'); 199 assert.ok( 200 Job.viewAllAllocations.includes(job.get('allocations.length') + ''), 201 `View link mentions ${job.get('allocations.length')} allocations` 202 ); 203 }); 204 205 test('Recent allocations shows an empty message when the job has no allocations', async function (assert) { 206 assert.expect(2); 207 208 this.server.create('node'); 209 const mirageJob = makeMirageJob(this.server); 210 211 await this.store.findAll('job'); 212 213 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 214 215 this.setProperties(commonProperties(job)); 216 await render(commonTemplate); 217 218 assert.ok( 219 Job.recentAllocationsEmptyState.headline.includes('No Allocations'), 220 'No allocations empty message' 221 ); 222 223 await componentA11yAudit(this.element, assert); 224 }); 225 226 test('Active deployment can be promoted', async function (assert) { 227 this.server.create('node'); 228 const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); 229 230 await this.store.findAll('job'); 231 232 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 233 const deployment = await job.get('latestDeployment'); 234 235 this.setProperties(commonProperties(job)); 236 await render(commonTemplate); 237 238 await click('[data-test-promote-canary]'); 239 240 const requests = this.server.pretender.handledRequests; 241 242 assert.ok( 243 requests 244 .filterBy('method', 'POST') 245 .findBy('url', `/v1/deployment/promote/${deployment.get('id')}`), 246 'A promote POST request was made' 247 ); 248 }); 249 250 test('When promoting the active deployment fails, an error is shown', async function (assert) { 251 assert.expect(4); 252 253 this.server.pretender.post('/v1/deployment/promote/:id', () => [ 254 403, 255 {}, 256 '', 257 ]); 258 259 this.server.create('node'); 260 const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); 261 262 await this.store.findAll('job'); 263 264 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 265 266 this.setProperties(commonProperties(job)); 267 await render(commonTemplate); 268 269 await click('[data-test-promote-canary]'); 270 271 assert.equal( 272 find('[data-test-job-error-title]').textContent, 273 'Could Not Promote Deployment', 274 'Appropriate error is shown' 275 ); 276 assert.ok( 277 find('[data-test-job-error-body]').textContent.includes('ACL'), 278 'The error message mentions ACLs' 279 ); 280 281 await componentA11yAudit( 282 this.element, 283 assert, 284 'scrollable-region-focusable' 285 ); //keyframe animation fades from opacity 0 286 287 await click('[data-test-job-error-close]'); 288 289 assert.notOk( 290 find('[data-test-job-error-title]'), 291 'Error message is dismissable' 292 ); 293 }); 294 295 test('Active deployment can be failed', async function (assert) { 296 this.server.create('node'); 297 const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); 298 299 await this.store.findAll('job'); 300 301 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 302 const deployment = await job.get('latestDeployment'); 303 304 this.setProperties(commonProperties(job)); 305 await render(commonTemplate); 306 307 await click('.active-deployment [data-test-fail]'); 308 309 const requests = this.server.pretender.handledRequests; 310 311 assert.ok( 312 requests 313 .filterBy('method', 'POST') 314 .findBy('url', `/v1/deployment/fail/${deployment.get('id')}`), 315 'A fail POST request was made' 316 ); 317 }); 318 319 test('When failing the active deployment fails, an error is shown', async function (assert) { 320 assert.expect(4); 321 322 this.server.pretender.post('/v1/deployment/fail/:id', () => [403, {}, '']); 323 324 this.server.create('node'); 325 const mirageJob = makeMirageJob(this.server, { activeDeployment: true }); 326 327 await this.store.findAll('job'); 328 329 const job = this.store.peekAll('job').findBy('plainId', mirageJob.id); 330 331 this.setProperties(commonProperties(job)); 332 await render(commonTemplate); 333 334 await click('.active-deployment [data-test-fail]'); 335 336 assert.equal( 337 find('[data-test-job-error-title]').textContent, 338 'Could Not Fail Deployment', 339 'Appropriate error is shown' 340 ); 341 assert.ok( 342 find('[data-test-job-error-body]').textContent.includes('ACL'), 343 'The error message mentions ACLs' 344 ); 345 346 await componentA11yAudit( 347 this.element, 348 assert, 349 'scrollable-region-focusable' 350 ); //keyframe animation fades from opacity 0 351 352 await click('[data-test-job-error-close]'); 353 354 assert.notOk( 355 find('[data-test-job-error-title]'), 356 'Error message is dismissable' 357 ); 358 }); 359 });