github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/volumes-list-test.js (about) 1 /* eslint-disable qunit/require-expect */ 2 import { currentURL, visit } from '@ember/test-helpers'; 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 pageSizeSelect from './behaviors/page-size-select'; 8 import VolumesList from 'nomad-ui/tests/pages/storage/volumes/list'; 9 import percySnapshot from '@percy/ember'; 10 import faker from 'nomad-ui/mirage/faker'; 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 | volumes list', function (hooks) { 25 setupApplicationTest(hooks); 26 setupMirage(hooks); 27 28 hooks.beforeEach(function () { 29 faker.seed(1); 30 server.create('node'); 31 server.create('csi-plugin', { createVolumes: false }); 32 window.localStorage.clear(); 33 }); 34 35 test('it passes an accessibility audit', async function (assert) { 36 await VolumesList.visit(); 37 await a11yAudit(assert); 38 }); 39 40 test('visiting /csi redirects to /csi/volumes', async function (assert) { 41 await visit('/csi'); 42 43 assert.equal(currentURL(), '/csi/volumes'); 44 }); 45 46 test('visiting /csi/volumes', async function (assert) { 47 await VolumesList.visit(); 48 49 assert.equal(currentURL(), '/csi/volumes'); 50 assert.equal(document.title, 'CSI Volumes - Nomad'); 51 }); 52 53 test('/csi/volumes should list the first page of volumes sorted by name', async function (assert) { 54 const volumeCount = VolumesList.pageSize + 1; 55 server.createList('csi-volume', volumeCount); 56 57 await VolumesList.visit(); 58 59 await percySnapshot(assert); 60 61 const sortedVolumes = server.db.csiVolumes.sortBy('id'); 62 assert.equal(VolumesList.volumes.length, VolumesList.pageSize); 63 VolumesList.volumes.forEach((volume, index) => { 64 assert.equal(volume.name, sortedVolumes[index].id, 'Volumes are ordered'); 65 }); 66 }); 67 68 test('each volume row should contain information about the volume', async function (assert) { 69 const volume = server.create('csi-volume'); 70 const readAllocs = server.createList('allocation', 2, { shallow: true }); 71 const writeAllocs = server.createList('allocation', 3, { shallow: true }); 72 readAllocs.forEach((alloc) => assignReadAlloc(volume, alloc)); 73 writeAllocs.forEach((alloc) => assignWriteAlloc(volume, alloc)); 74 75 await VolumesList.visit(); 76 77 const volumeRow = VolumesList.volumes.objectAt(0); 78 79 let controllerHealthStr = 'Node Only'; 80 if (volume.controllerRequired || volume.controllersExpected > 0) { 81 const healthy = volume.controllersHealthy; 82 const expected = volume.controllersExpected; 83 const isHealthy = healthy > 0; 84 controllerHealthStr = `${ 85 isHealthy ? 'Healthy' : 'Unhealthy' 86 } ( ${healthy} / ${expected} )`; 87 } 88 89 const nodeHealthStr = volume.nodesHealthy > 0 ? 'Healthy' : 'Unhealthy'; 90 91 assert.equal(volumeRow.name, volume.id); 92 assert.notOk(volumeRow.hasNamespace); 93 assert.equal( 94 volumeRow.schedulable, 95 volume.schedulable ? 'Schedulable' : 'Unschedulable' 96 ); 97 assert.equal(volumeRow.controllerHealth, controllerHealthStr); 98 assert.equal( 99 volumeRow.nodeHealth, 100 `${nodeHealthStr} ( ${volume.nodesHealthy} / ${volume.nodesExpected} )` 101 ); 102 assert.equal(volumeRow.provider, volume.provider); 103 assert.equal(volumeRow.allocations, readAllocs.length + writeAllocs.length); 104 }); 105 106 test('each volume row should link to the corresponding volume', async function (assert) { 107 const [, secondNamespace] = server.createList('namespace', 2); 108 const volume = server.create('csi-volume', { 109 namespaceId: secondNamespace.id, 110 }); 111 112 await VolumesList.visit({ namespace: '*' }); 113 114 await VolumesList.volumes.objectAt(0).clickName(); 115 assert.equal( 116 currentURL(), 117 `/csi/volumes/${volume.id}@${secondNamespace.id}` 118 ); 119 120 await VolumesList.visit({ namespace: '*' }); 121 assert.equal(currentURL(), '/csi/volumes?namespace=*'); 122 123 await VolumesList.volumes.objectAt(0).clickRow(); 124 assert.equal( 125 currentURL(), 126 `/csi/volumes/${volume.id}@${secondNamespace.id}` 127 ); 128 }); 129 130 test('when there are no volumes, there is an empty message', async function (assert) { 131 await VolumesList.visit(); 132 133 await percySnapshot(assert); 134 135 assert.ok(VolumesList.isEmpty); 136 assert.equal(VolumesList.emptyState.headline, 'No Volumes'); 137 }); 138 139 test('when there are volumes, but no matches for a search, there is an empty message', async function (assert) { 140 server.create('csi-volume', { id: 'cat 1' }); 141 server.create('csi-volume', { id: 'cat 2' }); 142 143 await VolumesList.visit(); 144 145 await VolumesList.search('dog'); 146 assert.ok(VolumesList.isEmpty); 147 assert.equal(VolumesList.emptyState.headline, 'No Matches'); 148 }); 149 150 test('searching resets the current page', async function (assert) { 151 server.createList('csi-volume', VolumesList.pageSize + 1); 152 153 await VolumesList.visit(); 154 await VolumesList.nextPage(); 155 156 assert.equal(currentURL(), '/csi/volumes?page=2'); 157 158 await VolumesList.search('foobar'); 159 160 assert.equal(currentURL(), '/csi/volumes?search=foobar'); 161 }); 162 163 test('when the cluster has namespaces, each volume row includes the volume namespace', async function (assert) { 164 server.createList('namespace', 2); 165 const volume = server.create('csi-volume'); 166 167 await VolumesList.visit({ namespace: '*' }); 168 169 const volumeRow = VolumesList.volumes.objectAt(0); 170 assert.equal(volumeRow.namespace, volume.namespaceId); 171 }); 172 173 test('when the namespace query param is set, only matching volumes are shown and the namespace value is forwarded to app state', async function (assert) { 174 server.createList('namespace', 2); 175 const volume1 = server.create('csi-volume', { 176 namespaceId: server.db.namespaces[0].id, 177 }); 178 const volume2 = server.create('csi-volume', { 179 namespaceId: server.db.namespaces[1].id, 180 }); 181 182 await VolumesList.visit(); 183 assert.equal(VolumesList.volumes.length, 2); 184 185 const firstNamespace = server.db.namespaces[0]; 186 await VolumesList.visit({ namespace: firstNamespace.id }); 187 assert.equal(VolumesList.volumes.length, 1); 188 assert.equal(VolumesList.volumes.objectAt(0).name, volume1.id); 189 190 const secondNamespace = server.db.namespaces[1]; 191 await VolumesList.visit({ namespace: secondNamespace.id }); 192 193 assert.equal(VolumesList.volumes.length, 1); 194 assert.equal(VolumesList.volumes.objectAt(0).name, volume2.id); 195 }); 196 197 test('when accessing volumes is forbidden, a message is shown with a link to the tokens page', async function (assert) { 198 server.pretender.get('/v1/volumes', () => [403, {}, null]); 199 200 await VolumesList.visit(); 201 assert.equal(VolumesList.error.title, 'Not Authorized'); 202 203 await VolumesList.error.seekHelp(); 204 assert.equal(currentURL(), '/settings/tokens'); 205 }); 206 207 pageSizeSelect({ 208 resourceName: 'volume', 209 pageObject: VolumesList, 210 pageObjectList: VolumesList.volumes, 211 async setup() { 212 server.createList('csi-volume', VolumesList.pageSize); 213 await VolumesList.visit(); 214 }, 215 }); 216 217 testSingleSelectFacet('Namespace', { 218 facet: VolumesList.facets.namespace, 219 paramName: 'namespace', 220 expectedOptions: ['All (*)', 'default', 'namespace-2'], 221 optionToSelect: 'namespace-2', 222 async beforeEach() { 223 server.create('namespace', { id: 'default' }); 224 server.create('namespace', { id: 'namespace-2' }); 225 server.createList('csi-volume', 2, { namespaceId: 'default' }); 226 server.createList('csi-volume', 2, { namespaceId: 'namespace-2' }); 227 await VolumesList.visit(); 228 }, 229 filter(volume, selection) { 230 return volume.namespaceId === selection; 231 }, 232 }); 233 234 function testSingleSelectFacet( 235 label, 236 { facet, paramName, beforeEach, filter, expectedOptions, optionToSelect } 237 ) { 238 test(`the ${label} facet has the correct options`, async function (assert) { 239 await beforeEach(); 240 await facet.toggle(); 241 242 let expectation; 243 if (typeof expectedOptions === 'function') { 244 expectation = expectedOptions(server.db.jobs); 245 } else { 246 expectation = expectedOptions; 247 } 248 249 assert.deepEqual( 250 facet.options.map((option) => option.label.trim()), 251 expectation, 252 'Options for facet are as expected' 253 ); 254 }); 255 256 test(`the ${label} facet filters the volumes list by ${label}`, async function (assert) { 257 await beforeEach(); 258 await facet.toggle(); 259 260 const option = facet.options.findOneBy('label', optionToSelect); 261 const selection = option.key; 262 await option.select(); 263 264 const expectedVolumes = server.db.csiVolumes 265 .filter((volume) => filter(volume, selection)) 266 .sortBy('id'); 267 268 VolumesList.volumes.forEach((volume, index) => { 269 assert.equal( 270 volume.name, 271 expectedVolumes[index].name, 272 `Volume at ${index} is ${expectedVolumes[index].name}` 273 ); 274 }); 275 }); 276 277 test(`selecting an option in the ${label} facet updates the ${paramName} query param`, async function (assert) { 278 await beforeEach(); 279 await facet.toggle(); 280 281 const option = facet.options.objectAt(1); 282 const selection = option.key; 283 await option.select(); 284 285 assert.ok( 286 currentURL().includes(`${paramName}=${selection}`), 287 'URL has the correct query param key and value' 288 ); 289 }); 290 } 291 });