Skip to content

Commit 1fbd6f8

Browse files
authored
[Job Launcher][Server] JobService tests (#3373)
* - Created tests for JobService - Added checks for `stripeCustomerId` in JobService to ensure proper handling of users without a Stripe account. - Updated PaymentService to throw errors when `stripeCustomerId` is not present. - Modified UserEntity to allow `stripeCustomerId` to be nullable. - Created migration to enforce non-null constraints on certain fields in the Job entity. - Refactored fixtures for jobs and users to accommodate changes in the structure and ensure proper testing. - Enhanced test cases in PaymentService and ManifestService to reflect the new logic and structure.
1 parent 9fa7dd1 commit 1fbd6f8

File tree

15 files changed

+2098
-241
lines changed

15 files changed

+2098
-241
lines changed

packages/apps/job-launcher/server/src/common/interfaces/job.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ export interface IJob extends IBase {
66
chainId: number;
77
fee: number;
88
fundAmount: number;
9-
escrowAddress: string;
9+
escrowAddress: string | null;
1010
manifestUrl: string;
1111
manifestHash: string;
1212
status: JobStatus;
13-
failedReason?: string;
14-
retriesCount?: number;
13+
failedReason: string | null;
14+
retriesCount: number;
1515
waitUntil: Date;
1616
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm';
2+
3+
export class RemoveNullableFromJobEntity1748612934037
4+
implements MigrationInterface
5+
{
6+
name = 'RemoveNullableFromJobEntity1748612934037';
7+
8+
public async up(queryRunner: QueryRunner): Promise<void> {
9+
await queryRunner.query(`
10+
DROP INDEX "hmt"."IDX_59f6c552b618c432f019500e7c"
11+
`);
12+
await queryRunner.query(`
13+
ALTER TABLE "hmt"."jobs"
14+
ALTER COLUMN "chain_id"
15+
SET NOT NULL
16+
`);
17+
await queryRunner.query(`
18+
ALTER TABLE "hmt"."jobs"
19+
ALTER COLUMN "reputation_oracle"
20+
SET NOT NULL
21+
`);
22+
await queryRunner.query(`
23+
ALTER TABLE "hmt"."jobs"
24+
ALTER COLUMN "exchange_oracle"
25+
SET NOT NULL
26+
`);
27+
await queryRunner.query(`
28+
ALTER TABLE "hmt"."jobs"
29+
ALTER COLUMN "recording_oracle"
30+
SET NOT NULL
31+
`);
32+
await queryRunner.query(`
33+
CREATE UNIQUE INDEX "IDX_59f6c552b618c432f019500e7c" ON "hmt"."jobs" ("chain_id", "escrow_address")
34+
`);
35+
}
36+
37+
public async down(queryRunner: QueryRunner): Promise<void> {
38+
await queryRunner.query(`
39+
DROP INDEX "hmt"."IDX_59f6c552b618c432f019500e7c"
40+
`);
41+
await queryRunner.query(`
42+
ALTER TABLE "hmt"."jobs"
43+
ALTER COLUMN "recording_oracle" DROP NOT NULL
44+
`);
45+
await queryRunner.query(`
46+
ALTER TABLE "hmt"."jobs"
47+
ALTER COLUMN "exchange_oracle" DROP NOT NULL
48+
`);
49+
await queryRunner.query(`
50+
ALTER TABLE "hmt"."jobs"
51+
ALTER COLUMN "reputation_oracle" DROP NOT NULL
52+
`);
53+
await queryRunner.query(`
54+
ALTER TABLE "hmt"."jobs"
55+
ALTER COLUMN "chain_id" DROP NOT NULL
56+
`);
57+
await queryRunner.query(`
58+
CREATE UNIQUE INDEX "IDX_59f6c552b618c432f019500e7c" ON "hmt"."jobs" ("chain_id", "escrow_address")
59+
`);
60+
}
61+
}

packages/apps/job-launcher/server/src/modules/cron-job/cron-job.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,11 @@ export class CronJobService {
265265
for (const jobEntity of jobEntities) {
266266
try {
267267
if (
268-
await this.jobService.isEscrowFunded(
268+
jobEntity.escrowAddress &&
269+
(await this.jobService.isEscrowFunded(
269270
jobEntity.chainId,
270271
jobEntity.escrowAddress,
271-
)
272+
))
272273
) {
273274
const { amountRefunded } =
274275
await this.jobService.processEscrowCancellation(jobEntity);
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { faker } from '@faker-js/faker';
2+
import { ChainId } from '@human-protocol/sdk';
3+
import {
4+
getMockedProvider,
5+
getMockedRegion,
6+
} from '../../../test/fixtures/storage';
7+
import {
8+
AudinoJobType,
9+
CvatJobType,
10+
EscrowFundToken,
11+
FortuneJobType,
12+
JobCaptchaShapeType,
13+
} from '../../common/enums/job';
14+
import { PaymentCurrency } from '../../common/enums/payment';
15+
import {
16+
JobAudinoDto,
17+
JobCaptchaDto,
18+
JobCvatDto,
19+
JobFortuneDto,
20+
} from './job.dto';
21+
import { JobEntity } from './job.entity';
22+
import { JobStatus } from '../../common/enums/job';
23+
24+
export const createFortuneJobDto = (overrides = {}): JobFortuneDto => ({
25+
chainId: ChainId.POLYGON_AMOY,
26+
submissionsRequired: faker.number.int({ min: 1, max: 10 }),
27+
requesterTitle: faker.lorem.words(3),
28+
requesterDescription: faker.lorem.sentence(),
29+
paymentAmount: faker.number.float({ min: 1, max: 100, fractionDigits: 6 }),
30+
paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)),
31+
escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)),
32+
exchangeOracle: faker.finance.ethereumAddress(),
33+
recordingOracle: faker.finance.ethereumAddress(),
34+
reputationOracle: faker.finance.ethereumAddress(),
35+
...overrides,
36+
});
37+
38+
export const createCvatJobDto = (overrides = {}): JobCvatDto => ({
39+
chainId: ChainId.POLYGON_AMOY,
40+
data: {
41+
dataset: {
42+
provider: getMockedProvider(),
43+
region: getMockedRegion(),
44+
bucketName: faker.lorem.word(),
45+
path: faker.system.filePath(),
46+
},
47+
},
48+
labels: [{ name: faker.lorem.word(), nodes: [faker.string.uuid()] }],
49+
requesterDescription: faker.lorem.sentence(),
50+
userGuide: faker.internet.url(),
51+
minQuality: faker.number.float({ min: 0.1, max: 1 }),
52+
groundTruth: {
53+
provider: getMockedProvider(),
54+
region: getMockedRegion(),
55+
bucketName: faker.lorem.word(),
56+
path: faker.system.filePath(),
57+
},
58+
type: faker.helpers.arrayElement(Object.values(CvatJobType)),
59+
paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)),
60+
paymentAmount: faker.number.int({ min: 1, max: 1000 }),
61+
escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)),
62+
exchangeOracle: faker.finance.ethereumAddress(),
63+
recordingOracle: faker.finance.ethereumAddress(),
64+
reputationOracle: faker.finance.ethereumAddress(),
65+
...overrides,
66+
});
67+
68+
export const createAudinoJobDto = (overrides = {}): JobAudinoDto => ({
69+
chainId: ChainId.POLYGON_AMOY,
70+
data: {
71+
dataset: {
72+
provider: getMockedProvider(),
73+
region: getMockedRegion(),
74+
bucketName: faker.lorem.word(),
75+
path: faker.system.filePath(),
76+
},
77+
},
78+
groundTruth: {
79+
provider: getMockedProvider(),
80+
region: getMockedRegion(),
81+
bucketName: faker.lorem.word(),
82+
path: faker.system.filePath(),
83+
},
84+
labels: [{ name: faker.lorem.word() }],
85+
segmentDuration: faker.number.int({ min: 10, max: 100 }),
86+
requesterDescription: faker.lorem.sentence(),
87+
userGuide: faker.internet.url(),
88+
qualifications: [faker.lorem.word()],
89+
minQuality: faker.number.int({ min: 1, max: 100 }),
90+
type: AudinoJobType.AUDIO_TRANSCRIPTION,
91+
paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)),
92+
paymentAmount: faker.number.int({ min: 1, max: 1000 }),
93+
escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)),
94+
exchangeOracle: faker.finance.ethereumAddress(),
95+
recordingOracle: faker.finance.ethereumAddress(),
96+
reputationOracle: faker.finance.ethereumAddress(),
97+
...overrides,
98+
});
99+
100+
export const createCaptchaJobDto = (overrides = {}): JobCaptchaDto => ({
101+
chainId: ChainId.POLYGON_AMOY,
102+
data: {
103+
provider: getMockedProvider(),
104+
region: getMockedRegion(),
105+
bucketName: faker.lorem.word(),
106+
path: faker.system.filePath(),
107+
},
108+
accuracyTarget: faker.number.float({ min: 0.1, max: 1, fractionDigits: 4 }),
109+
minRequests: faker.number.int({ min: 1, max: 5 }),
110+
maxRequests: faker.number.int({ min: 6, max: 10 }),
111+
annotations: {
112+
typeOfJob: faker.helpers.arrayElement(Object.values(JobCaptchaShapeType)),
113+
labelingPrompt: faker.lorem.sentence(),
114+
groundTruths: faker.internet.url(),
115+
exampleImages: [faker.internet.url(), faker.internet.url()],
116+
taskBidPrice: faker.number.float({ min: 0.1, max: 10, fractionDigits: 6 }),
117+
label: faker.lorem.word(),
118+
},
119+
completionDate: faker.date.future(),
120+
paymentCurrency: faker.helpers.arrayElement(Object.values(PaymentCurrency)),
121+
paymentAmount: faker.number.int({ min: 1, max: 1000 }),
122+
escrowFundToken: faker.helpers.arrayElement(Object.values(EscrowFundToken)),
123+
exchangeOracle: faker.finance.ethereumAddress(),
124+
recordingOracle: faker.finance.ethereumAddress(),
125+
reputationOracle: faker.finance.ethereumAddress(),
126+
advanced: {},
127+
...overrides,
128+
});
129+
130+
export const createJobEntity = (
131+
overrides: Partial<JobEntity> = {},
132+
): JobEntity => {
133+
const entity = new JobEntity();
134+
entity.id = faker.number.int();
135+
entity.chainId = ChainId.POLYGON_AMOY;
136+
entity.reputationOracle = faker.finance.ethereumAddress();
137+
entity.exchangeOracle = faker.finance.ethereumAddress();
138+
entity.recordingOracle = faker.finance.ethereumAddress();
139+
entity.escrowAddress = faker.finance.ethereumAddress();
140+
entity.fee = faker.number.float({ min: 0.01, max: 1, fractionDigits: 6 });
141+
entity.fundAmount = faker.number.float({
142+
min: 1,
143+
max: 1000,
144+
fractionDigits: 6,
145+
});
146+
entity.token = faker.helpers.arrayElement(
147+
Object.values(EscrowFundToken),
148+
) as EscrowFundToken;
149+
entity.manifestUrl = faker.internet.url();
150+
entity.manifestHash = faker.string.uuid();
151+
entity.failedReason = null;
152+
entity.requestType = FortuneJobType.FORTUNE;
153+
entity.status = faker.helpers.arrayElement(Object.values(JobStatus));
154+
entity.userId = faker.number.int();
155+
entity.payments = [];
156+
entity.contentModerationRequests = [];
157+
entity.retriesCount = faker.number.int({ min: 0, max: 4 });
158+
entity.waitUntil = faker.date.future();
159+
Object.assign(entity, overrides);
160+
return entity;
161+
};

