import * as chai from "chai" ; import { assert } from "chai" ; import * as chaiAsPromised from "chai-as-promised" ; import "clarify" ; import { BaseError } from "make-error-cause" ; import * as sinon from "sinon" ; import { Duplex } from "stream" ; import { inspect } from "util" ; import * as util from "util" ; import { ChatClient } from "./client/client" ; import { SingleConnection } from "./client/connection" ; chai . config . includeStack = true ; chai . use ( chaiAsPromised ) ; afterEach ( function ( ) { sinon . restore ( ) ; } ) ; afterEach ( function ( ) { if ( this . currentTest != null && this . currentTest . err != null ) { console . error ( inspect ( this . currentTest . err , { colors : true } ) ) ; console . error ( "Below is the default mocha output:" ) ; } } ) ; export function errorOf ( p ) { return p . catch ( ( e ) => e ) ; } export async function causeOf ( p ) { return ( await errorOf ( p ) ) . cause ; } function assertLink ( e , chain , depth = 0 ) { const [ errorType , message , ... newChain ] = chain ; const actualPrototype = Object . getPrototypeOf ( e ) ; const expectedPrototype = errorType . prototype ; assert . strictEqual ( actualPrototype , expectedPrototype , `Error at depth ${ depth } should be directly instanceof ` + `${ util . inspect ( expectedPrototype ) }, ` + `is instance of: ${ util . inspect ( actualPrototype ) }` ) ; assert . strictEqual ( e . message , message , `Error at depth ${ depth } should have error message "${ message }"` ) ; const cause = e . cause ; if ( newChain . length > 0 ) { assert ( "cause" in e , `Error at depth ${ depth } should have a cause` ) ; assert ( cause != null , `Error at depth ${ depth } should have a cause` ) ; assertLink ( cause ! , newChain , depth + 1 ) ; } else { assert ( cause == null , `Error at depth ${ depth } should not have a cause, ` + `but has the following cause: ${ inspect ( cause ) }` ) ; } } export function assertErrorChain ( p , ... chain ) ; export function assertErrorChain ( e , ... chain ) ; export function assertErrorChain ( e , ... chain ) { if ( e instanceof Error || e == null ) { assert ( e != null , "Error must be non-null" ) ; assertLink ( e ! , chain ) ; } else { return ( async ( ) => { if ( ! Array . isArray ( e ) ) { e = [ e ] ; } for ( const eElement of e ) { await assert . isRejected ( eElement ) ; const error = await errorOf ( eElement ) ; assertLink ( error , chain ) ; } } ) ( ) ; } } export function assertThrowsChain ( f , ... chain ) { try { f ( ) ; } catch ( e ) { assertErrorChain ( e as Error , ... chain ) ; return ; } assert . fail ( "Function did not throw an exception" ) ; } export type MockTransportData = { transport : Duplex ; data : any [ ] ; emit : ( ... lines : string [ ] ) => void ; end : ( error ? : Error ) => void ; emitAndEnd : ( ... lines : string [ ] ) => void ; } ; export function createMockTransport ( ) { const data = [ ] ; const transport = new Duplex ( { autoDestroy : true , emitClose : true , decodeStrings : false , defaultEncoding : "utf-8" , encoding : "utf-8" , write ( chunk , encoding , callback ) { data . push ( chunk . toString ( ) ) ; callback ( ) ; } , read ( ) { } , } ) ; const emit = ( ... lines ) : void => { transport . push ( lines . map ( ( line ) => line + "\r\n" ) . join ( "" ) ) ; } ; const end = ( error ? ) : void => { transport . destroy ( error ) ; } ; const emitAndEnd = ( ... lines ) : void => { setImmediate ( emit , ... lines ) ; setImmediate ( end ) ; } ; return { transport , data , emit , end , emitAndEnd , } ; } export type FakeConnectionData = { client : SingleConnection ; clientError : Promise < void > ; } & MockTransportData ; export function fakeConnection ( ) { sinon . stub ( SingleConnection . prototype , "onConnect" ) ; const transport = createMockTransport ( ) ; const fakeConn = new SingleConnection ( { connection : { type : "duplex" , stream : ( ) => transport . transport , preSetup : true , } , } ) ; fakeConn . connect ( ) ; return { ... transport , client : fakeConn , clientError : new Promise < void > ( ( resolve , reject ) => { fakeConn . once ( "error" , ( e ) => reject ( e ) ) ; fakeConn . once ( "close" , ( ) => resolve ( ) ) ; } ) , } ; } export type FakeClientData = { client : ChatClient ; clientError : Promise < void > ; transports : MockTransportData [ ] ; emit : ( ... lines : string [ ] ) => void ; end : ( ) => void ; emitAndEnd : ( ... lines : string [ ] ) => void ; } ; export function fakeClient ( connect = true ) { const transports = [ ] ; const getStream = ( ) : Duplex => { const newTransport = createMockTransport ( ) ; transports . push ( newTransport ) ; return newTransport . transport ; } ; const client = new ChatClient ( { connection : { type : "duplex" , stream : getStream , preSetup : true , } , installDefaultMixins : false , } ) ; if ( connect ) { client . connect ( ) ; } return { emit : ( ... lines ) => transports [ 0 ] . emit ( ... lines ) , emitAndEnd : ( ... lines ) => { transports [ 0 ] . emit ( ... lines ) ; setImmediate ( ( ) => client . destroy ( ) ) ; } , end : ( ) => { client . destroy ( ) ; } , client , clientError : new Promise < void > ( ( resolve , reject ) => { client . once ( "error" , ( e ) => reject ( e ) ) ; client . once ( "close" , ( ) => resolve ( ) ) ; } ) , transports , } ; }