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  }