feat: 支持单元测试
This commit is contained in:
65
src/common/dto/api-response.dto.spec.ts
Normal file
65
src/common/dto/api-response.dto.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { ApiResponseDto } from './api-response.dto';
|
||||
|
||||
describe('ApiResponseDto', () => {
|
||||
describe('success', () => {
|
||||
it('should create a success response with data', () => {
|
||||
const data = { id: '1', name: 'test' };
|
||||
const response = ApiResponseDto.success(data);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(response.data).toEqual(data);
|
||||
expect(response.message).toBeNull();
|
||||
expect(response.timestamp).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it('should create a success response with null data', () => {
|
||||
const response = ApiResponseDto.success(null);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(response.data).toBeNull();
|
||||
expect(response.message).toBeNull();
|
||||
});
|
||||
|
||||
it('should create a success response with array data', () => {
|
||||
const data = [1, 2, 3];
|
||||
const response = ApiResponseDto.success(data);
|
||||
|
||||
expect(response.success).toBe(true);
|
||||
expect(response.data).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error', () => {
|
||||
it('should create an error response with message', () => {
|
||||
const response = ApiResponseDto.error('Something went wrong');
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.data).toBeNull();
|
||||
expect(response.message).toBe('Something went wrong');
|
||||
expect(response.timestamp).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it('should create an error response with Chinese message', () => {
|
||||
const response = ApiResponseDto.error('积分不足');
|
||||
|
||||
expect(response.success).toBe(false);
|
||||
expect(response.message).toBe('积分不足');
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should set default message to null', () => {
|
||||
const dto = new ApiResponseDto(true, 'data');
|
||||
|
||||
expect(dto.message).toBeNull();
|
||||
});
|
||||
|
||||
it('should set all properties correctly', () => {
|
||||
const dto = new ApiResponseDto(false, null, 'error message');
|
||||
|
||||
expect(dto.success).toBe(false);
|
||||
expect(dto.data).toBeNull();
|
||||
expect(dto.message).toBe('error message');
|
||||
});
|
||||
});
|
||||
});
|
||||
97
src/common/filters/http-exception.filter.spec.ts
Normal file
97
src/common/filters/http-exception.filter.spec.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { HttpExceptionFilter } from './http-exception.filter';
|
||||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
ArgumentsHost,
|
||||
BadRequestException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
describe('HttpExceptionFilter', () => {
|
||||
let filter: HttpExceptionFilter;
|
||||
|
||||
const mockJson = jest.fn();
|
||||
const mockStatus = jest.fn().mockReturnValue({ json: mockJson });
|
||||
const mockGetResponse = jest.fn().mockReturnValue({ status: mockStatus });
|
||||
const mockGetRequest = jest
|
||||
.fn()
|
||||
.mockReturnValue({ url: '/api/v1/test' });
|
||||
|
||||
const mockHost: ArgumentsHost = {
|
||||
switchToHttp: () => ({
|
||||
getResponse: mockGetResponse,
|
||||
getRequest: mockGetRequest,
|
||||
}),
|
||||
} as unknown as ArgumentsHost;
|
||||
|
||||
beforeEach(() => {
|
||||
filter = new HttpExceptionFilter();
|
||||
jest.clearAllMocks();
|
||||
mockStatus.mockReturnValue({ json: mockJson });
|
||||
});
|
||||
|
||||
it('should handle HttpException with string response', () => {
|
||||
const exception = new HttpException('Not Found', HttpStatus.NOT_FOUND);
|
||||
|
||||
filter.catch(exception, mockHost);
|
||||
|
||||
expect(mockStatus).toHaveBeenCalledWith(HttpStatus.NOT_FOUND);
|
||||
expect(mockJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
success: false,
|
||||
data: null,
|
||||
path: '/api/v1/test',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle BadRequestException with object response', () => {
|
||||
const exception = new BadRequestException('参数错误');
|
||||
|
||||
filter.catch(exception, mockHost);
|
||||
|
||||
expect(mockStatus).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
|
||||
expect(mockJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
success: false,
|
||||
path: '/api/v1/test',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle NotFoundException', () => {
|
||||
const exception = new NotFoundException('资源不存在');
|
||||
|
||||
filter.catch(exception, mockHost);
|
||||
|
||||
expect(mockStatus).toHaveBeenCalledWith(HttpStatus.NOT_FOUND);
|
||||
});
|
||||
|
||||
it('should handle generic Error with 500 status', () => {
|
||||
const exception = new Error('Unexpected error');
|
||||
|
||||
filter.catch(exception, mockHost);
|
||||
|
||||
expect(mockStatus).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(mockJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
success: false,
|
||||
message: 'Unexpected error',
|
||||
path: '/api/v1/test',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unknown exception with 500 status', () => {
|
||||
filter.catch('unexpected string error', mockHost);
|
||||
|
||||
expect(mockStatus).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(mockJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
success: false,
|
||||
message: 'Internal server error',
|
||||
path: '/api/v1/test',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
79
src/common/guards/jwt-auth.guard.spec.ts
Normal file
79
src/common/guards/jwt-auth.guard.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { JwtAuthGuard, JwtPayload } from './jwt-auth.guard';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
|
||||
describe('JwtAuthGuard', () => {
|
||||
let guard: JwtAuthGuard;
|
||||
|
||||
const mockJwtService = {
|
||||
verifyAsync: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
guard = new JwtAuthGuard(mockJwtService as unknown as JwtService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function createMockContext(authHeader?: string): ExecutionContext {
|
||||
const mockRequest: Record<string, unknown> = {
|
||||
headers: authHeader ? { authorization: authHeader } : {},
|
||||
};
|
||||
|
||||
return {
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as unknown as ExecutionContext;
|
||||
}
|
||||
|
||||
it('should allow access with valid Bearer token', async () => {
|
||||
const payload: JwtPayload = { sub: 'user-1', openid: 'wx-openid' };
|
||||
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
||||
|
||||
const context = createMockContext('Bearer valid-token');
|
||||
const result = await guard.canActivate(context);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockJwtService.verifyAsync).toHaveBeenCalledWith('valid-token');
|
||||
});
|
||||
|
||||
it('should attach user payload to request', async () => {
|
||||
const payload: JwtPayload = { sub: 'user-1', openid: 'wx-openid' };
|
||||
mockJwtService.verifyAsync.mockResolvedValue(payload);
|
||||
|
||||
const context = createMockContext('Bearer valid-token');
|
||||
const request = context.switchToHttp().getRequest() as Record<string, unknown>;
|
||||
await guard.canActivate(context);
|
||||
|
||||
expect(request.user).toEqual(payload);
|
||||
});
|
||||
|
||||
it('should throw UnauthorizedException when no Authorization header', async () => {
|
||||
const context = createMockContext();
|
||||
|
||||
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw UnauthorizedException when token type is not Bearer', async () => {
|
||||
const context = createMockContext('Basic some-token');
|
||||
|
||||
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw UnauthorizedException when token is invalid', async () => {
|
||||
mockJwtService.verifyAsync.mockRejectedValue(new Error('invalid token'));
|
||||
|
||||
const context = createMockContext('Bearer invalid-token');
|
||||
|
||||
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user