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