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  }