Skip to content

Commit a8ca67f

Browse files
committed
test: processPaidEscrows
1 parent cb9d54d commit a8ca67f

File tree

1 file changed

+285
-1
lines changed

1 file changed

+285
-1
lines changed

packages/apps/reputation-oracle/server/src/modules/escrow-completion/escrow-completion.service.spec.ts

Lines changed: 285 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ jest.mock('@human-protocol/sdk', () => {
1111

1212
import { faker } from '@faker-js/faker';
1313
import { 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';
1520
import { Test } from '@nestjs/testing';
1621
import * as crypto from 'crypto';
1722
import stringify from 'json-stable-stringify';
@@ -65,6 +70,7 @@ const mockCvatPayoutsCalculator = createMock<CvatPayoutsCalculator>();
6570

6671
const mockedEscrowClient = jest.mocked(EscrowClient);
6772
const mockedEscrowUtils = jest.mocked(EscrowUtils);
73+
const mockedOperatorUtils = jest.mocked(OperatorUtils);
6874

6975
describe('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

Comments
 (0)