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 });