Skip to content

Commit

Permalink
feat: 🎸 get complexity for period
Browse files Browse the repository at this point in the history
  • Loading branch information
sansan committed Jun 19, 2024
1 parent 4390fe8 commit b97e686
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 22 deletions.
62 changes: 41 additions & 21 deletions src/checkpoints/checkpoints.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { CheckpointsController } from '~/checkpoints/checkpoints.controller';
import { CheckpointsService } from '~/checkpoints/checkpoints.service';
import { CheckpointDetailsModel } from '~/checkpoints/models/checkpoint-details.model';
import { CheckpointScheduleModel } from '~/checkpoints/models/checkpoint-schedule.model';
import { PeriodComplexityModel } from '~/checkpoints/models/period-complexity.model';
import { ScheduleComplexityModel } from '~/checkpoints/models/schedule-complexity.model';
import { PaginatedResultsModel } from '~/common/models/paginated-results.model';
import { ResultsModel } from '~/common/models/results.model';
import { testValues } from '~/test-utils/consts';
import { MockCheckpoint, MockCheckpointSchedule } from '~/test-utils/mocks';
import { MockCheckpointsService } from '~/test-utils/service-mocks';

const { did, signer, txResult } = testValues;
const { did, signer, txResult, ticker } = testValues;

describe('CheckpointsController', () => {
let controller: CheckpointsController;
Expand Down Expand Up @@ -41,7 +42,6 @@ describe('CheckpointsController', () => {
const createdAt = new Date();
const totalSupply = new BigNumber(1000);
const id = new BigNumber(1);
const ticker = 'TICKER';

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

const result = await controller.getCheckpoints(
{ ticker: 'TICKER' },
{ size: new BigNumber(1) }
);
const result = await controller.getCheckpoints({ ticker }, { size: new BigNumber(1) });

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

const result = await controller.getCheckpoints(
{ ticker: 'TICKER' },
{ ticker },
{ size: new BigNumber(1), start: 'START_KEY' }
);

Expand All @@ -115,7 +112,7 @@ describe('CheckpointsController', () => {
signer: 'signer',
};

const result = await controller.createCheckpoint({ ticker: 'TICKER' }, body);
const result = await controller.createCheckpoint({ ticker }, body);

expect(result).toEqual({
...txResult,
Expand Down Expand Up @@ -144,12 +141,12 @@ describe('CheckpointsController', () => {

mockCheckpointsService.findSchedulesByTicker.mockResolvedValue(mockSchedules);

const result = await controller.getSchedules({ ticker: 'TICKER' });
const result = await controller.getSchedules({ ticker });

const mockResult = [
new CheckpointScheduleModel({
id: new BigNumber(1),
ticker: 'TICKER',
ticker,
pendingPoints: [mockDate],
expiryDate: null,
remainingCheckpoints: new BigNumber(1),
Expand All @@ -164,16 +161,18 @@ describe('CheckpointsController', () => {
describe('getSchedule', () => {
it('should call the service and return the Checkpoint Schedule details', async () => {
const mockDate = new Date('10/14/1987');
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { getCheckpoints, ...schedule } = new MockCheckpointSchedule();
const mockScheduleWithDetails = {
schedule: new MockCheckpointSchedule(),
schedule,
details: {
remainingCheckpoints: new BigNumber(1),
nextCheckpointDate: mockDate,
},
};
mockCheckpointsService.findScheduleById.mockResolvedValue(mockScheduleWithDetails);

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

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

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { getCheckpoints, ...schedule } = new MockCheckpointSchedule();
const mockScheduleWithDetails = {
schedule: new MockCheckpointSchedule(),
schedule,
details: {
remainingCheckpoints: new BigNumber(1),
nextCheckpointDate: mockDate,
Expand All @@ -211,7 +212,7 @@ describe('CheckpointsController', () => {
points: [mockDate],
};

const result = await controller.createSchedule({ ticker: 'TICKER' }, body);
const result = await controller.createSchedule({ ticker }, body);

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

const result = await controller.getHolders(
{
ticker: 'TICKER',
ticker,
id: new BigNumber(1),
},
{ size: new BigNumber(10) }
Expand All @@ -275,7 +276,6 @@ describe('CheckpointsController', () => {
describe('getAssetBalance', () => {
it('should return the balance of an Asset for an Identity at a given Checkpoint', async () => {
const balance = new BigNumber(10);
const ticker = 'TICKER';
const id = new BigNumber(1);

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

const result = await controller.deleteSchedule(
{ id: new BigNumber(1), ticker: 'TICKER' },
{ signer }
);
const result = await controller.deleteSchedule({ id: new BigNumber(1), ticker }, { signer });

expect(result).toEqual(txResult);
});
Expand All @@ -311,7 +308,6 @@ describe('CheckpointsController', () => {
const createdAt = new Date();
const totalSupply = new BigNumber(1000);
const id = new BigNumber(1);
const ticker = 'TICKER';

const mockCheckpoint = new MockCheckpoint();
mockCheckpointsService.findCheckpointsByScheduleId.mockResolvedValue([
Expand Down Expand Up @@ -354,4 +350,28 @@ describe('CheckpointsController', () => {
]);
});
});

describe('getPeriodComplexity', () => {
it('should call the service and return the Checkpoint Schedule complexity for given period', async () => {
const complexity = new BigNumber(10000);
mockCheckpointsService.getComplexityForPeriod.mockResolvedValue(complexity);
const start = new Date();
const end = new Date();
const result = await controller.getPeriodComplexity(
{ ticker, id: new BigNumber(1) },
{ start, end }
);

const mockResult = new PeriodComplexityModel({
complexity,
});
expect(result).toEqual(mockResult);
expect(mockCheckpointsService.getComplexityForPeriod).toBeCalledWith(
ticker,
new BigNumber(1),
start,
end
);
});
});
});
52 changes: 51 additions & 1 deletion src/checkpoints/checkpoints.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { CheckpointsService } from '~/checkpoints/checkpoints.service';
import { CheckpointParamsDto } from '~/checkpoints/dto/checkpoint.dto';
import { CheckPointBalanceParamsDto } from '~/checkpoints/dto/checkpoint-balance.dto';
import { CreateCheckpointScheduleDto } from '~/checkpoints/dto/create-checkpoint-schedule.dto';
import { PeriodQueryDto } from '~/checkpoints/dto/period-query.dto';
import { CheckpointDetailsModel } from '~/checkpoints/models/checkpoint-details.model';
import { CheckpointScheduleModel } from '~/checkpoints/models/checkpoint-schedule.model';
import { CreatedCheckpointModel } from '~/checkpoints/models/created-checkpoint.model';
import { CreatedCheckpointScheduleModel } from '~/checkpoints/models/created-checkpoint-schedule.model';
import { PeriodComplexityModel } from '~/checkpoints/models/period-complexity.model';
import { ScheduleComplexityModel } from '~/checkpoints/models/schedule-complexity.model';
import { ApiArrayResponse, ApiTransactionResponse } from '~/common/decorators/swagger';
import { IsTicker } from '~/common/decorators/validation';
Expand Down Expand Up @@ -463,7 +465,7 @@ export class CheckpointsController {
new CheckpointDetailsModel({ id, createdAt, totalSupply })
);
}

@ApiOperation({
summary: 'Fetch Asset Schedules complexity',
})
Expand Down Expand Up @@ -497,4 +499,52 @@ export class CheckpointsController {
});
});
}

@ApiOperation({
summary: 'Fetch details of an Asset Checkpoint Schedule',
})
@ApiParam({
name: 'ticker',
description: 'The ticker of the Asset whose Checkpoint Schedule is to be fetched',
type: 'string',
example: 'TICKER',
})
@ApiParam({
name: 'id',
description: 'The ID of the Checkpoint Schedule to be fetched',
type: 'string',
example: '1',
})
@ApiQuery({
name: 'start',
description: 'Start date for the period for which to fetch complexity',
type: 'string',
required: false,
example: '2021-01-01',
})
@ApiQuery({
name: 'end',
description: 'End date for the period for which to fetch complexity',
type: 'string',
required: false,
example: '2021-01-31',
})
@ApiOkResponse({
description: 'The complexity of the Schedule for the given period',
type: ScheduleComplexityModel,
})
@ApiNotFoundResponse({
description: 'Either the Asset or the Checkpoint Schedule does not exist',
})
@Get('schedules/:id/complexity')
public async getPeriodComplexity(
@Param() { ticker, id }: CheckpointScheduleParamsDto,
@Query() { start, end }: PeriodQueryDto
): Promise<PeriodComplexityModel> {
const complexity = await this.checkpointsService.getComplexityForPeriod(ticker, id, start, end);

return new PeriodComplexityModel({
complexity,
});
}
}
62 changes: 62 additions & 0 deletions src/checkpoints/checkpoints.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,4 +477,66 @@ describe('CheckpointsService', () => {
expect(mockAssetsService.findFungible).toBeCalledWith('TICKER');
});
});

describe('getComplexityForPeriod', () => {
let mockAsset: MockAsset;
const ticker = 'TICKER';
const id = new BigNumber(1);

const CHECKPOINT_DATE = new Date('2021-01-01');
const START_DATE = new Date('2021-01-15');
const END_DATE = new Date('2021-01-31');
const PENDING_POINT_DATE_1 = new Date('2021-01-21');
const PENDING_POINT_DATE_2 = new Date('2021-01-15');

beforeEach(() => {
mockAsset = new MockAsset();
mockAssetsService.findFungible.mockResolvedValue(mockAsset);
});

const setupMocks = (createdAtDate: Date, pendingPoints: Date[] = []): void => {
const schedule = new MockCheckpointSchedule();
schedule.pendingPoints = pendingPoints;
const mockCheckpoint = new MockCheckpoint();
mockCheckpoint.createdAt.mockResolvedValue(createdAtDate);
schedule.getCheckpoints.mockResolvedValue([mockCheckpoint]);

const mockScheduleWithDetails = {
schedule,
};
mockAsset.checkpoints.schedules.getOne.mockResolvedValue(mockScheduleWithDetails);
};

it('should equal the length of all checkpoints for schedule if no period given', async () => {
setupMocks(new Date());

const result = await service.getComplexityForPeriod(ticker, id);

expect(result).toEqual(new BigNumber(1));
});

it('should filter out checkpoints that are outside given period', async () => {
setupMocks(CHECKPOINT_DATE, [PENDING_POINT_DATE_2]);

const result = await service.getComplexityForPeriod(ticker, id, START_DATE, END_DATE);

expect(result).toEqual(new BigNumber(1));
});

it('should filter if just start is given', async () => {
setupMocks(CHECKPOINT_DATE, [PENDING_POINT_DATE_1]);

const result = await service.getComplexityForPeriod(ticker, id, START_DATE);

expect(result).toEqual(new BigNumber(1));
});

it('should filter if just end is given', async () => {
setupMocks(CHECKPOINT_DATE, [PENDING_POINT_DATE_1]);

const result = await service.getComplexityForPeriod(ticker, id, undefined, START_DATE);

expect(result).toEqual(new BigNumber(1));
});
});
});
23 changes: 23 additions & 0 deletions src/checkpoints/checkpoints.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,27 @@ export class CheckpointsService {

return { schedules, maxComplexity };
}

public async getComplexityForPeriod(
ticker: string,
id: BigNumber,
start?: Date,
end?: Date
): Promise<BigNumber> {
const { schedule } = await this.findScheduleById(ticker, id);

const checkpoints = await schedule.getCheckpoints();
const pendingPoints = schedule.pendingPoints;
const checkpointDatePromises = checkpoints.map(checkpoint => checkpoint.createdAt());

const pastCheckpoints = await Promise.all(checkpointDatePromises);

const allCheckpoints = [...pendingPoints, ...pastCheckpoints];

const checkpointsInPeriod = allCheckpoints.filter(
date => (!start || date >= start) && (!end || date <= end)
);

return new BigNumber(checkpointsInPeriod.length);
}
}
13 changes: 13 additions & 0 deletions src/checkpoints/dto/period-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* istanbul ignore file */

import { IsDate, IsOptional } from 'class-validator';

export class PeriodQueryDto {
@IsOptional()
@IsDate()
readonly start?: Date;

@IsOptional()
@IsDate()
readonly end?: Date;
}
20 changes: 20 additions & 0 deletions src/checkpoints/models/period-complexity.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* istanbul ignore file */

import { ApiProperty } from '@nestjs/swagger';
import { BigNumber } from '@polymeshassociation/polymesh-sdk';

import { FromBigNumber } from '~/common/decorators/transformation';

export class PeriodComplexityModel {
@ApiProperty({
description: 'Total calculated complexity for given period',
type: 'string',
example: '10000',
})
@FromBigNumber()
readonly complexity: BigNumber;

constructor(model: PeriodComplexityModel) {
Object.assign(this, model);
}
}
1 change: 1 addition & 0 deletions src/test-utils/service-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export class MockCheckpointsService {
findOne = jest.fn();
findCheckpointsByScheduleId = jest.fn();
getComplexityForAsset = jest.fn();
getComplexityForPeriod = jest.fn();
}

export class MockAuthService {
Expand Down

0 comments on commit b97e686

Please sign in to comment.