Skip to content

Commit 4fa4320

Browse files
committed
tests
1 parent 80d5c0f commit 4fa4320

File tree

5 files changed

+997
-0
lines changed

5 files changed

+997
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
import { DatacapAllocator } from '@src/domain/application/application';
4+
5+
import {
6+
CreateApplicationCommand,
7+
CreateApplicationCommandHandler,
8+
} from './create-application.command';
9+
import { Container } from 'inversify';
10+
import { TYPES } from '@src/types';
11+
12+
const baseCommandInput = {
13+
guid: 'guid-123',
14+
applicationId: 'app-123',
15+
applicationNumber: 42,
16+
applicantName: 'Test Applicant',
17+
applicantAddress: 'f01234',
18+
applicantOrgName: 'Test Org',
19+
applicantOrgAddresses: 'addr-1, addr-2',
20+
allocationTrancheSchedule: 'monthly',
21+
allocationAudit: 'audit',
22+
allocationDistributionRequired: 'distribution',
23+
allocationRequiredStorageProviders: 'providers',
24+
bookkeepingRepo: 'repo',
25+
allocationRequiredReplicas: 'replicas',
26+
datacapAllocationLimits: 'limits',
27+
applicantGithubHandle: 'main-handle',
28+
otherGithubHandles: '@FooUser, BarUser\n@baz-user',
29+
onChainAddressForDataCapAllocation: 'f09999',
30+
};
31+
32+
const mocks = vi.hoisted(() => ({
33+
getMultisigInfo: vi.fn(),
34+
logger: {
35+
info: vi.fn(),
36+
debug: vi.fn(),
37+
error: vi.fn(),
38+
warn: vi.fn(),
39+
},
40+
repository: {
41+
getById: vi.fn(),
42+
save: vi.fn(),
43+
},
44+
lotusClient: {
45+
getActorId: vi.fn(),
46+
},
47+
pullRequestService: {
48+
createPullRequest: vi.fn(),
49+
},
50+
}));
51+
52+
vi.mock('@src/infrastructure/clients/filfox', () => ({
53+
getMultisigInfo: mocks.getMultisigInfo,
54+
}));
55+
56+
describe('CreateApplicationCommandHandler', () => {
57+
let container: Container;
58+
let handler: CreateApplicationCommandHandler;
59+
60+
beforeEach(() => {
61+
vi.clearAllMocks();
62+
63+
container = new Container();
64+
container.bind(TYPES.Logger).toConstantValue(mocks.logger);
65+
container.bind(TYPES.DatacapAllocatorRepository).toConstantValue(mocks.repository);
66+
container.bind(TYPES.LotusClient).toConstantValue(mocks.lotusClient);
67+
container.bind(TYPES.PullRequestService).toConstantValue(mocks.pullRequestService);
68+
container.bind(CreateApplicationCommandHandler).toSelf();
69+
handler = container.get(CreateApplicationCommandHandler);
70+
71+
mocks.getMultisigInfo.mockResolvedValue({
72+
multisig: {
73+
signers: ['signer-1', 'signer-2'],
74+
approvalThreshold: 2,
75+
},
76+
});
77+
78+
mocks.pullRequestService.createPullRequest.mockResolvedValue({
79+
number: 1,
80+
url: 'https://github.com/test/test/pull/1',
81+
commentId: 1,
82+
});
83+
84+
mocks.lotusClient.getActorId.mockResolvedValue('f01234');
85+
});
86+
87+
describe('normalizeGithubHandles', () => {
88+
it('returns normalized handles', () => {
89+
const result = handler.normalizeGithubHandles('@Foo, Bar \n @baz');
90+
expect(result).toEqual(['foo', 'bar', 'baz']);
91+
});
92+
93+
it('returns an empty array when input is invalid', () => {
94+
const result = handler.normalizeGithubHandles('');
95+
expect(result).toEqual([]);
96+
});
97+
});
98+
99+
describe('handle', () => {
100+
it('returns an error when the application already exists', async () => {
101+
const existingAllocator = DatacapAllocator.create({
102+
applicationId: 'existing',
103+
applicationNumber: 1,
104+
applicantName: 'Existing',
105+
applicantAddress: 'f0',
106+
applicantOrgName: 'Org',
107+
applicantOrgAddresses: 'Addr',
108+
allocationTrancheSchedule: 'schedule',
109+
allocationAudit: 'audit',
110+
allocationDistributionRequired: 'dist',
111+
allocationRequiredStorageProviders: 'providers',
112+
bookkeepingRepo: 'repo',
113+
allocationRequiredReplicas: 'replicas',
114+
datacapAllocationLimits: 'limits',
115+
applicantGithubHandle: 'handle',
116+
otherGithubHandles: [],
117+
onChainAddressForDataCapAllocation: 'f011',
118+
});
119+
mocks.repository.getById.mockResolvedValue(existingAllocator);
120+
const result = await handler.handle(new CreateApplicationCommand(baseCommandInput));
121+
122+
expect(result).toStrictEqual({
123+
success: false,
124+
error: new Error('Application already exists'),
125+
});
126+
127+
expect(mocks.repository.save).not.toHaveBeenCalled();
128+
expect(mocks.pullRequestService.createPullRequest).not.toHaveBeenCalled();
129+
});
130+
131+
it('should save the application', async () => {
132+
mocks.repository.getById.mockRejectedValue(null);
133+
const createApplicationCommand = new CreateApplicationCommand(baseCommandInput);
134+
const result = await handler.handle(createApplicationCommand);
135+
136+
expect(result).toStrictEqual({
137+
success: true,
138+
data: { guid: baseCommandInput.guid },
139+
});
140+
141+
expect(mocks.pullRequestService.createPullRequest).toHaveBeenCalledWith(
142+
expect.any(DatacapAllocator),
143+
);
144+
expect(mocks.pullRequestService.createPullRequest).toHaveBeenCalledWith(
145+
expect.objectContaining({
146+
guid: baseCommandInput.applicationId,
147+
applicationNumber: baseCommandInput.applicationNumber,
148+
applicantName: baseCommandInput.applicantName,
149+
}),
150+
);
151+
152+
expect(mocks.repository.save).toHaveBeenCalledWith(expect.any(DatacapAllocator), -1);
153+
expect(mocks.repository.save).toHaveBeenCalledWith(
154+
expect.objectContaining({
155+
guid: baseCommandInput.applicationId,
156+
applicationNumber: baseCommandInput.applicationNumber,
157+
applicantName: baseCommandInput.applicantName,
158+
applicantAddress: baseCommandInput.applicantAddress,
159+
applicantOrgName: baseCommandInput.applicantOrgName,
160+
applicantOrgAddresses: baseCommandInput.applicantOrgAddresses,
161+
allocationTrancheSchedule: baseCommandInput.allocationTrancheSchedule,
162+
allocationAudit: baseCommandInput.allocationAudit,
163+
allocationDistributionRequired: baseCommandInput.allocationDistributionRequired,
164+
allocationRequiredStorageProviders: baseCommandInput.allocationRequiredStorageProviders,
165+
allocationBookkeepingRepo: baseCommandInput.bookkeepingRepo,
166+
allocationRequiredReplicas: baseCommandInput.allocationRequiredReplicas,
167+
allocationDatacapAllocationLimits: baseCommandInput.datacapAllocationLimits,
168+
applicantGithubHandle: baseCommandInput.applicantGithubHandle,
169+
onChainAddressForDataCapAllocation: baseCommandInput.onChainAddressForDataCapAllocation,
170+
allocatorActorId: 'f01234',
171+
allocatorMultisigAddress: baseCommandInput.onChainAddressForDataCapAllocation,
172+
allocatorMultisigSigners: ['signer-1', 'signer-2'],
173+
allocatorMultisigThreshold: 2,
174+
applicationPullRequest: expect.objectContaining({
175+
prNumber: 1,
176+
prUrl: 'https://github.com/test/test/pull/1',
177+
commentId: 1,
178+
}),
179+
}),
180+
-1,
181+
);
182+
});
183+
});
184+
});
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { TYPES } from '@src/types';
2+
import { Container } from 'inversify';
3+
import { afterEach, beforeEach, expect, vi } from 'vitest';
4+
import { CreateApplicationCommand } from './create-application.command';
5+
import { describe, it } from 'vitest';
6+
import { subscribeApplicationSubmissions } from './subscribe-application-submissions.service';
7+
8+
const mocks = vi.hoisted(() => ({
9+
logger: {
10+
info: vi.fn((...args) => console.log(...args)),
11+
error: vi.fn((...args) => console.error(...args)),
12+
debug: vi.fn((...args) => console.debug(...args)),
13+
},
14+
repository: {
15+
getById: vi.fn(),
16+
save: vi.fn(),
17+
},
18+
lotusClient: {
19+
getActorId: vi.fn(),
20+
},
21+
pullRequestService: {
22+
createPullRequest: vi.fn(),
23+
},
24+
airtableClient: {
25+
getTableRecords: vi.fn(),
26+
},
27+
commandBus: {
28+
send: vi.fn(),
29+
},
30+
}));
31+
32+
vi.mock('@src/config', () => ({
33+
default: {
34+
SUBSCRIBE_APPLICATION_SUBMISSIONS_POLLING_INTERVAL: 1000,
35+
},
36+
}));
37+
38+
describe('subscribeApplicationSubmissions', () => {
39+
let container: Container;
40+
let interval: NodeJS.Timeout;
41+
42+
beforeEach(async () => {
43+
container = new Container();
44+
container.bind(TYPES.Logger).toConstantValue(mocks.logger);
45+
container.bind(TYPES.DatacapAllocatorRepository).toConstantValue(mocks.repository);
46+
container.bind(TYPES.LotusClient).toConstantValue(mocks.lotusClient);
47+
container.bind(TYPES.PullRequestService).toConstantValue(mocks.pullRequestService);
48+
container.bind(TYPES.AirtableClient).toConstantValue(mocks.airtableClient);
49+
container.bind(TYPES.CommandBus).toConstantValue(mocks.commandBus);
50+
51+
mocks.airtableClient.getTableRecords.mockResolvedValue([
52+
{
53+
id: '123',
54+
fields: {
55+
'Allocator Pathway Name': 'Test Applicant',
56+
'Organization Name': 'Test Org',
57+
'On-chain address for DC Allocation': 'f01234',
58+
},
59+
},
60+
]);
61+
mocks.commandBus.send.mockResolvedValue({
62+
success: true,
63+
data: { guid: '123' },
64+
});
65+
66+
vi.useFakeTimers();
67+
interval = await subscribeApplicationSubmissions(container);
68+
});
69+
70+
afterEach(() => {
71+
vi.clearAllMocks();
72+
clearInterval(interval);
73+
vi.useRealTimers();
74+
});
75+
76+
it('should create an application', async () => {
77+
await vi.advanceTimersByTimeAsync(1000);
78+
79+
expect(mocks.airtableClient.getTableRecords).toHaveBeenCalledTimes(1);
80+
expect(mocks.commandBus.send).toHaveBeenCalledTimes(1);
81+
expect(mocks.commandBus.send).toHaveBeenCalledWith(expect.any(CreateApplicationCommand));
82+
expect(mocks.logger.info).toHaveBeenCalledWith('Record 123 processed successfully');
83+
});
84+
85+
it('should not create an application if the record is not valid', async () => {
86+
mocks.airtableClient.getTableRecords.mockResolvedValue([
87+
{
88+
id: '123',
89+
fields: {
90+
'Allocator Pathway Name': 'Test Applicant',
91+
'Organization Name': 'Test Org',
92+
},
93+
},
94+
]);
95+
96+
await vi.advanceTimersByTimeAsync(1000);
97+
98+
expect(mocks.airtableClient.getTableRecords).toHaveBeenCalledTimes(1);
99+
expect(mocks.commandBus.send).not.toHaveBeenCalled();
100+
expect(mocks.logger.info).toHaveBeenCalledWith('Skipping record 123...');
101+
});
102+
103+
it('should not create an application if the record has already been processed', async () => {
104+
await vi.advanceTimersByTimeAsync(1000);
105+
106+
expect(mocks.airtableClient.getTableRecords).toHaveBeenCalledTimes(1);
107+
expect(mocks.commandBus.send).toHaveBeenCalledTimes(1);
108+
expect(mocks.commandBus.send).toHaveBeenCalledWith(expect.any(CreateApplicationCommand));
109+
});
110+
111+
it('should handle command bus throw', async () => {
112+
mocks.commandBus.send.mockResolvedValue({
113+
success: false,
114+
error: new Error('Command bus error'),
115+
});
116+
117+
await vi.advanceTimersByTimeAsync(1000);
118+
119+
expect(mocks.airtableClient.getTableRecords).toHaveBeenCalledTimes(1);
120+
expect(mocks.commandBus.send).toHaveBeenCalledTimes(1);
121+
expect(mocks.commandBus.send).toHaveBeenCalledWith(expect.any(CreateApplicationCommand));
122+
expect(mocks.logger.error).toHaveBeenCalledWith(
123+
'Record 123 processed with error: Error: Command bus error',
124+
);
125+
});
126+
127+
it('should handle airtable client throw', async () => {
128+
mocks.airtableClient.getTableRecords.mockRejectedValue(new Error('Airtable client error'));
129+
130+
await vi.advanceTimersByTimeAsync(1000);
131+
132+
expect(mocks.airtableClient.getTableRecords).toHaveBeenCalledTimes(1);
133+
expect(mocks.logger.error).toHaveBeenCalledWith('Error processing application submissions:');
134+
expect(mocks.logger.error).toHaveBeenCalledWith(new Error('Airtable client error'));
135+
});
136+
});

0 commit comments

Comments
 (0)