Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-setup/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ services:

graph-node-db:
container_name: hp-dev-graph-node-db
image: postgres:latest
image: postgres:16
restart: *default-restart
logging:
<<: *default-logging
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export enum JobStatus {
ACTIVE = 'active',
PAUSED = 'paused',
COMPLETED = 'completed',
CANCELED = 'canceled',
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ describe('AssignmentService', () => {
id: 1,
manifestUrl: MOCK_MANIFEST_URL,
reputationNetwork: reputationNetwork,
status: JobStatus.PAUSED,
status: JobStatus.CANCELED,
} as any);

await expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,54 +667,4 @@ describe('JobService', () => {
).rejects.toThrow(`Solution not found in Escrow: ${escrowAddress}`);
});
});

describe('pauseJob', () => {
const webhook: WebhookDto = {
chainId,
escrowAddress,
eventType: EventType.ABUSE_DETECTED,
};

it('should create a new job in the database', async () => {
jest
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
.mockResolvedValue({
chainId: chainId,
escrowAddress: escrowAddress,
status: JobStatus.ACTIVE,
} as JobEntity);
const result = await jobService.pauseJob(webhook);

expect(result).toEqual(undefined);
expect(jobRepository.updateOne).toHaveBeenCalledWith({
chainId: chainId,
escrowAddress: escrowAddress,
status: JobStatus.PAUSED,
});
});

it('should fail if job not exists', async () => {
jest
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
.mockResolvedValue(null);

await expect(jobService.pauseJob(webhook)).rejects.toThrow(
ErrorJob.NotFound,
);
});

it('should fail if job is not in Active status', async () => {
jest
.spyOn(jobRepository, 'findOneByChainIdAndEscrowAddress')
.mockResolvedValue({
chainId: chainId,
escrowAddress: escrowAddress,
status: JobStatus.CANCELED,
} as JobEntity);

await expect(jobService.pauseJob(webhook)).rejects.toThrow(
ErrorJob.InvalidStatus,
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -382,34 +382,4 @@ export class JobService {

return manifest;
}

public async pauseJob(webhook: WebhookDto): Promise<void> {
const jobEntity = await this.jobRepository.findOneByChainIdAndEscrowAddress(
webhook.chainId,
webhook.escrowAddress,
);
if (!jobEntity) {
throw new ServerError(ErrorJob.NotFound);
}
if (jobEntity.status !== JobStatus.ACTIVE) {
throw new ConflictError(ErrorJob.InvalidStatus);
}
jobEntity.status = JobStatus.PAUSED;
await this.jobRepository.updateOne(jobEntity);
}

public async resumeJob(webhook: WebhookDto): Promise<void> {
const jobEntity = await this.jobRepository.findOneByChainIdAndEscrowAddress(
webhook.chainId,
webhook.escrowAddress,
);
if (!jobEntity) {
throw new ServerError(ErrorJob.NotFound);
}
if (jobEntity.status !== JobStatus.PAUSED) {
throw new ConflictError(ErrorJob.InvalidStatus);
}
jobEntity.status = JobStatus.ACTIVE;
await this.jobRepository.updateOne(jobEntity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ describe('WebhookService', () => {
});

it('should handle an incoming escrow abuse webhook', async () => {
jest.spyOn(jobService, 'pauseJob').mockResolvedValue();
jest.spyOn(jobService, 'cancelJob').mockResolvedValue();
const webhook: WebhookDto = {
chainId,
escrowAddress,
Expand All @@ -178,16 +178,6 @@ describe('WebhookService', () => {
expect(await webhookService.handleWebhook(webhook)).toBe(undefined);
});

it('should handle an incoming escrow resume webhook', async () => {
jest.spyOn(jobService, 'resumeJob').mockResolvedValue();
const webhook: WebhookDto = {
chainId,
escrowAddress,
eventType: EventType.ABUSE_DISMISSED,
};
expect(await webhookService.handleWebhook(webhook)).toBe(undefined);
});

it('should return an error when the event type is invalid', async () => {
const webhook: WebhookDto = {
chainId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,8 @@ export class WebhookService {
break;

case EventType.ABUSE_DETECTED:
await this.jobService.pauseJob(webhook);
break;

case EventType.ABUSE_DISMISSED:
await this.jobService.resumeJob(webhook);
await this.jobService.cancelJob(webhook);
break;

default:
throw new ValidationError(
`Invalid webhook event type: ${webhook.eventType}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export class JobService {
// DISABLE HMT
if (
requestType !== HCaptchaJobType.HCAPTCHA &&
dto.chainId !== ChainId.LOCALHOST &&
(dto.escrowFundToken === EscrowFundToken.HMT ||
dto.paymentCurrency === PaymentCurrency.HMT)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ export class PaymentController {
throw new ValidationError(ErrorPayment.InvalidChainId);
}

if (chainId === ChainId.LOCALHOST) {
return tokens;
}

// Disable HMT
const { [EscrowFundToken.HMT]: _omit, ...withoutHMT } = tokens;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,6 @@ describe('AbuseService', () => {
.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator
.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator)
.mockResolvedValueOnce({
webhookUrl: webhookUrl2,
} as IOperator);
mockAbuseSlackBot.sendAbuseNotification
.mockResolvedValueOnce(undefined)
.mockResolvedValueOnce(undefined);
Expand All @@ -325,9 +318,6 @@ describe('AbuseService', () => {
mockedEscrowUtils.getEscrow.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator);
mockAbuseRepository.findToClassify.mockResolvedValueOnce(
mockAbuseEntities,
);
Expand All @@ -345,32 +335,6 @@ describe('AbuseService', () => {
});
});

it('should handle errors when createOutgoingWebhook fails', async () => {
const mockAbuseEntities = [generateAbuseEntity({ retriesCount: 0 })];

mockAbuseRepository.findToClassify.mockResolvedValueOnce(
mockAbuseEntities,
);
mockedEscrowUtils.getEscrow.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator);

mockOutgoingWebhookService.createOutgoingWebhook.mockRejectedValueOnce(
new DatabaseError('Failed to create webhook'),
);

await abuseService.processAbuseRequests();

expect(mockAbuseRepository.findToClassify).toHaveBeenCalledTimes(1);
expect(mockAbuseRepository.updateOne).toHaveBeenCalledWith({
...mockAbuseEntities[0],
retriesCount: 1,
});
});

it('should continue if createOutgoingWebhook throws a duplicated error', async () => {
const mockAbuseEntities = [generateAbuseEntity(), generateAbuseEntity()];

Expand All @@ -384,13 +348,6 @@ describe('AbuseService', () => {
.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator
.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator)
.mockResolvedValueOnce({
webhookUrl: webhookUrl2,
} as IOperator);

mockOutgoingWebhookService.createOutgoingWebhook.mockRejectedValueOnce(
new DatabaseError(DatabaseErrorMessages.DUPLICATED),
Expand Down Expand Up @@ -450,24 +407,6 @@ describe('AbuseService', () => {
retriesCount: 1,
});
});

it('should increment retries when operator data is missing', async () => {
const abuseEntity = generateAbuseEntity({ retriesCount: 0 });
mockAbuseRepository.findToClassify.mockResolvedValueOnce([abuseEntity]);

mockedEscrowUtils.getEscrow.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator.mockResolvedValueOnce(null);

await abuseService.processAbuseRequests();

expect(mockAbuseRepository.findToClassify).toHaveBeenCalledTimes(1);
expect(mockAbuseRepository.updateOne).toHaveBeenCalledWith({
...abuseEntity,
retriesCount: 1,
});
});
});

describe('processClassifiedAbuses', () => {
Expand All @@ -485,10 +424,15 @@ describe('AbuseService', () => {
} as unknown as StakingClient);
mockedEscrowUtils.getEscrow.mockResolvedValueOnce({
launcher: fakeAddress,
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator);
mockedOperatorUtils.getOperator
.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator)
.mockResolvedValueOnce({
webhookUrl: webhookUrl2,
} as IOperator);
mockOutgoingWebhookService.createOutgoingWebhook.mockResolvedValueOnce(
undefined,
);
Expand All @@ -511,7 +455,17 @@ describe('AbuseService', () => {
chainId: mockAbuseEntities[0].chainId,
eventType: OutgoingWebhookEventType.ABUSE_DETECTED,
},
expect.any(String),
webhookUrl1,
);
expect(
mockOutgoingWebhookService.createOutgoingWebhook,
).toHaveBeenCalledWith(
{
escrowAddress: mockAbuseEntities[0].escrowAddress,
chainId: mockAbuseEntities[0].chainId,
eventType: OutgoingWebhookEventType.ABUSE_DETECTED,
},
webhookUrl2,
);
expect(mockAbuseRepository.updateOne).toHaveBeenCalledWith({
...mockAbuseEntities[0],
Expand All @@ -527,12 +481,6 @@ describe('AbuseService', () => {
mockAbuseRepository.findClassified.mockResolvedValueOnce(
mockAbuseEntities,
);
mockedEscrowUtils.getEscrow.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator.mockResolvedValueOnce({
webhookUrl: webhookUrl1,
} as IOperator);

await abuseService.processClassifiedAbuses();

Expand All @@ -541,6 +489,11 @@ describe('AbuseService', () => {
...mockAbuseEntities[0],
status: AbuseStatus.COMPLETED,
});
expect(mockedEscrowUtils.getEscrow).not.toHaveBeenCalled();
expect(mockedOperatorUtils.getOperator).not.toHaveBeenCalled();
expect(
mockOutgoingWebhookService.createOutgoingWebhook,
).not.toHaveBeenCalled();
});

it('should handle empty results from findClassified', async () => {
Expand Down Expand Up @@ -572,26 +525,5 @@ describe('AbuseService', () => {
retriesCount: 1,
});
});

it('should increment retries when operator is missing (REJECTED)', async () => {
const abuseEntity = generateAbuseEntity({
retriesCount: 0,
decision: AbuseDecision.REJECTED,
});

mockAbuseRepository.findClassified.mockResolvedValueOnce([abuseEntity]);
mockedEscrowUtils.getEscrow.mockResolvedValueOnce({
exchangeOracle: fakeAddress,
} as unknown as IEscrow);
mockedOperatorUtils.getOperator.mockResolvedValueOnce(null);

await abuseService.processClassifiedAbuses();

expect(mockAbuseRepository.findClassified).toHaveBeenCalledTimes(1);
expect(mockAbuseRepository.updateOne).toHaveBeenCalledWith({
...abuseEntity,
retriesCount: 1,
});
});
});
});
Loading
Loading