github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/integration/components/task-log-test.js (about)

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