Files
trackgo-be/src/common/http/response/transform.interceptor.spec.ts
T
shancheas 0550cbe764 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.
2026-08-21 15:56:54 +07:00

117 lines
3.1 KiB
TypeScript

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);
});
});