Files
invest-mind-store/apps/api/test/modules/user/user.service.integration.spec.ts
2026-01-06 10:49:19 +08:00

322 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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