github.com/hernad/nomad@v1.6.112/ui/tests/acceptance/job-dispatch-test.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  /* eslint-disable ember/no-test-module-for */
     7  /* eslint-disable qunit/require-expect */
     8  /* eslint-disable qunit/no-conditional-assertions */
     9  import { module, test } from 'qunit';
    10  import { setupApplicationTest } from 'ember-qunit';
    11  import { setupMirage } from 'ember-cli-mirage/test-support';
    12  import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror';
    13  import JobDispatch from 'nomad-ui/tests/pages/jobs/dispatch';
    14  import JobDetail from 'nomad-ui/tests/pages/jobs/detail';
    15  import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
    16  import { currentURL } from '@ember/test-helpers';
    17  
    18  const REQUIRED_INDICATOR = '*';
    19  
    20  moduleForJobDispatch('Acceptance | job dispatch', () => {
    21    server.createList('namespace', 2);
    22    const namespace = server.db.namespaces[0];
    23  
    24    return server.create('job', 'parameterized', {
    25      status: 'running',
    26      namespaceId: namespace.name,
    27    });
    28  });
    29  
    30  moduleForJobDispatch('Acceptance | job dispatch (with namespace)', () => {
    31    server.createList('namespace', 2);
    32    const namespace = server.db.namespaces[1];
    33  
    34    return server.create('job', 'parameterized', {
    35      status: 'running',
    36      namespaceId: namespace.name,
    37    });
    38  });
    39  
    40  function moduleForJobDispatch(title, jobFactory) {
    41    let job, namespace, managementToken, clientToken;
    42  
    43    module(title, function (hooks) {
    44      setupApplicationTest(hooks);
    45      setupCodeMirror(hooks);
    46      setupMirage(hooks);
    47  
    48      hooks.beforeEach(function () {
    49        // Required for placing allocations (a result of dispatching jobs)
    50        server.create('node-pool');
    51        server.create('node');
    52  
    53        job = jobFactory();
    54        namespace = server.db.namespaces.find(job.namespaceId);
    55  
    56        managementToken = server.create('token');
    57        clientToken = server.create('token');
    58  
    59        window.localStorage.nomadTokenSecret = managementToken.secretId;
    60      });
    61  
    62      test('it passes an accessibility audit', async function (assert) {
    63        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
    64        await a11yAudit(assert);
    65      });
    66  
    67      test('the dispatch button is displayed with management token', async function (assert) {
    68        await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
    69        assert.notOk(JobDetail.dispatchButton.isDisabled);
    70      });
    71  
    72      test('the dispatch button is displayed when allowed', async function (assert) {
    73        window.localStorage.nomadTokenSecret = clientToken.secretId;
    74  
    75        const policy = server.create('policy', {
    76          id: 'dispatch',
    77          name: 'dispatch',
    78          rulesJSON: {
    79            Namespaces: [
    80              {
    81                Name: namespace.name,
    82                Capabilities: ['list-jobs', 'dispatch-job'],
    83              },
    84            ],
    85          },
    86        });
    87  
    88        clientToken.policyIds = [policy.id];
    89        clientToken.save();
    90  
    91        await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
    92        assert.notOk(JobDetail.dispatchButton.isDisabled);
    93  
    94        // Reset clientToken policies.
    95        clientToken.policyIds = [];
    96        clientToken.save();
    97      });
    98  
    99      test('the dispatch button is disabled when not allowed', async function (assert) {
   100        window.localStorage.nomadTokenSecret = clientToken.secretId;
   101  
   102        await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
   103        assert.ok(JobDetail.dispatchButton.isDisabled);
   104      });
   105  
   106      test('all meta fields are displayed', async function (assert) {
   107        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
   108        assert.equal(
   109          JobDispatch.metaFields.length,
   110          job.parameterizedJob.MetaOptional.length +
   111            job.parameterizedJob.MetaRequired.length
   112        );
   113      });
   114  
   115      test('required meta fields are properly indicated', async function (assert) {
   116        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
   117  
   118        JobDispatch.metaFields.forEach((f) => {
   119          const hasIndicator = f.label.includes(REQUIRED_INDICATOR);
   120          const isRequired = job.parameterizedJob.MetaRequired.includes(
   121            f.field.id
   122          );
   123  
   124          if (isRequired) {
   125            assert.ok(hasIndicator, `${f.label} contains required indicator.`);
   126          } else {
   127            assert.notOk(
   128              hasIndicator,
   129              `${f.label} doesn't contain required indicator.`
   130            );
   131          }
   132        });
   133      });
   134  
   135      test('job without meta fields', async function (assert) {
   136        const jobWithoutMeta = server.create('job', 'parameterized', {
   137          status: 'running',
   138          namespaceId: namespace.name,
   139          parameterizedJob: {
   140            MetaRequired: null,
   141            MetaOptional: null,
   142          },
   143        });
   144  
   145        await JobDispatch.visit({ id: `${jobWithoutMeta.id}@${namespace.name}` });
   146        assert.ok(JobDispatch.dispatchButton.isPresent);
   147      });
   148  
   149      test('payload text area is hidden when forbidden', async function (assert) {
   150        job.parameterizedJob.Payload = 'forbidden';
   151        job.save();
   152  
   153        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
   154  
   155        assert.ok(JobDispatch.payload.emptyMessage.isPresent);
   156        assert.notOk(JobDispatch.payload.editor.isPresent);
   157      });
   158  
   159      test('payload is indicated as required', async function (assert) {
   160        const jobPayloadRequired = server.create('job', 'parameterized', {
   161          status: 'running',
   162          namespaceId: namespace.name,
   163          parameterizedJob: {
   164            Payload: 'required',
   165          },
   166        });
   167        const jobPayloadOptional = server.create('job', 'parameterized', {
   168          status: 'running',
   169          namespaceId: namespace.name,
   170          parameterizedJob: {
   171            Payload: 'optional',
   172          },
   173        });
   174  
   175        await JobDispatch.visit({
   176          id: `${jobPayloadRequired.id}@${namespace.name}`,
   177        });
   178  
   179        let payloadTitle = JobDispatch.payload.title;
   180        assert.ok(
   181          payloadTitle.includes(REQUIRED_INDICATOR),
   182          `${payloadTitle} contains required indicator.`
   183        );
   184  
   185        await JobDispatch.visit({
   186          id: `${jobPayloadOptional.id}@${namespace.name}`,
   187        });
   188  
   189        payloadTitle = JobDispatch.payload.title;
   190        assert.notOk(
   191          payloadTitle.includes(REQUIRED_INDICATOR),
   192          `${payloadTitle} doesn't contain required indicator.`
   193        );
   194      });
   195  
   196      test('dispatch a job', async function (assert) {
   197        function countDispatchChildren() {
   198          return server.db.jobs.where((j) =>
   199            j.id.startsWith(`${job.id}/`)
   200          ).length;
   201        }
   202  
   203        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
   204  
   205        // Fill form.
   206        JobDispatch.metaFields.map((f) => f.field.input('meta value'));
   207        JobDispatch.payload.editor.fillIn('payload');
   208  
   209        const childrenCountBefore = countDispatchChildren();
   210        await JobDispatch.dispatchButton.click();
   211        const childrenCountAfter = countDispatchChildren();
   212  
   213        assert.equal(childrenCountAfter, childrenCountBefore + 1);
   214        assert.ok(
   215          currentURL().startsWith(`/jobs/${encodeURIComponent(`${job.id}/`)}`)
   216        );
   217        assert.ok(JobDetail.jobName);
   218      });
   219  
   220      test('fail when required meta field is empty', async function (assert) {
   221        // Make sure we have a required meta param.
   222        job.parameterizedJob.MetaRequired = ['required'];
   223        job.parameterizedJob.Payload = 'forbidden';
   224        job.save();
   225  
   226        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
   227  
   228        // Fill only optional meta params.
   229        JobDispatch.optionalMetaFields.map((f) => f.field.input('meta value'));
   230  
   231        await JobDispatch.dispatchButton.click();
   232  
   233        assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
   234      });
   235  
   236      test('fail when required payload is empty', async function (assert) {
   237        job.parameterizedJob.MetaRequired = [];
   238        job.parameterizedJob.Payload = 'required';
   239        job.save();
   240  
   241        await JobDispatch.visit({ id: `${job.id}@${namespace.name}` });
   242        await JobDispatch.dispatchButton.click();
   243  
   244        assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
   245      });
   246    });
   247  }