@@ -11,7 +11,12 @@ jest.mock('@human-protocol/sdk', () => {
1111
1212import { faker } from '@faker-js/faker' ;
1313import { createMock } from '@golevelup/ts-jest' ;
14- import { EscrowClient , EscrowStatus , EscrowUtils } from '@human-protocol/sdk' ;
14+ import {
15+ EscrowClient ,
16+ EscrowStatus ,
17+ EscrowUtils ,
18+ OperatorUtils ,
19+ } from '@human-protocol/sdk' ;
1520import { Test } from '@nestjs/testing' ;
1621import * as crypto from 'crypto' ;
1722import stringify from 'json-stable-stringify' ;
@@ -65,6 +70,7 @@ const mockCvatPayoutsCalculator = createMock<CvatPayoutsCalculator>();
6570
6671const mockedEscrowClient = jest . mocked ( EscrowClient ) ;
6772const mockedEscrowUtils = jest . mocked ( EscrowUtils ) ;
73+ const mockedOperatorUtils = jest . mocked ( OperatorUtils ) ;
6874
6975describe ( 'EscrowCompletionService' , ( ) => {
7076 let service : EscrowCompletionService ;
@@ -795,4 +801,282 @@ describe('EscrowCompletionService', () => {
795801 ) ;
796802 } ) ;
797803 } ) ;
804+
805+ describe ( 'processPaidEscrows' , ( ) => {
806+ const mockGetEscrowStatus = jest . fn ( ) ;
807+ const mockCompleteEscrow = jest . fn ( ) ;
808+ let launcherAddress : string ;
809+ let exchangeOracleAddress : string ;
810+
811+ beforeEach ( ( ) => {
812+ mockedEscrowClient . build . mockResolvedValue ( {
813+ getStatus : mockGetEscrowStatus ,
814+ complete : mockCompleteEscrow ,
815+ } as unknown as EscrowClient ) ;
816+
817+ launcherAddress = faker . finance . ethereumAddress ( ) ;
818+ exchangeOracleAddress = faker . finance . ethereumAddress ( ) ;
819+
820+ mockedEscrowUtils . getEscrow . mockResolvedValueOnce ( {
821+ launcher : launcherAddress ,
822+ exchangeOracle : exchangeOracleAddress ,
823+ } as any ) ;
824+ } ) ;
825+
826+ describe ( 'handle failures' , ( ) => {
827+ const testError = new Error ( faker . lorem . sentence ( ) ) ;
828+
829+ beforeEach ( ( ) => {
830+ mockGetEscrowStatus . mockRejectedValue ( testError ) ;
831+ } ) ;
832+
833+ it ( 'should process multiple items and handle failure for each' , async ( ) => {
834+ const paidPayoutsRecords = [
835+ generateEscrowCompletion ( EscrowCompletionStatus . PAID ) ,
836+ generateEscrowCompletion ( EscrowCompletionStatus . PAID ) ,
837+ ] ;
838+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce (
839+ _ . cloneDeep ( paidPayoutsRecords ) ,
840+ ) ;
841+
842+ await service . processPaidEscrows ( ) ;
843+
844+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
845+ 2 ,
846+ ) ;
847+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith (
848+ expect . objectContaining ( {
849+ id : paidPayoutsRecords [ 0 ] . id ,
850+ retriesCount : paidPayoutsRecords [ 0 ] . retriesCount + 1 ,
851+ } ) ,
852+ ) ;
853+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith (
854+ expect . objectContaining ( {
855+ id : paidPayoutsRecords [ 1 ] . id ,
856+ retriesCount : paidPayoutsRecords [ 1 ] . retriesCount + 1 ,
857+ } ) ,
858+ ) ;
859+ } ) ;
860+
861+ it ( 'should handle failure for item that has retries left' , async ( ) => {
862+ const paidPayoutsRecord = generateEscrowCompletion (
863+ EscrowCompletionStatus . PAID ,
864+ ) ;
865+ paidPayoutsRecord . retriesCount =
866+ mockServerConfigService . maxRetryCount - 1 ;
867+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce ( [
868+ {
869+ ...paidPayoutsRecord ,
870+ } ,
871+ ] ) ;
872+
873+ await service . processPaidEscrows ( ) ;
874+
875+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
876+ 1 ,
877+ ) ;
878+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith ( {
879+ ...paidPayoutsRecord ,
880+ retriesCount : paidPayoutsRecord . retriesCount + 1 ,
881+ waitUntil : expect . any ( Date ) ,
882+ } ) ;
883+ } ) ;
884+
885+ it ( 'should handle failure for item that has no retries left' , async ( ) => {
886+ const paidPayoutsRecord = generateEscrowCompletion (
887+ EscrowCompletionStatus . PAID ,
888+ ) ;
889+ paidPayoutsRecord . retriesCount = mockServerConfigService . maxRetryCount ;
890+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce ( [
891+ {
892+ ...paidPayoutsRecord ,
893+ } ,
894+ ] ) ;
895+
896+ await service . processPaidEscrows ( ) ;
897+
898+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
899+ 1 ,
900+ ) ;
901+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith ( {
902+ ...paidPayoutsRecord ,
903+ failureDetail : `Error message: ${ testError . message } ` ,
904+ status : 'failed' ,
905+ } ) ;
906+ } ) ;
907+
908+ it ( 'should handle failure when no webhook url for oracle' , async ( ) => {
909+ const paidPayoutsRecord = generateEscrowCompletion (
910+ EscrowCompletionStatus . PAID ,
911+ ) ;
912+ paidPayoutsRecord . retriesCount = mockServerConfigService . maxRetryCount ;
913+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce ( [
914+ {
915+ ...paidPayoutsRecord ,
916+ } ,
917+ ] ) ;
918+ mockGetEscrowStatus . mockResolvedValueOnce ( EscrowStatus . Paid ) ;
919+ mockedOperatorUtils . getOperator . mockResolvedValue ( {
920+ webhookUrl : '' ,
921+ } as any ) ;
922+
923+ await service . processPaidEscrows ( ) ;
924+
925+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
926+ 1 ,
927+ ) ;
928+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith ( {
929+ ...paidPayoutsRecord ,
930+ failureDetail : 'Error message: Webhook url is no set for oracle' ,
931+ status : 'failed' ,
932+ } ) ;
933+ } ) ;
934+
935+ it ( 'should handle error when creating webhooks' , async ( ) => {
936+ const paidPayoutsRecord = generateEscrowCompletion (
937+ EscrowCompletionStatus . PAID ,
938+ ) ;
939+ paidPayoutsRecord . retriesCount = mockServerConfigService . maxRetryCount ;
940+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce ( [
941+ {
942+ ...paidPayoutsRecord ,
943+ } ,
944+ ] ) ;
945+ mockGetEscrowStatus . mockResolvedValueOnce ( EscrowStatus . Paid ) ;
946+ mockedOperatorUtils . getOperator . mockResolvedValue ( {
947+ webhookUrl : faker . internet . url ( ) ,
948+ } as any ) ;
949+ mockOutgoingWebhookService . createOutgoingWebhook . mockRejectedValueOnce (
950+ testError ,
951+ ) ;
952+
953+ await service . processPaidEscrows ( ) ;
954+
955+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
956+ 1 ,
957+ ) ;
958+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith ( {
959+ ...paidPayoutsRecord ,
960+ failureDetail : expect . stringContaining (
961+ 'Failed to create outgoing webhook for oracle. Address: 0x' ,
962+ ) ,
963+ status : 'failed' ,
964+ } ) ;
965+ } ) ;
966+ } ) ;
967+
968+ it . each ( [ EscrowStatus . Partial , EscrowStatus . Paid ] ) (
969+ 'should properly complete escrow with status "%s"' ,
970+ async ( escrowStatus ) => {
971+ mockGetEscrowStatus . mockResolvedValueOnce ( escrowStatus ) ;
972+ const mockGasPrice = faker . number . bigInt ( ) ;
973+ mockWeb3Service . calculateGasPrice . mockResolvedValueOnce ( mockGasPrice ) ;
974+
975+ const paidPayoutsRecord = generateEscrowCompletion (
976+ EscrowCompletionStatus . PAID ,
977+ ) ;
978+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce ( [
979+ {
980+ ...paidPayoutsRecord ,
981+ } ,
982+ ] ) ;
983+
984+ const launcherWebhookUrl = faker . internet . url ( ) ;
985+ const exchangeOracleWebhookUrl = faker . internet . url ( ) ;
986+ mockedOperatorUtils . getOperator . mockImplementation (
987+ async ( _chainId , address ) => {
988+ let webhookUrl : string ;
989+ switch ( address ) {
990+ case launcherAddress :
991+ webhookUrl = launcherWebhookUrl ;
992+ break ;
993+ case exchangeOracleAddress :
994+ webhookUrl = exchangeOracleWebhookUrl ;
995+ break ;
996+ default :
997+ webhookUrl = faker . internet . url ( ) ;
998+ break ;
999+ }
1000+ return { webhookUrl } as any ;
1001+ } ,
1002+ ) ;
1003+
1004+ await service . processPaidEscrows ( ) ;
1005+
1006+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
1007+ 1 ,
1008+ ) ;
1009+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith ( {
1010+ ...paidPayoutsRecord ,
1011+ status : 'completed' ,
1012+ } ) ;
1013+ expect ( mockCompleteEscrow ) . toHaveBeenCalledWith (
1014+ paidPayoutsRecord . escrowAddress ,
1015+ {
1016+ gasPrice : mockGasPrice ,
1017+ } ,
1018+ ) ;
1019+ expect ( mockReputationService . assessEscrowParties ) . toHaveBeenCalledTimes (
1020+ 1 ,
1021+ ) ;
1022+ expect ( mockReputationService . assessEscrowParties ) . toHaveBeenCalledWith (
1023+ paidPayoutsRecord . chainId ,
1024+ paidPayoutsRecord . escrowAddress ,
1025+ ) ;
1026+
1027+ const expectedWebhookData = {
1028+ chainId : paidPayoutsRecord . chainId ,
1029+ escrowAddress : paidPayoutsRecord . escrowAddress ,
1030+ eventType : 'escrow_completed' ,
1031+ } ;
1032+ expect (
1033+ mockOutgoingWebhookService . createOutgoingWebhook ,
1034+ ) . toHaveBeenCalledTimes ( 2 ) ;
1035+ expect (
1036+ mockOutgoingWebhookService . createOutgoingWebhook ,
1037+ ) . toHaveBeenCalledWith ( expectedWebhookData , launcherWebhookUrl ) ;
1038+ expect (
1039+ mockOutgoingWebhookService . createOutgoingWebhook ,
1040+ ) . toHaveBeenCalledWith ( expectedWebhookData , exchangeOracleWebhookUrl ) ;
1041+ } ,
1042+ ) ;
1043+
1044+ it . each ( [
1045+ EscrowStatus . Cancelled ,
1046+ EscrowStatus . Pending ,
1047+ EscrowStatus . Complete ,
1048+ ] ) (
1049+ 'should not comlete escrow if its status is not partial or paid [%#]' ,
1050+ async ( escrowStatus ) => {
1051+ mockGetEscrowStatus . mockResolvedValueOnce ( escrowStatus ) ;
1052+
1053+ const paidPayoutsRecord = generateEscrowCompletion (
1054+ EscrowCompletionStatus . PAID ,
1055+ ) ;
1056+ mockEscrowCompletionRepository . findByStatus . mockResolvedValueOnce ( [
1057+ {
1058+ ...paidPayoutsRecord ,
1059+ } ,
1060+ ] ) ;
1061+
1062+ mockedOperatorUtils . getOperator . mockResolvedValue ( {
1063+ webhookUrl : faker . internet . url ( ) ,
1064+ } as any ) ;
1065+
1066+ await service . processPaidEscrows ( ) ;
1067+
1068+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledTimes (
1069+ 1 ,
1070+ ) ;
1071+ expect ( mockEscrowCompletionRepository . updateOne ) . toHaveBeenCalledWith ( {
1072+ ...paidPayoutsRecord ,
1073+ status : 'completed' ,
1074+ } ) ;
1075+ expect ( mockCompleteEscrow ) . toHaveBeenCalledTimes ( 0 ) ;
1076+ expect ( mockReputationService . assessEscrowParties ) . toHaveBeenCalledTimes (
1077+ 0 ,
1078+ ) ;
1079+ } ,
1080+ ) ;
1081+ } ) ;
7981082} ) ;
0 commit comments