github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/plugin-detail-test.js (about) 1 /* eslint-disable qunit/require-expect */ 2 import { module, test } from 'qunit'; 3 import { currentURL } from '@ember/test-helpers'; 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 { formatBytes, formatHertz } from 'nomad-ui/utils/units'; 9 import PluginDetail from 'nomad-ui/tests/pages/storage/plugins/detail'; 10 import Layout from 'nomad-ui/tests/pages/layout'; 11 12 module('Acceptance | plugin detail', function (hooks) { 13 setupApplicationTest(hooks); 14 setupMirage(hooks); 15 16 let plugin; 17 18 hooks.beforeEach(function () { 19 server.create('node'); 20 plugin = server.create('csi-plugin', { controllerRequired: true }); 21 }); 22 23 test('it passes an accessibility audit', async function (assert) { 24 await PluginDetail.visit({ id: plugin.id }); 25 await a11yAudit(assert); 26 }); 27 28 test('/csi/plugins/:id should have a breadcrumb trail linking back to Plugins and Storage', async function (assert) { 29 await PluginDetail.visit({ id: plugin.id }); 30 31 assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage'); 32 assert.equal(Layout.breadcrumbFor('csi.plugins').text, 'Plugins'); 33 assert.equal(Layout.breadcrumbFor('csi.plugins.plugin').text, plugin.id); 34 }); 35 36 test('/csi/plugins/:id should show the plugin name in the title', async function (assert) { 37 await PluginDetail.visit({ id: plugin.id }); 38 39 assert.equal(document.title, `CSI Plugin ${plugin.id} - Nomad`); 40 assert.equal(PluginDetail.title, plugin.id); 41 }); 42 43 test('/csi/plugins/:id should list additional details for the plugin below the title', async function (assert) { 44 await PluginDetail.visit({ id: plugin.id }); 45 46 assert.ok( 47 PluginDetail.controllerHealth.includes( 48 `${Math.round( 49 (plugin.controllersHealthy / plugin.controllersExpected) * 100 50 )}%` 51 ) 52 ); 53 assert.ok( 54 PluginDetail.controllerHealth.includes( 55 `${plugin.controllersHealthy}/${plugin.controllersExpected}` 56 ) 57 ); 58 assert.ok( 59 PluginDetail.nodeHealth.includes( 60 `${Math.round((plugin.nodesHealthy / plugin.nodesExpected) * 100)}%` 61 ) 62 ); 63 assert.ok( 64 PluginDetail.nodeHealth.includes( 65 `${plugin.nodesHealthy}/${plugin.nodesExpected}` 66 ) 67 ); 68 assert.ok(PluginDetail.provider.includes(plugin.provider)); 69 }); 70 71 test('/csi/plugins/:id should list all the controller plugin allocations for the plugin', async function (assert) { 72 await PluginDetail.visit({ id: plugin.id }); 73 74 assert.equal( 75 PluginDetail.controllerAllocations.length, 76 plugin.controllers.length 77 ); 78 plugin.controllers.models 79 .sortBy('updateTime') 80 .reverse() 81 .forEach((allocation, idx) => { 82 assert.equal( 83 PluginDetail.controllerAllocations.objectAt(idx).id, 84 allocation.allocID 85 ); 86 }); 87 }); 88 89 test('/csi/plugins/:id should list all the node plugin allocations for the plugin', async function (assert) { 90 await PluginDetail.visit({ id: plugin.id }); 91 92 assert.equal(PluginDetail.nodeAllocations.length, plugin.nodes.length); 93 plugin.nodes.models 94 .sortBy('updateTime') 95 .reverse() 96 .forEach((allocation, idx) => { 97 assert.equal( 98 PluginDetail.nodeAllocations.objectAt(idx).id, 99 allocation.allocID 100 ); 101 }); 102 }); 103 104 test('each allocation should have high-level details for the allocation', async function (assert) { 105 const controller = plugin.controllers.models 106 .sortBy('updateTime') 107 .reverse()[0]; 108 const allocation = server.db.allocations.find(controller.allocID); 109 const allocStats = server.db.clientAllocationStats.find(allocation.id); 110 const taskGroup = server.db.taskGroups.findBy({ 111 name: allocation.taskGroup, 112 jobId: allocation.jobId, 113 }); 114 115 const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); 116 const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); 117 const memoryUsed = tasks.reduce( 118 (sum, task) => sum + task.resources.MemoryMB, 119 0 120 ); 121 122 await PluginDetail.visit({ id: plugin.id }); 123 124 PluginDetail.controllerAllocations.objectAt(0).as((allocationRow) => { 125 assert.equal( 126 allocationRow.shortId, 127 allocation.id.split('-')[0], 128 'Allocation short ID' 129 ); 130 assert.equal( 131 allocationRow.createTime, 132 moment(allocation.createTime / 1000000).format('MMM D') 133 ); 134 assert.equal( 135 allocationRow.createTooltip, 136 moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ') 137 ); 138 assert.equal( 139 allocationRow.modifyTime, 140 moment(allocation.modifyTime / 1000000).fromNow() 141 ); 142 assert.equal( 143 allocationRow.health, 144 controller.healthy ? 'Healthy' : 'Unhealthy' 145 ); 146 assert.equal( 147 allocationRow.client, 148 server.db.nodes.find(allocation.nodeId).id.split('-')[0], 149 'Node ID' 150 ); 151 assert.equal( 152 allocationRow.clientTooltip.substr(0, 15), 153 server.db.nodes.find(allocation.nodeId).name.substr(0, 15), 154 'Node Name' 155 ); 156 assert.equal( 157 allocationRow.job, 158 server.db.jobs.find(allocation.jobId).name, 159 'Job name' 160 ); 161 assert.ok(allocationRow.taskGroup, 'Task group name'); 162 assert.ok(allocationRow.jobVersion, 'Job Version'); 163 assert.equal( 164 allocationRow.cpu, 165 Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 166 'CPU %' 167 ); 168 const roundedTicks = Math.floor( 169 allocStats.resourceUsage.CpuStats.TotalTicks 170 ); 171 assert.equal( 172 allocationRow.cpuTooltip, 173 `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, 174 'Detailed CPU information is in a tooltip' 175 ); 176 assert.equal( 177 allocationRow.mem, 178 allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed, 179 'Memory used' 180 ); 181 assert.equal( 182 allocationRow.memTooltip, 183 `${formatBytes( 184 allocStats.resourceUsage.MemoryStats.RSS 185 )} / ${formatBytes(memoryUsed, 'MiB')}`, 186 'Detailed memory information is in a tooltip' 187 ); 188 }); 189 }); 190 191 test('each allocation should link to the allocation detail page', async function (assert) { 192 const controller = plugin.controllers.models 193 .sortBy('updateTime') 194 .reverse()[0]; 195 196 await PluginDetail.visit({ id: plugin.id }); 197 await PluginDetail.controllerAllocations.objectAt(0).visit(); 198 199 assert.equal(currentURL(), `/allocations/${controller.allocID}`); 200 }); 201 202 test('when there are no plugin allocations, the tables present empty states', async function (assert) { 203 const emptyPlugin = server.create('csi-plugin', { 204 controllerRequired: true, 205 controllersHealthy: 0, 206 controllersExpected: 0, 207 nodesHealthy: 0, 208 nodesExpected: 0, 209 }); 210 211 await PluginDetail.visit({ id: emptyPlugin.id }); 212 213 assert.ok(PluginDetail.controllerTableIsEmpty); 214 assert.equal( 215 PluginDetail.controllerEmptyState.headline, 216 'No Controller Plugin Allocations' 217 ); 218 219 assert.ok(PluginDetail.nodeTableIsEmpty); 220 assert.equal( 221 PluginDetail.nodeEmptyState.headline, 222 'No Node Plugin Allocations' 223 ); 224 }); 225 226 test('when the plugin is node-only, the controller information is omitted', async function (assert) { 227 const nodeOnlyPlugin = server.create('csi-plugin', { 228 controllerRequired: false, 229 }); 230 231 await PluginDetail.visit({ id: nodeOnlyPlugin.id }); 232 233 assert.notOk(PluginDetail.controllerAvailabilityIsPresent); 234 assert.ok(PluginDetail.nodeAvailabilityIsPresent); 235 236 assert.notOk(PluginDetail.controllerHealthIsPresent); 237 assert.notOk(PluginDetail.controllerTableIsPresent); 238 }); 239 240 test('when there are more than 10 controller or node allocations, only 10 are shown', async function (assert) { 241 const manyAllocationsPlugin = server.create('csi-plugin', { 242 shallow: true, 243 controllerRequired: false, 244 nodesExpected: 15, 245 }); 246 247 await PluginDetail.visit({ id: manyAllocationsPlugin.id }); 248 249 assert.equal(PluginDetail.nodeAllocations.length, 10); 250 }); 251 252 test('the View All links under each allocation table link to a filtered view of the plugins allocation list', async function (assert) { 253 const serialize = (arr) => window.encodeURIComponent(JSON.stringify(arr)); 254 255 await PluginDetail.visit({ id: plugin.id }); 256 assert.ok( 257 PluginDetail.goToControllerAllocationsText.includes( 258 plugin.controllers.models.length 259 ) 260 ); 261 await PluginDetail.goToControllerAllocations(); 262 assert.equal( 263 currentURL(), 264 `/csi/plugins/${plugin.id}/allocations?type=${serialize(['controller'])}` 265 ); 266 267 await PluginDetail.visit({ id: plugin.id }); 268 assert.ok( 269 PluginDetail.goToNodeAllocationsText.includes(plugin.nodes.models.length) 270 ); 271 await PluginDetail.goToNodeAllocations(); 272 assert.equal( 273 currentURL(), 274 `/csi/plugins/${plugin.id}/allocations?type=${serialize(['node'])}` 275 ); 276 }); 277 });