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