import { interpret } from 'xstate' ; import { ethers } from 'ethers' ; import waitForExpect from 'wait-for-expect' ; import { StateVariables , isOpenChannel , simpleEthAllocation , exists } from '@statechannels/wallet-core' ; import { filter , first , map } from 'rxjs/operators' ; import { ChannelUpdated , JoinChannelEvent } from '../../event-types' ; import { ChannelStoreEntry } from '../../store/channel-store-entry' ; import { Store } from '../../store' ; import { MessagingService , MessagingServiceInterface } from '../../messaging' ; import { Application } from '..' ; import { participants , wallet1 } from './data' ; jest . setTimeout ( 10000 ) ; describe ( 'Channel setup, CREATE_CHANNEL role' , ( ) => { test . skip ( 'with direct funding strategy' , async ( ) => { const context = { type : 'CREATE_CHANNEL' , chainId : '0x00' , appData : '0x00' , appDefinition : ethers . constants . AddressZero , participants , outcome : simpleEthAllocation ( [ ] ) , challengeDuration : 500 , requestId : 5 , fundingStrategy : 'Direct' , applicationDomain : 'localhost' } ; const store = new Store ( ) ; await store . initialize ( [ wallet1 . privateKey ] ) ; const messagingService = new MessagingService ( store ) ; const spy = jest . fn ( x => x ) ; const service = interpret < any , any , any > ( Application . workflow ( store , messagingService , context ) . withConfig ( { services : { invokeCreateChannelAndFundProtocol : ( ) => new Promise < void > ( r => spy ( 'Channel was funded' ) && r ( ) ) } } ) ) ; service . start ( ) ; await waitForExpect ( async ( ) => expect ( service . state . value ) . toEqual ( 'confirmingWithUser' ) , 2000 ) ; const objective = store . outboxFeed . pipe ( map ( m => m . objectives ) , filter ( exists ) , map ( objectives => objectives [ 0 ] ) , filter ( isOpenChannel ) , first ( ) ) . toPromise ( ) ; service . state . children . invokeCreateChannelConfirmation . send ( { type : 'USER_APPROVES' } ) ; const result = await objective ; expect ( result ) . toMatchObject ( { type : 'OpenChannel' } ) ; await waitForExpect ( ( ) => expect ( service . state . value ) . toEqual ( 'running' ) ) ; expect ( spy ) . toHaveBeenLastCalledWith ( 'Channel was funded' ) ; } ) ; } ) ; describe ( 'Channel setup, JOIN_CHANNEL role' , ( ) => { test ( 'with direct funding strategy' , async ( ) => { const channelId = '0x0abc' ; const context = { fundingStrategy : 'Direct' , channelId , type : 'JOIN_CHANNEL' , applicationDomain : 'localhost' , requestId : 5 } ; const store = new Store ( ) ; await store . initialize ( ) ; const messagingService = new MessagingService ( store ) ; const spy = jest . fn ( x => x ) ; const enum expectations { channelFunded = 'Channel was funded' , domainSet = 'applicationDomain was set' } const service = interpret < any , any , any > ( Application . workflow ( store , messagingService , context ) . withConfig ( { actions : { sendJoinChannelResponse : spy , sendUpdateChannelResponse : spy } , services : { invokeCreateChannelAndFundProtocol : ( ) => new Promise < void > ( resolve => spy ( expectations . channelFunded ) && resolve ( ) ) , setapplicationDomain : ( ) => new Promise < void > ( resolve => spy ( expectations . domainSet ) && resolve ( ) ) } } ) ) ; service . start ( ) ; await waitForExpect ( async ( ) => { expect ( service . state . value ) . toEqual ( { joiningChannel : 'joining' } ) ; } , 2000 ) ; const joinEvent = { type : 'JOIN_CHANNEL' , channelId , requestId : 5 , applicationDomain : 'localhost' } ; service . send ( joinEvent ) ; expect ( spy ) . toHaveBeenCalledTimes ( 2 ) ; expect ( spy ) . toHaveBeenCalledWith ( context , joinEvent , expect . any ( Object ) ) ; expect ( spy ) . toHaveBeenCalledWith ( expectations . domainSet ) ; await waitForExpect ( async ( ) => { expect ( service . state . value ) . toEqual ( 'confirmingWithUser' ) ; } , 2000 ) ; service . state . children . invokeCreateChannelConfirmation . send ( { type : 'USER_APPROVES' } ) ; expect ( spy ) . toHaveBeenCalledTimes ( 3 ) ; expect ( spy ) . toHaveBeenCalledWith ( expectations . channelFunded ) ; await waitForExpect ( ( ) => expect ( service . state . value ) . toEqual ( 'running' ) ) ; } ) ; } ) ; it ( 'raises an channel updated action when the channel is updated' , async ( ) => { const store = new Store ( ) ; await store . initialize ( ) ; const messagingService = new MessagingService ( store ) ; const mockOptions = { actions : { sendChannelUpdatedNotification : jest . fn ( ) } } ; const service = interpret < any , any , any > ( Application . workflow ( store , messagingService ) . withConfig ( mockOptions , { fundingStrategy : 'Direct' , applicationDomain : 'localhost' } ) ) ; service . start ( ) ; service . send ( { type : 'CHANNEL_UPDATED' , channelId : '0x00' } ) ; await waitForExpect ( async ( ) => { expect ( mockOptions . actions . sendChannelUpdatedNotification ) . toHaveBeenCalled ( ) ; } , 2000 ) ; } ) ; it ( 'starts concluding when requested' , async ( ) => { const store = new Store ( ) ; await store . initialize ( ) ; const messagingService = new MessagingService ( store ) ; const channelId = ethers . utils . id ( 'channel' ) ; const services = { invokeClosingProtocol : jest . fn ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) , signFinalStateIfMyTurn : jest . fn ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) } ; const actions = { closeChannel : jest . fn ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) } ; const service = interpret < any , any , any > ( Application . workflow ( store , messagingService ) . withConfig ( { services , actions } as any , { fundingStrategy : 'Direct' , applicationDomain : 'localhost' } ) ) ; service . start ( 'running' ) ; service . send ( { type : 'PLAYER_REQUEST_CONCLUDE' , channelId } ) ; await waitForExpect ( async ( ) => { expect ( service . state . value ) . toEqual ( 'running' ) ; expect ( actions . closeChannel ) . toHaveBeenCalled ( ) ; } , 2000 ) ; } ) ; it ( 'starts challenging when requested' , async ( ) => { const store = new Store ( ) ; await store . initialize ( ) ; const messagingService = new MessagingService ( store ) ; const channelId = ethers . utils . id ( 'channel' ) ; const services = { invokeChallengingProtocol : jest . fn ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) } ; const actions = { sendChallengeChannelResponse : jest . fn ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) } ; const service = interpret < any , any , any > ( Application . workflow ( store , messagingService ) . withConfig ( { services , actions } as any , { fundingStrategy : 'Direct' , applicationDomain : 'localhost' } ) ) ; service . start ( 'running' ) ; service . send ( { type : 'PLAYER_REQUEST_CHALLENGE' , channelId } ) ; await waitForExpect ( async ( ) => { expect ( service . state . value ) . toEqual ( 'sendChallenge' ) ; expect ( services . invokeChallengingProtocol ) . toHaveBeenCalled ( ) ; expect ( service . state . actions . map ( a => a . type ) ) . toContain ( 'displayUi' ) ; } , 2000 ) ; } ) ; it ( 'starts concluding when receiving a final state' , async ( ) => { const store = new Store ( ) ; await store . initialize ( ) ; const messagingService = new MessagingService ( store ) ; const services = { invokeClosingProtocol : jest . fn ( ) . mockReturnValue ( new Promise ( ( ) => { } ) ) } ; const channelUpdate = { type : 'CHANNEL_UPDATED' , requestId : 5 , storeEntry : { latest : { isFinal : true } as StateVariables } as ChannelStoreEntry } ; const service = interpret < any , any , any > ( Application . workflow ( store , messagingService ) . withConfig ( { services } as any , { channelId : '0x0a' , fundingStrategy : 'Direct' , applicationDomain : 'localhost' } ) ) ; service . start ( 'running' ) ; service . send ( channelUpdate ) ; await waitForExpect ( async ( ) => { expect ( service . state . value ) . toEqual ( 'closing' ) ; expect ( services . invokeClosingProtocol ) . toHaveBeenCalled ( ) ; } ) ; } ) ;