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