From 8382d154c9775dba0cc2b44b6fa5d422041ed4c1 Mon Sep 17 00:00:00 2001 From: Kieran O'Neill Date: Mon, 18 Mar 2024 05:11:09 -0600 Subject: [PATCH] feat: get fees by integrator (#5) * feat: use get by integrator for fees * fix: only show next page url if there are more pages * test: use new api path for tests * docs: update readme with new commands --- README.md | 12 ++- docker-compose.test.yml | 2 +- package.json | 4 +- src/main.ts | 12 +-- src/modules/chains/e2e.test.ts | 6 +- .../dtos/FindByIntegratorAndPageOptionsDTO.ts | 17 +++++ ...ts => FindByIntegratorAndPageResultDTO.ts} | 2 +- .../dtos/FindByPageOptionsDTO.ts | 17 ----- src/modules/fee-repository/dtos/index.ts | 4 +- src/modules/fee-repository/service.ts | 16 ++-- .../types/IFindByPageAggregateResult.ts | 2 +- src/modules/fees/controller.ts | 76 ++++++++----------- .../fees/dtos/GetByChainIdOptionsDTO.ts | 17 ----- .../fees/dtos/GetByIntegratorOptionsDTO.ts | 17 +++++ src/modules/fees/dtos/GetFeesParamsDTO.ts | 7 +- .../fees/dtos/GetFeesResponseBodyDTO.ts | 4 +- src/modules/fees/dtos/index.ts | 2 +- src/modules/fees/e2e.test.ts | 55 +++++++------- src/modules/fees/service.ts | 14 ++-- .../versions/__snapshots__/e2e.test.ts.snap | 16 ---- src/modules/versions/e2e.test.ts | 16 +++- .../createAPIPathPrefix.ts | 13 ++++ src/utils/createAPIPathPrefix/index.ts | 1 + src/utils/parseVersion/index.ts | 1 - src/utils/parseVersion/parseVersion.ts | 5 -- 25 files changed, 169 insertions(+), 169 deletions(-) create mode 100644 src/modules/fee-repository/dtos/FindByIntegratorAndPageOptionsDTO.ts rename src/modules/fee-repository/dtos/{FindByPageResultDTO.ts => FindByIntegratorAndPageResultDTO.ts} (88%) delete mode 100644 src/modules/fee-repository/dtos/FindByPageOptionsDTO.ts delete mode 100644 src/modules/fees/dtos/GetByChainIdOptionsDTO.ts create mode 100644 src/modules/fees/dtos/GetByIntegratorOptionsDTO.ts delete mode 100644 src/modules/versions/__snapshots__/e2e.test.ts.snap create mode 100644 src/utils/createAPIPathPrefix/createAPIPathPrefix.ts create mode 100644 src/utils/createAPIPathPrefix/index.ts delete mode 100644 src/utils/parseVersion/index.ts delete mode 100644 src/utils/parseVersion/parseVersion.ts diff --git a/README.md b/README.md index baba805..5ccd9f1 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,14 @@ This documentation outlines the available endpoints available. ### 3.2. Useful commands -| Command | Description | -|-------------------|------------------------------------------------------------------------------------------------------------------------------------------| -| `yarn setup` | Creates an `.env` file to the `.config/` directory. | -| `yarn start` | Runs setup and starts Docker Compose. Intended for development purposes only. | +| Command | Description | +|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `yarn setup` | Creates an `.env` file to the `.config/` directory. | +| `yarn start` | Runs setup and starts Docker Compose. Intended for development purposes only. | +| `yarn test` | Runs the test script that starts up the Docker Compose test configuration and runs both unit and e2e tests. E2e tests require a running API and database. | +| `yarn test:e2e` | Runs the e2e tests (all files that are named `e2e.test.ts`). This command requires a running API at https://127.0.0.1:3000 and a database. | +| `yarn test:unit` | Simply runs the unit tests. These are self-contained and do not require any external dependencies. | + [Back to top ^][table-of-contents] diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 42329cc..7f4deef 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -20,7 +20,7 @@ services: interval: 5s retries: 3 start_period: 5s - test: ["CMD", "curl", "-f", "http://127.0.0.1:${APP_PORT:-3000}/versions"] + test: ["CMD", "curl", "-f", "http://127.0.0.1:${APP_PORT:-3000}/api/v1/versions"] image: plutus/api_test networks: - plutus_network_test diff --git a/package.json b/package.json index c5af59b..bf05511 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "setup": "./scripts/setup.sh", "start": "./scripts/run.sh", "test": "./scripts/test.sh", - "test:e2e": "jest --passWithNoTests --config=jest.config.e2e.ts", - "test:unit": "jest --passWithNoTests --config=jest.config.unit.ts" + "test:e2e": "APP_VERSION=$npm_package_version jest --passWithNoTests --config=jest.config.e2e.ts", + "test:unit": "APP_VERSION=$npm_package_version jest --passWithNoTests --config=jest.config.unit.ts" }, "devDependencies": { "@commitlint/cli": "^19.1.0", diff --git a/src/main.ts b/src/main.ts index 15b0912..ccd331a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,14 +14,13 @@ import AppModule from '@app/modules/app/module'; import type { IEnvironmentVariables, ILogLevel } from '@app/types'; // utils +import createAPIPathPrefix from '@app/utils/createAPIPathPrefix'; import createLoggerService from '@app/utils/createLoggerService'; -import parseVersion from '@app/utils/parseVersion'; (async () => { let app: NestApplication; let configService: ConfigService; let logger: LoggerService; - let versions: string[]; try { app = await NestFactory.create(AppModule); @@ -30,9 +29,6 @@ import parseVersion from '@app/utils/parseVersion'; configService.get(EnvironmentVariableKeyEnum.AppName), configService.get(EnvironmentVariableKeyEnum.LogLevel) ); - versions = parseVersion( - configService.get(EnvironmentVariableKeyEnum.AppVersion) - ); // setup middleware app.useLogger(logger); @@ -45,7 +41,11 @@ import parseVersion from '@app/utils/parseVersion'; }, }) ); - app.setGlobalPrefix(`${APIPathEnum.API}/v${versions[0]}`); + app.setGlobalPrefix( + createAPIPathPrefix( + configService.get(EnvironmentVariableKeyEnum.AppVersion) + ) + ); app.useGlobalPipes(new ValidationPipe()); // for validating query params // setup open api diff --git a/src/modules/chains/e2e.test.ts b/src/modules/chains/e2e.test.ts index 96ab319..11eb4c9 100644 --- a/src/modules/chains/e2e.test.ts +++ b/src/modules/chains/e2e.test.ts @@ -8,19 +8,21 @@ import { chains } from '@app/configs'; import { APIPathEnum } from '@app/enums'; // utils +import createAPIPathPrefix from '@app/utils/createAPIPathPrefix'; import mapChainConfigToChainResponseBody from '@app/utils/mapChainConfigToChainResponseBody'; describe(`/${APIPathEnum.Chains}`, () => { + const path: string = `${createAPIPathPrefix(process.env.APP_VERSION)}/${APIPathEnum.Chains}`; let agent: Agent; beforeAll(async () => { - agent = request(`http://127.0.0.1:3000`); + agent = request(`http://127.0.0.1:${process.env.APP_PORT}`); }); describe(`GET /${APIPathEnum.Chains}`, () => { it('should return the chain configuration', async () => { const response: Response = await agent - .get(`/${APIPathEnum.Chains}`) + .get(`/${path}`) .expect(HttpStatus.OK); expect(response.body).toEqual( diff --git a/src/modules/fee-repository/dtos/FindByIntegratorAndPageOptionsDTO.ts b/src/modules/fee-repository/dtos/FindByIntegratorAndPageOptionsDTO.ts new file mode 100644 index 0000000..f559e6a --- /dev/null +++ b/src/modules/fee-repository/dtos/FindByIntegratorAndPageOptionsDTO.ts @@ -0,0 +1,17 @@ +interface IProps { + integrator: string; + limit?: number; + page?: number; +} + +export default class FindByIntegratorAndPageOptionsDTO { + public readonly integrator: string; + public readonly limit?: number; + public readonly page?: number; + + constructor({ integrator, limit, page }: IProps) { + this.integrator = integrator; + this.limit = limit; + this.page = page; + } +} diff --git a/src/modules/fee-repository/dtos/FindByPageResultDTO.ts b/src/modules/fee-repository/dtos/FindByIntegratorAndPageResultDTO.ts similarity index 88% rename from src/modules/fee-repository/dtos/FindByPageResultDTO.ts rename to src/modules/fee-repository/dtos/FindByIntegratorAndPageResultDTO.ts index 5d6b5dd..3c7b779 100644 --- a/src/modules/fee-repository/dtos/FindByPageResultDTO.ts +++ b/src/modules/fee-repository/dtos/FindByIntegratorAndPageResultDTO.ts @@ -8,7 +8,7 @@ interface IProps { total: number; } -export default class FindByPageResultDTO { +export default class FindByIntegratorAndPageResultDTO { public readonly data: IFeeDocument[]; public readonly limit: number; public readonly page: number; diff --git a/src/modules/fee-repository/dtos/FindByPageOptionsDTO.ts b/src/modules/fee-repository/dtos/FindByPageOptionsDTO.ts deleted file mode 100644 index 3acfde7..0000000 --- a/src/modules/fee-repository/dtos/FindByPageOptionsDTO.ts +++ /dev/null @@ -1,17 +0,0 @@ -interface IProps { - chainId: string; - limit?: number; - page?: number; -} - -export default class FindByPageOptionsDTO { - public readonly chainId: string; - public readonly limit?: number; - public readonly page?: number; - - constructor({ chainId, limit, page }: IProps) { - this.chainId = chainId; - this.limit = limit; - this.page = page; - } -} diff --git a/src/modules/fee-repository/dtos/index.ts b/src/modules/fee-repository/dtos/index.ts index 26b9e76..e1a98ba 100644 --- a/src/modules/fee-repository/dtos/index.ts +++ b/src/modules/fee-repository/dtos/index.ts @@ -1,3 +1,3 @@ export { default as CreateOptionsDTO } from './CreateOptionsDTO'; -export { default as FindByPageOptionsDTO } from './FindByPageOptionsDTO'; -export { default as FindByPageResultDTO } from './FindByPageResultDTO'; +export { default as FindByIntegratorAndPageOptionsDTO } from './FindByIntegratorAndPageOptionsDTO'; +export { default as FindByIntegratorAndPageResultDTO } from './FindByIntegratorAndPageResultDTO'; diff --git a/src/modules/fee-repository/service.ts b/src/modules/fee-repository/service.ts index 7296e5f..e8f05ad 100644 --- a/src/modules/fee-repository/service.ts +++ b/src/modules/fee-repository/service.ts @@ -7,8 +7,8 @@ import { FEE_PAGINATION_MAX_LIMIT } from '@app/constants'; // dtos import { CreateOptionsDTO, - FindByPageOptionsDTO, - FindByPageResultDTO, + FindByIntegratorAndPageOptionsDTO, + FindByIntegratorAndPageResultDTO, } from './dtos'; // enums @@ -37,16 +37,16 @@ export default class FeeRepositoryService { return await this.model.create(dto); } - public async findByPage({ - chainId, + public async findByIntegratorAndPage({ + integrator, limit = FEE_PAGINATION_MAX_LIMIT, page = 1, - }: FindByPageOptionsDTO): Promise { + }: FindByIntegratorAndPageOptionsDTO): Promise { const result: IFindByPageAggregateResult[] = await this.model.aggregate([ { $match: { - chainId, + integrator, }, }, { @@ -68,11 +68,11 @@ export default class FeeRepositoryService { }, ]); - return new FindByPageResultDTO({ + return new FindByIntegratorAndPageResultDTO({ data: result[0].data, limit, page, - total: result[0].metadata[0].total, + total: result[0].metadata[0]?.total || 0, }); } diff --git a/src/modules/fee-repository/types/IFindByPageAggregateResult.ts b/src/modules/fee-repository/types/IFindByPageAggregateResult.ts index 0c74aaa..a6c894a 100644 --- a/src/modules/fee-repository/types/IFindByPageAggregateResult.ts +++ b/src/modules/fee-repository/types/IFindByPageAggregateResult.ts @@ -3,7 +3,7 @@ import type { IFeeDocument } from '@app/types'; interface IFindByPageAggregateResult { data: IFeeDocument[]; - metadata: Record<'total', number>; + metadata: Record<'total', number>[]; } export default IFindByPageAggregateResult; diff --git a/src/modules/fees/controller.ts b/src/modules/fees/controller.ts index 4c64442..d0f4f90 100644 --- a/src/modules/fees/controller.ts +++ b/src/modules/fees/controller.ts @@ -1,79 +1,67 @@ -import { - Controller, - Get, - NotFoundException, - Param, - Query, - Req, -} from '@nestjs/common'; -import { ApiNotFoundResponse, ApiOkResponse } from '@nestjs/swagger'; +import { Controller, Get, Param, Query, Req } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { ApiOkResponse } from '@nestjs/swagger'; import type { Request } from 'express'; -// configs -import { chains } from '@app/configs'; - // constants import { FEE_PAGINATION_MAX_LIMIT } from '@app/constants'; // dtos -import { FindByPageResultDTO } from '@app/modules/fee-repository'; +import { FindByIntegratorAndPageResultDTO } from '@app/modules/fee-repository'; import { - GetByChainIdOptionsDTO, + GetByIntegratorOptionsDTO, GetFeesParamsDTO, GetFeesQueryDTO, GetFeesResponseBodyDTO, } from './dtos'; // enums -import { APIPathEnum } from '@app/enums'; +import { APIPathEnum, EnvironmentVariableKeyEnum } from '@app/enums'; -// services +// providers import Service from './service'; // types -import type { IChainConfig } from '@app/types'; +import type { IEnvironmentVariables } from '@app/types'; // utils -import createChainId from '@app/utils/createChainId'; +import createAPIPathPrefix from '@app/utils/createAPIPathPrefix'; @Controller(APIPathEnum.Fees) export default class FeesController { - constructor(private readonly service: Service) {} + constructor( + private readonly configService: ConfigService, + private readonly service: Service + ) {} - @Get(':chainId') + @Get(':integrator') @ApiOkResponse({ - description: 'Gets the fees collected for a given chain.', + description: 'Gets the fees collected for a given integrator.', type: GetFeesResponseBodyDTO, }) - @ApiNotFoundResponse({ - description: 'If the chain ID cannot be found.', - }) - public async getByChainId( - @Param() { chainId }: GetFeesParamsDTO, + public async getByIntegrator( + @Param() { integrator }: GetFeesParamsDTO, @Query() query: GetFeesQueryDTO, @Req() req: Request ): Promise { - const chainConfig: IChainConfig | null = - chains.find((value) => createChainId(value) === chainId) || null; - let result: FindByPageResultDTO; - - if (!chainConfig) { - throw new NotFoundException(`unknown chain "${chainId}"`); - } - - result = await this.service.getByChainId( - new GetByChainIdOptionsDTO({ - chainId, - limit: query.limit - ? parseInt(query.limit, 10) - : FEE_PAGINATION_MAX_LIMIT, - page: query.page ? parseInt(query.page, 10) : 1, - }) - ); + const result: FindByIntegratorAndPageResultDTO = + await this.service.getByIntegrator( + new GetByIntegratorOptionsDTO({ + integrator, + limit: query.limit + ? parseInt(query.limit, 10) + : FEE_PAGINATION_MAX_LIMIT, + page: query.page ? parseInt(query.page, 10) : 1, + }) + ); return new GetFeesResponseBodyDTO({ ...result, - nextPageURL: `${req.protocol}://${req.get('host')}/${APIPathEnum.Fees}/${chainId}?limit=${result.limit}&page=${result.page + 1}`, + // only show the next page url + nextPageURL: + result.total > 0 && result.page < Math.ceil(result.total / result.limit) + ? `${req.protocol}://${req.get('host')}/${createAPIPathPrefix(this.configService.get(EnvironmentVariableKeyEnum.AppVersion))}/${APIPathEnum.Fees}/${integrator}?limit=${result.limit}&page=${result.page + 1}` + : null, }); } } diff --git a/src/modules/fees/dtos/GetByChainIdOptionsDTO.ts b/src/modules/fees/dtos/GetByChainIdOptionsDTO.ts deleted file mode 100644 index ddc4139..0000000 --- a/src/modules/fees/dtos/GetByChainIdOptionsDTO.ts +++ /dev/null @@ -1,17 +0,0 @@ -interface IProps { - chainId: string; - limit: number; - page: number; -} - -export default class GetByChainIdOptionsDTO { - public readonly chainId: string; - public readonly limit: number; - public readonly page: number; - - constructor({ chainId, limit, page }: IProps) { - this.chainId = chainId; - this.limit = limit; - this.page = page; - } -} diff --git a/src/modules/fees/dtos/GetByIntegratorOptionsDTO.ts b/src/modules/fees/dtos/GetByIntegratorOptionsDTO.ts new file mode 100644 index 0000000..58be229 --- /dev/null +++ b/src/modules/fees/dtos/GetByIntegratorOptionsDTO.ts @@ -0,0 +1,17 @@ +interface IProps { + integrator: string; + limit: number; + page: number; +} + +export default class GetByIntegratorOptionsDTO { + public readonly integrator: string; + public readonly limit: number; + public readonly page: number; + + constructor({ integrator, limit, page }: IProps) { + this.integrator = integrator; + this.limit = limit; + this.page = page; + } +} diff --git a/src/modules/fees/dtos/GetFeesParamsDTO.ts b/src/modules/fees/dtos/GetFeesParamsDTO.ts index 3e1e7a8..6ce885b 100644 --- a/src/modules/fees/dtos/GetFeesParamsDTO.ts +++ b/src/modules/fees/dtos/GetFeesParamsDTO.ts @@ -3,11 +3,10 @@ import { IsNotEmpty } from 'class-validator'; export default class GetFeesParamsDTO { @ApiProperty({ - description: - 'The ID of the chain. This is the concatenation of the chain namespace and reference as specified in CAIP-2.', - example: 'eip155:137', + description: 'The partner that was used to collect fees.', + example: '0x34B7BEb5Bb4E6504dBa8843883796eF9CbDe0a38', required: true, }) @IsNotEmpty() - public readonly chainId: string; + public readonly integrator: string; } diff --git a/src/modules/fees/dtos/GetFeesResponseBodyDTO.ts b/src/modules/fees/dtos/GetFeesResponseBodyDTO.ts index 3d0f60b..6c70ba2 100644 --- a/src/modules/fees/dtos/GetFeesResponseBodyDTO.ts +++ b/src/modules/fees/dtos/GetFeesResponseBodyDTO.ts @@ -6,7 +6,7 @@ import type { IFeeDocument } from '@app/types'; interface IProps { data: IFeeDocument[]; limit: number; - nextPageURL: string; + nextPageURL: string | null; page: number; total: number; } @@ -23,7 +23,7 @@ export default class GetFeesResponseBodyDTO { @ApiProperty({ description: 'The URL to the next page.', }) - public readonly nextPageURL: string; + public readonly nextPageURL: string | null; @ApiProperty({ description: 'The current page.', }) diff --git a/src/modules/fees/dtos/index.ts b/src/modules/fees/dtos/index.ts index 4c12b7e..cafb9f0 100644 --- a/src/modules/fees/dtos/index.ts +++ b/src/modules/fees/dtos/index.ts @@ -1,4 +1,4 @@ -export { default as GetByChainIdOptionsDTO } from './GetByChainIdOptionsDTO'; +export { default as GetByIntegratorOptionsDTO } from './GetByIntegratorOptionsDTO'; export { default as GetFeesParamsDTO } from './GetFeesParamsDTO'; export { default as GetFeesQueryDTO } from './GetFeesQueryDTO'; export { default as GetFeesResponseBodyDTO } from './GetFeesResponseBodyDTO'; diff --git a/src/modules/fees/e2e.test.ts b/src/modules/fees/e2e.test.ts index 67b113e..42f32ed 100644 --- a/src/modules/fees/e2e.test.ts +++ b/src/modules/fees/e2e.test.ts @@ -4,17 +4,19 @@ import { agent as request, Response, Agent } from 'supertest'; // constants import { FEE_PAGINATION_MAX_LIMIT } from '@app/constants'; +// dtos +import { GetFeesResponseBodyDTO } from './dtos'; + // enums import { APIPathEnum } from '@app/enums'; // helpers +import createAPIPathPrefix from '@app/utils/createAPIPathPrefix'; import seedDatabase from '../../../test/helpers/seedDatabase'; -// types -import type { IGetFeesResponseBody } from './types'; - describe(`/${APIPathEnum.Fees}`, () => { - const chainId: string = 'eip155:137'; + const integrator: string = '0xBB59e1AD8607F2131A9cA41673150303a2641259'; + const path: string = `${createAPIPathPrefix(process.env.APP_VERSION)}/${APIPathEnum.Fees}`; let agent: Agent; beforeAll(async () => { @@ -26,35 +28,38 @@ describe(`/${APIPathEnum.Fees}`, () => { }); describe(`GET /${APIPathEnum.Fees}`, () => { - it('should return empty values for an unknown chain', async () => { - await agent - .get(`/${APIPathEnum.Fees}/unknown`) - .expect(HttpStatus.NOT_FOUND); + it('should return empty values for an unknown integrator', async () => { + const response: Response = await agent + .get(`/${path}/unknown`) + .expect(HttpStatus.OK); + + expect((response.body as GetFeesResponseBodyDTO).data).toHaveLength(0); + expect((response.body as GetFeesResponseBodyDTO).nextPageURL).toBeNull(); + expect((response.body as GetFeesResponseBodyDTO).total).toBe(0); }); it('should return the first 25 entries without any pagination', async () => { // arrange // act const response: Response = await agent - .get(`/${APIPathEnum.Fees}/${chainId}`) + .get(`/${path}/${integrator}`) .expect(HttpStatus.OK); // assert const nextPageURL: URL = new URL( - (response.body as IGetFeesResponseBody).nextPageURL + (response.body as GetFeesResponseBodyDTO).nextPageURL ); - expect(response.status).toBe(HttpStatus.OK); - expect((response.body as IGetFeesResponseBody).data).toHaveLength( + expect((response.body as GetFeesResponseBodyDTO).data).toHaveLength( FEE_PAGINATION_MAX_LIMIT ); - expect((response.body as IGetFeesResponseBody).limit).toBe( + expect((response.body as GetFeesResponseBodyDTO).limit).toBe( FEE_PAGINATION_MAX_LIMIT ); expect(nextPageURL.searchParams.get('limit')).toBe( FEE_PAGINATION_MAX_LIMIT.toString() ); expect(nextPageURL.searchParams.get('page')).toBe('2'); - expect((response.body as IGetFeesResponseBody).page).toBe(1); + expect((response.body as GetFeesResponseBodyDTO).page).toBe(1); }); it('should return the page entries and the max limit if the limit is out of bounds', async () => { @@ -62,25 +67,24 @@ describe(`/${APIPathEnum.Fees}`, () => { const page: number = 1; // act const response: Response = await agent - .get(`/${APIPathEnum.Fees}/${chainId}?page=${page}&limit=255`) + .get(`/${path}/${integrator}?page=${page}&limit=255`) .expect(HttpStatus.OK); // assert const nextPageURL: URL = new URL( - (response.body as IGetFeesResponseBody).nextPageURL + (response.body as GetFeesResponseBodyDTO).nextPageURL ); - expect(response.status).toBe(HttpStatus.OK); - expect((response.body as IGetFeesResponseBody).data).toHaveLength( + expect((response.body as GetFeesResponseBodyDTO).data).toHaveLength( FEE_PAGINATION_MAX_LIMIT ); - expect((response.body as IGetFeesResponseBody).limit).toBe( + expect((response.body as GetFeesResponseBodyDTO).limit).toBe( FEE_PAGINATION_MAX_LIMIT ); expect(nextPageURL.searchParams.get('limit')).toBe( FEE_PAGINATION_MAX_LIMIT.toString() ); expect(nextPageURL.searchParams.get('page')).toBe((page + 1).toString()); - expect((response.body as IGetFeesResponseBody).page).toBe(page); + expect((response.body as GetFeesResponseBodyDTO).page).toBe(page); }); it('should return the second page using pagination but without a limit', async () => { @@ -88,25 +92,24 @@ describe(`/${APIPathEnum.Fees}`, () => { const page: number = 2; // act const response: Response = await agent - .get(`/${APIPathEnum.Fees}/${chainId}?page=${page}`) + .get(`/${path}/${integrator}?page=${page}`) .expect(HttpStatus.OK); // assert const nextPageURL: URL = new URL( - (response.body as IGetFeesResponseBody).nextPageURL + (response.body as GetFeesResponseBodyDTO).nextPageURL ); - expect(response.status).toBe(HttpStatus.OK); - expect((response.body as IGetFeesResponseBody).data).toHaveLength( + expect((response.body as GetFeesResponseBodyDTO).data).toHaveLength( FEE_PAGINATION_MAX_LIMIT ); - expect((response.body as IGetFeesResponseBody).limit).toBe( + expect((response.body as GetFeesResponseBodyDTO).limit).toBe( FEE_PAGINATION_MAX_LIMIT ); expect(nextPageURL.searchParams.get('limit')).toBe( FEE_PAGINATION_MAX_LIMIT.toString() ); expect(nextPageURL.searchParams.get('page')).toBe((page + 1).toString()); - expect((response.body as IGetFeesResponseBody).page).toBe(page); + expect((response.body as GetFeesResponseBodyDTO).page).toBe(page); }); }); }); diff --git a/src/modules/fees/service.ts b/src/modules/fees/service.ts index ef2db12..89bdeec 100644 --- a/src/modules/fees/service.ts +++ b/src/modules/fees/service.ts @@ -4,8 +4,8 @@ import { Injectable } from '@nestjs/common'; import { FEE_PAGINATION_MAX_LIMIT } from '@app/constants'; // dtos -import { FindByPageResultDTO } from '@app/modules/fee-repository'; -import { GetByChainIdOptionsDTO } from './dtos'; +import { FindByIntegratorAndPageResultDTO } from '@app/modules/fee-repository'; +import { GetByIntegratorOptionsDTO } from './dtos'; // providers import { FeeRepositoryService } from '@app/modules/fee-repository'; @@ -14,13 +14,13 @@ import { FeeRepositoryService } from '@app/modules/fee-repository'; export default class FeesService { constructor(private readonly feeRepositoryService: FeeRepositoryService) {} - public async getByChainId({ - chainId, + public async getByIntegrator({ + integrator, limit, page, - }: GetByChainIdOptionsDTO): Promise { - return await this.feeRepositoryService.findByPage({ - chainId, + }: GetByIntegratorOptionsDTO): Promise { + return await this.feeRepositoryService.findByIntegratorAndPage({ + integrator, limit: limit >= 0 && limit <= FEE_PAGINATION_MAX_LIMIT ? limit diff --git a/src/modules/versions/__snapshots__/e2e.test.ts.snap b/src/modules/versions/__snapshots__/e2e.test.ts.snap deleted file mode 100644 index 593ea30..0000000 --- a/src/modules/versions/__snapshots__/e2e.test.ts.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`/versions GET /versions should get the api info 1`] = ` -{ - "databases": [ - { - "status": "connected", - "type": "mongodb", - "version": "8.2.1", - }, - ], - "environment": "test", - "name": "plutus-test", - "version": "1.0.0", -} -`; diff --git a/src/modules/versions/e2e.test.ts b/src/modules/versions/e2e.test.ts index e773240..e834c12 100644 --- a/src/modules/versions/e2e.test.ts +++ b/src/modules/versions/e2e.test.ts @@ -1,10 +1,17 @@ import { HttpStatus } from '@nestjs/common'; import { agent as request, Response, Agent } from 'supertest'; +// dtos +import { VersionResponseBodyDTO } from './dtos'; + // enums import { APIPathEnum } from '@app/enums'; +// utils +import createAPIPathPrefix from '@app/utils/createAPIPathPrefix'; + describe(`/${APIPathEnum.Versions}`, () => { + const path: string = `${createAPIPathPrefix(process.env.APP_VERSION)}/${APIPathEnum.Versions}`; let agent: Agent; beforeAll(async () => { @@ -14,10 +21,15 @@ describe(`/${APIPathEnum.Versions}`, () => { describe(`GET /${APIPathEnum.Versions}`, () => { it('should get the api info', async () => { const response: Response = await agent - .get(`/${APIPathEnum.Versions}`) + .get(`/${path}`) .expect(HttpStatus.OK); - expect(response.body).toMatchSnapshot(); + expect((response.body as VersionResponseBodyDTO).name).toBe( + process.env.APP_NAME + ); + expect((response.body as VersionResponseBodyDTO).version).toBe( + process.env.APP_VERSION + ); }); }); }); diff --git a/src/utils/createAPIPathPrefix/createAPIPathPrefix.ts b/src/utils/createAPIPathPrefix/createAPIPathPrefix.ts new file mode 100644 index 0000000..002b8a7 --- /dev/null +++ b/src/utils/createAPIPathPrefix/createAPIPathPrefix.ts @@ -0,0 +1,13 @@ +// enums +import { APIPathEnum } from '@app/enums'; + +/** + * Convenience function to get the API prefix with the major version included. + * @param {string} version - the semantic version of the application. + * @returns {string} the API path prefix, i.e. `/api/v1`. + */ +export default function createAPIPathPrefix(version: string): string { + const [majorVersion] = version.replace(/[^\d.]/g, '').split('.'); + + return `${APIPathEnum.API}/v${majorVersion}`; +} diff --git a/src/utils/createAPIPathPrefix/index.ts b/src/utils/createAPIPathPrefix/index.ts new file mode 100644 index 0000000..f25104b --- /dev/null +++ b/src/utils/createAPIPathPrefix/index.ts @@ -0,0 +1 @@ +export { default } from './createAPIPathPrefix'; diff --git a/src/utils/parseVersion/index.ts b/src/utils/parseVersion/index.ts deleted file mode 100644 index 9d0cc99..0000000 --- a/src/utils/parseVersion/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './parseVersion'; diff --git a/src/utils/parseVersion/parseVersion.ts b/src/utils/parseVersion/parseVersion.ts deleted file mode 100644 index 49797ae..0000000 --- a/src/utils/parseVersion/parseVersion.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default function parseVersion(version: string): string[] { - const sanitizedVersion: string = version.replace(/[^\d.]/g, ''); - - return sanitizedVersion.split('.'); -}