Implement pagination response handling and related enhancements
- Introduced `@Pagination()` decorator to mark list endpoints for pagination.
- Added `TransformInterceptor` to wrap responses in a standardized format `{ data, meta }`.
- Created pagination-related utility functions and constants for managing pagination logic.
- Defined `PaginationQueryDto` for handling pagination query parameters.
- Established `PaginationMetaDto` for OpenAPI documentation of pagination metadata.
- Updated existing controller and service structures to support pagination in responses.
- Added unit tests for pagination utilities and interceptor to ensure correct functionality.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import { CallHandler, ExecutionContext, StreamableFile } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { of, firstValueFrom } from 'rxjs';
|
||||
import { PAGINATION_RESPONSE, RAW_RESPONSE } from './constants';
|
||||
import { TransformInterceptor } from './transform.interceptor';
|
||||
|
||||
describe('TransformInterceptor', () => {
|
||||
const reflector = {
|
||||
getAllAndOverride: jest.fn(),
|
||||
};
|
||||
const interceptor = new TransformInterceptor(
|
||||
reflector as unknown as Reflector,
|
||||
);
|
||||
|
||||
function createContext(
|
||||
query: Record<string, unknown> = {},
|
||||
): ExecutionContext {
|
||||
return {
|
||||
getHandler: () => jest.fn(),
|
||||
getClass: () => class TestController {},
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => ({ query }),
|
||||
}),
|
||||
} as unknown as ExecutionContext;
|
||||
}
|
||||
|
||||
function createHandler(payload: unknown): CallHandler {
|
||||
return { handle: () => of(payload) };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
reflector.getAllAndOverride.mockReset();
|
||||
});
|
||||
|
||||
it('passes through when not marked as pagination', async () => {
|
||||
reflector.getAllAndOverride.mockReturnValue(false);
|
||||
const payload = { id: '1' };
|
||||
|
||||
const result = await firstValueFrom(
|
||||
interceptor.intercept(createContext(), createHandler(payload)),
|
||||
);
|
||||
|
||||
expect(result).toEqual(payload);
|
||||
});
|
||||
|
||||
it('passes through when marked @RawResponse()', async () => {
|
||||
reflector.getAllAndOverride.mockImplementation((key: string) => {
|
||||
if (key === RAW_RESPONSE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const payload = { data: [], total: 0 };
|
||||
|
||||
const result = await firstValueFrom(
|
||||
interceptor.intercept(createContext(), createHandler(payload)),
|
||||
);
|
||||
|
||||
expect(result).toEqual(payload);
|
||||
});
|
||||
|
||||
it('wraps @Pagination() handler return as { data, meta }', async () => {
|
||||
reflector.getAllAndOverride.mockImplementation((key: string) => {
|
||||
if (key === RAW_RESPONSE) {
|
||||
return false;
|
||||
}
|
||||
if (key === PAGINATION_RESPONSE) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(
|
||||
interceptor.intercept(
|
||||
createContext({ page: '2', limit: '5' }),
|
||||
createHandler({ data: [{ id: 'a' }], total: 11 }),
|
||||
),
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
data: [{ id: 'a' }],
|
||||
meta: {
|
||||
currentPage: 2,
|
||||
itemCount: 1,
|
||||
itemsPerPage: 5,
|
||||
totalItems: 11,
|
||||
totalPages: 3,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not wrap null payloads', async () => {
|
||||
reflector.getAllAndOverride.mockImplementation((key: string) => {
|
||||
return key === PAGINATION_RESPONSE;
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(
|
||||
interceptor.intercept(createContext(), createHandler(null)),
|
||||
);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('does not wrap StreamableFile payloads', async () => {
|
||||
reflector.getAllAndOverride.mockImplementation((key: string) => {
|
||||
return key === PAGINATION_RESPONSE;
|
||||
});
|
||||
const file = new StreamableFile(Buffer.from('x'));
|
||||
|
||||
const result = await firstValueFrom(
|
||||
interceptor.intercept(createContext(), createHandler(file)),
|
||||
);
|
||||
|
||||
expect(result).toBe(file);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user