feat: integrate invitation access, world NPCs, and deployment

This commit is contained in:
2026-09-08 22:22:38 +08:00
parent 2a3125075f
commit 513a3eba31
75 changed files with 9566 additions and 1070 deletions

View File

@@ -71,6 +71,12 @@ export class LoginDto {
* 注册请求DTO
*/
export class RegisterDto {
@ApiProperty({ description: '邀请码', example: 'WT-ABCD-EFGH-IJKL' })
@IsString({ message: '邀请码必须是字符串' })
@IsNotEmpty({ message: '邀请码不能为空' })
@Length(8, 30, { message: '邀请码格式不正确' })
invitation_code: string;
/**
* 用户名
*/
@@ -383,12 +389,9 @@ export class EmailVerificationDto {
}
/**
* 发送邮箱验证码请求DTO
* 邮箱地址请求DTO
*/
export class SendEmailVerificationDto {
/**
* 邮箱地址
*/
export class EmailAddressDto {
@ApiProperty({
description: '邮箱地址',
example: 'test@example.com'
@@ -398,6 +401,17 @@ export class SendEmailVerificationDto {
email: string;
}
/**
* 发送邮箱验证码请求DTO
*/
export class SendEmailVerificationDto extends EmailAddressDto {
@ApiProperty({ description: '邀请码', example: 'WT-ABCD-EFGH-IJKL' })
@IsString({ message: '邀请码必须是字符串' })
@IsNotEmpty({ message: '邀请码不能为空' })
@Length(8, 30, { message: '邀请码格式不正确' })
invitation_code: string;
}
/**
* 验证码登录请求DTO
*/

View File

@@ -65,7 +65,7 @@ import {
VerificationCodeLoginDto,
SendLoginVerificationCodeDto,
RefreshTokenDto,
SendEmailVerificationDto
EmailAddressDto,
} from './dto/login.dto';
import {
LoginResponseDto,
@@ -490,11 +490,11 @@ export class LoginController {
summary: '调试验证码信息',
description: '获取验证码的详细调试信息(仅开发环境)'
})
@ApiBody({ type: SendEmailVerificationDto })
@ApiBody({ type: EmailAddressDto })
@Post('debug-verification-code')
@UsePipes(new ValidationPipe({ transform: true }))
async debugVerificationCode(
@Body() sendEmailVerificationDto: SendEmailVerificationDto,
@Body() sendEmailVerificationDto: EmailAddressDto,
@Res() res: Response
): Promise<void> {
const result = await this.loginService.debugVerificationCode(sendEmailVerificationDto.email);

View File

@@ -0,0 +1,240 @@
/**
* RegisterController 单元测试
*
* 功能描述:
* - 测试注册控制器的HTTP请求处理
* - 验证API响应格式和状态码
* - 测试邮箱验证流程
*
* 最近修改:
* - 2026-01-14: 架构重构 - 从business层移动到gateway层 (修改者: moyin)
* - 2026-01-12: 代码规范优化 - 创建缺失的控制器测试文件 (修改者: moyin)
*
* @author moyin
* @version 1.1.0
* @since 2026-01-12
* @lastModified 2026-01-14
*/
import { Test, TestingModule } from '@nestjs/testing';
import { Response } from 'express';
import { HttpStatus } from '@nestjs/common';
import { RegisterController } from './register.controller';
import { RegisterService } from '../../business/auth/register.service';
describe('RegisterController', () => {
let controller: RegisterController;
let registerService: jest.Mocked<RegisterService>;
let mockResponse: jest.Mocked<Response>;
beforeEach(async () => {
const mockRegisterService = {
register: jest.fn(),
sendEmailVerification: jest.fn(),
verifyEmailCode: jest.fn(),
resendEmailVerification: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [RegisterController],
providers: [
{
provide: RegisterService,
useValue: mockRegisterService,
},
],
}).compile();
controller = module.get<RegisterController>(RegisterController);
registerService = module.get(RegisterService);
// Mock Response object
mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
} as any;
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('register', () => {
it('should handle successful registration', async () => {
const registerDto = {
invitation_code: 'WT-TEST-CODE-0001',
username: 'newuser',
password: 'password123',
nickname: '新用户',
email: 'newuser@example.com',
email_verification_code: '123456',
};
const mockResult = {
success: true,
data: {
user: {
id: '1',
username: 'newuser',
nickname: '新用户',
role: 1,
created_at: new Date()
},
access_token: 'token',
refresh_token: 'refresh_token',
expires_in: 3600,
token_type: 'Bearer',
is_new_user: true,
message: '注册成功'
},
message: '注册成功'
};
registerService.register.mockResolvedValue(mockResult);
await controller.register(registerDto, mockResponse);
expect(registerService.register).toHaveBeenCalledWith(registerDto);
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.CREATED);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
it('should handle registration failure', async () => {
const registerDto = {
invitation_code: 'WT-TEST-CODE-0001',
username: 'existinguser',
password: 'password123',
nickname: '用户',
email: 'existing@example.com',
email_verification_code: '123456',
};
const mockResult = {
success: false,
message: '用户名已存在',
error_code: 'REGISTER_FAILED'
};
registerService.register.mockResolvedValue(mockResult);
await controller.register(registerDto, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
});
describe('sendEmailVerification', () => {
it('should handle email verification in production mode', async () => {
const sendEmailDto = {
email: 'test@example.com',
invitation_code: 'WT-TEST-CODE-0001',
};
const mockResult = {
success: true,
data: { is_test_mode: false },
message: '验证码已发送,请查收邮件'
};
registerService.sendEmailVerification.mockResolvedValue(mockResult);
await controller.sendEmailVerification(sendEmailDto, mockResponse);
expect(registerService.sendEmailVerification).toHaveBeenCalledWith(
'test@example.com',
'WT-TEST-CODE-0001',
);
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
it('should handle email verification in test mode', async () => {
const sendEmailDto = {
email: 'test@example.com',
invitation_code: 'WT-TEST-CODE-0001',
};
const mockResult = {
success: false,
data: {
verification_code: '123456',
is_test_mode: true
},
message: '⚠️ 测试模式:验证码已生成但未真实发送。请在控制台查看验证码,或配置邮件服务以启用真实发送。',
error_code: 'TEST_MODE_ONLY'
};
registerService.sendEmailVerification.mockResolvedValue(mockResult);
await controller.sendEmailVerification(sendEmailDto, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.PARTIAL_CONTENT);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
});
describe('verifyEmail', () => {
it('should handle email verification successfully', async () => {
const verifyEmailDto = {
email: 'test@example.com',
verification_code: '123456'
};
const mockResult = {
success: true,
message: '邮箱验证成功'
};
registerService.verifyEmailCode.mockResolvedValue(mockResult);
await controller.verifyEmail(verifyEmailDto, mockResponse);
expect(registerService.verifyEmailCode).toHaveBeenCalledWith('test@example.com', '123456');
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
it('should handle invalid verification code', async () => {
const verifyEmailDto = {
email: 'test@example.com',
verification_code: '000000'
};
const mockResult = {
success: false,
message: '验证码错误',
error_code: 'INVALID_VERIFICATION_CODE'
};
registerService.verifyEmailCode.mockResolvedValue(mockResult);
await controller.verifyEmail(verifyEmailDto, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
});
describe('resendEmailVerification', () => {
it('should handle resend email verification successfully', async () => {
const sendEmailDto = {
email: 'test@example.com'
};
const mockResult = {
success: true,
data: { is_test_mode: false },
message: '验证码已重新发送,请查收邮件'
};
registerService.resendEmailVerification.mockResolvedValue(mockResult);
await controller.resendEmailVerification(sendEmailDto, mockResponse);
expect(registerService.resendEmailVerification).toHaveBeenCalledWith('test@example.com');
expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.OK);
expect(mockResponse.json).toHaveBeenCalledWith(mockResult);
});
});
});

View File

@@ -52,7 +52,8 @@ import { RegisterService } from '../../business/auth/register.service';
import {
RegisterDto,
EmailVerificationDto,
SendEmailVerificationDto
SendEmailVerificationDto,
EmailAddressDto,
} from './dto/login.dto';
import {
RegisterResponseDto,
@@ -166,7 +167,8 @@ export class RegisterController {
email: registerDto.email,
phone: registerDto.phone,
skin_id: registerDto.skin_id,
email_verification_code: registerDto.email_verification_code
email_verification_code: registerDto.email_verification_code,
invitation_code: registerDto.invitation_code,
});
this.handleResponse(result, res, HttpStatus.CREATED);
@@ -209,7 +211,7 @@ export class RegisterController {
@Body() sendEmailVerificationDto: SendEmailVerificationDto,
@Res() res: Response
): Promise<void> {
const result = await this.registerService.sendEmailVerification(sendEmailVerificationDto.email);
const result = await this.registerService.sendEmailVerification(sendEmailVerificationDto.email, sendEmailVerificationDto.invitation_code);
this.handleResponse(result, res);
}
@@ -254,7 +256,7 @@ export class RegisterController {
summary: '重新发送邮箱验证码',
description: '重新向指定邮箱发送验证码'
})
@ApiBody({ type: SendEmailVerificationDto })
@ApiBody({ type: EmailAddressDto })
@SwaggerApiResponse({
status: 200,
description: '验证码重新发送成功',
@@ -277,7 +279,7 @@ export class RegisterController {
@Post('resend-email-verification')
@UsePipes(new ValidationPipe({ transform: true }))
async resendEmailVerification(
@Body() sendEmailVerificationDto: SendEmailVerificationDto,
@Body() sendEmailVerificationDto: EmailAddressDto,
@Res() res: Response
): Promise<void> {
const result = await this.registerService.resendEmailVerification(sendEmailVerificationDto.email);