packages/apps/job-launcher/server/src/modules/job/job.entity.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ import { ContentModerationRequestEntity } from '../content-moderation/content-mo
1111
@Entity({ schema: NS, name: 'jobs' })
1212
@Index(['chainId', 'escrowAddress'], { unique: true })
1313
export class JobEntity extends BaseEntity implements IJob {
14-
@Column({ type: 'int', nullable: true })
14+
@Column({ type: 'int' })
1515
public chainId: number;
1616

17-
@Column({ type: 'varchar', nullable: true })
17+
@Column({ type: 'varchar' })
1818
public reputationOracle: string;
1919

20-
@Column({ type: 'varchar', nullable: true })
20+
@Column({ type: 'varchar' })
2121
public exchangeOracle: string;
2222

23-
@Column({ type: 'varchar', nullable: true })
23+
@Column({ type: 'varchar' })
2424
public recordingOracle: string;
2525

2626
@Column({ type: 'varchar', nullable: true })
27-
public escrowAddress: string;
27+
public escrowAddress: string | null;
2828

2929
@Column({ type: 'decimal', precision: 30, scale: 18 })
3030
public fee: number;
@@ -42,7 +42,7 @@ export class JobEntity extends BaseEntity implements IJob {
4242
public manifestHash: string;
4343

4444
@Column({ type: 'varchar', nullable: true })
45-
public failedReason: string;
45+
public failedReason: string | null;
4646

4747
@Column({
4848
type: 'enum',

0 commit comments

Comments
 (0)