import { assert } from "chai" ; import * as sinon from "sinon" ; import { ConnectionError , MessageError } from "../client/errors" ; import { assertErrorChain , fakeConnection } from "../helpers.spec" ; import { parseTwitchMessage } from "../message/parser/twitch-message" ; import { BaseError } from "../utils/base-error" ; import { ignoreErrors } from "../utils/ignore-errors" ; import { awaitResponse , ResponseAwaiter } from "./await-response" ; import { TimeoutError } from "./timeout-error" ; describe ( "./await/await-response" , function ( ) { describe ( "ResponseAwaiter" , function ( ) { it ( "should add itself to list of waiters" , function ( ) { const { client , end } = fakeConnection ( ) ; const awaiter1 = new ResponseAwaiter ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter 1 failure" , } ) ; awaiter1 . promise . catch ( ignoreErrors ) ; const awaiter2 = new ResponseAwaiter ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter 2 failure" , } ) ; awaiter2 . promise . catch ( ignoreErrors ) ; assert . deepStrictEqual ( client . pendingResponses , [ awaiter1 , awaiter2 ] ) ; end ( ) ; } ) ; it ( "should resolve on matching incoming message" , async function ( ) { const { client , end } = fakeConnection ( ) ; const wantedMsg = parseTwitchMessage ( "PONG :tmi.twitch.tv" ) ; const promise = awaitResponse ( client , { success : ( msg ) => msg === wantedMsg , errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter failure" , } ) ; client . emitMessage ( wantedMsg ) ; end ( ) ; assert . strictEqual ( await promise , wantedMsg ) ; assert . deepStrictEqual ( client . pendingResponses , [ ] ) ; } ) ; it ( "should reject on matching incoming message" , async function ( ) { const { client , clientError , emitAndEnd } = fakeConnection ( ) ; const wantedMsg = "PONG :tmi.twitch.tv" ; const promise = awaitResponse ( client , { failure : ( msg ) => msg . rawSource === wantedMsg , errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter failure" , } ) ; emitAndEnd ( wantedMsg ) ; await assertErrorChain ( promise , BaseError , "test awaiter failure: Bad response message: PONG :tmi.twitch.tv" , MessageError , "Bad response message: PONG :tmi.twitch.tv" ) ; assert . deepStrictEqual ( client . pendingResponses , [ ] ) ; await assertErrorChain ( clientError , BaseError , "test awaiter failure: Bad response message: PONG :tmi.twitch.tv" , MessageError , "Bad response message: PONG :tmi.twitch.tv" ) ; } ) ; it ( "should reject on connection close (no error)" , async function ( ) { const { client , end , clientError } = fakeConnection ( ) ; const promise = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter failure" , } ) ; end ( ) ; const clientErrorAfterClose = new Promise ( ( resolve , reject ) => { client . once ( "error" , reject ) ; } ) ; await assertErrorChain ( [ promise , clientErrorAfterClose ] , BaseError , "test awaiter failure: Connection closed with no error" , ConnectionError , "Connection closed with no error" ) ; await clientError ; } ) ; it ( "should reject on connection close (with error)" , async function ( ) { const { client , end , clientError } = fakeConnection ( ) ; const promise = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter failure" , } ) ; end ( new Error ( "peer reset connection" ) ) ; const clientErrorAfterClose = new Promise ( ( resolve , reject ) => { let counter = 0 ; const target = 1 ; client . on ( "error" , ( e ) => { if ( counter ++ === target ) { reject ( e ) ; } } ) ; } ) ; await assertErrorChain ( promise , BaseError , "test awaiter failure: Connection closed due to error: Error occurred in transport layer: peer reset connection" , ConnectionError , "Connection closed due to error: Error occurred in transport layer: peer reset connection" , ConnectionError , "Error occurred in transport layer: peer reset connection" , Error , "peer reset connection" ) ; await assertErrorChain ( clientError , ConnectionError , "Error occurred in transport layer: peer reset connection" , Error , "peer reset connection" ) ; await assertErrorChain ( clientErrorAfterClose , BaseError , "test awaiter failure: Connection closed due to error: Error occurred in transport layer: peer reset connection" , ConnectionError , "Connection closed due to error: Error occurred in transport layer: peer reset connection" , ConnectionError , "Error occurred in transport layer: peer reset connection" , Error , "peer reset connection" ) ; } ) ; it ( "should timeout after specified timeout (noResponseAction = failure)" , async function ( ) { sinon . useFakeTimers ( ) ; const { client , clientError } = fakeConnection ( ) ; const promise = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter failure" , timeout : 3000 , } ) ; sinon . clock . tick ( 3000 ) ; await assertErrorChain ( [ promise , clientError ] , BaseError , "test awaiter failure: Timed out after waiting for response for 3000 milliseconds" , TimeoutError , "Timed out after waiting for response for 3000 milliseconds" ) ; } ) ; it ( "should timeout after specified timeout (noResponseAction = success)" , async function ( ) { sinon . useFakeTimers ( ) ; const { client , clientError , end } = fakeConnection ( ) ; const promise = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter failure" , timeout : 3000 , noResponseAction : "success" , } ) ; sinon . clock . tick ( 3000 ) ; end ( ) ; await Promise . all ( [ promise , clientError ] ) ; } ) ; it ( "should begin timeout only once awaiter is moved to head of queue" , async function ( ) { sinon . useFakeTimers ( ) ; const { client , clientError } = fakeConnection ( ) ; const promise1 = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter1 failure" , timeout : 1000 , } ) ; const promise2 = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter2 failure" , timeout : 1000 , } ) ; sinon . clock . tick ( 1000 ) ; await assertErrorChain ( [ promise1 , clientError ] , BaseError , "test awaiter1 failure: Timed out after waiting for response for 1000 milliseconds" , TimeoutError , "Timed out after waiting for response for 1000 milliseconds" ) ; sinon . clock . tick ( 1000 ) ; await assertErrorChain ( promise2 , BaseError , "test awaiter2 failure: Timed out after waiting for response for 1000 milliseconds" , TimeoutError , "Timed out after waiting for response for 1000 milliseconds" ) ; } ) ; it ( "should notify other awaiters that they are outpaced" , async function ( ) { const { client , emitAndEnd , clientError } = fakeConnection ( ) ; const promise1 = awaitResponse ( client , { errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter1 failure" , } ) ; const expectedMsg = "PONG :tmi.twitch.tv" ; const promise2 = awaitResponse ( client , { success : ( msg ) => msg . rawSource === expectedMsg , errorType : ( message , cause ) => new BaseError ( message , cause ) , errorMessage : "test awaiter2 failure" , } ) ; emitAndEnd ( expectedMsg ) ; await assertErrorChain ( [ promise1 , clientError ] , BaseError , "test awaiter1 failure: A response to a command issued later than this command was received" , TimeoutError , "A response to a command issued later than this command was received" ) ; const matchedMsg = await promise2 ; assert . strictEqual ( matchedMsg . rawSource , expectedMsg ) ; } ) ; } ) ; } ) ;