322 lines
12 KiB
TypeScript
322 lines
12 KiB
TypeScript
import { Test, TestingModule } from '@nestjs/testing';
|
||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||
import { Repository, Like } from 'typeorm';
|
||
import { UserService } from '../../../src/modules/user/user.service';
|
||
import { User } from '../../../src/modules/user/user.entity';
|
||
import { UserModule } from '../../../src/modules/user/user.module';
|
||
import { CreateUserDto } from '../../../src/modules/user/dto/create-user.dto';
|
||
import { getDatabaseConfig } from '../../../src/database/database.config';
|
||
import { ConflictException } from '@nestjs/common';
|
||
|
||
describe('UserService (集成测试)', () => {
|
||
let service: UserService;
|
||
let repository: Repository<User>;
|
||
let module: TestingModule;
|
||
|
||
beforeAll(async () => {
|
||
jest.setTimeout(30000); // 设置超时为 30 秒
|
||
// 创建测试模块,使用真实数据库连接
|
||
module = await Test.createTestingModule({
|
||
imports: [
|
||
ConfigModule.forRoot({
|
||
isGlobal: true,
|
||
envFilePath: ['.env.development', '.env'],
|
||
}),
|
||
TypeOrmModule.forRootAsync({
|
||
imports: [ConfigModule],
|
||
useFactory: (configService: ConfigService) => {
|
||
const config = getDatabaseConfig(configService);
|
||
// 使用测试数据库(如果配置了),否则使用开发数据库
|
||
const testDatabase =
|
||
configService.get<string>('DB_DATABASE') ||
|
||
(config.database as string);
|
||
return {
|
||
...config,
|
||
database: testDatabase,
|
||
synchronize: true, // 测试环境允许同步
|
||
dropSchema: false, // 不删除现有数据
|
||
} as typeof config;
|
||
},
|
||
inject: [ConfigService],
|
||
}),
|
||
UserModule,
|
||
],
|
||
}).compile();
|
||
|
||
service = module.get<UserService>(UserService);
|
||
repository = module.get<Repository<User>>(getRepositoryToken(User));
|
||
|
||
// 不清理原有数据,只清理可能存在的测试数据
|
||
// 使用测试数据前缀来识别和清理测试数据
|
||
await repository.delete({
|
||
username: Like('test_%'),
|
||
});
|
||
await repository.delete({
|
||
username: Like('findall_%'),
|
||
});
|
||
await repository.delete({
|
||
username: Like('duplicate_%'),
|
||
});
|
||
await repository.delete({
|
||
username: Like('unique_%'),
|
||
});
|
||
await repository.delete({
|
||
username: Like('full_%'),
|
||
});
|
||
});
|
||
|
||
afterAll(async () => {
|
||
// 测试结束后清理
|
||
// await repository.clear();
|
||
await module.close();
|
||
});
|
||
|
||
describe('create - 集成测试', () => {
|
||
it('应该成功在数据库中创建用户', async () => {
|
||
const createUserDto: CreateUserDto = {
|
||
username: 'test_user_1',
|
||
password: 'password123',
|
||
email: 'test1@example.com',
|
||
nickname: '测试用户1',
|
||
};
|
||
|
||
const result = await service.create(createUserDto);
|
||
console.log('regitser result', result);
|
||
expect(result).toBeDefined();
|
||
expect(result.userId).toBeDefined();
|
||
expect(result.username).toBe(createUserDto.username);
|
||
expect(result.email).toBe(createUserDto.email);
|
||
expect(result.nickname).toBe(createUserDto.nickname);
|
||
expect(result.status).toBe('active');
|
||
expect(result.role).toBe('user'); // 默认角色
|
||
expect(result.passwordHash).toBeDefined();
|
||
expect(result.passwordHash).not.toBe(createUserDto.password); // 密码应该被加密
|
||
|
||
// 验证数据确实保存到数据库
|
||
const savedUser = await repository.findOne({
|
||
where: { userId: result.userId },
|
||
});
|
||
expect(savedUser).toBeDefined();
|
||
expect(savedUser?.username).toBe(createUserDto.username);
|
||
expect(savedUser?.email).toBe(createUserDto.email);
|
||
|
||
// 清理
|
||
await repository.remove(result);
|
||
});
|
||
|
||
it('应该使用默认角色和状态', async () => {
|
||
const createUserDto: CreateUserDto = {
|
||
username: 'test_user_2',
|
||
password: 'password123',
|
||
email: 'test2@example.com',
|
||
// 不提供 role
|
||
};
|
||
|
||
const result = await service.create(createUserDto);
|
||
|
||
expect(result.role).toBe('user'); // 默认角色
|
||
expect(result.status).toBe('active'); // 默认状态
|
||
|
||
// 清理测试创建的用户
|
||
await repository.remove(result);
|
||
});
|
||
|
||
it('应该支持可选字段', async () => {
|
||
const createUserDto: CreateUserDto = {
|
||
username: 'test_user_3',
|
||
password: 'password123',
|
||
email: 'test3@example.com',
|
||
nickname: '测试用户3',
|
||
avatarUrl: 'https://example.com/avatar.jpg',
|
||
phone: '13800138000',
|
||
};
|
||
|
||
const result = await service.create(createUserDto);
|
||
|
||
expect(result.nickname).toBe(createUserDto.nickname);
|
||
expect(result.avatarUrl).toBe(createUserDto.avatarUrl);
|
||
expect(result.phone).toBe(createUserDto.phone);
|
||
|
||
// 清理测试创建的用户
|
||
await repository.remove(result);
|
||
});
|
||
|
||
it('应该抛出 ConflictException 当用户名已存在时', async () => {
|
||
const createUserDto: CreateUserDto = {
|
||
username: 'duplicate_username',
|
||
password: 'password123',
|
||
email: 'unique@example.com',
|
||
};
|
||
|
||
// 先创建一个用户
|
||
await service.create(createUserDto);
|
||
|
||
// 尝试创建相同用户名的用户
|
||
const duplicateDto: CreateUserDto = {
|
||
username: 'duplicate_username',
|
||
password: 'password456',
|
||
email: 'another@example.com',
|
||
};
|
||
|
||
await expect(service.create(duplicateDto)).rejects.toThrow(
|
||
ConflictException,
|
||
);
|
||
await expect(service.create(duplicateDto)).rejects.toThrow(
|
||
'用户名',
|
||
);
|
||
|
||
// 清理测试创建的用户
|
||
const user = await repository.findOne({
|
||
where: { username: 'duplicate_username' },
|
||
});
|
||
if (user) {
|
||
await repository.remove(user);
|
||
}
|
||
});
|
||
|
||
it('应该抛出 ConflictException 当邮箱已存在时', async () => {
|
||
const createUserDto: CreateUserDto = {
|
||
username: 'unique_username',
|
||
password: 'password123',
|
||
email: 'duplicate@example.com',
|
||
};
|
||
|
||
// 先创建一个用户
|
||
await service.create(createUserDto);
|
||
|
||
// 尝试创建相同邮箱的用户
|
||
const duplicateDto: CreateUserDto = {
|
||
username: 'another_username',
|
||
password: 'password456',
|
||
email: 'duplicate@example.com',
|
||
};
|
||
|
||
await expect(service.create(duplicateDto)).rejects.toThrow(
|
||
ConflictException,
|
||
);
|
||
await expect(service.create(duplicateDto)).rejects.toThrow('邮箱');
|
||
|
||
// 清理测试创建的用户
|
||
const user = await repository.findOne({
|
||
where: { email: 'duplicate@example.com' },
|
||
});
|
||
if (user) {
|
||
await repository.remove(user);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('findAll - 集成测试', () => {
|
||
it('应该返回所有用户列表', async () => {
|
||
// 先创建几个测试用户
|
||
const users: CreateUserDto[] = [
|
||
{
|
||
username: 'findall_user_1',
|
||
password: 'password123',
|
||
email: 'findall1@example.com',
|
||
nickname: '查询测试用户1',
|
||
},
|
||
{
|
||
username: 'findall_user_2',
|
||
password: 'password123',
|
||
email: 'findall2@example.com',
|
||
nickname: '查询测试用户2',
|
||
},
|
||
{
|
||
username: 'findall_user_3',
|
||
password: 'password123',
|
||
email: 'findall3@example.com',
|
||
nickname: '查询测试用户3',
|
||
// 注意:注册时不允许传入 role,所有注册用户的 role 固定为 'user'
|
||
},
|
||
];
|
||
|
||
const createdUsers: User[] = [];
|
||
for (const userDto of users) {
|
||
const user = await service.create(userDto);
|
||
createdUsers.push(user);
|
||
}
|
||
|
||
// 查询所有用户
|
||
const result = await service.findAll();
|
||
|
||
expect(result).toBeDefined();
|
||
expect(Array.isArray(result)).toBe(true);
|
||
expect(result.length).toBeGreaterThanOrEqual(users.length);
|
||
|
||
// 验证创建的用户都在列表中
|
||
const usernames = result.map((u) => u.username);
|
||
users.forEach((userDto) => {
|
||
expect(usernames).toContain(userDto.username);
|
||
});
|
||
|
||
// 验证排序(按创建时间倒序)
|
||
if (result.length > 1) {
|
||
for (let i = 0; i < result.length - 1; i++) {
|
||
expect(
|
||
result[i].createdAt.getTime(),
|
||
).toBeGreaterThanOrEqual(result[i + 1].createdAt.getTime());
|
||
}
|
||
}
|
||
|
||
// 清理测试创建的用户
|
||
for (const user of createdUsers) {
|
||
await repository.remove(user);
|
||
}
|
||
});
|
||
|
||
it('应该返回空数组当没有用户时', async () => {
|
||
// 注意:这个测试假设数据库中至少有一些用户
|
||
// 如果数据库为空,这个测试会失败
|
||
// 为了不破坏原有数据,我们只验证返回的是数组
|
||
const result = await service.findAll();
|
||
|
||
expect(result).toBeDefined();
|
||
expect(Array.isArray(result)).toBe(true);
|
||
// 不验证长度为0,因为可能已有其他用户数据
|
||
// expect(result.length).toBe(0);
|
||
});
|
||
|
||
it('应该包含用户的所有字段', async () => {
|
||
// 创建一个完整信息的用户
|
||
const createUserDto: CreateUserDto = {
|
||
username: 'full_info_user',
|
||
password: 'password123',
|
||
email: 'fullinfo@example.com',
|
||
nickname: '完整信息用户',
|
||
avatarUrl: 'https://example.com/avatar.jpg',
|
||
phone: '13800138000',
|
||
// 注意:注册时不允许传入 role,所有注册用户的 role 固定为 'user'
|
||
};
|
||
|
||
await service.create(createUserDto);
|
||
|
||
// 查询所有用户
|
||
const result = await service.findAll();
|
||
|
||
const foundUser = result.find(
|
||
(u) => u.username === createUserDto.username,
|
||
);
|
||
|
||
expect(foundUser).toBeDefined();
|
||
expect(foundUser?.userId).toBeDefined();
|
||
expect(foundUser?.username).toBe(createUserDto.username);
|
||
expect(foundUser?.email).toBe(createUserDto.email);
|
||
expect(foundUser?.nickname).toBe(createUserDto.nickname);
|
||
expect(foundUser?.avatarUrl).toBe(createUserDto.avatarUrl);
|
||
expect(foundUser?.phone).toBe(createUserDto.phone);
|
||
expect(foundUser?.role).toBe('user'); // 注册用户的 role 固定为 'user'
|
||
expect(foundUser?.status).toBe('active');
|
||
expect(foundUser?.createdAt).toBeDefined();
|
||
expect(foundUser?.updatedAt).toBeDefined();
|
||
|
||
// 清理测试创建的用户
|
||
if (foundUser) {
|
||
await repository.remove(foundUser);
|
||
}
|
||
});
|
||
});
|
||
});
|