github.com/hernad/nomad@v1.6.112/ui/tests/integration/components/task-log-test.js (about)

     1  /**
     2   * Copyright (c) HashiCorp, Inc.
     3   * SPDX-License-Identifier: MPL-2.0
     4   */
     5  
     6  import { run } from '@ember/runloop';
     7  import { module, test } from 'qunit';
     8  import { setupRenderingTest } from 'ember-qunit';
     9  import { find, click, render, settled } from '@ember/test-helpers';
    10  import hbs from 'htmlbars-inline-precompile';
    11  import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
    12  import Pretender from 'pretender';
    13  import { logEncode } from '../../../mirage/data/logs';
    14  
    15  const HOST = '1.1.1.1:1111';
    16  const allowedConnectionTime = 100;
    17  const commonProps = {
    18    interval: 200,
    19    allocation: {
    20      id: 'alloc-1',
    21      node: {
    22        httpAddr: HOST,
    23      },
    24    },
    25    taskState: 'task-name',
    26    clientTimeout: allowedConnectionTime,
    27    serverTimeout: allowedConnectionTime,
    28  };
    29  
    30  const logHead = [logEncode(['HEAD'], 0)];
    31  const logTail = [logEncode(['TAIL'], 0)];
    32  const streamFrames = ['one\n', 'two\n', 'three\n', 'four\n', 'five\n'];
    33  let streamPointer = 0;
    34  let logMode = null;
    35  
    36  module('Integration | Component | task log', function (hooks) {
    37    setupRenderingTest(hooks);
    38  
    39    hooks.beforeEach(function () {
    40      const handler = ({ queryParams }) => {
    41        let frames;
    42        let data;
    43  
    44        if (logMode === 'head') {
    45          frames = logHead;
    46        } else if (logMode === 'tail') {
    47          frames = logTail;
    48        } else {
    49          frames = streamFrames;
    50        }
    51  
    52        if (frames === streamFrames) {
    53          data = queryParams.plain
    54            ? frames[streamPointer]
    55            : logEncode(frames, streamPointer);
    56          streamPointer++;
    57        } else {
    58          data = queryParams.plain
    59            ? frames.join('')
    60            : logEncode(frames, frames.length - 1);
    61        }
    62  
    63        return [200, {}, data];
    64      };
    65  
    66      this.server = new Pretender(function () {
    67        this.get(`http://${HOST}/v1/client/fs/logs/:allocation_id`, handler);
    68        this.get('/v1/client/fs/logs/:allocation_id', handler);
    69        this.get('/v1/regions', () => [200, {}, '[]']);
    70      });
    71    });
    72  
    73    hooks.afterEach(function () {
    74      window.localStorage.clear();
    75      this.server.shutdown();
    76      streamPointer = 0;
    77      logMode = null;
    78    });
    79  
    80    test('Basic appearance', async function (assert) {
    81      assert.expect(8);
    82  
    83      run.later(run, run.cancelTimers, commonProps.interval);
    84  
    85      this.setProperties(commonProps);
    86      await render(
    87        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
    88      );
    89  
    90      assert.ok(find('[data-test-log-action="stdout"]'), 'Stdout button');
    91      assert.ok(find('[data-test-log-action="stderr"]'), 'Stderr button');
    92      assert.ok(find('[data-test-log-action="head"]'), 'Head button');
    93      assert.ok(find('[data-test-log-action="tail"]'), 'Tail button');
    94      assert.ok(
    95        find('[data-test-log-action="toggle-stream"]'),
    96        'Stream toggle button'
    97      );
    98  
    99      assert.ok(
   100        find('[data-test-log-box].is-full-bleed.is-dark'),
   101        'Body is full-bleed and dark'
   102      );
   103  
   104      assert.ok(
   105        find('pre.cli-window'),
   106        'Cli is preformatted and using the cli-window component class'
   107      );
   108  
   109      await componentA11yAudit(this.element, assert);
   110    });
   111  
   112    test('Streaming starts on creation', async function (assert) {
   113      assert.expect(3);
   114  
   115      run.later(run, run.cancelTimers, commonProps.interval);
   116  
   117      this.setProperties(commonProps);
   118      await render(
   119        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
   120      );
   121  
   122      const logUrlRegex = new RegExp(
   123        `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`
   124      );
   125      assert.ok(
   126        this.server.handledRequests.filter((req) => logUrlRegex.test(req.url))
   127          .length,
   128        'Log requests were made'
   129      );
   130  
   131      await settled();
   132      assert.equal(
   133        find('[data-test-log-cli]').textContent,
   134        streamFrames[0],
   135        'First chunk of streaming log is shown'
   136      );
   137  
   138      await componentA11yAudit(this.element, assert);
   139    });
   140  
   141    test('Clicking Head loads the log head', async function (assert) {
   142      logMode = 'head';
   143      run.later(run, run.cancelTimers, commonProps.interval);
   144  
   145      this.setProperties(commonProps);
   146      await render(
   147        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
   148      );
   149  
   150      click('[data-test-log-action="head"]');
   151  
   152      await settled();
   153      assert.ok(
   154        this.server.handledRequests.find(
   155          ({ queryParams: qp }) => qp.origin === 'start' && qp.offset === '0'
   156        ),
   157        'Log head request was made'
   158      );
   159      assert.equal(
   160        find('[data-test-log-cli]').textContent,
   161        logHead[0],
   162        'Head of the log is shown'
   163      );
   164    });
   165  
   166    test('Clicking Tail loads the log tail', async function (assert) {
   167      logMode = 'tail';
   168      run.later(run, run.cancelTimers, commonProps.interval);
   169  
   170      this.setProperties(commonProps);
   171      await render(
   172        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
   173      );
   174  
   175      click('[data-test-log-action="tail"]');
   176  
   177      await settled();
   178      assert.ok(
   179        this.server.handledRequests.find(
   180          ({ queryParams: qp }) => qp.origin === 'end'
   181        ),
   182        'Log tail request was made'
   183      );
   184      assert.equal(
   185        find('[data-test-log-cli]').textContent,
   186        logTail[0],
   187        'Tail of the log is shown'
   188      );
   189    });
   190  
   191    test('Clicking toggleStream starts and stops the log stream', async function (assert) {
   192      assert.expect(3);
   193  
   194      run.later(run, run.cancelTimers, commonProps.interval);
   195  
   196      const { interval } = commonProps;
   197      this.setProperties(commonProps);
   198      await render(
   199        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} @interval={{interval}} />`
   200      );
   201  
   202      run.later(() => {
   203        click('[data-test-log-action="toggle-stream"]');
   204      }, interval);
   205  
   206      await settled();
   207      assert.equal(
   208        find('[data-test-log-cli]').textContent,
   209        streamFrames[0],
   210        'First frame loaded'
   211      );
   212  
   213      run.later(() => {
   214        assert.equal(
   215          find('[data-test-log-cli]').textContent,
   216          streamFrames[0],
   217          'Still only first frame'
   218        );
   219        click('[data-test-log-action="toggle-stream"]');
   220        run.later(run, run.cancelTimers, interval * 2);
   221      }, interval * 2);
   222  
   223      await settled();
   224      assert.equal(
   225        find('[data-test-log-cli]').textContent,
   226        streamFrames[0] + streamFrames[0] + streamFrames[1],
   227        'Now includes second frame'
   228      );
   229    });
   230  
   231    test('Clicking stderr switches the log to standard error', async function (assert) {
   232      run.later(run, run.cancelTimers, commonProps.interval);
   233  
   234      this.setProperties(commonProps);
   235      await render(
   236        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
   237      );
   238  
   239      click('[data-test-log-action="stderr"]');
   240      run.later(run, run.cancelTimers, commonProps.interval);
   241  
   242      await settled();
   243      assert.ok(
   244        this.server.handledRequests.filter(
   245          (req) => req.queryParams.type === 'stderr'
   246        ).length,
   247        'stderr log requests were made'
   248      );
   249    });
   250  
   251    test('Clicking stderr/stdout mode buttons does nothing when the mode remains the same', async function (assert) {
   252      const { interval } = commonProps;
   253  
   254      run.later(() => {
   255        click('[data-test-log-action="stdout"]');
   256        run.later(run, run.cancelTimers, interval * 6);
   257      }, interval * 2);
   258  
   259      this.setProperties(commonProps);
   260      await render(
   261        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
   262      );
   263  
   264      assert.equal(
   265        find('[data-test-log-cli]').textContent,
   266        streamFrames[0] + streamFrames[0] + streamFrames[1],
   267        'Now includes second frame'
   268      );
   269    });
   270  
   271    test('When the client is inaccessible, task-log falls back to requesting logs through the server', async function (assert) {
   272      run.later(run, run.cancelTimers, allowedConnectionTime * 2);
   273  
   274      // override client response to timeout
   275      this.server.get(
   276        `http://${HOST}/v1/client/fs/logs/:allocation_id`,
   277        () => [400, {}, ''],
   278        allowedConnectionTime * 2
   279      );
   280  
   281      this.setProperties(commonProps);
   282      await render(hbs`<TaskLog
   283        @allocation={{allocation}}
   284        @task={{taskState}}
   285        @clientTimeout={{clientTimeout}}
   286        @serverTimeout={{serverTimeout}} />`);
   287  
   288      const clientUrlRegex = new RegExp(
   289        `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`
   290      );
   291      assert.ok(
   292        this.server.handledRequests.filter((req) => clientUrlRegex.test(req.url))
   293          .length,
   294        'Log request was initially made directly to the client'
   295      );
   296  
   297      await settled();
   298      const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`;
   299      assert.ok(
   300        this.server.handledRequests.filter((req) => req.url.startsWith(serverUrl))
   301          .length,
   302        'Log request was later made to the server'
   303      );
   304  
   305      assert.ok(
   306        this.server.handledRequests.filter((req) =>
   307          clientUrlRegex.test(req.url)
   308        )[0].aborted,
   309        'Client log request was aborted'
   310      );
   311    });
   312  
   313    test('When both the client and the server are inaccessible, an error message is shown', async function (assert) {
   314      assert.expect(5);
   315  
   316      run.later(run, run.cancelTimers, allowedConnectionTime * 5);
   317  
   318      // override client and server responses to timeout
   319      this.server.get(
   320        `http://${HOST}/v1/client/fs/logs/:allocation_id`,
   321        () => [400, {}, ''],
   322        allowedConnectionTime * 2
   323      );
   324      this.server.get(
   325        '/v1/client/fs/logs/:allocation_id',
   326        () => [400, {}, ''],
   327        allowedConnectionTime * 2
   328      );
   329  
   330      this.setProperties(commonProps);
   331      await render(hbs`<TaskLog
   332        @allocation={{allocation}}
   333        @task={{taskState}}
   334        @clientTimeout={{clientTimeout}}
   335        @serverTimeout={{serverTimeout}} />`);
   336  
   337      const clientUrlRegex = new RegExp(
   338        `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`
   339      );
   340      assert.ok(
   341        this.server.handledRequests.filter((req) => clientUrlRegex.test(req.url))
   342          .length,
   343        'Log request was initially made directly to the client'
   344      );
   345      const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`;
   346      assert.ok(
   347        this.server.handledRequests.filter((req) => req.url.startsWith(serverUrl))
   348          .length,
   349        'Log request was later made to the server'
   350      );
   351      assert.ok(
   352        find('[data-test-connection-error]'),
   353        'An error message is shown'
   354      );
   355  
   356      await click('[data-test-connection-error-dismiss]');
   357      assert.notOk(
   358        find('[data-test-connection-error]'),
   359        'The error message is dismissable'
   360      );
   361  
   362      await componentA11yAudit(this.element, assert);
   363    });
   364  
   365    test('When the client is inaccessible, the server is accessible, and stderr is pressed before the client timeout occurs, the no connection error is not shown', async function (assert) {
   366      // override client response to timeout
   367      this.server.get(
   368        `http://${HOST}/v1/client/fs/logs/:allocation_id`,
   369        () => [400, {}, ''],
   370        allowedConnectionTime * 2
   371      );
   372  
   373      // Click stderr before the client request responds
   374      run.later(() => {
   375        click('[data-test-log-action="stderr"]');
   376        run.later(run, run.cancelTimers, commonProps.interval * 5);
   377      }, allowedConnectionTime / 2);
   378  
   379      this.setProperties(commonProps);
   380      await render(hbs`<TaskLog
   381        @allocation={{allocation}}
   382        @task={{taskState}}
   383        @clientTimeout={{clientTimeout}}
   384        @serverTimeout={{serverTimeout}} />`);
   385  
   386      const clientUrlRegex = new RegExp(
   387        `${HOST}/v1/client/fs/logs/${commonProps.allocation.id}`
   388      );
   389      const clientRequests = this.server.handledRequests.filter((req) =>
   390        clientUrlRegex.test(req.url)
   391      );
   392      assert.ok(
   393        clientRequests.find((req) => req.queryParams.type === 'stdout'),
   394        'Client request for stdout'
   395      );
   396      assert.ok(
   397        clientRequests.find((req) => req.queryParams.type === 'stderr'),
   398        'Client request for stderr'
   399      );
   400  
   401      const serverUrl = `/v1/client/fs/logs/${commonProps.allocation.id}`;
   402      assert.ok(
   403        this.server.handledRequests
   404          .filter((req) => req.url.startsWith(serverUrl))
   405          .find((req) => req.queryParams.type === 'stderr'),
   406        'Server request for stderr'
   407      );
   408  
   409      assert.notOk(
   410        find('[data-test-connection-error]'),
   411        'An error message is not shown'
   412      );
   413    });
   414  
   415    test('The log streaming mode is persisted in localStorage', async function (assert) {
   416      window.localStorage.nomadLogMode = JSON.stringify('stderr');
   417  
   418      run.later(run, run.cancelTimers, commonProps.interval);
   419  
   420      this.setProperties(commonProps);
   421      await render(
   422        hbs`<TaskLog @allocation={{allocation}} @task={{taskState}} />`
   423      );
   424  
   425      assert.ok(
   426        this.server.handledRequests.filter(
   427          (req) => req.queryParams.type === 'stderr'
   428        ).length
   429      );
   430      assert.notOk(
   431        this.server.handledRequests.filter(
   432          (req) => req.queryParams.type === 'stdout'
   433        ).length
   434      );
   435  
   436      click('[data-test-log-action="stdout"]');
   437      run.later(run, run.cancelTimers, commonProps.interval);
   438  
   439      await settled();
   440      assert.ok(
   441        this.server.handledRequests.filter(
   442          (req) => req.queryParams.type === 'stdout'
   443        ).length
   444      );
   445      assert.equal(window.localStorage.nomadLogMode, JSON.stringify('stdout'));
   446    });
   447  });