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