github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/tests/integration/components/job-editor-test.js (about)

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