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  }