Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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 @@ -102,7 +102,7 @@ services:
container_name: hp-dev-graph-node
# In case of issues on Mac M1 rebuild the image for it locally
# https://github.com/graphprotocol/graph-node/blob/master/docker/README.md#running-graph-node-on-an-macbook-m1
image: graphprotocol/graph-node
image: graphprotocol/graph-node:v0.41.1
restart: *default-restart
logging:
<<: *default-logging
Expand Down
14 changes: 7 additions & 7 deletions packages/apps/dashboard/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@
"@mui/system": "^7.3.9",
"@mui/x-data-grid": "^8.7.0",
"@mui/x-date-pickers": "^8.26.0",
"@tanstack/react-query": "^5.67.2",
"@tanstack/react-query": "^5.91.3",
"@types/react-router-dom": "^5.3.3",
"@types/recharts": "^1.8.29",
"axios": "^1.7.2",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-number-format": "^5.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-number-format": "^5.4.5",
"react-router-dom": "^7.13.0",
"recharts": "^2.13.0-alpha.4",
"simplebar-react": "^3.3.2",
Expand All @@ -47,10 +47,10 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^10.0.3",
"eslint": "^10.1.0",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-prettier": "^5.5.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { type ReactElement, useEffect, useState } from 'react';

import Stack from '@mui/material/Stack';
import { AxiosError } from 'axios';
Expand Down Expand Up @@ -38,7 +38,7 @@ const renderCurrentResultType = (

const renderType: Record<
keyof AddressDetails,
{ title: string; icon: JSX.Element }
{ title: string; icon: ReactElement }
> = {
operator: {
title: 'Wallet Address',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { FC } from 'react';
import type { FC, ReactElement } from 'react';

import clsx from 'clsx';

const ShadowIcon: FC<{
className?: string;
title?: string;
img: string | JSX.Element;
img: string | ReactElement;
}> = ({ className, title, img }) => {
return (
<div className={clsx('shadow-icon', className)}>
Expand Down
6 changes: 3 additions & 3 deletions packages/apps/dashboard/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@golevelup/ts-jest": "^1.2.1",
"@golevelup/ts-jest": "^3.0.0",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.14",
"@types/express": "^5.0.6",
"@types/jest": "30.0.0",
"@types/node": "22.10.5",
"eslint": "^10.0.3",
"eslint": "^10.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^29.15.0",
"eslint-plugin-jest": "^29.15.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
"jest": "^29.7.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/apps/faucet/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@
"@human-protocol/sdk": "workspace:*",
"@mui/icons-material": "^7.3.8",
"@mui/material": "^5.16.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-loading-skeleton": "^3.3.1",
"react-router-dom": "^7.13.0",
"serve": "^14.2.4",
"viem": "2.x"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.4",
"dotenv": "^17.2.2",
"eslint": "^10.0.3",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^5.5.5",
Expand Down
4 changes: 2 additions & 2 deletions packages/apps/faucet/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@human-protocol/sdk": "workspace:*",
"axios": "^1.3.4",
"body-parser": "^1.20.0",
"cors": "^2.8.5",
"cors": "^2.8.6",
"express": "^5.2.1",
"express-rate-limit": "^7.3.0",
"node-cache": "^5.1.2",
Expand All @@ -31,7 +31,7 @@
"@types/jest": "^29.5.14",
"@types/node": "^22.15.16",
"concurrently": "^9.1.2",
"eslint": "^10.0.3",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
Expand Down
12 changes: 6 additions & 6 deletions packages/apps/fortune/exchange-oracle/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,24 @@
"@mui/icons-material": "^7.3.8",
"@mui/material": "^5.16.7",
"@tanstack/query-sync-storage-persister": "^5.68.0",
"@tanstack/react-query": "^5.67.2",
"@tanstack/react-query": "^5.91.3",
"@tanstack/react-query-persist-client": "^5.80.7",
"axios": "^1.7.2",
"ethers": "^6.16.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.0",
"serve": "^14.2.4",
"viem": "2.x",
"wagmi": "^2.14.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.3",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^10.0.3",
"eslint": "^10.1.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^16.3.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/apps/fortune/exchange-oracle/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@golevelup/ts-jest": "^0.6.1",
"@golevelup/ts-jest": "^3.0.0",
"@nestjs/cli": "^11.0.16",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.14",
Expand All @@ -73,9 +73,9 @@
"@types/node": "22.10.5",
"@types/passport": "^1",
"@types/pg": "8.11.10",
"eslint": "^10.0.3",
"eslint": "^10.1.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^29.15.0",
"eslint-plugin-jest": "^29.15.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.3.0",
"jest": "^29.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { createMock } from '@golevelup/ts-jest';
import { HMToken__factory } from '@human-protocol/core/typechain-types';
import { Encryption, EscrowClient, OperatorUtils } from '@human-protocol/sdk';
import {
Encryption,
EncryptionUtils,
EscrowClient,
OperatorUtils,
} from '@human-protocol/sdk';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { Test } from '@nestjs/testing';
Expand Down Expand Up @@ -69,7 +74,6 @@ jest.mock('minio', () => {

describe('JobService', () => {
let jobService: JobService;
let web3Service: Web3Service;
let storageService: StorageService;
let jobRepository: JobRepository;
let assignmentRepository: AssignmentRepository;
Expand Down Expand Up @@ -136,14 +140,18 @@ describe('JobService', () => {
}).compile();

jobService = moduleRef.get<JobService>(JobService);
web3Service = moduleRef.get<Web3Service>(Web3Service);
storageService = moduleRef.get<StorageService>(StorageService);
jobRepository = moduleRef.get<JobRepository>(JobRepository);
assignmentRepository =
moduleRef.get<AssignmentRepository>(AssignmentRepository);
webhookRepository = moduleRef.get<WebhookRepository>(WebhookRepository);
});

afterEach(() => {
(jobService as any).manifestCache.clear();
jest.clearAllMocks();
});

describe('createJob', () => {
beforeAll(async () => {
jest.spyOn(jobRepository, 'createUnique');
Expand Down Expand Up @@ -497,7 +505,6 @@ describe('JobService', () => {
.mockResolvedValue(solutionsUrl);

await jobService.solveJob(assignment.id, 'solution');
expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
expect(webhookRepository.createUnique).toHaveBeenCalledWith({
escrowAddress,
chainId,
Expand All @@ -515,7 +522,6 @@ describe('JobService', () => {
await expect(jobService.solveJob(1, 'solution')).rejects.toThrow(
new ConflictError(ErrorAssignment.InvalidStatus),
);
expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
});

it('should fail if user is not assigned to the job', async () => {
Expand Down Expand Up @@ -556,7 +562,6 @@ describe('JobService', () => {
await expect(jobService.solveJob(1, 'solution')).rejects.toThrow(
'This job has already been completed',
);
expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
});

it('should fail if user has already submitted a solution', async () => {
Expand Down Expand Up @@ -589,7 +594,100 @@ describe('JobService', () => {
await expect(jobService.solveJob(1, 'solution')).rejects.toThrow(
new ValidationError(ErrorJob.SolutionAlreadySubmitted),
);
expect(web3Service.getSigner).toHaveBeenCalledWith(chainId);
});
});

describe('getManifest', () => {
const downloadFileFromUrlMock = jest.mocked(downloadFileFromUrl);

it('should fetch and parse a non encrypted manifest', async () => {
const manifest: ManifestDto = {
requesterTitle: 'Example Title',
requesterDescription: 'Example Description',
submissionsRequired: 5,
fundAmount: 100,
};

downloadFileFromUrlMock.mockResolvedValue(JSON.stringify(manifest));
EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(false);

const result = await jobService.getManifest(
chainId,
escrowAddress,
MOCK_MANIFEST_URL,
);

expect(result).toEqual(manifest);
expect(Encryption.build).not.toHaveBeenCalled();
});

it('should fetch and decrypt an encrypted manifest', async () => {
const manifest: ManifestDto = {
requesterTitle: 'Example Title',
requesterDescription: 'Example Description',
submissionsRequired: 5,
fundAmount: 100,
};

downloadFileFromUrlMock.mockResolvedValue('encrypted-content');
EncryptionUtils.isEncrypted = jest.fn().mockReturnValue(true);
(Encryption.build as any).mockImplementation(() => ({
decrypt: jest.fn().mockResolvedValue(JSON.stringify(manifest)),
}));

const result = await jobService.getManifest(
chainId,
escrowAddress,
MOCK_MANIFEST_URL,
);

expect(result).toEqual(manifest);
expect(Encryption.build).toHaveBeenCalled();
});

it('should cache the manifest in memory for repeated requests', async () => {
const manifest: ManifestDto = {
requesterTitle: 'Example Title',
requesterDescription: 'Example Description',
submissionsRequired: 5,
fundAmount: 100,
};

downloadFileFromUrlMock.mockResolvedValue(manifest);

const firstManifest = await jobService.getManifest(
chainId,
escrowAddress,
MOCK_MANIFEST_URL,
);
const secondManifest = await jobService.getManifest(
chainId,
escrowAddress,
MOCK_MANIFEST_URL,
);

expect(firstManifest).toEqual(manifest);
expect(secondManifest).toEqual(manifest);
expect(downloadFileFromUrlMock).toHaveBeenCalledTimes(1);
});

it('should retry downloading the manifest after a failed request', async () => {
downloadFileFromUrlMock.mockRejectedValue(
new Error('Storage file not found'),
);
jest
.spyOn(webhookRepository, 'createUnique')
.mockResolvedValue({} as any);

await expect(
jobService.getManifest(chainId, escrowAddress, MOCK_MANIFEST_URL),
).rejects.toThrow(ErrorJob.ManifestNotFound);
await expect(
jobService.getManifest(chainId, escrowAddress, MOCK_MANIFEST_URL),
).rejects.toThrow(ErrorJob.ManifestNotFound);

expect(downloadFileFromUrlMock).toHaveBeenCalledTimes(2);
expect(webhookRepository.createUnique).toHaveBeenCalledTimes(2);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import { JobRepository } from './job.repository';

@Injectable()
export class JobService {
private readonly manifestCache = new Map<string, Promise<ManifestDto>>();

constructor(
private readonly pgpConfigService: PGPConfigService,
public readonly jobRepository: JobRepository,
Expand Down Expand Up @@ -344,6 +346,32 @@ export class JobService {
chainId: number,
escrowAddress: string,
manifestUrl: string,
): Promise<ManifestDto> {
const cacheKey = `${chainId}:${escrowAddress}`;

const cachedManifest = this.manifestCache.get(cacheKey);
if (cachedManifest) {
return cachedManifest;
}

const manifestRequest = this.fetchManifest(
chainId,
escrowAddress,
manifestUrl,
).catch((error) => {
this.manifestCache.delete(cacheKey);
throw error;
});

this.manifestCache.set(cacheKey, manifestRequest);

return manifestRequest;
}

private async fetchManifest(
chainId: number,
escrowAddress: string,
manifestUrl: string,
): Promise<ManifestDto> {
let manifest: ManifestDto | null = null;

Expand Down
Loading
Loading