github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/ui/tests/integration/job-editor-test.js (about)

     1  import { assign } from '@ember/polyfills';
     2  import { run } from '@ember/runloop';
     3  import { module, test } from 'qunit';
     4  import { setupRenderingTest } from 'ember-qunit';
     5  import { settled } from '@ember/test-helpers';
     6  import hbs from 'htmlbars-inline-precompile';
     7  import { create } from 'ember-cli-page-object';
     8  import sinon from 'sinon';
     9  import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
    10  import jobEditor from 'nomad-ui/tests/pages/components/job-editor';
    11  import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer';
    12  import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror';
    13  
    14  const Editor = create(jobEditor());
    15  
    16  module('Integration | Component | job-editor', function(hooks) {
    17    setupRenderingTest(hooks);
    18    setupCodeMirror(hooks);
    19  
    20    hooks.beforeEach(async function() {
    21      window.localStorage.clear();
    22  
    23      fragmentSerializerInitializer(this.owner);
    24  
    25      this.store = this.owner.lookup('service:store');
    26      this.server = startMirage();
    27  
    28      // Required for placing allocations (a result of creating jobs)
    29      this.server.create('node');
    30  
    31      await Editor.setContext(this);
    32    });
    33  
    34    hooks.afterEach(async function() {
    35      this.server.shutdown();
    36      await Editor.removeContext();
    37    });
    38  
    39    const newJobName = 'new-job';
    40    const newJobTaskGroupName = 'redis';
    41    const jsonJob = overrides => {
    42      return JSON.stringify(
    43        assign(
    44          {},
    45          {
    46            Name: newJobName,
    47            Namespace: 'default',
    48            Datacenters: ['dc1'],
    49            Priority: 50,
    50            TaskGroups: [
    51              {
    52                Name: newJobTaskGroupName,
    53                Tasks: [
    54                  {
    55                    Name: 'redis',
    56                    Driver: 'docker',
    57                  },
    58                ],
    59              },
    60            ],
    61          },
    62          overrides
    63        ),
    64        null,
    65        2
    66      );
    67    };
    68  
    69    const hclJob = () => `
    70    job "${newJobName}" {
    71      namespace = "default"
    72      datacenters = ["dc1"]
    73  
    74      task "${newJobTaskGroupName}" {
    75        driver = "docker"
    76      }
    77    }
    78    `;
    79  
    80    const commonTemplate = hbs`
    81      {{job-editor
    82        job=job
    83        context=context
    84        onSubmit=onSubmit}}
    85    `;
    86  
    87    const cancelableTemplate = hbs`
    88      {{job-editor
    89        job=job
    90        context=context
    91        cancelable=true
    92        onSubmit=onSubmit
    93        onCancel=onCancel}}
    94    `;
    95  
    96    const renderNewJob = async (component, job) => {
    97      component.setProperties({ job, onSubmit: sinon.spy(), context: 'new' });
    98      component.render(commonTemplate);
    99      await settled();
   100    };
   101  
   102    const renderEditJob = async (component, job) => {
   103      component.setProperties({ job, onSubmit: sinon.spy(), onCancel: sinon.spy(), context: 'edit' });
   104      await component.render(cancelableTemplate);
   105    };
   106  
   107    const planJob = async spec => {
   108      await Editor.editor.fillIn(spec);
   109      await settled();
   110      await Editor.plan();
   111      await settled();
   112    };
   113  
   114    test('the default state is an editor with an explanation popup', async function(assert) {
   115      let job;
   116      run(() => {
   117        job = this.store.createRecord('job');
   118      });
   119  
   120      await settled();
   121      await renderNewJob(this, job);
   122      assert.ok(Editor.editorHelp.isPresent, 'Editor explanation popup is present');
   123      assert.ok(Editor.editor.isPresent, 'Editor is present');
   124    });
   125  
   126    test('the explanation popup can be dismissed', async function(assert) {
   127      let job;
   128      run(() => {
   129        job = this.store.createRecord('job');
   130      });
   131  
   132      await settled();
   133      await renderNewJob(this, job);
   134      await Editor.editorHelp.dismiss();
   135      await settled();
   136      assert.notOk(Editor.editorHelp.isPresent, 'Editor explanation popup is gone');
   137      assert.equal(
   138        window.localStorage.nomadMessageJobEditor,
   139        'false',
   140        'Dismissal is persisted in localStorage'
   141      );
   142    });
   143  
   144    test('the explanation popup is not shown once the dismissal state is set in localStorage', async function(assert) {
   145      window.localStorage.nomadMessageJobEditor = 'false';
   146  
   147      let job;
   148      run(() => {
   149        job = this.store.createRecord('job');
   150      });
   151  
   152      await settled();
   153      await renderNewJob(this, job);
   154      assert.notOk(Editor.editorHelp.isPresent, 'Editor explanation popup is gone');
   155    });
   156  
   157    test('submitting a json job skips the parse endpoint', async function(assert) {
   158      const spec = jsonJob();
   159      let job;
   160      run(() => {
   161        job = this.store.createRecord('job');
   162      });
   163  
   164      await settled();
   165      await renderNewJob(this, job);
   166      await planJob(spec);
   167      const requests = this.server.pretender.handledRequests.mapBy('url');
   168      assert.notOk(requests.includes('/v1/jobs/parse'), 'JSON job spec is not parsed');
   169      assert.ok(requests.includes(`/v1/job/${newJobName}/plan`), 'JSON job spec is still planned');
   170    });
   171  
   172    test('submitting an hcl job requires the parse endpoint', async function(assert) {
   173      const spec = hclJob();
   174      let job;
   175      run(() => {
   176        job = this.store.createRecord('job');
   177      });
   178  
   179      await settled();
   180      await renderNewJob(this, job);
   181      await planJob(spec);
   182      const requests = this.server.pretender.handledRequests.mapBy('url');
   183      assert.ok(requests.includes('/v1/jobs/parse'), 'HCL job spec is parsed first');
   184      assert.ok(requests.includes(`/v1/job/${newJobName}/plan`), 'HCL job spec is planned');
   185      assert.ok(
   186        requests.indexOf('/v1/jobs/parse') < requests.indexOf(`/v1/job/${newJobName}/plan`),
   187        'Parse comes before Plan'
   188      );
   189    });
   190  
   191    test('when a job is successfully parsed and planned, the plan is shown to the user', async function(assert) {
   192      const spec = hclJob();
   193      let job;
   194      run(() => {
   195        job = this.store.createRecord('job');
   196      });
   197  
   198      await settled();
   199      await renderNewJob(this, job);
   200      await planJob(spec);
   201      assert.ok(Editor.planOutput, 'The plan is outputted');
   202      assert.notOk(Editor.editor.isPresent, 'The editor is replaced with the plan output');
   203      assert.ok(Editor.planHelp.isPresent, 'The plan explanation popup is shown');
   204    });
   205  
   206    test('from the plan screen, the cancel button goes back to the editor with the job still in tact', async function(assert) {
   207      const spec = hclJob();
   208      let job;
   209      run(() => {
   210        job = this.store.createRecord('job');
   211      });
   212  
   213      await settled();
   214      await renderNewJob(this, job);
   215      await planJob(spec);
   216      await Editor.cancel();
   217      await settled();
   218      assert.ok(Editor.editor.isPresent, 'The editor is shown again');
   219      assert.equal(Editor.editor.contents, spec, 'The spec that was planned is still in the editor');
   220    });
   221  
   222    test('when parse fails, the parse error message is shown', async function(assert) {
   223      const spec = hclJob();
   224      const errorMessage = 'Parse Failed!! :o';
   225  
   226      let job;
   227      run(() => {
   228        job = this.store.createRecord('job');
   229      });
   230  
   231      this.server.pretender.post('/v1/jobs/parse', () => [400, {}, errorMessage]);
   232  
   233      await settled();
   234      await renderNewJob(this, job);
   235      await planJob(spec);
   236      assert.notOk(Editor.planError.isPresent, 'Plan error is not shown');
   237      assert.notOk(Editor.runError.isPresent, 'Run error is not shown');
   238  
   239      assert.ok(Editor.parseError.isPresent, 'Parse error is shown');
   240      assert.equal(
   241        Editor.parseError.message,
   242        errorMessage,
   243        'The error message from the server is shown in the error in the UI'
   244      );
   245    });
   246  
   247    test('when plan fails, the plan error message is shown', async function(assert) {
   248      const spec = hclJob();
   249      const errorMessage = 'Plan Failed!! :o';
   250  
   251      let job;
   252      run(() => {
   253        job = this.store.createRecord('job');
   254      });
   255  
   256      this.server.pretender.post(`/v1/job/${newJobName}/plan`, () => [400, {}, errorMessage]);
   257  
   258      await settled();
   259      await renderNewJob(this, job);
   260      await planJob(spec);
   261      assert.notOk(Editor.parseError.isPresent, 'Parse error is not shown');
   262      assert.notOk(Editor.runError.isPresent, 'Run error is not shown');
   263  
   264      assert.ok(Editor.planError.isPresent, 'Plan error is shown');
   265      assert.equal(
   266        Editor.planError.message,
   267        errorMessage,
   268        'The error message from the server is shown in the error in the UI'
   269      );
   270    });
   271  
   272    test('when run fails, the run error message is shown', async function(assert) {
   273      const spec = hclJob();
   274      const errorMessage = 'Run Failed!! :o';
   275  
   276      let job;
   277      run(() => {
   278        job = this.store.createRecord('job');
   279      });
   280  
   281      this.server.pretender.post('/v1/jobs', () => [400, {}, errorMessage]);
   282  
   283      await settled();
   284      await renderNewJob(this, job);
   285      await planJob(spec);
   286      await Editor.run();
   287      await settled();
   288      assert.notOk(Editor.planError.isPresent, 'Plan error is not shown');
   289      assert.notOk(Editor.parseError.isPresent, 'Parse error is not shown');
   290  
   291      assert.ok(Editor.runError.isPresent, 'Run error is shown');
   292      assert.equal(
   293        Editor.runError.message,
   294        errorMessage,
   295        'The error message from the server is shown in the error in the UI'
   296      );
   297    });
   298  
   299    test('when the scheduler dry-run has warnings, the warnings are shown to the user', async function(assert) {
   300      const spec = jsonJob({ Unschedulable: true });
   301      let job;
   302      run(() => {
   303        job = this.store.createRecord('job');
   304      });
   305  
   306      await settled();
   307      await renderNewJob(this, job);
   308      await planJob(spec);
   309      assert.ok(
   310        Editor.dryRunMessage.errored,
   311        'The scheduler dry-run message is in the warning state'
   312      );
   313      assert.notOk(
   314        Editor.dryRunMessage.succeeded,
   315        'The success message is not shown in addition to the warning message'
   316      );
   317      assert.ok(
   318        Editor.dryRunMessage.body.includes(newJobTaskGroupName),
   319        'The scheduler dry-run message includes the warning from send back by the API'
   320      );
   321    });
   322  
   323    test('when the scheduler dry-run has no warnings, a success message is shown to the user', async function(assert) {
   324      const spec = hclJob();
   325      let job;
   326      run(() => {
   327        job = this.store.createRecord('job');
   328      });
   329  
   330      await settled();
   331      await renderNewJob(this, job);
   332      await planJob(spec);
   333      assert.ok(
   334        Editor.dryRunMessage.succeeded,
   335        'The scheduler dry-run message is in the success state'
   336      );
   337      assert.notOk(
   338        Editor.dryRunMessage.errored,
   339        'The warning message is not shown in addition to the success message'
   340      );
   341    });
   342  
   343    test('when a job is submitted in the edit context, a POST request is made to the update job endpoint', async function(assert) {
   344      const spec = hclJob();
   345      let job;
   346      run(() => {
   347        job = this.store.createRecord('job');
   348      });
   349  
   350      await settled();
   351      await renderEditJob(this, job);
   352      await planJob(spec);
   353      await Editor.run();
   354      const requests = this.server.pretender.handledRequests.filterBy('method', 'POST').mapBy('url');
   355      assert.ok(requests.includes(`/v1/job/${newJobName}`), 'A request was made to job update');
   356      assert.notOk(requests.includes('/v1/jobs'), 'A request was not made to job create');
   357    });
   358  
   359    test('when a job is submitted in the new context, a POST request is made to the create job endpoint', async function(assert) {
   360      const spec = hclJob();
   361      let job;
   362      run(() => {
   363        job = this.store.createRecord('job');
   364      });
   365  
   366      await settled();
   367      await renderNewJob(this, job);
   368      await planJob(spec);
   369      await Editor.run();
   370      const requests = this.server.pretender.handledRequests.filterBy('method', 'POST').mapBy('url');
   371      assert.ok(requests.includes('/v1/jobs'), 'A request was made to job create');
   372      assert.notOk(
   373        requests.includes(`/v1/job/${newJobName}`),
   374        'A request was not made to job update'
   375      );
   376    });
   377  
   378    test('when a job is successfully submitted, the onSubmit hook is called', async function(assert) {
   379      const spec = hclJob();
   380      let job;
   381      run(() => {
   382        job = this.store.createRecord('job');
   383      });
   384  
   385      await settled();
   386      await renderNewJob(this, job);
   387      await planJob(spec);
   388      await Editor.run();
   389      await settled();
   390      assert.ok(
   391        this.get('onSubmit').calledWith(newJobName, 'default'),
   392        'The onSubmit hook was called with the correct arguments'
   393      );
   394    });
   395  
   396    test('when the job-editor cancelable flag is false, there is no cancel button in the header', async function(assert) {
   397      let job;
   398      run(() => {
   399        job = this.store.createRecord('job');
   400      });
   401  
   402      await settled();
   403      await renderNewJob(this, job);
   404      assert.notOk(Editor.cancelEditingIsAvailable, 'No way to cancel editing');
   405    });
   406  
   407    test('when the job-editor cancelable flag is true, there is a cancel button in the header', async function(assert) {
   408      let job;
   409      run(() => {
   410        job = this.store.createRecord('job');
   411      });
   412  
   413      await settled();
   414      await renderEditJob(this, job);
   415      assert.ok(Editor.cancelEditingIsAvailable, 'Cancel editing button exists');
   416    });
   417  
   418    test('when the job-editor cancel button is clicked, the onCancel hook is called', async function(assert) {
   419      let job;
   420      run(() => {
   421        job = this.store.createRecord('job');
   422      });
   423  
   424      await settled();
   425      await renderEditJob(this, job);
   426      await Editor.cancelEditing();
   427      assert.ok(this.get('onCancel').calledOnce, 'The onCancel hook was called');
   428    });
   429  });