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