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 });