import { AccountService , CreateAskDTO , CreateAssetDTO , IExternalDeviceService , IProductInfo , OrderService , TransferService , testUtils } from '@energyweb/exchange' ; import { Filter } from '@energyweb/exchange-core-irec' ; import { IExternalDeviceId } from '@energyweb/origin-backend-core' ; import { DatabaseService } from '@energyweb/origin-backend-utils' ; import { HttpStatus , INestApplication } from '@nestjs/common' ; import { expect } from 'chai' ; import moment from 'moment' ; import request from 'supertest' ; import { CreateBidDTO , OrderBookOrderDTO , TradePriceInfoDTO , ProductDTO , ProductFilterDTO } from '../src' ; import { authenticatedUser , bootstrapTestInstance , TestUser } from './exchange' ; const sleep = ( ms ) => new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ; type OrderBook = { asks : OrderBookOrderDTO [ ] ; bids : OrderBookOrderDTO [ ] ; lastTradedPrice : TradePriceInfoDTO ; } ; describe ( 'orderbook tests' , ( ) => { let app ; let transferService ; let databaseService ; let orderService ; let accountService ; const user1Id = String ( authenticatedUser . organization . id ) ; const user2Id = '2' ; const solarAsset = { address : '0x9876' , tokenId : '0' , deviceId : 'deviceId1' , generationFrom : new Date ( '2020-01-01' ) , generationTo : new Date ( '2020-01-31' ) } ; const windAsset = { address : '0x9876' , tokenId : '1' , deviceId : 'deviceId2' , generationFrom : new Date ( '2020-01-01' ) , generationTo : new Date ( '2020-01-31' ) } ; const marineAsset = { address : '0x9876' , tokenId : '2' , deviceId : 'deviceId3' , generationFrom : new Date ( '2020-01-01' ) , generationTo : new Date ( '2020-01-31' ) } ; const createDeposit = async ( address , amount = '1000' , asset , blockNumber = 123456 ) => { const deposit = await transferService . createDeposit ( { address , transactionHash : `0x${ ( ( Math . random ( ) * 0xffffff ) << 0 ) . toString ( 16 ) }` , amount , blockNumber , asset } ) ; await transferService . setAsConfirmed ( deposit . transactionHash , 10000 ) ; return deposit ; } ; const createAsks = async ( address ) => { const deposit = await createDeposit ( address , '1000' , solarAsset ) ; const createAsk1 = { assetId : deposit . asset . id , volume : '100' , price : 100 , validFrom : new Date ( ) } ; const createAsk2 = { assetId : deposit . asset . id , volume : '100' , price : 150 , validFrom : new Date ( ) } ; await orderService . createAsk ( user1Id , createAsk1 ) ; await orderService . createAsk ( user1Id , createAsk2 ) ; } ; const createBids = async ( ) => { const createBid1 = { price : 50 , volume : '100' , validFrom : new Date ( ) , product : { deviceType : [ 'Wind' ] , generationFrom : moment ( ) . startOf ( 'month' ) . toISOString ( ) , generationTo : moment ( ) . startOf ( 'month' ) . add ( 1 , 'month' ) . toISOString ( ) } } ; const createBid2 = { price : 60 , volume : '100' , validFrom : new Date ( ) , product : { deviceType : [ 'Solar' ] } } ; await orderService . createBid ( user2Id , createBid1 ) ; await orderService . createBid ( user2Id , createBid2 ) ; } ; let windTradeLastTradePrice ; const createWindTrades = async ( address ) => { const deposit = await createDeposit ( address , '1000' , windAsset ) ; const createAsk1 = { assetId : deposit . asset . id , volume : '500' , price : 1000 , validFrom : new Date ( ) } ; const createAsk2 = { assetId : deposit . asset . id , volume : '500' , price : 1100 , validFrom : new Date ( ) } ; await orderService . createAsk ( user1Id , createAsk1 ) ; await orderService . createAsk ( user1Id , createAsk2 ) ; const createBid1 = { price : 1200 , volume : '500' , validFrom : new Date ( ) , product : { deviceType : [ 'Wind' ] } } ; await orderService . createBid ( user2Id , createBid1 ) ; await orderService . createBid ( user2Id , createBid1 ) ; windTradeLastTradePrice = { price : 1100 , assetId : deposit . asset . id } ; } ; let marineTradeLastTradePrice ; const createMarineTrades = async ( address ) => { const deposit = await createDeposit ( address , '1000' , marineAsset ) ; const createAsk1 = { assetId : deposit . asset . id , volume : '500' , price : 3000 , validFrom : new Date ( ) } ; await orderService . createAsk ( user1Id , createAsk1 ) ; const createBid1 = { price : 3000 , volume : '500' , validFrom : new Date ( ) , product : { deviceType : [ 'Marine' ] } } ; await orderService . createBid ( user2Id , createBid1 ) ; marineTradeLastTradePrice = { price : 3000 , assetId : deposit . asset . id } ; } ; const createTrades = async ( address ) => { await createWindTrades ( address ) ; await sleep ( 2000 ) ; await createMarineTrades ( address ) ; await sleep ( 2000 ) ; } ; const productByDeviceId = new Map < string , IProductInfo > ( [ [ 'deviceId1' , { deviceType : 'Solar;Photovoltaic;Classic silicon' , country : 'Thailand' , region : 'Central' , province : 'Nakhon Pathom' , operationalSince : 2016 , gridOperator : 'TH-PEA' } ] , [ 'deviceId2' , { deviceType : 'Wind;Onshore' , country : 'Thailand' , region : 'Central' , province : 'Nakhon Pathom' , operationalSince : 2016 , gridOperator : 'TH-PEA' } ] , [ 'deviceId3' , { deviceType : 'Marine;Tidal;Offshore' , country : 'Thailand' , region : 'Central' , province : 'Nakhon Pathom' , operationalSince : 2016 , gridOperator : 'TH-PEA' } ] ] ) ; const deviceServiceMock = { getDeviceProductInfo : async ( { id } ) : Promise < IProductInfo > => { return productByDeviceId . get ( id ) ; } } as unknown as IExternalDeviceService ; before ( async ( ) => { ( { transferService , orderService , databaseService , accountService , app } = await bootstrapTestInstance ( deviceServiceMock ) ) ; await app . init ( ) ; const address = await testUtils . createDepositAddress ( accountService , user1Id ) ; await createTrades ( address ) ; await createAsks ( address ) ; await createBids ( ) ; await sleep ( 2000 ) ; } ) ; after ( async ( ) => { await databaseService . cleanUp ( ) ; await app . close ( ) ; } ) ; const defaultAllFilter = { deviceVintageFilter : Filter . All , generationTimeFilter : Filter . All , locationFilter : Filter . All , deviceTypeFilter : Filter . All , gridOperatorFilter : Filter . All } ; [ { ... defaultAllFilter , deviceTypeFilter : Filter . Specific } , { ... defaultAllFilter , deviceVintageFilter : Filter . Specific } , { ... defaultAllFilter , generationTimeFilter : Filter . Specific , generationFrom : new Date ( ) . toISOString ( ) } , { ... defaultAllFilter , deviceTypeFilter : Filter . Specific , deviceType : [ 'LOL' ] } , { ... defaultAllFilter , deviceVintageFilter : 'LOL' as unknown as Filter } ] . forEach ( ( params ) : void => { it ( `should return 400 when filter is invalid: ${ JSON . stringify ( params ) }` , async ( ) => { await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . send ( params ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . BAD_REQUEST ) ; } ) ; } ) ; it ( 'should return all orders' , async ( ) => { const { body : { asks , bids } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 2 ) ; expect ( bids ) . to . have . length ( 2 ) ; } ) ; it ( 'returned asks should have assetId' , async ( ) => { const { body : { asks } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; asks . forEach ( ( ask ) => expect ( ask ) . to . have . property ( 'assetId' ) ) ; } ) ; it ( 'should return orders filtered by default filter' , async ( ) => { const { body : { asks , bids , lastTradedPrice } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . send ( defaultAllFilter ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 2 ) ; expect ( bids ) . to . have . length ( 2 ) ; expect ( lastTradedPrice . price ) . equals ( marineTradeLastTradePrice . price ) ; expect ( lastTradedPrice . assetId ) . equals ( marineTradeLastTradePrice . assetId ) ; } ) ; it ( 'should return orders filtered by device type (Solar)' , async ( ) => { const { body : { asks , bids } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . send ( { ... defaultAllFilter , deviceTypeFilter : Filter . Specific , deviceType : [ 'Solar' ] } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 2 ) ; expect ( bids ) . to . have . length ( 1 ) ; } ) ; it ( 'should return orders filtered by device type (Wind)' , async ( ) => { const { body : { asks , bids , lastTradedPrice } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . send ( { ... defaultAllFilter , deviceTypeFilter : Filter . Specific , deviceType : [ 'Wind' ] } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 0 ) ; expect ( bids ) . to . have . length ( 1 ) ; expect ( lastTradedPrice . price ) . equals ( windTradeLastTradePrice . price ) ; expect ( lastTradedPrice . assetId ) . equals ( windTradeLastTradePrice . assetId ) ; } ) ; it ( 'should return orders filtered by generation date' , async ( ) => { const { body : { asks , bids } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . send ( { ... defaultAllFilter , generationTimeFilter : Filter . Specific , generationFrom : moment ( ) . startOf ( 'month' ) . toISOString ( ) , generationTo : moment ( ) . startOf ( 'month' ) . add ( 1 , 'month' ) . toISOString ( ) } ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 0 ) ; expect ( bids ) . to . have . length ( 1 ) ; expect ( bids [ 0 ] . product . deviceType ) . to . deep . equal ( [ 'Wind' ] ) ; } ) ; it ( 'should return asks filtered by generation date if filter is included in asks' , async ( ) => { const { body : { asks } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . send ( { ... defaultAllFilter , generationTimeFilter : Filter . Specific , generationFrom : new Date ( '2020-01-02' ) . toISOString ( ) , generationTo : new Date ( '2020-01-05' ) . toISOString ( ) } ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 2 ) ; } ) ; it ( 'should return asks filtered by generation date if filter is broader than asks' , async ( ) => { const { body : { asks } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . send ( { ... defaultAllFilter , generationTimeFilter : Filter . Specific , generationFrom : new Date ( '2019-01-01' ) . toISOString ( ) , generationTo : new Date ( '2021-01-31' ) . toISOString ( ) } ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 2 ) ; } ) ; it ( 'should return empty orders list if time mismatch' , async ( ) => { const { body : { asks , bids } } = await request ( app . getHttpServer ( ) ) . post ( '/orderbook/search' ) . send ( { ... defaultAllFilter , generationTimeFilter : Filter . Specific , generationFrom : moment ( ) . startOf ( 'month' ) . add ( 2 , 'hours' ) . toISOString ( ) , generationTo : moment ( ) . startOf ( 'month' ) . add ( 1 , 'month' ) . add ( 2 , 'hours' ) . toISOString ( ) } ) . set ( { 'test-user' : TestUser . OrganizationDeviceManager } ) . expect ( 'Content-Type' , /application\/json/ ) . expect ( HttpStatus . OK ) ; expect ( asks ) . to . have . length ( 0 ) ; expect ( bids ) . to . have . length ( 0 ) ; } ) ; } ) ;