github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/behaviors/fs.js (about) 1 /* eslint-disable qunit/require-expect */ 2 import { test } from 'qunit'; 3 import { currentURL, visit } from '@ember/test-helpers'; 4 5 import { filesForPath } from 'nomad-ui/mirage/config'; 6 import { formatBytes } from 'nomad-ui/utils/units'; 7 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 8 9 import Response from 'ember-cli-mirage/response'; 10 import moment from 'moment'; 11 12 import FS from 'nomad-ui/tests/pages/allocations/fs'; 13 14 const fileSort = (prop, files) => { 15 let dir = []; 16 let file = []; 17 files.forEach((f) => { 18 if (f.isDir) { 19 dir.push(f); 20 } else { 21 file.push(f); 22 } 23 }); 24 25 return dir.sortBy(prop).concat(file.sortBy(prop)); 26 }; 27 28 export default function browseFilesystem({ 29 pageObjectVisitPathFunctionName, 30 pageObjectVisitFunctionName, 31 visitSegments, 32 getExpectedPathBase, 33 getTitleComponent, 34 getBreadcrumbComponent, 35 getFilesystemRoot, 36 }) { 37 test('it passes an accessibility audit', async function (assert) { 38 await FS[pageObjectVisitFunctionName]( 39 visitSegments({ allocation: this.allocation, task: this.task }) 40 ); 41 await a11yAudit(assert); 42 }); 43 44 test('visiting filesystem root', async function (assert) { 45 await FS[pageObjectVisitFunctionName]( 46 visitSegments({ allocation: this.allocation, task: this.task }) 47 ); 48 49 const pathBaseWithTrailingSlash = getExpectedPathBase({ 50 allocation: this.allocation, 51 task: this.task, 52 }); 53 const pathBaseWithoutTrailingSlash = pathBaseWithTrailingSlash.slice(0, -1); 54 55 assert.equal(currentURL(), pathBaseWithoutTrailingSlash, 'No redirect'); 56 }); 57 58 test('visiting filesystem paths', async function (assert) { 59 const paths = [ 60 'some-file.log', 61 'a/deep/path/to/a/file.log', 62 '/', 63 'Unicode™®', 64 ]; 65 66 const testPath = async (filePath) => { 67 let pathWithLeadingSlash = filePath; 68 69 if (!pathWithLeadingSlash.startsWith('/')) { 70 pathWithLeadingSlash = `/${filePath}`; 71 } 72 73 await FS[pageObjectVisitPathFunctionName]({ 74 ...visitSegments({ allocation: this.allocation, task: this.task }), 75 path: filePath, 76 }); 77 assert.equal( 78 currentURL(), 79 `${getExpectedPathBase({ 80 allocation: this.allocation, 81 task: this.task, 82 })}${encodeURIComponent(filePath)}`, 83 'No redirect' 84 ); 85 assert.equal( 86 document.title, 87 `${pathWithLeadingSlash} - ${getTitleComponent({ 88 allocation: this.allocation, 89 task: this.task, 90 })} - Nomad` 91 ); 92 assert.equal( 93 FS.breadcrumbsText, 94 `${getBreadcrumbComponent({ 95 allocation: this.allocation, 96 task: this.task, 97 })} ${filePath.replace(/\//g, ' ')}`.trim() 98 ); 99 }; 100 101 await paths.reduce(async (prev, filePath) => { 102 await prev; 103 return testPath(filePath); 104 }, Promise.resolve()); 105 }); 106 107 test('navigating allocation filesystem', async function (assert) { 108 const objects = { allocation: this.allocation, task: this.task }; 109 await FS[pageObjectVisitPathFunctionName]({ 110 ...visitSegments(objects), 111 path: '/', 112 }); 113 114 const sortedFiles = fileSort( 115 'name', 116 filesForPath(this.server.schema.allocFiles, getFilesystemRoot(objects)) 117 .models 118 ); 119 120 assert.ok(FS.fileViewer.isHidden); 121 122 assert.equal(FS.directoryEntries.length, 4); 123 124 assert.equal(FS.breadcrumbsText, getBreadcrumbComponent(objects)); 125 126 assert.equal(FS.breadcrumbs.length, 1); 127 assert.ok(FS.breadcrumbs[0].isActive); 128 assert.equal(FS.breadcrumbs[0].text, getBreadcrumbComponent(objects)); 129 130 FS.directoryEntries[0].as((directory) => { 131 const fileRecord = sortedFiles[0]; 132 assert.equal( 133 directory.name, 134 fileRecord.name, 135 'directories should come first' 136 ); 137 assert.ok(directory.isDirectory); 138 assert.equal(directory.size, '', 'directory sizes are hidden'); 139 assert.equal( 140 directory.lastModified, 141 moment(fileRecord.modTime).fromNow() 142 ); 143 assert.notOk( 144 directory.path.includes('//'), 145 'paths shouldn’t have redundant separators' 146 ); 147 }); 148 149 FS.directoryEntries[2].as((file) => { 150 const fileRecord = sortedFiles[2]; 151 assert.equal(file.name, fileRecord.name); 152 assert.ok(file.isFile); 153 assert.equal(file.size, formatBytes(fileRecord.size)); 154 assert.equal(file.lastModified, moment(fileRecord.modTime).fromNow()); 155 }); 156 157 await FS.directoryEntries[0].visit(); 158 159 assert.equal(FS.directoryEntries.length, 1); 160 161 assert.equal(FS.breadcrumbs.length, 2); 162 assert.equal( 163 FS.breadcrumbsText, 164 `${getBreadcrumbComponent(objects)} ${this.directory.name}` 165 ); 166 167 assert.notOk(FS.breadcrumbs[0].isActive); 168 169 assert.equal(FS.breadcrumbs[1].text, this.directory.name); 170 assert.ok(FS.breadcrumbs[1].isActive); 171 172 await FS.directoryEntries[0].visit(); 173 174 assert.equal(FS.directoryEntries.length, 1); 175 assert.notOk( 176 FS.directoryEntries[0].path.includes('//'), 177 'paths shouldn’t have redundant separators' 178 ); 179 180 assert.equal(FS.breadcrumbs.length, 3); 181 assert.equal( 182 FS.breadcrumbsText, 183 `${getBreadcrumbComponent(objects)} ${this.directory.name} ${ 184 this.nestedDirectory.name 185 }` 186 ); 187 assert.equal(FS.breadcrumbs[2].text, this.nestedDirectory.name); 188 189 assert.notOk( 190 FS.breadcrumbs[0].path.includes('//'), 191 'paths shouldn’t have redundant separators' 192 ); 193 assert.notOk( 194 FS.breadcrumbs[1].path.includes('//'), 195 'paths shouldn’t have redundant separators' 196 ); 197 198 await FS.breadcrumbs[1].visit(); 199 assert.equal( 200 FS.breadcrumbsText, 201 `${getBreadcrumbComponent(objects)} ${this.directory.name}` 202 ); 203 assert.equal(FS.breadcrumbs.length, 2); 204 }); 205 206 test('sorting allocation filesystem directory', async function (assert) { 207 this.server.get('/client/fs/ls/:allocation_id', () => { 208 return [ 209 { 210 Name: 'aaa-big-old-file', 211 IsDir: false, 212 Size: 19190000, 213 ModTime: moment().subtract(1, 'year').format(), 214 }, 215 { 216 Name: 'mmm-small-mid-file', 217 IsDir: false, 218 Size: 1919, 219 ModTime: moment().subtract(6, 'month').format(), 220 }, 221 { 222 Name: 'zzz-med-new-file', 223 IsDir: false, 224 Size: 191900, 225 ModTime: moment().format(), 226 }, 227 { 228 Name: 'aaa-big-old-directory', 229 IsDir: true, 230 Size: 19190000, 231 ModTime: moment().subtract(1, 'year').format(), 232 }, 233 { 234 Name: 'mmm-small-mid-directory', 235 IsDir: true, 236 Size: 1919, 237 ModTime: moment().subtract(6, 'month').format(), 238 }, 239 { 240 Name: 'zzz-med-new-directory', 241 IsDir: true, 242 Size: 191900, 243 ModTime: moment().format(), 244 }, 245 ]; 246 }); 247 248 await FS[pageObjectVisitPathFunctionName]({ 249 ...visitSegments({ allocation: this.allocation, task: this.task }), 250 path: '/', 251 }); 252 253 assert.deepEqual(FS.directoryEntryNames(), [ 254 'aaa-big-old-directory', 255 'mmm-small-mid-directory', 256 'zzz-med-new-directory', 257 'aaa-big-old-file', 258 'mmm-small-mid-file', 259 'zzz-med-new-file', 260 ]); 261 262 await FS.sortBy('Name'); 263 264 assert.deepEqual(FS.directoryEntryNames(), [ 265 'zzz-med-new-file', 266 'mmm-small-mid-file', 267 'aaa-big-old-file', 268 'zzz-med-new-directory', 269 'mmm-small-mid-directory', 270 'aaa-big-old-directory', 271 ]); 272 273 await FS.sortBy('ModTime'); 274 275 assert.deepEqual(FS.directoryEntryNames(), [ 276 'zzz-med-new-file', 277 'mmm-small-mid-file', 278 'aaa-big-old-file', 279 'zzz-med-new-directory', 280 'mmm-small-mid-directory', 281 'aaa-big-old-directory', 282 ]); 283 284 await FS.sortBy('ModTime'); 285 286 assert.deepEqual(FS.directoryEntryNames(), [ 287 'aaa-big-old-directory', 288 'mmm-small-mid-directory', 289 'zzz-med-new-directory', 290 'aaa-big-old-file', 291 'mmm-small-mid-file', 292 'zzz-med-new-file', 293 ]); 294 295 await FS.sortBy('Size'); 296 297 assert.deepEqual( 298 FS.directoryEntryNames(), 299 [ 300 'aaa-big-old-file', 301 'zzz-med-new-file', 302 'mmm-small-mid-file', 303 'zzz-med-new-directory', 304 'mmm-small-mid-directory', 305 'aaa-big-old-directory', 306 ], 307 'expected files to be sorted by descending size and directories to be sorted by descending name' 308 ); 309 310 await FS.sortBy('Size'); 311 312 assert.deepEqual( 313 FS.directoryEntryNames(), 314 [ 315 'aaa-big-old-directory', 316 'mmm-small-mid-directory', 317 'zzz-med-new-directory', 318 'mmm-small-mid-file', 319 'zzz-med-new-file', 320 'aaa-big-old-file', 321 ], 322 'expected directories to be sorted by name and files to be sorted by ascending size' 323 ); 324 }); 325 326 test('viewing a file', async function (assert) { 327 const objects = { allocation: this.allocation, task: this.task }; 328 const node = server.db.nodes.find(this.allocation.nodeId); 329 330 server.get( 331 `http://${node.httpAddr}/v1/client/fs/readat/:allocation_id`, 332 function () { 333 return new Response(500); 334 } 335 ); 336 337 await FS[pageObjectVisitPathFunctionName]({ 338 ...visitSegments(objects), 339 path: '/', 340 }); 341 342 const sortedFiles = fileSort( 343 'name', 344 filesForPath(this.server.schema.allocFiles, getFilesystemRoot(objects)) 345 .models 346 ); 347 const fileRecord = sortedFiles.find((f) => !f.isDir); 348 const fileIndex = sortedFiles.indexOf(fileRecord); 349 350 await FS.directoryEntries[fileIndex].visit(); 351 352 assert.equal( 353 FS.breadcrumbsText, 354 `${getBreadcrumbComponent(objects)} ${fileRecord.name}` 355 ); 356 357 assert.ok(FS.fileViewer.isPresent); 358 359 const requests = this.server.pretender.handledRequests; 360 const secondAttempt = requests.pop(); 361 const firstAttempt = requests.pop(); 362 363 assert.equal( 364 firstAttempt.url.split('?')[0], 365 `//${node.httpAddr}/v1/client/fs/readat/${this.allocation.id}`, 366 'Client is hit first' 367 ); 368 assert.equal(firstAttempt.status, 500, 'Client request fails'); 369 assert.equal( 370 secondAttempt.url.split('?')[0], 371 `/v1/client/fs/readat/${this.allocation.id}`, 372 'Server is hit second' 373 ); 374 }); 375 376 test('viewing an empty directory', async function (assert) { 377 await FS[pageObjectVisitPathFunctionName]({ 378 ...visitSegments({ allocation: this.allocation, task: this.task }), 379 path: 'empty-directory', 380 }); 381 382 assert.ok(FS.isEmptyDirectory); 383 }); 384 385 test('viewing paths that produce stat API errors', async function (assert) { 386 this.server.get('/client/fs/stat/:allocation_id', () => { 387 return new Response(500, {}, 'no such file or directory'); 388 }); 389 390 await FS[pageObjectVisitPathFunctionName]({ 391 ...visitSegments({ allocation: this.allocation, task: this.task }), 392 path: '/what-is-this', 393 }); 394 assert.notEqual( 395 FS.error.title, 396 'Not Found', 397 '500 is not interpreted as 404' 398 ); 399 assert.equal( 400 FS.error.title, 401 'Server Error', 402 '500 is not interpreted as 500' 403 ); 404 405 await visit('/'); 406 407 this.server.get('/client/fs/stat/:allocation_id', () => { 408 return new Response(999); 409 }); 410 411 await FS[pageObjectVisitPathFunctionName]({ 412 ...visitSegments({ allocation: this.allocation, task: this.task }), 413 path: '/what-is-this', 414 }); 415 assert.equal(FS.error.title, 'Error', 'other statuses are passed through'); 416 }); 417 418 test('viewing paths that produce ls API errors', async function (assert) { 419 this.server.get('/client/fs/ls/:allocation_id', () => { 420 return new Response(500, {}, 'no such file or directory'); 421 }); 422 423 await FS[pageObjectVisitPathFunctionName]({ 424 ...visitSegments({ allocation: this.allocation, task: this.task }), 425 path: this.directory.name, 426 }); 427 assert.notEqual( 428 FS.error.title, 429 'Not Found', 430 '500 is not interpreted as 404' 431 ); 432 assert.equal( 433 FS.error.title, 434 'Server Error', 435 '500 is not interpreted as 404' 436 ); 437 438 await visit('/'); 439 440 this.server.get('/client/fs/ls/:allocation_id', () => { 441 return new Response(999); 442 }); 443 444 await FS[pageObjectVisitPathFunctionName]({ 445 ...visitSegments({ allocation: this.allocation, task: this.task }), 446 path: this.directory.name, 447 }); 448 assert.equal(FS.error.title, 'Error', 'other statuses are passed through'); 449 }); 450 }