github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/job-deployments-test.js (about) 1 import { currentURL } from '@ember/test-helpers'; 2 import { get } from '@ember/object'; 3 import { module, test } from 'qunit'; 4 import { setupApplicationTest } from 'ember-qunit'; 5 import { setupMirage } from 'ember-cli-mirage/test-support'; 6 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 7 import moment from 'moment'; 8 import Deployments from 'nomad-ui/tests/pages/jobs/job/deployments'; 9 10 const sum = (list, key, getter = (a) => a) => 11 list.reduce((sum, item) => sum + getter(get(item, key)), 0); 12 13 let job; 14 let deployments; 15 let sortedDeployments; 16 17 module('Acceptance | job deployments', function (hooks) { 18 setupApplicationTest(hooks); 19 setupMirage(hooks); 20 21 hooks.beforeEach(function () { 22 server.create('node'); 23 job = server.create('job'); 24 deployments = server.schema.deployments.where({ jobId: job.id }); 25 sortedDeployments = deployments.sort((a, b) => { 26 const aVersion = server.db.jobVersions.findBy({ 27 jobId: a.jobId, 28 version: a.versionNumber, 29 }); 30 const bVersion = server.db.jobVersions.findBy({ 31 jobId: b.jobId, 32 version: b.versionNumber, 33 }); 34 if (aVersion.submitTime < bVersion.submitTime) { 35 return 1; 36 } else if (aVersion.submitTime > bVersion.submitTime) { 37 return -1; 38 } 39 return 0; 40 }); 41 }); 42 43 test('it passes an accessibility audit', async function (assert) { 44 assert.expect(1); 45 46 await Deployments.visit({ id: job.id }); 47 await a11yAudit(assert); 48 }); 49 50 test('/jobs/:id/deployments should list all job deployments', async function (assert) { 51 await Deployments.visit({ id: job.id }); 52 53 assert.equal( 54 Deployments.deployments.length, 55 deployments.length, 56 'Each deployment gets a row in the timeline' 57 ); 58 assert.equal(document.title, `Job ${job.name} deployments - Nomad`); 59 }); 60 61 test('each deployment mentions the deployment shortId, status, version, and time since it was submitted', async function (assert) { 62 await Deployments.visit({ id: job.id }); 63 64 const deployment = sortedDeployments.models[0]; 65 const version = server.db.jobVersions.findBy({ 66 jobId: deployment.jobId, 67 version: deployment.versionNumber, 68 }); 69 const deploymentRow = Deployments.deployments.objectAt(0); 70 71 assert.ok( 72 deploymentRow.text.includes(deployment.id.split('-')[0]), 73 'Short ID' 74 ); 75 assert.equal(deploymentRow.status, deployment.status, 'Status'); 76 assert.ok( 77 deploymentRow.statusClass.includes(classForStatus(deployment.status)), 78 'Status Class' 79 ); 80 assert.ok( 81 deploymentRow.version.includes(deployment.versionNumber), 82 'Version #' 83 ); 84 assert.ok( 85 deploymentRow.submitTime.includes( 86 moment(version.submitTime / 1000000).fromNow() 87 ), 88 'Submit time ago' 89 ); 90 }); 91 92 test('when the deployment is running and needs promotion, the deployment item says so', async function (assert) { 93 // Ensure the deployment needs deployment 94 const deployment = sortedDeployments.models[0]; 95 const taskGroupSummary = deployment.deploymentTaskGroupSummaryIds.map( 96 (id) => server.schema.deploymentTaskGroupSummaries.find(id) 97 )[0]; 98 99 deployment.update('status', 'running'); 100 deployment.save(); 101 102 taskGroupSummary.update({ 103 desiredCanaries: 1, 104 placedCanaries: [], 105 promoted: false, 106 }); 107 108 taskGroupSummary.save(); 109 110 await Deployments.visit({ id: job.id }); 111 112 const deploymentRow = Deployments.deployments.objectAt(0); 113 assert.ok( 114 deploymentRow.promotionIsRequired, 115 'Requires Promotion badge found' 116 ); 117 }); 118 119 test('each deployment item can be opened to show details', async function (assert) { 120 await Deployments.visit({ id: job.id }); 121 122 const deploymentRow = Deployments.deployments.objectAt(0); 123 assert.notOk(deploymentRow.hasDetails, 'No deployment body'); 124 125 await deploymentRow.toggle(); 126 assert.ok(deploymentRow.hasDetails, 'Deployment body found'); 127 }); 128 129 test('when open, a deployment shows the deployment metrics', async function (assert) { 130 await Deployments.visit({ id: job.id }); 131 132 const deployment = sortedDeployments.models[0]; 133 const deploymentRow = Deployments.deployments.objectAt(0); 134 const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map( 135 (id) => server.db.deploymentTaskGroupSummaries.find(id) 136 ); 137 138 await deploymentRow.toggle(); 139 140 assert.equal( 141 deploymentRow.metricFor('canaries').text, 142 `${sum(taskGroupSummaries, 'placedCanaries', (a) => a.length)} / ${sum( 143 taskGroupSummaries, 144 'desiredCanaries' 145 )}`, 146 'Canaries, both places and desired, are in the metrics' 147 ); 148 149 assert.equal( 150 deploymentRow.metricFor('placed').text, 151 sum(taskGroupSummaries, 'placedAllocs'), 152 'Placed allocs aggregates across task groups' 153 ); 154 155 assert.equal( 156 deploymentRow.metricFor('desired').text, 157 sum(taskGroupSummaries, 'desiredTotal'), 158 'Desired allocs aggregates across task groups' 159 ); 160 161 assert.equal( 162 deploymentRow.metricFor('healthy').text, 163 sum(taskGroupSummaries, 'healthyAllocs'), 164 'Healthy allocs aggregates across task groups' 165 ); 166 167 assert.equal( 168 deploymentRow.metricFor('unhealthy').text, 169 sum(taskGroupSummaries, 'unhealthyAllocs'), 170 'Unhealthy allocs aggregates across task groups' 171 ); 172 173 assert.equal( 174 deploymentRow.notification, 175 deployment.statusDescription, 176 'Status description is in the metrics block' 177 ); 178 }); 179 180 test('when open, a deployment shows a list of all task groups and their respective stats', async function (assert) { 181 await Deployments.visit({ id: job.id }); 182 183 const deployment = sortedDeployments.models[0]; 184 const deploymentRow = Deployments.deployments.objectAt(0); 185 const taskGroupSummaries = deployment.deploymentTaskGroupSummaryIds.map( 186 (id) => server.db.deploymentTaskGroupSummaries.find(id) 187 ); 188 189 await deploymentRow.toggle(); 190 191 assert.ok(deploymentRow.hasTaskGroups, 'Task groups found'); 192 193 assert.equal( 194 deploymentRow.taskGroups.length, 195 taskGroupSummaries.length, 196 'One row per task group' 197 ); 198 199 const taskGroup = taskGroupSummaries[0]; 200 const taskGroupRow = deploymentRow.taskGroups.findOneBy( 201 'name', 202 taskGroup.name 203 ); 204 205 assert.equal(taskGroupRow.name, taskGroup.name, 'Name'); 206 assert.equal( 207 taskGroupRow.promotion, 208 promotionTestForTaskGroup(taskGroup), 209 'Needs Promotion' 210 ); 211 assert.equal( 212 taskGroupRow.autoRevert, 213 taskGroup.autoRevert ? 'Yes' : 'No', 214 'Auto Revert' 215 ); 216 assert.equal( 217 taskGroupRow.canaries, 218 `${taskGroup.placedCanaries.length} / ${taskGroup.desiredCanaries}`, 219 'Canaries' 220 ); 221 assert.equal( 222 taskGroupRow.allocs, 223 `${taskGroup.placedAllocs} / ${taskGroup.desiredTotal}`, 224 'Allocs' 225 ); 226 assert.equal( 227 taskGroupRow.healthy, 228 taskGroup.healthyAllocs, 229 'Healthy Allocs' 230 ); 231 assert.equal( 232 taskGroupRow.unhealthy, 233 taskGroup.unhealthyAllocs, 234 'Unhealthy Allocs' 235 ); 236 assert.equal( 237 taskGroupRow.progress, 238 moment(taskGroup.requireProgressBy).format("MMM DD, 'YY HH:mm:ss ZZ"), 239 'Progress By' 240 ); 241 }); 242 243 test('when open, a deployment shows a list of all allocations for the deployment', async function (assert) { 244 await Deployments.visit({ id: job.id }); 245 246 const deployment = sortedDeployments.models[0]; 247 const deploymentRow = Deployments.deployments.objectAt(0); 248 249 // TODO: Make this less brittle. This logic is copied from the mirage config, 250 // since there is no reference to allocations on the deployment model. 251 const allocations = server.db.allocations 252 .where({ jobId: deployment.jobId }) 253 .slice(0, 3); 254 await deploymentRow.toggle(); 255 256 assert.ok(deploymentRow.hasAllocations, 'Allocations found'); 257 assert.equal( 258 deploymentRow.allocations.length, 259 allocations.length, 260 'One row per allocation' 261 ); 262 263 const allocation = allocations[0]; 264 const allocationRow = deploymentRow.allocations.objectAt(0); 265 266 assert.equal( 267 allocationRow.shortId, 268 allocation.id.split('-')[0], 269 'Allocation is as expected' 270 ); 271 }); 272 273 test('when the job for the deployments is not found, an error message is shown, but the URL persists', async function (assert) { 274 await Deployments.visit({ id: 'not-a-real-job' }); 275 276 assert.equal( 277 server.pretender.handledRequests 278 .filter((request) => !request.url.includes('policy')) 279 .findBy('status', 404).url, 280 '/v1/job/not-a-real-job', 281 'A request to the nonexistent job is made' 282 ); 283 assert.equal( 284 currentURL(), 285 '/jobs/not-a-real-job/deployments', 286 'The URL persists' 287 ); 288 assert.ok(Deployments.error.isPresent, 'Error message is shown'); 289 assert.equal( 290 Deployments.error.title, 291 'Not Found', 292 'Error message is for 404' 293 ); 294 }); 295 296 function classForStatus(status) { 297 const classMap = { 298 running: 'is-running', 299 successful: 'is-primary', 300 paused: 'is-light', 301 failed: 'is-error', 302 cancelled: 'is-cancelled', 303 }; 304 305 return classMap[status] || 'is-dark'; 306 } 307 308 function promotionTestForTaskGroup(taskGroup) { 309 if (taskGroup.desiredCanaries > 0 && taskGroup.promoted === false) { 310 return 'Yes'; 311 } else if (taskGroup.desiredCanaries > 0) { 312 return 'No'; 313 } 314 return 'N/A'; 315 } 316 });