github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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 { AbortController } from 'fetch';
     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 ({ region, namespace } = {}) => {
    22        if (namespace) window.localStorage.nomadActiveNamespace = namespace;
    23        if (region) window.localStorage.nomadActiveRegion = region;
    24  
    25        this.server.create('namespace');
    26        this.server.create('namespace', { id: 'some-namespace' });
    27        this.server.create('node');
    28        this.server.create('job', { id: 'job-1', namespaceId: 'default' });
    29        this.server.create('job', { id: 'job-2', namespaceId: 'some-namespace' });
    30  
    31        this.server.create('region', { id: 'region-1' });
    32        this.server.create('region', { id: 'region-2' });
    33  
    34        this.system = this.owner.lookup('service:system');
    35  
    36        // Namespace, default region, and all regions are requests that all
    37        // job requests depend on. Fetching them ahead of time means testing
    38        // job adapter behavior in isolation.
    39        await this.system.get('namespaces');
    40        this.system.get('shouldIncludeRegion');
    41        await this.system.get('defaultRegion');
    42  
    43        // Reset the handledRequests array to avoid accounting for this
    44        // namespaces request everywhere.
    45        this.server.pretender.handledRequests.length = 0;
    46      };
    47  
    48      this.initializeWithJob = async (props = {}) => {
    49        await this.initializeUI(props);
    50  
    51        const job = await this.store.findRecord(
    52          'job',
    53          JSON.stringify(['job-1', props.namespace || 'default'])
    54        );
    55        this.server.pretender.handledRequests.length = 0;
    56        return job;
    57      };
    58    });
    59  
    60    hooks.afterEach(function() {
    61      this.server.shutdown();
    62    });
    63  
    64    test('The job endpoint is the only required endpoint for fetching a job', async function(assert) {
    65      await this.initializeUI();
    66  
    67      const { pretender } = this.server;
    68      const jobName = 'job-1';
    69      const jobNamespace = 'default';
    70      const jobId = JSON.stringify([jobName, jobNamespace]);
    71  
    72      this.subject().findRecord(null, { modelName: 'job' }, jobId);
    73  
    74      await settled();
    75      assert.deepEqual(
    76        pretender.handledRequests.mapBy('url'),
    77        [`/v1/job/${jobName}`],
    78        'The only request made is /job/:id'
    79      );
    80    });
    81  
    82    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) {
    83      await this.initializeUI({ namespace: 'some-namespace' });
    84  
    85      const { pretender } = this.server;
    86      const jobName = 'job-1';
    87      const jobNamespace = 'default';
    88      const jobId = JSON.stringify([jobName, jobNamespace]);
    89  
    90      this.subject().findRecord(null, { modelName: 'job' }, jobId);
    91      await settled();
    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    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) {
   101      await this.initializeUI({ namespace: 'red-herring' });
   102  
   103      const { pretender } = this.server;
   104      const jobName = 'job-1';
   105      const jobNamespace = 'default';
   106      const jobId = JSON.stringify([jobName, jobNamespace]);
   107  
   108      this.subject().findRecord(null, { modelName: 'job' }, jobId);
   109      await settled();
   110  
   111      assert.deepEqual(
   112        pretender.handledRequests.mapBy('url'),
   113        [`/v1/job/${jobName}`],
   114        'The request made is /job/:id with no namespace query param'
   115      );
   116    });
   117  
   118    test('When the job has a namespace other than default, it is in the URL', async function(assert) {
   119      await this.initializeUI();
   120  
   121      const { pretender } = this.server;
   122      const jobName = 'job-2';
   123      const jobNamespace = 'some-namespace';
   124      const jobId = JSON.stringify([jobName, jobNamespace]);
   125  
   126      this.subject().findRecord(null, { modelName: 'job' }, jobId);
   127      await settled();
   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    test('When there is no token set in the token service, no 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  
   142      this.subject().findRecord(null, { modelName: 'job' }, jobId);
   143      await settled();
   144  
   145      assert.notOk(
   146        pretender.handledRequests.mapBy('requestHeaders').some(headers => headers['X-Nomad-Token']),
   147        'No token header present on either job request'
   148      );
   149    });
   150  
   151    test('When a token is set in the token service, then X-Nomad-Token header is set', async function(assert) {
   152      await this.initializeUI();
   153  
   154      const { pretender } = this.server;
   155      const jobId = JSON.stringify(['job-1', 'default']);
   156      const secret = 'here is the secret';
   157  
   158      this.subject().set('token.secret', secret);
   159      this.subject().findRecord(null, { modelName: 'job' }, jobId);
   160      await settled();
   161  
   162      assert.ok(
   163        pretender.handledRequests
   164          .mapBy('requestHeaders')
   165          .every(headers => headers['X-Nomad-Token'] === secret),
   166        'The token header is present on both job requests'
   167      );
   168    });
   169  
   170    test('findAll can be watched', async function(assert) {
   171      await this.initializeUI();
   172  
   173      const { pretender } = this.server;
   174  
   175      const request = () =>
   176        this.subject().findAll(null, { modelName: 'job' }, null, {
   177          reload: true,
   178          adapterOptions: { watch: true },
   179        });
   180  
   181      request();
   182      assert.equal(
   183        pretender.handledRequests[0].url,
   184        '/v1/jobs?index=1',
   185        'Second request is a blocking request for jobs'
   186      );
   187  
   188      await settled();
   189      request();
   190      assert.equal(
   191        pretender.handledRequests[1].url,
   192        '/v1/jobs?index=2',
   193        'Third request is a blocking request with an incremented index param'
   194      );
   195  
   196      await settled();
   197    });
   198  
   199    test('findRecord can be watched', async function(assert) {
   200      await this.initializeUI();
   201  
   202      const jobId = JSON.stringify(['job-1', 'default']);
   203      const { pretender } = this.server;
   204  
   205      const request = () =>
   206        this.subject().findRecord(null, { modelName: 'job' }, jobId, {
   207          reload: true,
   208          adapterOptions: { watch: true },
   209        });
   210  
   211      request();
   212      assert.equal(
   213        pretender.handledRequests[0].url,
   214        '/v1/job/job-1?index=1',
   215        'Second request is a blocking request for job-1'
   216      );
   217  
   218      await settled();
   219      request();
   220      assert.equal(
   221        pretender.handledRequests[1].url,
   222        '/v1/job/job-1?index=2',
   223        'Third request is a blocking request with an incremented index param'
   224      );
   225  
   226      await settled();
   227    });
   228  
   229    test('relationships can be reloaded', 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');
   237      await settled();
   238      assert.equal(
   239        pretender.handledRequests[0].url,
   240        `/v1/job/${plainId}/summary`,
   241        'Relationship was reloaded'
   242      );
   243    });
   244  
   245    test('relationship reloads can be watched', async function(assert) {
   246      await this.initializeUI();
   247  
   248      const { pretender } = this.server;
   249      const plainId = 'job-1';
   250      const mockModel = makeMockModel(plainId);
   251  
   252      this.subject().reloadRelationship(mockModel, 'summary', { watch: true });
   253      assert.equal(
   254        pretender.handledRequests[0].url,
   255        '/v1/job/job-1/summary?index=1',
   256        'First request is a blocking request for job-1 summary relationship'
   257      );
   258  
   259      await settled();
   260      this.subject().reloadRelationship(mockModel, 'summary', { watch: true });
   261      await settled();
   262  
   263      assert.equal(
   264        pretender.handledRequests[1].url,
   265        '/v1/job/job-1/summary?index=2',
   266        'Second request is a blocking request with an incremented index param'
   267      );
   268    });
   269  
   270    test('findAll can be canceled', async function(assert) {
   271      await this.initializeUI();
   272  
   273      const { pretender } = this.server;
   274      const controller = new AbortController();
   275  
   276      pretender.get('/v1/jobs', () => [200, {}, '[]'], true);
   277  
   278      this.subject()
   279        .findAll(null, { modelName: 'job' }, null, {
   280          reload: true,
   281          adapterOptions: { watch: true, abortController: controller },
   282        })
   283        .catch(() => {});
   284  
   285      const { request: xhr } = pretender.requestReferences[0];
   286      assert.equal(xhr.status, 0, 'Request is still pending');
   287  
   288      // Schedule the cancelation before waiting
   289      run.next(() => {
   290        controller.abort();
   291      });
   292  
   293      await settled();
   294      assert.ok(xhr.aborted, 'Request was aborted');
   295    });
   296  
   297    test('findRecord can be canceled', async function(assert) {
   298      await this.initializeUI();
   299  
   300      const { pretender } = this.server;
   301      const jobId = JSON.stringify(['job-1', 'default']);
   302      const controller = new AbortController();
   303  
   304      pretender.get('/v1/job/:id', () => [200, {}, '{}'], true);
   305  
   306      this.subject().findRecord(null, { modelName: 'job' }, jobId, {
   307        reload: true,
   308        adapterOptions: { watch: true, abortController: controller },
   309      });
   310  
   311      const { request: xhr } = pretender.requestReferences[0];
   312      assert.equal(xhr.status, 0, 'Request is still pending');
   313  
   314      // Schedule the cancelation before waiting
   315      run.next(() => {
   316        controller.abort();
   317      });
   318  
   319      await settled();
   320      assert.ok(xhr.aborted, 'Request was aborted');
   321    });
   322  
   323    test('relationship reloads can be canceled', async function(assert) {
   324      await this.initializeUI();
   325  
   326      const { pretender } = this.server;
   327      const plainId = 'job-1';
   328      const controller = new AbortController();
   329      const mockModel = makeMockModel(plainId);
   330      pretender.get('/v1/job/:id/summary', () => [200, {}, '{}'], true);
   331  
   332      this.subject().reloadRelationship(mockModel, 'summary', {
   333        watch: true,
   334        abortController: controller,
   335      });
   336  
   337      const { request: xhr } = pretender.requestReferences[0];
   338      assert.equal(xhr.status, 0, 'Request is still pending');
   339  
   340      // Schedule the cancelation before waiting
   341      run.next(() => {
   342        controller.abort();
   343      });
   344  
   345      await settled();
   346      assert.ok(xhr.aborted, 'Request was aborted');
   347    });
   348  
   349    test('requests can be canceled even if multiple requests for the same URL were made', async function(assert) {
   350      await this.initializeUI();
   351  
   352      const { pretender } = this.server;
   353      const jobId = JSON.stringify(['job-1', 'default']);
   354      const controller1 = new AbortController();
   355      const controller2 = new AbortController();
   356  
   357      pretender.get('/v1/job/:id', () => [200, {}, '{}'], true);
   358  
   359      this.subject().findRecord(null, { modelName: 'job' }, jobId, {
   360        reload: true,
   361        adapterOptions: { watch: true, abortController: controller1 },
   362      });
   363  
   364      this.subject().findRecord(null, { modelName: 'job' }, jobId, {
   365        reload: true,
   366        adapterOptions: { watch: true, abortController: controller2 },
   367      });
   368  
   369      const { request: xhr } = pretender.requestReferences[0];
   370      const { request: xhr2 } = pretender.requestReferences[1];
   371      assert.equal(xhr.status, 0, 'Request is still pending');
   372      assert.equal(pretender.requestReferences.length, 2, 'Two findRecord requests were made');
   373      assert.equal(
   374        pretender.requestReferences.mapBy('url').uniq().length,
   375        1,
   376        'The two requests have the same URL'
   377      );
   378  
   379      // Schedule the cancelation and resolution before waiting
   380      run.next(() => {
   381        controller1.abort();
   382        pretender.resolve(xhr2);
   383      });
   384  
   385      await settled();
   386      assert.ok(xhr.aborted, 'Request one was aborted');
   387      assert.notOk(xhr2.aborted, 'Request two was not aborted');
   388    });
   389  
   390    test('when there is no region set, requests are made without the region query param', async function(assert) {
   391      await this.initializeUI();
   392  
   393      const { pretender } = this.server;
   394      const jobName = 'job-1';
   395      const jobNamespace = 'default';
   396      const jobId = JSON.stringify([jobName, jobNamespace]);
   397  
   398      await settled();
   399      this.subject().findRecord(null, { modelName: 'job' }, jobId);
   400      this.subject().findAll(null, { modelName: 'job' }, null);
   401      await settled();
   402  
   403      assert.deepEqual(
   404        pretender.handledRequests.mapBy('url'),
   405        [`/v1/job/${jobName}`, '/v1/jobs'],
   406        'No requests include the region query param'
   407      );
   408    });
   409  
   410    test('when there is a region set, requests are made with the region query param', async function(assert) {
   411      const region = 'region-2';
   412  
   413      await this.initializeUI({ region });
   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      await settled();
   424  
   425      assert.deepEqual(
   426        pretender.handledRequests.mapBy('url'),
   427        [`/v1/job/${jobName}?region=${region}`, `/v1/jobs?region=${region}`],
   428        'Requests include the region query param'
   429      );
   430    });
   431  
   432    test('when the region is set to the default region, requests are made without the region query param', async function(assert) {
   433      await this.initializeUI({ region: 'region-1' });
   434  
   435      const { pretender } = this.server;
   436      const jobName = 'job-1';
   437      const jobNamespace = 'default';
   438      const jobId = JSON.stringify([jobName, jobNamespace]);
   439  
   440      await settled();
   441      this.subject().findRecord(null, { modelName: 'job' }, jobId);
   442      this.subject().findAll(null, { modelName: 'job' }, null);
   443      await settled();
   444  
   445      assert.deepEqual(
   446        pretender.handledRequests.mapBy('url'),
   447        [`/v1/job/${jobName}`, '/v1/jobs'],
   448        'No requests include the region query param'
   449      );
   450    });
   451  
   452    test('fetchRawDefinition requests include the activeRegion', async function(assert) {
   453      const region = 'region-2';
   454      const job = await this.initializeWithJob({ region });
   455  
   456      await this.subject().fetchRawDefinition(job);
   457  
   458      const request = this.server.pretender.handledRequests[0];
   459      assert.equal(request.url, `/v1/job/${job.plainId}?region=${region}`);
   460      assert.equal(request.method, 'GET');
   461    });
   462  
   463    test('forcePeriodic requests include the activeRegion', async function(assert) {
   464      const region = 'region-2';
   465      const job = await this.initializeWithJob({ region });
   466      job.set('periodic', true);
   467  
   468      await this.subject().forcePeriodic(job);
   469  
   470      const request = this.server.pretender.handledRequests[0];
   471      assert.equal(request.url, `/v1/job/${job.plainId}/periodic/force?region=${region}`);
   472      assert.equal(request.method, 'POST');
   473    });
   474  
   475    test('stop requests include the activeRegion', async function(assert) {
   476      const region = 'region-2';
   477      const job = await this.initializeWithJob({ region });
   478  
   479      await this.subject().stop(job);
   480  
   481      const request = this.server.pretender.handledRequests[0];
   482      assert.equal(request.url, `/v1/job/${job.plainId}?region=${region}`);
   483      assert.equal(request.method, 'DELETE');
   484    });
   485  
   486    test('parse requests include the activeRegion', async function(assert) {
   487      const region = 'region-2';
   488      await this.initializeUI({ region });
   489  
   490      await this.subject().parse('job "name-goes-here" {');
   491  
   492      const request = this.server.pretender.handledRequests[0];
   493      assert.equal(request.url, `/v1/jobs/parse?region=${region}`);
   494      assert.equal(request.method, 'POST');
   495      assert.deepEqual(JSON.parse(request.requestBody), {
   496        JobHCL: 'job "name-goes-here" {',
   497        Canonicalize: true,
   498      });
   499    });
   500  
   501    test('plan requests include the activeRegion', async function(assert) {
   502      const region = 'region-2';
   503      const job = await this.initializeWithJob({ region });
   504      job.set('_newDefinitionJSON', {});
   505  
   506      await this.subject().plan(job);
   507  
   508      const request = this.server.pretender.handledRequests[0];
   509      assert.equal(request.url, `/v1/job/${job.plainId}/plan?region=${region}`);
   510      assert.equal(request.method, 'POST');
   511    });
   512  
   513    test('run requests include the activeRegion', async function(assert) {
   514      const region = 'region-2';
   515      const job = await this.initializeWithJob({ region });
   516      job.set('_newDefinitionJSON', {});
   517  
   518      await this.subject().run(job);
   519  
   520      const request = this.server.pretender.handledRequests[0];
   521      assert.equal(request.url, `/v1/jobs?region=${region}`);
   522      assert.equal(request.method, 'POST');
   523    });
   524  
   525    test('update requests include the activeRegion', async function(assert) {
   526      const region = 'region-2';
   527      const job = await this.initializeWithJob({ region });
   528      job.set('_newDefinitionJSON', {});
   529  
   530      await this.subject().update(job);
   531  
   532      const request = this.server.pretender.handledRequests[0];
   533      assert.equal(request.url, `/v1/job/${job.plainId}?region=${region}`);
   534      assert.equal(request.method, 'POST');
   535    });
   536  
   537    test('scale requests include the activeRegion', async function(assert) {
   538      const region = 'region-2';
   539      const job = await this.initializeWithJob({ region });
   540  
   541      await this.subject().scale(job, 'group-1', 5, 'Reason: a test');
   542  
   543      const request = this.server.pretender.handledRequests[0];
   544      assert.equal(request.url, `/v1/job/${job.plainId}/scale?region=${region}`);
   545      assert.equal(request.method, 'POST');
   546    });
   547  });
   548  
   549  function makeMockModel(id, options) {
   550    return assign(
   551      {
   552        relationshipFor(name) {
   553          return {
   554            kind: 'belongsTo',
   555            type: 'job-summary',
   556            key: name,
   557          };
   558        },
   559        belongsTo(name) {
   560          return {
   561            link() {
   562              return `/v1/job/${id}/${name}`;
   563            },
   564          };
   565        },
   566      },
   567      options
   568    );
   569  }