Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 🎸 get complexity for period #280

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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);
}
sansan marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading