Skip to content

Commit b97e686

Browse files
committed
feat: 🎸 get complexity for period
1 parent 4390fe8 commit b97e686

7 files changed

+211
-22
lines changed

‎src/checkpoints/checkpoints.controller.spec.ts

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import { CheckpointsController } from '~/checkpoints/checkpoints.controller';
66
import { CheckpointsService } from '~/checkpoints/checkpoints.service';
77
import { CheckpointDetailsModel } from '~/checkpoints/models/checkpoint-details.model';
88
import { CheckpointScheduleModel } from '~/checkpoints/models/checkpoint-schedule.model';
9+
import { PeriodComplexityModel } from '~/checkpoints/models/period-complexity.model';
910
import { ScheduleComplexityModel } from '~/checkpoints/models/schedule-complexity.model';
1011
import { PaginatedResultsModel } from '~/common/models/paginated-results.model';
1112
import { ResultsModel } from '~/common/models/results.model';
1213
import { testValues } from '~/test-utils/consts';
1314
import { MockCheckpoint, MockCheckpointSchedule } from '~/test-utils/mocks';
1415
import { MockCheckpointsService } from '~/test-utils/service-mocks';
1516

16-
const { did, signer, txResult } = testValues;
17+
const { did, signer, txResult, ticker } = testValues;
1718

1819
describe('CheckpointsController', () => {
1920
let controller: CheckpointsController;
@@ -41,7 +42,6 @@ describe('CheckpointsController', () => {
4142
const createdAt = new Date();
4243
const totalSupply = new BigNumber(1000);
4344
const id = new BigNumber(1);
44-
const ticker = 'TICKER';
4545

4646
const mockCheckpoint = new MockCheckpoint();
4747
mockCheckpoint.createdAt.mockResolvedValue(createdAt);
@@ -83,10 +83,7 @@ describe('CheckpointsController', () => {
8383
it('should return the list of Checkpoints created on an Asset', async () => {
8484
mockCheckpointsService.findAllByTicker.mockResolvedValue(mockCheckpoints);
8585

86-
const result = await controller.getCheckpoints(
87-
{ ticker: 'TICKER' },
88-
{ size: new BigNumber(1) }
89-
);
86+
const result = await controller.getCheckpoints({ ticker }, { size: new BigNumber(1) });
9087

9188
expect(result).toEqual(mockResult);
9289
});
@@ -95,7 +92,7 @@ describe('CheckpointsController', () => {
9592
mockCheckpointsService.findAllByTicker.mockResolvedValue(mockCheckpoints);
9693

9794
const result = await controller.getCheckpoints(
98-
{ ticker: 'TICKER' },
95+
{ ticker },
9996
{ size: new BigNumber(1), start: 'START_KEY' }
10097
);
10198

@@ -115,7 +112,7 @@ describe('CheckpointsController', () => {
115112
signer: 'signer',
116113
};
117114

118-
const result = await controller.createCheckpoint({ ticker: 'TICKER' }, body);
115+
const result = await controller.createCheckpoint({ ticker }, body);
119116

120117
expect(result).toEqual({
121118
...txResult,
@@ -144,12 +141,12 @@ describe('CheckpointsController', () => {
144141

145142
mockCheckpointsService.findSchedulesByTicker.mockResolvedValue(mockSchedules);
146143

147-
const result = await controller.getSchedules({ ticker: 'TICKER' });
144+
const result = await controller.getSchedules({ ticker });
148145

149146
const mockResult = [
150147
new CheckpointScheduleModel({
151148
id: new BigNumber(1),
152-
ticker: 'TICKER',
149+
ticker,
153150
pendingPoints: [mockDate],
154151
expiryDate: null,
155152
remainingCheckpoints: new BigNumber(1),
@@ -164,16 +161,18 @@ describe('CheckpointsController', () => {
164161
describe('getSchedule', () => {
165162
it('should call the service and return the Checkpoint Schedule details', async () => {
166163
const mockDate = new Date('10/14/1987');
164+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
165+
const { getCheckpoints, ...schedule } = new MockCheckpointSchedule();
167166
const mockScheduleWithDetails = {
168-
schedule: new MockCheckpointSchedule(),
167+
schedule,
169168
details: {
170169
remainingCheckpoints: new BigNumber(1),
171170
nextCheckpointDate: mockDate,
172171
},
173172
};
174173
mockCheckpointsService.findScheduleById.mockResolvedValue(mockScheduleWithDetails);
175174

176-
const result = await controller.getSchedule({ ticker: 'TICKER', id: new BigNumber(1) });
175+
const result = await controller.getSchedule({ ticker, id: new BigNumber(1) });
177176

178177
const mockResult = new CheckpointScheduleModel({
179178
id: mockScheduleWithDetails.schedule.id,
@@ -197,8 +196,10 @@ describe('CheckpointsController', () => {
197196
};
198197
mockCheckpointsService.createScheduleByTicker.mockResolvedValue(response);
199198

199+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
200+
const { getCheckpoints, ...schedule } = new MockCheckpointSchedule();
200201
const mockScheduleWithDetails = {
201-
schedule: new MockCheckpointSchedule(),
202+
schedule,
202203
details: {
203204
remainingCheckpoints: new BigNumber(1),
204205
nextCheckpointDate: mockDate,
@@ -211,7 +212,7 @@ describe('CheckpointsController', () => {
211212
points: [mockDate],
212213
};
213214

214-
const result = await controller.createSchedule({ ticker: 'TICKER' }, body);
215+
const result = await controller.createSchedule({ ticker }, body);
215216

216217
const mockCreatedSchedule = new CheckpointScheduleModel({
217218
id: mockScheduleWithDetails.schedule.id,
@@ -262,7 +263,7 @@ describe('CheckpointsController', () => {
262263

263264
const result = await controller.getHolders(
264265
{
265-
ticker: 'TICKER',
266+
ticker,
266267
id: new BigNumber(1),
267268
},
268269
{ size: new BigNumber(10) }
@@ -275,7 +276,6 @@ describe('CheckpointsController', () => {
275276
describe('getAssetBalance', () => {
276277
it('should return the balance of an Asset for an Identity at a given Checkpoint', async () => {
277278
const balance = new BigNumber(10);
278-
const ticker = 'TICKER';
279279
const id = new BigNumber(1);
280280

281281
const balanceModel = new IdentityBalanceModel({ balance, identity: did });
@@ -297,10 +297,7 @@ describe('CheckpointsController', () => {
297297
it('should return the transaction details', async () => {
298298
mockCheckpointsService.deleteScheduleByTicker.mockResolvedValue(txResult);
299299

300-
const result = await controller.deleteSchedule(
301-
{ id: new BigNumber(1), ticker: 'TICKER' },
302-
{ signer }
303-
);
300+
const result = await controller.deleteSchedule({ id: new BigNumber(1), ticker }, { signer });
304301

305302
expect(result).toEqual(txResult);
306303
});
@@ -311,7 +308,6 @@ describe('CheckpointsController', () => {
311308
const createdAt = new Date();
312309
const totalSupply = new BigNumber(1000);
313310
const id = new BigNumber(1);
314-
const ticker = 'TICKER';
315311

316312
const mockCheckpoint = new MockCheckpoint();
317313
mockCheckpointsService.findCheckpointsByScheduleId.mockResolvedValue([
@@ -354,4 +350,28 @@ describe('CheckpointsController', () => {
354350
]);
355351
});
356352
});
353+
354+
describe('getPeriodComplexity', () => {
355+
it('should call the service and return the Checkpoint Schedule complexity for given period', async () => {
356+
const complexity = new BigNumber(10000);
357+
mockCheckpointsService.getComplexityForPeriod.mockResolvedValue(complexity);
358+
const start = new Date();
359+
const end = new Date();
360+
const result = await controller.getPeriodComplexity(
361+
{ ticker, id: new BigNumber(1) },
362+
{ start, end }
363+
);
364+
365+
const mockResult = new PeriodComplexityModel({
366+
complexity,
367+
});
368+
expect(result).toEqual(mockResult);
369+
expect(mockCheckpointsService.getComplexityForPeriod).toBeCalledWith(
370+
ticker,
371+
new BigNumber(1),
372+
start,
373+
end
374+
);
375+
});
376+
});
357377
});

‎src/checkpoints/checkpoints.controller.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import { CheckpointsService } from '~/checkpoints/checkpoints.service';
1717
import { CheckpointParamsDto } from '~/checkpoints/dto/checkpoint.dto';
1818
import { CheckPointBalanceParamsDto } from '~/checkpoints/dto/checkpoint-balance.dto';
1919
import { CreateCheckpointScheduleDto } from '~/checkpoints/dto/create-checkpoint-schedule.dto';
20+
import { PeriodQueryDto } from '~/checkpoints/dto/period-query.dto';
2021
import { CheckpointDetailsModel } from '~/checkpoints/models/checkpoint-details.model';
2122
import { CheckpointScheduleModel } from '~/checkpoints/models/checkpoint-schedule.model';
2223
import { CreatedCheckpointModel } from '~/checkpoints/models/created-checkpoint.model';
2324
import { CreatedCheckpointScheduleModel } from '~/checkpoints/models/created-checkpoint-schedule.model';
25+
import { PeriodComplexityModel } from '~/checkpoints/models/period-complexity.model';
2426
import { ScheduleComplexityModel } from '~/checkpoints/models/schedule-complexity.model';
2527
import { ApiArrayResponse, ApiTransactionResponse } from '~/common/decorators/swagger';
2628
import { IsTicker } from '~/common/decorators/validation';
@@ -463,7 +465,7 @@ export class CheckpointsController {
463465
new CheckpointDetailsModel({ id, createdAt, totalSupply })
464466
);
465467
}
466-
468+
467469
@ApiOperation({
468470
summary: 'Fetch Asset Schedules complexity',
469471
})
@@ -497,4 +499,52 @@ export class CheckpointsController {
497499
});
498500
});
499501
}
502+
503+
@ApiOperation({
504+
summary: 'Fetch details of an Asset Checkpoint Schedule',
505+
})
506+
@ApiParam({
507+
name: 'ticker',
508+
description: 'The ticker of the Asset whose Checkpoint Schedule is to be fetched',
509+
type: 'string',
510+
example: 'TICKER',
511+
})
512+
@ApiParam({
513+
name: 'id',
514+
description: 'The ID of the Checkpoint Schedule to be fetched',
515+
type: 'string',
516+
example: '1',
517+
})
518+
@ApiQuery({
519+
name: 'start',
520+
description: 'Start date for the period for which to fetch complexity',
521+
type: 'string',
522+
required: false,
523+
example: '2021-01-01',
524+
})
525+
@ApiQuery({
526+
name: 'end',
527+
description: 'End date for the period for which to fetch complexity',
528+
type: 'string',
529+
required: false,
530+
example: '2021-01-31',
531+
})
532+
@ApiOkResponse({
533+
description: 'The complexity of the Schedule for the given period',
534+
type: ScheduleComplexityModel,
535+
})
536+
@ApiNotFoundResponse({
537+
description: 'Either the Asset or the Checkpoint Schedule does not exist',
538+
})
539+
@Get('schedules/:id/complexity')
540+
public async getPeriodComplexity(
541+
@Param() { ticker, id }: CheckpointScheduleParamsDto,
542+
@Query() { start, end }: PeriodQueryDto
543+
): Promise<PeriodComplexityModel> {
544+
const complexity = await this.checkpointsService.getComplexityForPeriod(ticker, id, start, end);
545+
546+
return new PeriodComplexityModel({
547+
complexity,
548+
});
549+
}
500550
}

‎src/checkpoints/checkpoints.service.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,4 +477,66 @@ describe('CheckpointsService', () => {
477477
expect(mockAssetsService.findFungible).toBeCalledWith('TICKER');
478478
});
479479
});
480+
481+
describe('getComplexityForPeriod', () => {
482+
let mockAsset: MockAsset;
483+
const ticker = 'TICKER';
484+
const id = new BigNumber(1);
485+
486+
const CHECKPOINT_DATE = new Date('2021-01-01');
487+
const START_DATE = new Date('2021-01-15');
488+
const END_DATE = new Date('2021-01-31');
489+
const PENDING_POINT_DATE_1 = new Date('2021-01-21');
490+
const PENDING_POINT_DATE_2 = new Date('2021-01-15');
491+
492+
beforeEach(() => {
493+
mockAsset = new MockAsset();
494+
mockAssetsService.findFungible.mockResolvedValue(mockAsset);
495+
});
496+
497+
const setupMocks = (createdAtDate: Date, pendingPoints: Date[] = []): void => {
498+
const schedule = new MockCheckpointSchedule();
499+
schedule.pendingPoints = pendingPoints;
500+
const mockCheckpoint = new MockCheckpoint();
501+
mockCheckpoint.createdAt.mockResolvedValue(createdAtDate);
502+
schedule.getCheckpoints.mockResolvedValue([mockCheckpoint]);
503+
504+
const mockScheduleWithDetails = {
505+
schedule,
506+
};
507+
mockAsset.checkpoints.schedules.getOne.mockResolvedValue(mockScheduleWithDetails);
508+
};
509+
510+
it('should equal the length of all checkpoints for schedule if no period given', async () => {
511+
setupMocks(new Date());
512+
513+
const result = await service.getComplexityForPeriod(ticker, id);
514+
515+
expect(result).toEqual(new BigNumber(1));
516+
});
517+
518+
it('should filter out checkpoints that are outside given period', async () => {
519+
setupMocks(CHECKPOINT_DATE, [PENDING_POINT_DATE_2]);
520+
521+
const result = await service.getComplexityForPeriod(ticker, id, START_DATE, END_DATE);
522+
523+
expect(result).toEqual(new BigNumber(1));
524+
});
525+
526+
it('should filter if just start is given', async () => {
527+
setupMocks(CHECKPOINT_DATE, [PENDING_POINT_DATE_1]);
528+
529+
const result = await service.getComplexityForPeriod(ticker, id, START_DATE);
530+
531+
expect(result).toEqual(new BigNumber(1));
532+
});
533+
534+
it('should filter if just end is given', async () => {
535+
setupMocks(CHECKPOINT_DATE, [PENDING_POINT_DATE_1]);
536+
537+
const result = await service.getComplexityForPeriod(ticker, id, undefined, START_DATE);
538+
539+
expect(result).toEqual(new BigNumber(1));
540+
});
541+
});
480542
});

‎src/checkpoints/checkpoints.service.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,27 @@ export class CheckpointsService {
152152

153153
return { schedules, maxComplexity };
154154
}
155+
156+
public async getComplexityForPeriod(
157+
ticker: string,
158+
id: BigNumber,
159+
start?: Date,
160+
end?: Date
161+
): Promise<BigNumber> {
162+
const { schedule } = await this.findScheduleById(ticker, id);
163+
164+
const checkpoints = await schedule.getCheckpoints();
165+
const pendingPoints = schedule.pendingPoints;
166+
const checkpointDatePromises = checkpoints.map(checkpoint => checkpoint.createdAt());
167+
168+
const pastCheckpoints = await Promise.all(checkpointDatePromises);
169+
170+
const allCheckpoints = [...pendingPoints, ...pastCheckpoints];
171+
172+
const checkpointsInPeriod = allCheckpoints.filter(
173+
date => (!start || date >= start) && (!end || date <= end)
174+
);
175+
176+
return new BigNumber(checkpointsInPeriod.length);
177+
}
155178
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* istanbul ignore file */
2+
3+
import { IsDate, IsOptional } from 'class-validator';
4+
5+
export class PeriodQueryDto {
6+
@IsOptional()
7+
@IsDate()
8+
readonly start?: Date;
9+
10+
@IsOptional()
11+
@IsDate()
12+
readonly end?: Date;
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* istanbul ignore file */
2+
3+
import { ApiProperty } from '@nestjs/swagger';
4+
import { BigNumber } from '@polymeshassociation/polymesh-sdk';
5+
6+
import { FromBigNumber } from '~/common/decorators/transformation';
7+
8+
export class PeriodComplexityModel {
9+
@ApiProperty({
10+
description: 'Total calculated complexity for given period',
11+
type: 'string',
12+
example: '10000',
13+
})
14+
@FromBigNumber()
15+
readonly complexity: BigNumber;
16+
17+
constructor(model: PeriodComplexityModel) {
18+
Object.assign(this, model);
19+
}
20+
}

‎src/test-utils/service-mocks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export class MockCheckpointsService {
221221
findOne = jest.fn();
222222
findCheckpointsByScheduleId = jest.fn();
223223
getComplexityForAsset = jest.fn();
224+
getComplexityForPeriod = jest.fn();
224225
}
225226

226227
export class MockAuthService {

0 commit comments

Comments
 (0)