github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/ui/tests/unit/adapters/job-test.js (about) 1 import EmberObject from '@ember/object'; 2 import { getOwner } from '@ember/application'; 3 import { run } from '@ember/runloop'; 4 import { assign } from '@ember/polyfills'; 5 import { test } from 'ember-qunit'; 6 import wait from 'ember-test-helpers/wait'; 7 import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; 8 import moduleForAdapter from '../../helpers/module-for-adapter'; 9 10 moduleForAdapter('job', 'Unit | Adapter | Job', { 11 needs: [ 12 'adapter:application', 13 'adapter:job', 14 'adapter:namespace', 15 'model:task-group', 16 'model:allocation', 17 'model:deployment', 18 'model:evaluation', 19 'model:job-summary', 20 'model:job-version', 21 'model:namespace', 22 'model:task-group-summary', 23 'serializer:namespace', 24 'serializer:job', 25 'serializer:job-summary', 26 'service:token', 27 'service:system', 28 'service:watchList', 29 'transform:fragment', 30 'transform:fragment-array', 31 ], 32 beforeEach() { 33 window.sessionStorage.clear(); 34 window.localStorage.clear(); 35 36 this.server = startMirage(); 37 this.server.create('namespace'); 38 this.server.create('namespace', { id: 'some-namespace' }); 39 this.server.create('node'); 40 this.server.create('job', { id: 'job-1', namespaceId: 'default' }); 41 this.server.create('job', { id: 'job-2', namespaceId: 'some-namespace' }); 42 43 this.server.create('region', { id: 'region-1' }); 44 this.server.create('region', { id: 'region-2' }); 45 46 this.system = getOwner(this).lookup('service:system'); 47 48 // Namespace, default region, and all regions are requests that all 49 // job requests depend on. Fetching them ahead of time means testing 50 // job adapter behavior in isolation. 51 this.system.get('namespaces'); 52 this.system.get('shouldIncludeRegion'); 53 this.system.get('defaultRegion'); 54 55 // Reset the handledRequests array to avoid accounting for this 56 // namespaces request everywhere. 57 this.server.pretender.handledRequests.length = 0; 58 }, 59 afterEach() { 60 this.server.shutdown(); 61 }, 62 }); 63 64 test('The job endpoint is the only required endpoint for fetching a job', function(assert) { 65 const { pretender } = this.server; 66 const jobName = 'job-1'; 67 const jobNamespace = 'default'; 68 const jobId = JSON.stringify([jobName, jobNamespace]); 69 70 return wait().then(() => { 71 this.subject().findRecord(null, { modelName: 'job' }, jobId); 72 73 assert.deepEqual( 74 pretender.handledRequests.mapBy('url'), 75 [`/v1/job/${jobName}`], 76 'The only request made is /job/:id' 77 ); 78 }); 79 }); 80 81 test('When a namespace is set in localStorage but a job in the default namespace is requested, the namespace query param is not present', function(assert) { 82 window.localStorage.nomadActiveNamespace = 'some-namespace'; 83 84 const { pretender } = this.server; 85 const jobName = 'job-1'; 86 const jobNamespace = 'default'; 87 const jobId = JSON.stringify([jobName, jobNamespace]); 88 89 this.system.get('namespaces'); 90 return wait().then(() => { 91 this.subject().findRecord(null, { modelName: 'job' }, jobId); 92 93 assert.deepEqual( 94 pretender.handledRequests.mapBy('url'), 95 [`/v1/job/${jobName}`], 96 'The only request made is /job/:id with no namespace query param' 97 ); 98 }); 99 }); 100 101 test('When a namespace is in localStorage and the requested job is in the default namespace, the namespace query param is left out', function(assert) { 102 window.localStorage.nomadActiveNamespace = 'red-herring'; 103 104 const { pretender } = this.server; 105 const jobName = 'job-1'; 106 const jobNamespace = 'default'; 107 const jobId = JSON.stringify([jobName, jobNamespace]); 108 109 return wait().then(() => { 110 this.subject().findRecord(null, { modelName: 'job' }, jobId); 111 112 assert.deepEqual( 113 pretender.handledRequests.mapBy('url'), 114 [`/v1/job/${jobName}`], 115 'The request made is /job/:id with no namespace query param' 116 ); 117 }); 118 }); 119 120 test('When the job has a namespace other than default, it is in the URL', function(assert) { 121 const { pretender } = this.server; 122 const jobName = 'job-2'; 123 const jobNamespace = 'some-namespace'; 124 const jobId = JSON.stringify([jobName, jobNamespace]); 125 126 return wait().then(() => { 127 this.subject().findRecord(null, { modelName: 'job' }, jobId); 128 129 assert.deepEqual( 130 pretender.handledRequests.mapBy('url'), 131 [`/v1/job/${jobName}?namespace=${jobNamespace}`], 132 'The only request made is /job/:id?namespace=:namespace' 133 ); 134 }); 135 }); 136 137 test('When there is no token set in the token service, no x-nomad-token header is set', function(assert) { 138 const { pretender } = this.server; 139 const jobId = JSON.stringify(['job-1', 'default']); 140 141 return wait().then(() => { 142 this.subject().findRecord(null, { modelName: 'job' }, jobId); 143 144 assert.notOk( 145 pretender.handledRequests.mapBy('requestHeaders').some(headers => headers['X-Nomad-Token']), 146 'No token header present on either job request' 147 ); 148 }); 149 }); 150 151 test('When a token is set in the token service, then x-nomad-token header is set', function(assert) { 152 const { pretender } = this.server; 153 const jobId = JSON.stringify(['job-1', 'default']); 154 const secret = 'here is the secret'; 155 156 return wait().then(() => { 157 this.subject().set('token.secret', secret); 158 this.subject().findRecord(null, { modelName: 'job' }, jobId); 159 160 assert.ok( 161 pretender.handledRequests 162 .mapBy('requestHeaders') 163 .every(headers => headers['X-Nomad-Token'] === secret), 164 'The token header is present on both job requests' 165 ); 166 }); 167 }); 168 169 test('findAll can be watched', function(assert) { 170 const { pretender } = this.server; 171 172 const request = () => 173 this.subject().findAll(null, { modelName: 'job' }, null, { 174 reload: true, 175 adapterOptions: { watch: true }, 176 }); 177 178 request(); 179 assert.equal( 180 pretender.handledRequests[0].url, 181 '/v1/jobs?index=1', 182 'Second request is a blocking request for jobs' 183 ); 184 185 return wait().then(() => { 186 request(); 187 assert.equal( 188 pretender.handledRequests[1].url, 189 '/v1/jobs?index=2', 190 'Third request is a blocking request with an incremented index param' 191 ); 192 193 return wait(); 194 }); 195 }); 196 197 test('findRecord can be watched', function(assert) { 198 const jobId = JSON.stringify(['job-1', 'default']); 199 const { pretender } = this.server; 200 201 const request = () => 202 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 203 reload: true, 204 adapterOptions: { watch: true }, 205 }); 206 207 request(); 208 assert.equal( 209 pretender.handledRequests[0].url, 210 '/v1/job/job-1?index=1', 211 'Second request is a blocking request for job-1' 212 ); 213 214 return wait().then(() => { 215 request(); 216 assert.equal( 217 pretender.handledRequests[1].url, 218 '/v1/job/job-1?index=2', 219 'Third request is a blocking request with an incremented index param' 220 ); 221 222 return wait(); 223 }); 224 }); 225 226 test('relationships can be reloaded', function(assert) { 227 const { pretender } = this.server; 228 const plainId = 'job-1'; 229 const mockModel = makeMockModel(plainId); 230 231 this.subject().reloadRelationship(mockModel, 'summary'); 232 return wait().then(() => { 233 assert.equal( 234 pretender.handledRequests[0].url, 235 `/v1/job/${plainId}/summary`, 236 'Relationship was reloaded' 237 ); 238 }); 239 }); 240 241 test('relationship reloads can be watched', function(assert) { 242 const { pretender } = this.server; 243 const plainId = 'job-1'; 244 const mockModel = makeMockModel(plainId); 245 246 this.subject().reloadRelationship(mockModel, 'summary', true); 247 assert.equal( 248 pretender.handledRequests[0].url, 249 '/v1/job/job-1/summary?index=1', 250 'First request is a blocking request for job-1 summary relationship' 251 ); 252 253 return wait().then(() => { 254 this.subject().reloadRelationship(mockModel, 'summary', true); 255 assert.equal( 256 pretender.handledRequests[1].url, 257 '/v1/job/job-1/summary?index=2', 258 'Second request is a blocking request with an incremented index param' 259 ); 260 }); 261 }); 262 263 test('findAll can be canceled', function(assert) { 264 const { pretender } = this.server; 265 pretender.get('/v1/jobs', () => [200, {}, '[]'], true); 266 267 this.subject() 268 .findAll(null, { modelName: 'job' }, null, { 269 reload: true, 270 adapterOptions: { watch: true }, 271 }) 272 .catch(() => {}); 273 274 const { request: xhr } = pretender.requestReferences[0]; 275 assert.equal(xhr.status, 0, 'Request is still pending'); 276 277 // Schedule the cancelation before waiting 278 run.next(() => { 279 this.subject().cancelFindAll('job'); 280 }); 281 282 return wait().then(() => { 283 assert.ok(xhr.aborted, 'Request was aborted'); 284 }); 285 }); 286 287 test('findRecord can be canceled', function(assert) { 288 const { pretender } = this.server; 289 const jobId = JSON.stringify(['job-1', 'default']); 290 291 pretender.get('/v1/job/:id', () => [200, {}, '{}'], true); 292 293 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 294 reload: true, 295 adapterOptions: { watch: true }, 296 }); 297 298 const { request: xhr } = pretender.requestReferences[0]; 299 assert.equal(xhr.status, 0, 'Request is still pending'); 300 301 // Schedule the cancelation before waiting 302 run.next(() => { 303 this.subject().cancelFindRecord('job', jobId); 304 }); 305 306 return wait().then(() => { 307 assert.ok(xhr.aborted, 'Request was aborted'); 308 }); 309 }); 310 311 test('relationship reloads can be canceled', function(assert) { 312 const { pretender } = this.server; 313 const plainId = 'job-1'; 314 const mockModel = makeMockModel(plainId); 315 pretender.get('/v1/job/:id/summary', () => [200, {}, '{}'], true); 316 317 this.subject().reloadRelationship(mockModel, 'summary', true); 318 319 const { request: xhr } = pretender.requestReferences[0]; 320 assert.equal(xhr.status, 0, 'Request is still pending'); 321 322 // Schedule the cancelation before waiting 323 run.next(() => { 324 this.subject().cancelReloadRelationship(mockModel, 'summary'); 325 }); 326 327 return wait().then(() => { 328 assert.ok(xhr.aborted, 'Request was aborted'); 329 }); 330 }); 331 332 test('requests can be canceled even if multiple requests for the same URL were made', function(assert) { 333 const { pretender } = this.server; 334 const jobId = JSON.stringify(['job-1', 'default']); 335 336 pretender.get('/v1/job/:id', () => [200, {}, '{}'], true); 337 338 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 339 reload: true, 340 adapterOptions: { watch: true }, 341 }); 342 343 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 344 reload: true, 345 adapterOptions: { watch: true }, 346 }); 347 348 const { request: xhr } = pretender.requestReferences[0]; 349 assert.equal(xhr.status, 0, 'Request is still pending'); 350 assert.equal(pretender.requestReferences.length, 2, 'Two findRecord requests were made'); 351 assert.equal( 352 pretender.requestReferences.mapBy('url').uniq().length, 353 1, 354 'The two requests have the same URL' 355 ); 356 357 // Schedule the cancelation before waiting 358 run.next(() => { 359 this.subject().cancelFindRecord('job', jobId); 360 }); 361 362 return wait().then(() => { 363 assert.ok(xhr.aborted, 'Request was aborted'); 364 }); 365 }); 366 367 test('canceling a find record request will never cancel a request with the same url but different method', function(assert) { 368 const { pretender } = this.server; 369 const jobId = JSON.stringify(['job-1', 'default']); 370 371 pretender.get('/v1/job/:id', () => [200, {}, '{}'], true); 372 pretender.delete('/v1/job/:id', () => [204, {}, ''], 200); 373 374 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 375 reload: true, 376 adapterOptions: { watch: true }, 377 }); 378 379 this.subject().stop(EmberObject.create({ id: jobId })); 380 381 const { request: getXHR } = pretender.requestReferences[0]; 382 const { request: deleteXHR } = pretender.requestReferences[1]; 383 assert.equal(getXHR.status, 0, 'Get request is still pending'); 384 assert.equal(deleteXHR.status, 0, 'Delete request is still pending'); 385 386 // Schedule the cancelation before waiting 387 run.next(() => { 388 this.subject().cancelFindRecord('job', jobId); 389 }); 390 391 return wait().then(() => { 392 assert.ok(getXHR.aborted, 'Get request was aborted'); 393 assert.notOk(deleteXHR.aborted, 'Delete request was aborted'); 394 }); 395 }); 396 397 test('when there is no region set, requests are made without the region query param', function(assert) { 398 const { pretender } = this.server; 399 const jobName = 'job-1'; 400 const jobNamespace = 'default'; 401 const jobId = JSON.stringify([jobName, jobNamespace]); 402 403 return wait().then(() => { 404 this.subject().findRecord(null, { modelName: 'job' }, jobId); 405 this.subject().findAll(null, { modelName: 'job' }, null); 406 407 assert.deepEqual( 408 pretender.handledRequests.mapBy('url'), 409 [`/v1/job/${jobName}`, '/v1/jobs'], 410 'No requests include the region query param' 411 ); 412 }); 413 }); 414 415 test('when there is a region set, requests are made with the region query param', function(assert) { 416 const region = 'region-2'; 417 window.localStorage.nomadActiveRegion = region; 418 419 const { pretender } = this.server; 420 const jobName = 'job-1'; 421 const jobNamespace = 'default'; 422 const jobId = JSON.stringify([jobName, jobNamespace]); 423 424 return wait().then(() => { 425 this.subject().findRecord(null, { modelName: 'job' }, jobId); 426 this.subject().findAll(null, { modelName: 'job' }, null); 427 428 assert.deepEqual( 429 pretender.handledRequests.mapBy('url'), 430 [`/v1/job/${jobName}?region=${region}`, `/v1/jobs?region=${region}`], 431 'Requests include the region query param' 432 ); 433 }); 434 }); 435 436 test('when the region is set to the default region, requests are made without the region query param', function(assert) { 437 window.localStorage.nomadActiveRegion = 'region-1'; 438 439 const { pretender } = this.server; 440 const jobName = 'job-1'; 441 const jobNamespace = 'default'; 442 const jobId = JSON.stringify([jobName, jobNamespace]); 443 444 return wait().then(() => { 445 this.subject().findRecord(null, { modelName: 'job' }, jobId); 446 this.subject().findAll(null, { modelName: 'job' }, null); 447 448 assert.deepEqual( 449 pretender.handledRequests.mapBy('url'), 450 [`/v1/job/${jobName}`, '/v1/jobs'], 451 'No requests include the region query param' 452 ); 453 }); 454 }); 455 456 function makeMockModel(id, options) { 457 return assign( 458 { 459 relationshipFor(name) { 460 return { 461 kind: 'belongsTo', 462 type: 'job-summary', 463 key: name, 464 }; 465 }, 466 belongsTo(name) { 467 return { 468 link() { 469 return `/v1/job/${id}/${name}`; 470 }, 471 }; 472 }, 473 }, 474 options 475 ); 476 }