2017-12-15 21:39:18 +00:00
import { run } from '@ember/runloop' ;
2019-03-13 00:04:16 +00:00
import { module , test } from 'qunit' ;
import { setupRenderingTest } from 'ember-qunit' ;
2019-07-23 19:40:32 +00:00
import { find , click , render , settled } from '@ember/test-helpers' ;
2017-11-17 00:31:18 +00:00
import hbs from 'htmlbars-inline-precompile' ;
2020-08-25 15:56:02 +00:00
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit' ;
2017-11-17 00:31:18 +00:00
import Pretender from 'pretender' ;
2020-08-25 15:56:02 +00:00
import { logEncode } from '../../../mirage/data/logs' ;
2017-11-17 00:31:18 +00:00
const HOST = '1.1.1.1:1111' ;
2018-02-27 21:38:31 +00:00
const allowedConnectionTime = 100 ;
2017-11-17 00:31:18 +00:00
const commonProps = {
2018-05-24 22:46:55 +00:00
interval : 200 ,
2017-11-17 00:31:18 +00:00
allocation : {
id : 'alloc-1' ,
node : {
httpAddr : HOST ,
} ,
} ,
2020-06-01 19:03:56 +00:00
taskState : 'task-name' ,
2018-02-27 21:38:31 +00:00
clientTimeout : allowedConnectionTime ,
serverTimeout : allowedConnectionTime ,
2017-11-17 00:31:18 +00:00
} ;
2019-06-26 21:52:41 +00:00
const logHead = [ logEncode ( [ 'HEAD' ] , 0 ) ] ;
const logTail = [ logEncode ( [ 'TAIL' ] , 0 ) ] ;
2017-11-17 00:31:18 +00:00
const streamFrames = [ 'one\n' , 'two\n' , 'three\n' , 'four\n' , 'five\n' ] ;
let streamPointer = 0 ;
2019-06-26 21:52:41 +00:00
let logMode = null ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
module ( 'Integration | Component | task log' , function ( hooks ) {
setupRenderingTest ( hooks ) ;
hooks . beforeEach ( function ( ) {
2018-02-26 20:23:01 +00:00
const handler = ( { queryParams } ) => {
let frames ;
let data ;
2019-06-26 21:52:41 +00:00
if ( logMode === 'head' ) {
2018-02-26 20:23:01 +00:00
frames = logHead ;
2019-06-26 21:52:41 +00:00
} else if ( logMode === 'tail' ) {
2018-02-26 20:23:01 +00:00
frames = logTail ;
} else {
frames = streamFrames ;
}
if ( frames === streamFrames ) {
data = queryParams . plain ? frames [ streamPointer ] : logEncode ( frames , streamPointer ) ;
streamPointer ++ ;
} else {
data = queryParams . plain ? frames . join ( '' ) : logEncode ( frames , frames . length - 1 ) ;
}
return [ 200 , { } , data ] ;
} ;
2017-11-17 00:31:18 +00:00
this . server = new Pretender ( function ( ) {
2018-02-26 20:23:01 +00:00
this . get ( ` http:// ${ HOST } /v1/client/fs/logs/:allocation_id ` , handler ) ;
this . get ( '/v1/client/fs/logs/:allocation_id' , handler ) ;
2018-08-09 22:35:25 +00:00
this . get ( '/v1/regions' , ( ) => [ 200 , { } , '[]' ] ) ;
2017-11-17 00:31:18 +00:00
} ) ;
2019-03-13 00:04:16 +00:00
} ) ;
hooks . afterEach ( function ( ) {
2020-07-01 00:09:52 +00:00
window . localStorage . clear ( ) ;
2017-11-17 00:31:18 +00:00
this . server . shutdown ( ) ;
streamPointer = 0 ;
2019-06-26 21:52:41 +00:00
logMode = null ;
2019-03-13 00:04:16 +00:00
} ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
test ( 'Basic appearance' , async function ( assert ) {
2019-03-14 06:45:15 +00:00
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2019-03-13 00:04:16 +00:00
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
assert . ok ( find ( '[data-test-log-action="stdout"]' ) , 'Stdout button' ) ;
assert . ok ( find ( '[data-test-log-action="stderr"]' ) , 'Stderr button' ) ;
assert . ok ( find ( '[data-test-log-action="head"]' ) , 'Head button' ) ;
assert . ok ( find ( '[data-test-log-action="tail"]' ) , 'Tail button' ) ;
assert . ok ( find ( '[data-test-log-action="toggle-stream"]' ) , 'Stream toggle button' ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
assert . ok ( find ( '[data-test-log-box].is-full-bleed.is-dark' ) , 'Body is full-bleed and dark' ) ;
2017-11-17 00:31:18 +00:00
2019-03-14 06:45:15 +00:00
assert . ok (
find ( 'pre.cli-window' ) ,
'Cli is preformatted and using the cli-window component class'
) ;
2020-08-25 15:56:02 +00:00
await componentA11yAudit ( this . element , assert ) ;
2017-11-17 00:31:18 +00:00
} ) ;
2019-03-13 00:04:16 +00:00
test ( 'Streaming starts on creation' , async function ( assert ) {
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
const logUrlRegex = new RegExp ( ` ${ HOST } /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ) ;
2017-11-17 00:31:18 +00:00
assert . ok (
2019-03-13 00:04:16 +00:00
this . server . handledRequests . filter ( req => logUrlRegex . test ( req . url ) ) . length ,
'Log requests were made'
2017-11-17 00:31:18 +00:00
) ;
2019-03-13 00:04:16 +00:00
2019-03-14 06:45:15 +00:00
await settled ( ) ;
assert . equal (
find ( '[data-test-log-cli]' ) . textContent ,
streamFrames [ 0 ] ,
'First chunk of streaming log is shown'
) ;
2020-08-25 15:56:02 +00:00
await componentA11yAudit ( this . element , assert ) ;
2017-11-17 00:31:18 +00:00
} ) ;
2019-03-13 00:04:16 +00:00
test ( 'Clicking Head loads the log head' , async function ( assert ) {
2019-06-26 21:52:41 +00:00
logMode = 'head' ;
2019-03-14 06:45:15 +00:00
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2019-03-13 00:04:16 +00:00
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
click ( '[data-test-log-action="head"]' ) ;
2017-11-17 00:31:18 +00:00
2019-03-14 06:45:15 +00:00
await settled ( ) ;
assert . ok (
this . server . handledRequests . find (
2019-06-26 21:52:41 +00:00
( { queryParams : qp } ) => qp . origin === 'start' && qp . offset === '0'
2019-03-14 06:45:15 +00:00
) ,
'Log head request was made'
) ;
assert . equal ( find ( '[data-test-log-cli]' ) . textContent , logHead [ 0 ] , 'Head of the log is shown' ) ;
2017-11-17 00:31:18 +00:00
} ) ;
2019-03-13 00:04:16 +00:00
test ( 'Clicking Tail loads the log tail' , async function ( assert ) {
2019-06-26 21:52:41 +00:00
logMode = 'tail' ;
2019-03-14 06:45:15 +00:00
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2019-03-13 00:04:16 +00:00
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
click ( '[data-test-log-action="tail"]' ) ;
2017-11-17 00:31:18 +00:00
2019-03-14 06:45:15 +00:00
await settled ( ) ;
assert . ok (
2019-06-26 21:52:41 +00:00
this . server . handledRequests . find ( ( { queryParams : qp } ) => qp . origin === 'end' ) ,
2019-03-14 06:45:15 +00:00
'Log tail request was made'
) ;
assert . equal ( find ( '[data-test-log-cli]' ) . textContent , logTail [ 0 ] , 'Tail of the log is shown' ) ;
2019-03-13 00:04:16 +00:00
} ) ;
test ( 'Clicking toggleStream starts and stops the log stream' , async function ( assert ) {
2019-03-14 06:45:15 +00:00
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2019-03-13 00:04:16 +00:00
const { interval } = commonProps ;
this . setProperties ( commonProps ) ;
2020-07-01 00:09:52 +00:00
await render (
hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} @interval={{interval}} /> `
) ;
2017-11-17 00:31:18 +00:00
run . later ( ( ) => {
2018-01-08 21:08:14 +00:00
click ( '[data-test-log-action="toggle-stream"]' ) ;
2019-03-13 00:04:16 +00:00
} , interval ) ;
2019-03-14 06:45:15 +00:00
await settled ( ) ;
assert . equal ( find ( '[data-test-log-cli]' ) . textContent , streamFrames [ 0 ] , 'First frame loaded' ) ;
run . later ( ( ) => {
assert . equal (
find ( '[data-test-log-cli]' ) . textContent ,
streamFrames [ 0 ] ,
'Still only first frame'
) ;
click ( '[data-test-log-action="toggle-stream"]' ) ;
run . later ( run , run . cancelTimers , interval * 2 ) ;
} , interval * 2 ) ;
await settled ( ) ;
assert . equal (
find ( '[data-test-log-cli]' ) . textContent ,
streamFrames [ 0 ] + streamFrames [ 0 ] + streamFrames [ 1 ] ,
'Now includes second frame'
) ;
2017-11-17 00:31:18 +00:00
} ) ;
2019-03-13 00:04:16 +00:00
test ( 'Clicking stderr switches the log to standard error' , async function ( assert ) {
2019-03-14 06:45:15 +00:00
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2019-03-13 00:04:16 +00:00
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
2017-11-17 00:31:18 +00:00
2019-03-13 00:04:16 +00:00
click ( '[data-test-log-action="stderr"]' ) ;
run . later ( run , run . cancelTimers , commonProps . interval ) ;
2017-11-17 00:31:18 +00:00
2019-03-14 06:45:15 +00:00
await settled ( ) ;
assert . ok (
this . server . handledRequests . filter ( req => req . queryParams . type === 'stderr' ) . length ,
'stderr log requests were made'
) ;
2017-11-17 00:31:18 +00:00
} ) ;
2018-02-26 20:23:01 +00:00
2020-04-28 05:18:49 +00:00
test ( 'Clicking stderr/stdout mode buttons does nothing when the mode remains the same' , async function ( assert ) {
const { interval } = commonProps ;
run . later ( ( ) => {
click ( '[data-test-log-action="stdout"]' ) ;
run . later ( run , run . cancelTimers , interval * 6 ) ;
} , interval * 2 ) ;
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
2020-04-28 05:18:49 +00:00
await settled ( ) ;
assert . equal (
find ( '[data-test-log-cli]' ) . textContent ,
streamFrames [ 0 ] + streamFrames [ 0 ] + streamFrames [ 1 ] ,
'Now includes second frame'
) ;
} ) ;
2019-03-13 00:04:16 +00:00
test ( 'When the client is inaccessible, task-log falls back to requesting logs through the server' , async function ( assert ) {
run . later ( run , run . cancelTimers , allowedConnectionTime * 2 ) ;
2018-02-26 20:23:01 +00:00
2019-03-13 00:04:16 +00:00
// override client response to timeout
this . server . get (
` http:// ${ HOST } /v1/client/fs/logs/:allocation_id ` ,
( ) => [ 400 , { } , '' ] ,
allowedConnectionTime * 2
) ;
2018-02-26 20:23:01 +00:00
2019-03-13 00:04:16 +00:00
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog
@ allocation = { { allocation } }
@ task = { { taskState } }
@ clientTimeout = { { clientTimeout } }
@ serverTimeout = { { serverTimeout } } / > ` );
2018-02-26 20:23:01 +00:00
2019-03-13 00:04:16 +00:00
const clientUrlRegex = new RegExp ( ` ${ HOST } /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ) ;
2018-02-26 20:23:01 +00:00
assert . ok (
2019-03-13 00:04:16 +00:00
this . server . handledRequests . filter ( req => clientUrlRegex . test ( req . url ) ) . length ,
'Log request was initially made directly to the client'
2018-02-26 20:23:01 +00:00
) ;
2019-03-13 00:04:16 +00:00
2019-03-14 06:45:15 +00:00
await settled ( ) ;
const serverUrl = ` /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ;
assert . ok (
this . server . handledRequests . filter ( req => req . url . startsWith ( serverUrl ) ) . length ,
'Log request was later made to the server'
) ;
2020-04-28 04:03:33 +00:00
assert . ok (
this . server . handledRequests . filter ( req => clientUrlRegex . test ( req . url ) ) [ 0 ] . aborted ,
'Client log request was aborted'
) ;
2018-02-26 20:23:01 +00:00
} ) ;
2018-02-27 21:38:31 +00:00
2019-03-13 00:04:16 +00:00
test ( 'When both the client and the server are inaccessible, an error message is shown' , async function ( assert ) {
run . later ( run , run . cancelTimers , allowedConnectionTime * 5 ) ;
// override client and server responses to timeout
this . server . get (
` http:// ${ HOST } /v1/client/fs/logs/:allocation_id ` ,
( ) => [ 400 , { } , '' ] ,
allowedConnectionTime * 2
) ;
this . server . get (
'/v1/client/fs/logs/:allocation_id' ,
( ) => [ 400 , { } , '' ] ,
allowedConnectionTime * 2
) ;
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog
@ allocation = { { allocation } }
@ task = { { taskState } }
@ clientTimeout = { { clientTimeout } }
@ serverTimeout = { { serverTimeout } } / > ` );
2018-02-27 21:38:31 +00:00
2019-03-14 06:45:15 +00:00
await settled ( ) ;
const clientUrlRegex = new RegExp ( ` ${ HOST } /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ) ;
assert . ok (
this . server . handledRequests . filter ( req => clientUrlRegex . test ( req . url ) ) . length ,
'Log request was initially made directly to the client'
) ;
const serverUrl = ` /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ;
assert . ok (
this . server . handledRequests . filter ( req => req . url . startsWith ( serverUrl ) ) . length ,
'Log request was later made to the server'
) ;
assert . ok ( find ( '[data-test-connection-error]' ) , 'An error message is shown' ) ;
2020-04-28 06:47:55 +00:00
await click ( '[data-test-connection-error-dismiss]' ) ;
assert . notOk ( find ( '[data-test-connection-error]' ) , 'The error message is dismissable' ) ;
2020-08-25 15:56:02 +00:00
await componentA11yAudit ( this . element , assert ) ;
2018-02-27 21:38:31 +00:00
} ) ;
2020-04-28 06:34:01 +00:00
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 ) {
// override client response to timeout
this . server . get (
` http:// ${ HOST } /v1/client/fs/logs/:allocation_id ` ,
( ) => [ 400 , { } , '' ] ,
allowedConnectionTime * 2
) ;
// Click stderr before the client request responds
run . later ( ( ) => {
click ( '[data-test-log-action="stderr"]' ) ;
run . later ( run , run . cancelTimers , commonProps . interval * 5 ) ;
} , allowedConnectionTime / 2 ) ;
this . setProperties ( commonProps ) ;
2020-06-01 19:03:56 +00:00
await render ( hbs ` <TaskLog
@ allocation = { { allocation } }
@ task = { { taskState } }
@ clientTimeout = { { clientTimeout } }
@ serverTimeout = { { serverTimeout } } / > ` );
2020-04-28 06:34:01 +00:00
await settled ( ) ;
const clientUrlRegex = new RegExp ( ` ${ HOST } /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ) ;
const clientRequests = this . server . handledRequests . filter ( req => clientUrlRegex . test ( req . url ) ) ;
assert . ok (
clientRequests . find ( req => req . queryParams . type === 'stdout' ) ,
'Client request for stdout'
) ;
assert . ok (
clientRequests . find ( req => req . queryParams . type === 'stderr' ) ,
'Client request for stderr'
) ;
const serverUrl = ` /v1/client/fs/logs/ ${ commonProps . allocation . id } ` ;
assert . ok (
this . server . handledRequests
. filter ( req => req . url . startsWith ( serverUrl ) )
. find ( req => req . queryParams . type === 'stderr' ) ,
'Server request for stderr'
) ;
assert . notOk ( find ( '[data-test-connection-error]' ) , 'An error message is not shown' ) ;
} ) ;
2020-07-01 00:09:52 +00:00
test ( 'The log streaming mode is persisted in localStorage' , async function ( assert ) {
window . localStorage . nomadLogMode = JSON . stringify ( 'stderr' ) ;
run . later ( run , run . cancelTimers , commonProps . interval ) ;
this . setProperties ( commonProps ) ;
await render ( hbs ` <TaskLog @allocation={{allocation}} @task={{taskState}} /> ` ) ;
await settled ( ) ;
assert . ok ( this . server . handledRequests . filter ( req => req . queryParams . type === 'stderr' ) . length ) ;
assert . notOk (
this . server . handledRequests . filter ( req => req . queryParams . type === 'stdout' ) . length
) ;
click ( '[data-test-log-action="stdout"]' ) ;
run . later ( run , run . cancelTimers , commonProps . interval ) ;
await settled ( ) ;
assert . ok ( this . server . handledRequests . filter ( req => req . queryParams . type === 'stdout' ) . length ) ;
assert . equal ( window . localStorage . nomadLogMode , JSON . stringify ( 'stdout' ) ) ;
} ) ;
2018-02-27 21:38:31 +00:00
} ) ;