github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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 await settled(); 61 assert.deepEqual( 62 pretender.handledRequests.mapBy('url'), 63 [`/v1/job/${jobName}`], 64 'The only request made is /job/:id' 65 ); 66 }); 67 68 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) { 69 window.localStorage.nomadActiveNamespace = 'some-namespace'; 70 71 await this.initializeUI(); 72 73 const { pretender } = this.server; 74 const jobName = 'job-1'; 75 const jobNamespace = 'default'; 76 const jobId = JSON.stringify([jobName, jobNamespace]); 77 78 this.subject().findRecord(null, { modelName: 'job' }, jobId); 79 await settled(); 80 81 assert.deepEqual( 82 pretender.handledRequests.mapBy('url'), 83 [`/v1/job/${jobName}`], 84 'The only request made is /job/:id with no namespace query param' 85 ); 86 }); 87 88 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) { 89 window.localStorage.nomadActiveNamespace = 'red-herring'; 90 91 await this.initializeUI(); 92 93 const { pretender } = this.server; 94 const jobName = 'job-1'; 95 const jobNamespace = 'default'; 96 const jobId = JSON.stringify([jobName, jobNamespace]); 97 98 this.subject().findRecord(null, { modelName: 'job' }, jobId); 99 await settled(); 100 101 assert.deepEqual( 102 pretender.handledRequests.mapBy('url'), 103 [`/v1/job/${jobName}`], 104 'The request made is /job/:id with no namespace query param' 105 ); 106 }); 107 108 test('When the job has a namespace other than default, it is in the URL', async function(assert) { 109 await this.initializeUI(); 110 111 const { pretender } = this.server; 112 const jobName = 'job-2'; 113 const jobNamespace = 'some-namespace'; 114 const jobId = JSON.stringify([jobName, jobNamespace]); 115 116 this.subject().findRecord(null, { modelName: 'job' }, jobId); 117 await settled(); 118 119 assert.deepEqual( 120 pretender.handledRequests.mapBy('url'), 121 [`/v1/job/${jobName}?namespace=${jobNamespace}`], 122 'The only request made is /job/:id?namespace=:namespace' 123 ); 124 }); 125 126 test('When there is no token set in the token service, no x-nomad-token header is set', async function(assert) { 127 await this.initializeUI(); 128 129 const { pretender } = this.server; 130 const jobId = JSON.stringify(['job-1', 'default']); 131 132 this.subject().findRecord(null, { modelName: 'job' }, jobId); 133 await settled(); 134 135 assert.notOk( 136 pretender.handledRequests.mapBy('requestHeaders').some(headers => headers['X-Nomad-Token']), 137 'No token header present on either job request' 138 ); 139 }); 140 141 test('When a token is set in the token service, then x-nomad-token header is set', async function(assert) { 142 await this.initializeUI(); 143 144 const { pretender } = this.server; 145 const jobId = JSON.stringify(['job-1', 'default']); 146 const secret = 'here is the secret'; 147 148 this.subject().set('token.secret', secret); 149 this.subject().findRecord(null, { modelName: 'job' }, jobId); 150 await settled(); 151 152 assert.ok( 153 pretender.handledRequests 154 .mapBy('requestHeaders') 155 .every(headers => headers['X-Nomad-Token'] === secret), 156 'The token header is present on both job requests' 157 ); 158 }); 159 160 test('findAll can be watched', async function(assert) { 161 await this.initializeUI(); 162 163 const { pretender } = this.server; 164 165 const request = () => 166 this.subject().findAll(null, { modelName: 'job' }, null, { 167 reload: true, 168 adapterOptions: { watch: true }, 169 }); 170 171 request(); 172 assert.equal( 173 pretender.handledRequests[0].url, 174 '/v1/jobs?index=1', 175 'Second request is a blocking request for jobs' 176 ); 177 178 await settled(); 179 request(); 180 assert.equal( 181 pretender.handledRequests[1].url, 182 '/v1/jobs?index=2', 183 'Third request is a blocking request with an incremented index param' 184 ); 185 186 await settled(); 187 }); 188 189 test('findRecord can be watched', async function(assert) { 190 await this.initializeUI(); 191 192 const jobId = JSON.stringify(['job-1', 'default']); 193 const { pretender } = this.server; 194 195 const request = () => 196 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 197 reload: true, 198 adapterOptions: { watch: true }, 199 }); 200 201 request(); 202 assert.equal( 203 pretender.handledRequests[0].url, 204 '/v1/job/job-1?index=1', 205 'Second request is a blocking request for job-1' 206 ); 207 208 await settled(); 209 request(); 210 assert.equal( 211 pretender.handledRequests[1].url, 212 '/v1/job/job-1?index=2', 213 'Third request is a blocking request with an incremented index param' 214 ); 215 216 await settled(); 217 }); 218 219 test('relationships can be reloaded', async function(assert) { 220 await this.initializeUI(); 221 222 const { pretender } = this.server; 223 const plainId = 'job-1'; 224 const mockModel = makeMockModel(plainId); 225 226 this.subject().reloadRelationship(mockModel, 'summary'); 227 await settled(); 228 assert.equal( 229 pretender.handledRequests[0].url, 230 `/v1/job/${plainId}/summary`, 231 'Relationship was reloaded' 232 ); 233 }); 234 235 test('relationship reloads can be watched', async function(assert) { 236 await this.initializeUI(); 237 238 const { pretender } = this.server; 239 const plainId = 'job-1'; 240 const mockModel = makeMockModel(plainId); 241 242 this.subject().reloadRelationship(mockModel, 'summary', { watch: true }); 243 assert.equal( 244 pretender.handledRequests[0].url, 245 '/v1/job/job-1/summary?index=1', 246 'First request is a blocking request for job-1 summary relationship' 247 ); 248 249 await settled(); 250 this.subject().reloadRelationship(mockModel, 'summary', { watch: true }); 251 await settled(); 252 253 assert.equal( 254 pretender.handledRequests[1].url, 255 '/v1/job/job-1/summary?index=2', 256 'Second request is a blocking request with an incremented index param' 257 ); 258 }); 259 260 test('findAll can be canceled', async function(assert) { 261 await this.initializeUI(); 262 263 const { pretender } = this.server; 264 const token = new XHRToken(); 265 266 pretender.get('/v1/jobs', () => [200, {}, '[]'], true); 267 268 this.subject() 269 .findAll(null, { modelName: 'job' }, null, { 270 reload: true, 271 adapterOptions: { watch: true, abortToken: token }, 272 }) 273 .catch(() => {}); 274 275 const { request: xhr } = pretender.requestReferences[0]; 276 assert.equal(xhr.status, 0, 'Request is still pending'); 277 278 // Schedule the cancelation before waiting 279 run.next(() => { 280 token.abort(); 281 }); 282 283 await settled(); 284 assert.ok(xhr.aborted, 'Request was aborted'); 285 }); 286 287 test('findRecord can be canceled', async function(assert) { 288 await this.initializeUI(); 289 290 const { pretender } = this.server; 291 const jobId = JSON.stringify(['job-1', 'default']); 292 const token = new XHRToken(); 293 294 pretender.get('/v1/job/:id', () => [200, {}, '{}'], true); 295 296 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 297 reload: true, 298 adapterOptions: { watch: true, abortToken: token }, 299 }); 300 301 const { request: xhr } = pretender.requestReferences[0]; 302 assert.equal(xhr.status, 0, 'Request is still pending'); 303 304 // Schedule the cancelation before waiting 305 run.next(() => { 306 token.abort(); 307 }); 308 309 await settled(); 310 assert.ok(xhr.aborted, 'Request was aborted'); 311 }); 312 313 test('relationship reloads can be canceled', async function(assert) { 314 await this.initializeUI(); 315 316 const { pretender } = this.server; 317 const plainId = 'job-1'; 318 const token = new XHRToken(); 319 const mockModel = makeMockModel(plainId); 320 pretender.get('/v1/job/:id/summary', () => [200, {}, '{}'], true); 321 322 this.subject().reloadRelationship(mockModel, 'summary', { watch: true, abortToken: token }); 323 324 const { request: xhr } = pretender.requestReferences[0]; 325 assert.equal(xhr.status, 0, 'Request is still pending'); 326 327 // Schedule the cancelation before waiting 328 run.next(() => { 329 token.abort(); 330 }); 331 332 await settled(); 333 assert.ok(xhr.aborted, 'Request was aborted'); 334 }); 335 336 test('requests can be canceled even if multiple requests for the same URL were made', async function(assert) { 337 await this.initializeUI(); 338 339 const { pretender } = this.server; 340 const jobId = JSON.stringify(['job-1', 'default']); 341 const token1 = new XHRToken(); 342 const token2 = new XHRToken(); 343 344 pretender.get('/v1/job/:id', () => [200, {}, '{}'], true); 345 346 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 347 reload: true, 348 adapterOptions: { watch: true, abortToken: token1 }, 349 }); 350 351 this.subject().findRecord(null, { modelName: 'job' }, jobId, { 352 reload: true, 353 adapterOptions: { watch: true, abortToken: token2 }, 354 }); 355 356 const { request: xhr } = pretender.requestReferences[0]; 357 const { request: xhr2 } = pretender.requestReferences[1]; 358 assert.equal(xhr.status, 0, 'Request is still pending'); 359 assert.equal(pretender.requestReferences.length, 2, 'Two findRecord requests were made'); 360 assert.equal( 361 pretender.requestReferences.mapBy('url').uniq().length, 362 1, 363 'The two requests have the same URL' 364 ); 365 366 // Schedule the cancelation and resolution before waiting 367 run.next(() => { 368 token1.abort(); 369 pretender.resolve(xhr2); 370 }); 371 372 await settled(); 373 assert.ok(xhr.aborted, 'Request one was aborted'); 374 assert.notOk(xhr2.aborted, 'Request two was not aborted'); 375 }); 376 377 test('when there is no region set, requests are made without the region query param', async function(assert) { 378 await this.initializeUI(); 379 380 const { pretender } = this.server; 381 const jobName = 'job-1'; 382 const jobNamespace = 'default'; 383 const jobId = JSON.stringify([jobName, jobNamespace]); 384 385 await settled(); 386 this.subject().findRecord(null, { modelName: 'job' }, jobId); 387 this.subject().findAll(null, { modelName: 'job' }, null); 388 await settled(); 389 390 assert.deepEqual( 391 pretender.handledRequests.mapBy('url'), 392 [`/v1/job/${jobName}`, '/v1/jobs'], 393 'No requests include the region query param' 394 ); 395 }); 396 397 test('when there is a region set, requests are made with the region query param', async function(assert) { 398 const region = 'region-2'; 399 window.localStorage.nomadActiveRegion = region; 400 401 await this.initializeUI(); 402 403 const { pretender } = this.server; 404 const jobName = 'job-1'; 405 const jobNamespace = 'default'; 406 const jobId = JSON.stringify([jobName, jobNamespace]); 407 408 await settled(); 409 this.subject().findRecord(null, { modelName: 'job' }, jobId); 410 this.subject().findAll(null, { modelName: 'job' }, null); 411 await settled(); 412 413 assert.deepEqual( 414 pretender.handledRequests.mapBy('url'), 415 [`/v1/job/${jobName}?region=${region}`, `/v1/jobs?region=${region}`], 416 'Requests include the region query param' 417 ); 418 }); 419 420 test('when the region is set to the default region, requests are made without the region query param', async function(assert) { 421 window.localStorage.nomadActiveRegion = 'region-1'; 422 423 await this.initializeUI(); 424 425 const { pretender } = this.server; 426 const jobName = 'job-1'; 427 const jobNamespace = 'default'; 428 const jobId = JSON.stringify([jobName, jobNamespace]); 429 430 await settled(); 431 this.subject().findRecord(null, { modelName: 'job' }, jobId); 432 this.subject().findAll(null, { modelName: 'job' }, null); 433 await settled(); 434 435 assert.deepEqual( 436 pretender.handledRequests.mapBy('url'), 437 [`/v1/job/${jobName}`, '/v1/jobs'], 438 'No requests include the region query param' 439 ); 440 }); 441 }); 442 443 function makeMockModel(id, options) { 444 return assign( 445 { 446 relationshipFor(name) { 447 return { 448 kind: 'belongsTo', 449 type: 'job-summary', 450 key: name, 451 }; 452 }, 453 belongsTo(name) { 454 return { 455 link() { 456 return `/v1/job/${id}/${name}`; 457 }, 458 }; 459 }, 460 }, 461 options 462 ); 463 }