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 }