github.com/hernad/nomad@v1.6.112/ui/tests/acceptance/job-versions-test.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 /* eslint-disable qunit/require-expect */ 7 /* eslint-disable qunit/no-conditional-assertions */ 8 import { currentURL } from '@ember/test-helpers'; 9 import { module, test } from 'qunit'; 10 import { setupApplicationTest } from 'ember-qunit'; 11 import { setupMirage } from 'ember-cli-mirage/test-support'; 12 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 13 import Versions from 'nomad-ui/tests/pages/jobs/job/versions'; 14 import Layout from 'nomad-ui/tests/pages/layout'; 15 import moment from 'moment'; 16 17 let job; 18 let namespace; 19 let versions; 20 21 module('Acceptance | job versions', function (hooks) { 22 setupApplicationTest(hooks); 23 setupMirage(hooks); 24 25 hooks.beforeEach(async function () { 26 server.create('node-pool'); 27 server.create('namespace'); 28 namespace = server.create('namespace'); 29 30 job = server.create('job', { 31 namespaceId: namespace.id, 32 createAllocations: false, 33 }); 34 versions = server.db.jobVersions.where({ jobId: job.id }); 35 36 const managementToken = server.create('token'); 37 window.localStorage.nomadTokenSecret = managementToken.secretId; 38 39 await Versions.visit({ id: `${job.id}@${namespace.id}` }); 40 }); 41 42 test('it passes an accessibility audit', async function (assert) { 43 await a11yAudit(assert); 44 }); 45 46 test('/jobs/:id/versions should list all job versions', async function (assert) { 47 assert.equal( 48 Versions.versions.length, 49 versions.length, 50 'Each version gets a row in the timeline' 51 ); 52 assert.equal(document.title, `Job ${job.name} versions - Nomad`); 53 }); 54 55 test('each version mentions the version number, the stability, and the submitted time', async function (assert) { 56 const version = versions.sortBy('submitTime').reverse()[0]; 57 const formattedSubmitTime = moment(version.submitTime / 1000000).format( 58 "MMM DD, 'YY HH:mm:ss ZZ" 59 ); 60 const versionRow = Versions.versions.objectAt(0); 61 62 assert.ok( 63 versionRow.text.includes(`Version #${version.version}`), 64 'Version #' 65 ); 66 assert.equal(versionRow.stability, version.stable.toString(), 'Stability'); 67 assert.equal(versionRow.submitTime, formattedSubmitTime, 'Submit time'); 68 }); 69 70 test('all versions but the current one have a button to revert to that version', async function (assert) { 71 let versionRowToRevertTo; 72 73 Versions.versions.forEach((versionRow) => { 74 if (versionRow.number === job.version) { 75 assert.ok(versionRow.revertToButton.isHidden); 76 } else { 77 assert.ok(versionRow.revertToButton.isPresent); 78 79 versionRowToRevertTo = versionRow; 80 } 81 }); 82 83 if (versionRowToRevertTo) { 84 const versionNumberRevertingTo = versionRowToRevertTo.number; 85 await versionRowToRevertTo.revertToButton.idle(); 86 await versionRowToRevertTo.revertToButton.confirm(); 87 88 const revertRequest = this.server.pretender.handledRequests.find( 89 (request) => request.url.includes('revert') 90 ); 91 92 assert.equal( 93 revertRequest.url, 94 `/v1/job/${job.id}/revert?namespace=${namespace.id}` 95 ); 96 97 assert.deepEqual(JSON.parse(revertRequest.requestBody), { 98 JobID: job.id, 99 JobVersion: versionNumberRevertingTo, 100 }); 101 102 assert.equal(currentURL(), `/jobs/${job.id}@${namespace.id}`); 103 } 104 }); 105 106 test('when reversion fails, the error message from the API is piped through to the alert', async function (assert) { 107 const versionRowToRevertTo = Versions.versions.filter( 108 (versionRow) => versionRow.revertToButton.isPresent 109 )[0]; 110 111 if (versionRowToRevertTo) { 112 const message = 'A plaintext error message'; 113 server.pretender.post('/v1/job/:id/revert', () => [500, {}, message]); 114 115 await versionRowToRevertTo.revertToButton.idle(); 116 await versionRowToRevertTo.revertToButton.confirm(); 117 118 assert.ok(Layout.inlineError.isShown); 119 assert.ok(Layout.inlineError.isDanger); 120 assert.ok(Layout.inlineError.title.includes('Could Not Revert')); 121 assert.equal(Layout.inlineError.message, message); 122 123 await Layout.inlineError.dismiss(); 124 125 assert.notOk(Layout.inlineError.isShown); 126 } else { 127 assert.expect(0); 128 } 129 }); 130 131 test('when reversion has no effect, the error message explains', async function (assert) { 132 const versionRowToRevertTo = Versions.versions.filter( 133 (versionRow) => versionRow.revertToButton.isPresent 134 )[0]; 135 136 if (versionRowToRevertTo) { 137 // The default Mirage implementation updates the job version as passed in, this does nothing 138 server.pretender.post('/v1/job/:id/revert', () => [200, {}, '{}']); 139 140 await versionRowToRevertTo.revertToButton.idle(); 141 await versionRowToRevertTo.revertToButton.confirm(); 142 143 assert.ok(Layout.inlineError.isShown); 144 assert.ok(Layout.inlineError.isWarning); 145 assert.ok(Layout.inlineError.title.includes('Reversion Had No Effect')); 146 assert.equal( 147 Layout.inlineError.message, 148 'Reverting to an identical older version doesn’t produce a new version' 149 ); 150 } else { 151 assert.expect(0); 152 } 153 }); 154 155 test('when the job for the versions is not found, an error message is shown, but the URL persists', async function (assert) { 156 await Versions.visit({ id: 'not-a-real-job' }); 157 158 assert.equal( 159 server.pretender.handledRequests 160 .filter((request) => !request.url.includes('policy')) 161 .findBy('status', 404).url, 162 '/v1/job/not-a-real-job', 163 'A request to the nonexistent job is made' 164 ); 165 assert.equal( 166 currentURL(), 167 '/jobs/not-a-real-job/versions', 168 'The URL persists' 169 ); 170 assert.ok(Versions.error.isPresent, 'Error message is shown'); 171 assert.equal(Versions.error.title, 'Not Found', 'Error message is for 404'); 172 }); 173 }); 174 175 module('Acceptance | job versions (with client token)', function (hooks) { 176 setupApplicationTest(hooks); 177 setupMirage(hooks); 178 179 hooks.beforeEach(async function () { 180 server.create('node-pool'); 181 job = server.create('job', { createAllocations: false }); 182 versions = server.db.jobVersions.where({ jobId: job.id }); 183 184 server.create('token'); 185 const clientToken = server.create('token'); 186 window.localStorage.nomadTokenSecret = clientToken.secretId; 187 188 await Versions.visit({ id: job.id }); 189 }); 190 191 test('reversion buttons are disabled when the token lacks permissions', async function (assert) { 192 const versionRowWithReversion = Versions.versions.filter( 193 (versionRow) => versionRow.revertToButton.isPresent 194 )[0]; 195 196 if (versionRowWithReversion) { 197 assert.ok(versionRowWithReversion.revertToButtonIsDisabled); 198 } else { 199 assert.expect(0); 200 } 201 202 window.localStorage.clear(); 203 }); 204 205 test('reversion buttons are available when the client token has permissions', async function (assert) { 206 const REVERT_NAMESPACE = 'revert-namespace'; 207 window.localStorage.clear(); 208 const clientToken = server.create('token'); 209 210 server.create('namespace', { id: REVERT_NAMESPACE }); 211 212 const job = server.create('job', { 213 groupCount: 0, 214 createAllocations: false, 215 shallow: true, 216 noActiveDeployment: true, 217 namespaceId: REVERT_NAMESPACE, 218 }); 219 220 const policy = server.create('policy', { 221 id: 'something', 222 name: 'something', 223 rulesJSON: { 224 Namespaces: [ 225 { 226 Name: REVERT_NAMESPACE, 227 Capabilities: ['submit-job'], 228 }, 229 ], 230 }, 231 }); 232 233 clientToken.policyIds = [policy.id]; 234 clientToken.save(); 235 236 window.localStorage.nomadTokenSecret = clientToken.secretId; 237 238 versions = server.db.jobVersions.where({ jobId: job.id }); 239 await Versions.visit({ id: job.id, namespace: REVERT_NAMESPACE }); 240 const versionRowWithReversion = Versions.versions.filter( 241 (versionRow) => versionRow.revertToButton.isPresent 242 )[0]; 243 244 if (versionRowWithReversion) { 245 assert.ok(versionRowWithReversion.revertToButtonIsDisabled); 246 } else { 247 assert.expect(0); 248 } 249 }); 250 });