github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/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({
    99        job,
   100        onSubmit: sinon.spy(),
   101        onCancel: sinon.spy(),
   102        context: 'edit',
   103      });
   104      await component.render(cancelableTemplate);
   105    };
   106  
   107    const planJob = async (spec) => {
   108      const cm = getCodeMirrorInstance(['data-test-editor']);
   109      cm.setValue(spec);
   110      await Editor.plan();
   111    };
   112  
   113    test('the default state is an editor with an explanation popup', async function (assert) {
   114      assert.expect(2);
   115  
   116      const job = await this.store.createRecord('job');
   117  
   118      await renderNewJob(this, job);
   119      assert.ok(Editor.editor.isPresent, 'Editor is present');
   120  
   121      await componentA11yAudit(this.element, assert);
   122    });
   123  
   124    test('submitting a json job skips the parse endpoint', async function (assert) {
   125      const spec = jsonJob();
   126      const job = await this.store.createRecord('job');
   127  
   128      await renderNewJob(this, job);
   129      await planJob(spec);
   130      console.log('wait');
   131      const requests = this.server.pretender.handledRequests.mapBy('url');
   132      assert.notOk(
   133        requests.includes('/v1/jobs/parse'),
   134        'JSON job spec is not parsed'
   135      );
   136      assert.ok(
   137        requests.includes(`/v1/job/${newJobName}/plan`),
   138        'JSON job spec is still planned'
   139      );
   140    });
   141  
   142    test('submitting an hcl job requires the parse endpoint', async function (assert) {
   143      const spec = hclJob();
   144      const job = await this.store.createRecord('job');
   145  
   146      await renderNewJob(this, job);
   147      await planJob(spec);
   148      const requests = this.server.pretender.handledRequests.mapBy('url');
   149      assert.ok(
   150        requests.includes('/v1/jobs/parse?namespace=*'),
   151        'HCL job spec is parsed first'
   152      );
   153      assert.ok(
   154        requests.includes(`/v1/job/${newJobName}/plan`),
   155        'HCL job spec is planned'
   156      );
   157      assert.ok(
   158        requests.indexOf('/v1/jobs/parse') <
   159          requests.indexOf(`/v1/job/${newJobName}/plan`),
   160        'Parse comes before Plan'
   161      );
   162    });
   163  
   164    test('when a job is successfully parsed and planned, the plan is shown to the user', async function (assert) {
   165      assert.expect(4);
   166  
   167      const spec = hclJob();
   168      const job = await this.store.createRecord('job');
   169  
   170      await renderNewJob(this, job);
   171      await planJob(spec);
   172      assert.ok(Editor.planOutput, 'The plan is outputted');
   173      assert.notOk(
   174        Editor.editor.isPresent,
   175        'The editor is replaced with the plan output'
   176      );
   177      assert.ok(Editor.planHelp.isPresent, 'The plan explanation popup is shown');
   178  
   179      await componentA11yAudit(this.element, assert);
   180    });
   181  
   182    test('from the plan screen, the cancel button goes back to the editor with the job still in tact', async function (assert) {
   183      const spec = hclJob();
   184      const job = await this.store.createRecord('job');
   185  
   186      await renderNewJob(this, job);
   187      await planJob(spec);
   188      await Editor.cancel();
   189      assert.ok(Editor.editor.isPresent, 'The editor is shown again');
   190      assert.equal(
   191        Editor.editor.contents,
   192        spec,
   193        'The spec that was planned is still in the editor'
   194      );
   195    });
   196  
   197    test('when parse fails, the parse error message is shown', async function (assert) {
   198      assert.expect(5);
   199  
   200      const spec = hclJob();
   201      const errorMessage = 'Parse Failed!! :o';
   202      const job = await this.store.createRecord('job');
   203  
   204      this.server.pretender.post('/v1/jobs/parse', () => [400, {}, errorMessage]);
   205  
   206      await renderNewJob(this, job);
   207      await planJob(spec);
   208      assert.notOk(Editor.planError.isPresent, 'Plan error is not shown');
   209      assert.notOk(Editor.runError.isPresent, 'Run error is not shown');
   210  
   211      assert.ok(Editor.parseError.isPresent, 'Parse error is shown');
   212      assert.equal(
   213        Editor.parseError.message,
   214        errorMessage,
   215        'The error message from the server is shown in the error in the UI'
   216      );
   217  
   218      await componentA11yAudit(this.element, assert);
   219    });
   220  
   221    test('when plan fails, the plan error message is shown', async function (assert) {
   222      assert.expect(5);
   223  
   224      const spec = hclJob();
   225      const errorMessage = 'Plan Failed!! :o';
   226      const job = await this.store.createRecord('job');
   227  
   228      this.server.pretender.post(`/v1/job/${newJobName}/plan`, () => [
   229        400,
   230        {},
   231        errorMessage,
   232      ]);
   233  
   234      await renderNewJob(this, job);
   235      await planJob(spec);
   236      assert.notOk(Editor.parseError.isPresent, 'Parse error is not shown');
   237      assert.notOk(Editor.runError.isPresent, 'Run error is not shown');
   238  
   239      assert.ok(Editor.planError.isPresent, 'Plan error is shown');
   240      assert.equal(
   241        Editor.planError.message,
   242        errorMessage,
   243        'The error message from the server is shown in the error in the UI'
   244      );
   245  
   246      await componentA11yAudit(this.element, assert);
   247    });
   248  
   249    test('when run fails, the run error message is shown', async function (assert) {
   250      assert.expect(5);
   251  
   252      const spec = hclJob();
   253      const errorMessage = 'Run Failed!! :o';
   254      const job = await this.store.createRecord('job');
   255  
   256      this.server.pretender.post('/v1/jobs', () => [400, {}, errorMessage]);
   257  
   258      await renderNewJob(this, job);
   259      await planJob(spec);
   260      await Editor.run();
   261      assert.notOk(Editor.planError.isPresent, 'Plan error is not shown');
   262      assert.notOk(Editor.parseError.isPresent, 'Parse error is not shown');
   263  
   264      assert.ok(Editor.runError.isPresent, 'Run error is shown');
   265      assert.equal(
   266        Editor.runError.message,
   267        errorMessage,
   268        'The error message from the server is shown in the error in the UI'
   269      );
   270  
   271      await componentA11yAudit(this.element, assert);
   272    });
   273  
   274    test('when the scheduler dry-run has warnings, the warnings are shown to the user', async function (assert) {
   275      assert.expect(4);
   276  
   277      const spec = jsonJob({ Unschedulable: true });
   278      const job = await this.store.createRecord('job');
   279  
   280      await renderNewJob(this, job);
   281      await planJob(spec);
   282      assert.ok(
   283        Editor.dryRunMessage.errored,
   284        'The scheduler dry-run message is in the warning state'
   285      );
   286      assert.notOk(
   287        Editor.dryRunMessage.succeeded,
   288        'The success message is not shown in addition to the warning message'
   289      );
   290      assert.ok(
   291        Editor.dryRunMessage.body.includes(newJobTaskGroupName),
   292        'The scheduler dry-run message includes the warning from send back by the API'
   293      );
   294  
   295      await componentA11yAudit(this.element, assert);
   296    });
   297  
   298    test('when the scheduler dry-run has no warnings, a success message is shown to the user', async function (assert) {
   299      assert.expect(3);
   300  
   301      const spec = hclJob();
   302      const job = await this.store.createRecord('job');
   303  
   304      await renderNewJob(this, job);
   305      await planJob(spec);
   306      assert.ok(
   307        Editor.dryRunMessage.succeeded,
   308        'The scheduler dry-run message is in the success state'
   309      );
   310      assert.notOk(
   311        Editor.dryRunMessage.errored,
   312        'The warning message is not shown in addition to the success message'
   313      );
   314  
   315      await componentA11yAudit(this.element, assert);
   316    });
   317  
   318    test('when a job is submitted in the edit context, a POST request is made to the update job endpoint', async function (assert) {
   319      const spec = hclJob();
   320      const job = await this.store.createRecord('job');
   321  
   322      await renderEditJob(this, job);
   323      await planJob(spec);
   324      await Editor.run();
   325      const requests = this.server.pretender.handledRequests
   326        .filterBy('method', 'POST')
   327        .mapBy('url');
   328      assert.ok(
   329        requests.includes(`/v1/job/${newJobName}`),
   330        'A request was made to job update'
   331      );
   332      assert.notOk(
   333        requests.includes('/v1/jobs'),
   334        'A request was not made to job create'
   335      );
   336    });
   337  
   338    test('when a job is submitted in the new context, a POST request is made to the create job endpoint', async function (assert) {
   339      const spec = hclJob();
   340      const job = await this.store.createRecord('job');
   341  
   342      await renderNewJob(this, job);
   343      await planJob(spec);
   344      await Editor.run();
   345      const requests = this.server.pretender.handledRequests
   346        .filterBy('method', 'POST')
   347        .mapBy('url');
   348      assert.ok(
   349        requests.includes('/v1/jobs'),
   350        'A request was made to job create'
   351      );
   352      assert.notOk(
   353        requests.includes(`/v1/job/${newJobName}`),
   354        'A request was not made to job update'
   355      );
   356    });
   357  
   358    test('when a job is successfully submitted, the onSubmit hook is called', async function (assert) {
   359      const spec = hclJob();
   360      const job = await this.store.createRecord('job');
   361  
   362      await renderNewJob(this, job);
   363      await planJob(spec);
   364      await Editor.run();
   365      assert.ok(
   366        this.onSubmit.calledWith(newJobName, 'default'),
   367        'The onSubmit hook was called with the correct arguments'
   368      );
   369    });
   370  
   371    test('when the job-editor cancelable flag is false, there is no cancel button in the header', async function (assert) {
   372      const job = await this.store.createRecord('job');
   373  
   374      await renderNewJob(this, job);
   375      assert.notOk(Editor.cancelEditingIsAvailable, 'No way to cancel editing');
   376    });
   377  
   378    test('when the job-editor cancelable flag is true, there is a cancel button in the header', async function (assert) {
   379      assert.expect(2);
   380  
   381      const job = await this.store.createRecord('job');
   382  
   383      await renderEditJob(this, job);
   384      assert.ok(Editor.cancelEditingIsAvailable, 'Cancel editing button exists');
   385  
   386      await componentA11yAudit(this.element, assert);
   387    });
   388  
   389    test('when the job-editor cancel button is clicked, the onCancel hook is called', async function (assert) {
   390      const job = await this.store.createRecord('job');
   391  
   392      await renderEditJob(this, job);
   393      await Editor.cancelEditing();
   394      assert.ok(this.onCancel.calledOnce, 'The onCancel hook was called');
   395    });
   396  });