github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/volume-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 VolumeDetail from 'nomad-ui/tests/pages/storage/volumes/detail'; 10 import Layout from 'nomad-ui/tests/pages/layout'; 11 12 const assignWriteAlloc = (volume, alloc) => { 13 volume.writeAllocs.add(alloc); 14 volume.allocations.add(alloc); 15 volume.save(); 16 }; 17 18 const assignReadAlloc = (volume, alloc) => { 19 volume.readAllocs.add(alloc); 20 volume.allocations.add(alloc); 21 volume.save(); 22 }; 23 24 module('Acceptance | volume detail', function (hooks) { 25 setupApplicationTest(hooks); 26 setupMirage(hooks); 27 28 let volume; 29 30 hooks.beforeEach(function () { 31 server.create('node'); 32 server.create('csi-plugin', { createVolumes: false }); 33 volume = server.create('csi-volume'); 34 }); 35 36 test('it passes an accessibility audit', async function (assert) { 37 await VolumeDetail.visit({ id: `${volume.id}@default` }); 38 await a11yAudit(assert); 39 }); 40 41 test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function (assert) { 42 await VolumeDetail.visit({ id: `${volume.id}@default` }); 43 44 assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage'); 45 assert.equal(Layout.breadcrumbFor('csi.volumes').text, 'Volumes'); 46 assert.equal(Layout.breadcrumbFor('csi.volumes.volume').text, volume.name); 47 }); 48 49 test('/csi/volumes/:id should show the volume name in the title', async function (assert) { 50 await VolumeDetail.visit({ id: `${volume.id}@default` }); 51 52 assert.equal(document.title, `CSI Volume ${volume.name} - Nomad`); 53 assert.equal(VolumeDetail.title, volume.name); 54 }); 55 56 test('/csi/volumes/:id should list additional details for the volume below the title', async function (assert) { 57 await VolumeDetail.visit({ id: `${volume.id}@default` }); 58 59 assert.ok( 60 VolumeDetail.health.includes( 61 volume.schedulable ? 'Schedulable' : 'Unschedulable' 62 ) 63 ); 64 assert.ok(VolumeDetail.provider.includes(volume.provider)); 65 assert.ok(VolumeDetail.externalId.includes(volume.externalId)); 66 assert.notOk( 67 VolumeDetail.hasNamespace, 68 'Namespace is omitted when there is only one namespace' 69 ); 70 }); 71 72 test('/csi/volumes/:id should list all write allocations the volume is attached to', async function (assert) { 73 const writeAllocations = server.createList('allocation', 2); 74 const readAllocations = server.createList('allocation', 3); 75 writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); 76 readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); 77 78 await VolumeDetail.visit({ id: `${volume.id}@default` }); 79 80 assert.equal(VolumeDetail.writeAllocations.length, writeAllocations.length); 81 writeAllocations 82 .sortBy('modifyIndex') 83 .reverse() 84 .forEach((allocation, idx) => { 85 assert.equal( 86 allocation.id, 87 VolumeDetail.writeAllocations.objectAt(idx).id 88 ); 89 }); 90 }); 91 92 test('/csi/volumes/:id should list all read allocations the volume is attached to', async function (assert) { 93 const writeAllocations = server.createList('allocation', 2); 94 const readAllocations = server.createList('allocation', 3); 95 writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); 96 readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); 97 98 await VolumeDetail.visit({ id: `${volume.id}@default` }); 99 100 assert.equal(VolumeDetail.readAllocations.length, readAllocations.length); 101 readAllocations 102 .sortBy('modifyIndex') 103 .reverse() 104 .forEach((allocation, idx) => { 105 assert.equal( 106 allocation.id, 107 VolumeDetail.readAllocations.objectAt(idx).id 108 ); 109 }); 110 }); 111 112 test('each allocation should have high-level details for the allocation', async function (assert) { 113 const allocation = server.create('allocation', { clientStatus: 'running' }); 114 assignWriteAlloc(volume, allocation); 115 116 const allocStats = server.db.clientAllocationStats.find(allocation.id); 117 const taskGroup = server.db.taskGroups.findBy({ 118 name: allocation.taskGroup, 119 jobId: allocation.jobId, 120 }); 121 122 const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); 123 const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); 124 const memoryUsed = tasks.reduce( 125 (sum, task) => sum + task.resources.MemoryMB, 126 0 127 ); 128 129 await VolumeDetail.visit({ id: `${volume.id}@default` }); 130 131 VolumeDetail.writeAllocations.objectAt(0).as((allocationRow) => { 132 assert.equal( 133 allocationRow.shortId, 134 allocation.id.split('-')[0], 135 'Allocation short ID' 136 ); 137 assert.equal( 138 allocationRow.createTime, 139 moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), 140 'Allocation create time' 141 ); 142 assert.equal( 143 allocationRow.modifyTime, 144 moment(allocation.modifyTime / 1000000).fromNow(), 145 'Allocation modify time' 146 ); 147 assert.equal( 148 allocationRow.status, 149 allocation.clientStatus, 150 'Client status' 151 ); 152 assert.equal( 153 allocationRow.job, 154 server.db.jobs.find(allocation.jobId).name, 155 'Job name' 156 ); 157 assert.ok(allocationRow.taskGroup, 'Task group name'); 158 assert.ok(allocationRow.jobVersion, 'Job Version'); 159 assert.equal( 160 allocationRow.client, 161 server.db.nodes.find(allocation.nodeId).id.split('-')[0], 162 'Node ID' 163 ); 164 assert.equal( 165 allocationRow.clientTooltip.substr(0, 15), 166 server.db.nodes.find(allocation.nodeId).name.substr(0, 15), 167 'Node Name' 168 ); 169 assert.equal( 170 allocationRow.cpu, 171 Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 172 'CPU %' 173 ); 174 const roundedTicks = Math.floor( 175 allocStats.resourceUsage.CpuStats.TotalTicks 176 ); 177 assert.equal( 178 allocationRow.cpuTooltip, 179 `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, 180 'Detailed CPU information is in a tooltip' 181 ); 182 assert.equal( 183 allocationRow.mem, 184 allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed, 185 'Memory used' 186 ); 187 assert.equal( 188 allocationRow.memTooltip, 189 `${formatBytes( 190 allocStats.resourceUsage.MemoryStats.RSS 191 )} / ${formatBytes(memoryUsed, 'MiB')}`, 192 'Detailed memory information is in a tooltip' 193 ); 194 }); 195 }); 196 197 test('each allocation should link to the allocation detail page', async function (assert) { 198 const allocation = server.create('allocation'); 199 assignWriteAlloc(volume, allocation); 200 201 await VolumeDetail.visit({ id: `${volume.id}@default` }); 202 await VolumeDetail.writeAllocations.objectAt(0).visit(); 203 204 assert.equal(currentURL(), `/allocations/${allocation.id}`); 205 }); 206 207 test('when there are no write allocations, the table presents an empty state', async function (assert) { 208 await VolumeDetail.visit({ id: `${volume.id}@default` }); 209 210 assert.ok(VolumeDetail.writeTableIsEmpty); 211 assert.equal(VolumeDetail.writeEmptyState.headline, 'No Write Allocations'); 212 }); 213 214 test('when there are no read allocations, the table presents an empty state', async function (assert) { 215 await VolumeDetail.visit({ id: `${volume.id}@default` }); 216 217 assert.ok(VolumeDetail.readTableIsEmpty); 218 assert.equal(VolumeDetail.readEmptyState.headline, 'No Read Allocations'); 219 }); 220 221 test('the constraints table shows access mode and attachment mode', async function (assert) { 222 await VolumeDetail.visit({ id: `${volume.id}@default` }); 223 224 assert.equal(VolumeDetail.constraints.accessMode, volume.accessMode); 225 assert.equal( 226 VolumeDetail.constraints.attachmentMode, 227 volume.attachmentMode 228 ); 229 }); 230 }); 231 232 // Namespace test: details shows the namespace 233 module('Acceptance | volume detail (with namespaces)', function (hooks) { 234 setupApplicationTest(hooks); 235 setupMirage(hooks); 236 237 let volume; 238 239 hooks.beforeEach(function () { 240 server.createList('namespace', 2); 241 server.create('node'); 242 server.create('csi-plugin', { createVolumes: false }); 243 volume = server.create('csi-volume'); 244 }); 245 246 test('/csi/volumes/:id detail ribbon includes the namespace of the volume', async function (assert) { 247 await VolumeDetail.visit({ id: `${volume.id}@${volume.namespaceId}` }); 248 249 assert.ok(VolumeDetail.hasNamespace); 250 assert.ok(VolumeDetail.namespace.includes(volume.namespaceId || 'default')); 251 }); 252 });