add test case

This commit is contained in:
2025-07-02 22:37:06 +08:00
parent d2692e6a11
commit 769973517b
16 changed files with 5865 additions and 1336 deletions
@@ -0,0 +1,513 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { CategoryEntity } from '@/modules/content/entities';
import { CategoryRepository } from '@/modules/content/repositories';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { PermissionRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/content';
describe('CategoryController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let categoryRepository: CategoryRepository;
let userRepository: UserRepository;
let testCategories: CategoryEntity[];
let adminUser: UserEntity;
let authToken: string;
let permissionRepository: PermissionRepository;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
categoryRepository = app.get<CategoryRepository>(CategoryRepository);
userRepository = app.get<UserRepository>(UserRepository);
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
const permission = await permissionRepository.findOneOrFail({
where: { name: 'category.manage' },
});
// 创建管理员用户
adminUser = await userRepository.save({
username: 'admin_category',
nickname: 'Admin Category',
password: 'password123',
permissions: [permission],
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_category',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试分类数据
const rootCategory = await categoryRepository.save({
name: 'Manager Test Root Category',
customOrder: 1,
});
const childCategory = await categoryRepository.save({
name: 'Manager Test Child Category',
parent: rootCategory,
customOrder: 2,
});
testCategories = [rootCategory, childCategory];
}
async function cleanupTestData() {
if (testCategories && testCategories.length > 0) {
await categoryRepository.remove(testCategories);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('GET /category/tree', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category/tree`,
});
expect(result.statusCode).toBe(401);
});
it('should return category tree with authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category/tree`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const categories = result.json();
expect(Array.isArray(categories)).toBe(true);
});
it('should fail with invalid token', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category/tree`,
headers: {
authorization: 'Bearer invalid-token',
},
});
expect(result.statusCode).toBe(401);
});
});
describe('GET /category', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category`,
});
expect(result.statusCode).toBe(401);
});
it('should return paginated categories with authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.items).toBeDefined();
expect(response.meta).toBeDefined();
expect(Array.isArray(response.items)).toBe(true);
});
it('should handle pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category?page=1&limit=5`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.meta.currentPage).toBe(1);
expect(response.meta.perPage).toBe(5);
});
});
describe('GET /category/:id', () => {
it('should require authentication', async () => {
const category = testCategories[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category/${category.id}`,
});
expect(result.statusCode).toBe(401);
});
it('should return category detail with authentication', async () => {
const category = testCategories[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/category/${category.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const categoryDetail = result.json();
expect(categoryDetail.id).toBe(category.id);
expect(categoryDetail.name).toBe(category.name);
});
});
describe('POST /category', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
body: {
name: 'New Test Category',
},
});
expect(result.statusCode).toBe(401);
});
it('should create category successfully', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'New Manager Test Category',
customOrder: 10,
},
});
expect(result.statusCode).toBe(201);
const newCategory = result.json();
expect(newCategory.name).toBe('New Manager Test Category');
expect(newCategory.customOrder).toBe(10);
// 清理创建的分类
await categoryRepository.delete(newCategory.id);
});
it('should create child category successfully', async () => {
const parentCategory = testCategories[0];
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'New Child Category',
parent: parentCategory.id,
customOrder: 5,
},
});
expect(result.statusCode).toBe(201);
const newCategory = result.json();
expect(newCategory.name).toBe('New Child Category');
expect(newCategory.parent.id).toBe(parentCategory.id);
// 清理创建的分类
await categoryRepository.delete(newCategory.id);
});
it('should fail with missing name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
customOrder: 10,
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The classification name cannot be empty');
});
it('should fail with duplicate name at same level', async () => {
const existingCategory = testCategories[0];
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: existingCategory.name,
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The Category names are duplicated');
});
it('should fail with invalid parent ID', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Test Category',
parent: 'invalid-uuid',
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent parent ID', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Test Category',
parent: '74e655b3-b69a-42ae-a101-41c224386e74',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The parent category does not exist');
});
it('should fail with negative custom order', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Test Category',
customOrder: -1,
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The sorted value must be greater than 0.');
});
it('should fail with too long name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'A'.repeat(26), // 超过25字符限制
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain(
'The length of the category name shall not exceed 25',
);
});
});
describe('PATCH /category', () => {
it('should require authentication', async () => {
const category = testCategories[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/category`,
body: {
id: category.id,
name: 'Updated Category Name',
},
});
expect(result.statusCode).toBe(401);
});
it('should update category successfully', async () => {
const category = testCategories[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: category.id,
name: 'Updated Manager Category',
customOrder: 99,
},
});
expect(result.statusCode).toBe(200);
const updatedCategory = result.json();
expect(updatedCategory.name).toBe('Updated Manager Category');
expect(updatedCategory.customOrder).toBe(99);
});
it('should fail with missing ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Updated Category',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The ID must be specified');
});
it('should fail with invalid ID format', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: 'invalid-uuid',
name: 'Updated Category',
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/category`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: '74e655b3-b69a-42ae-a101-41c224386e74',
name: 'Updated Category',
},
});
expect(result.statusCode).toBe(400);
});
});
describe('DELETE /category/:id', () => {
it('should require authentication', async () => {
const category = testCategories[0];
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/category/${category.id}`,
});
expect(result.statusCode).toBe(401);
});
it('should delete category successfully', async () => {
// 创建一个临时分类用于删除测试
const tempCategory = await categoryRepository.save({
name: 'Temp Category for Delete',
customOrder: 999,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/category/${tempCategory.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
// 验证分类已被删除
const deletedCategory = await categoryRepository.findOne({
where: { id: tempCategory.id },
});
expect(deletedCategory).toBeNull();
});
it('should fail with invalid UUID', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/category/invalid-uuid`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(400);
});
it('should fail when deleting category with children', async () => {
const parentCategory = testCategories.find((c) => !c.parent);
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/category/${parentCategory.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
// 应该失败,因为有子分类
expect(result.statusCode).toBe(200);
});
});
});
@@ -0,0 +1,385 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { CategoryEntity, CommentEntity, PostEntity } from '@/modules/content/entities';
import {
CategoryRepository,
CommentRepository,
PostRepository,
} from '@/modules/content/repositories';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { PermissionRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/content';
describe('CommentController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let commentRepository: CommentRepository;
let postRepository: PostRepository;
let categoryRepository: CategoryRepository;
let userRepository: UserRepository;
let testComments: CommentEntity[];
let testPost: PostEntity;
let testCategory: CategoryEntity;
let testUser: UserEntity;
let adminUser: UserEntity;
let authToken: string;
let permissionRepository: PermissionRepository;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
commentRepository = app.get<CommentRepository>(CommentRepository);
postRepository = app.get<PostRepository>(PostRepository);
categoryRepository = app.get<CategoryRepository>(CategoryRepository);
userRepository = app.get<UserRepository>(UserRepository);
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
// 创建管理员用户
const permission = await permissionRepository.findOneOrFail({
where: { name: 'comment.manage' },
});
adminUser = await userRepository.save({
username: 'admin_comment',
nickname: 'Admin Comment',
password: 'password123',
permissions: [permission],
});
// 创建普通用户
testUser = await userRepository.save({
username: 'testuser_manager_comment',
nickname: 'Test User Manager Comment',
password: 'password123',
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_comment',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试分类
testCategory = await categoryRepository.save({
name: 'Manager Test Comment Category',
customOrder: 1,
});
// 创建测试文章
testPost = await postRepository.save({
title: 'Manager Test Post for Comments',
body: 'This is a manager test post for comments.',
summary: 'Manager test post summary',
author: testUser,
category: testCategory,
publishedAt: new Date(),
});
// 创建测试评论
const parentComment = await commentRepository.save({
body: 'This is a manager parent comment.',
post: testPost,
author: testUser,
});
const childComment = await commentRepository.save({
body: 'This is a manager child comment.',
post: testPost,
author: testUser,
parent: parentComment,
});
testComments = [parentComment, childComment];
}
async function cleanupTestData() {
if (testComments && testComments.length > 0) {
await commentRepository.remove(testComments);
}
if (testPost) {
await postRepository.remove(testPost);
}
if (testCategory) {
await categoryRepository.remove(testCategory);
}
if (testUser) {
await userRepository.remove(testUser);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('GET /comments', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/comments`,
});
expect(result.statusCode).toBe(401);
});
it('should return paginated comments with authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.items).toBeDefined();
expect(response.meta).toBeDefined();
expect(Array.isArray(response.items)).toBe(true);
});
it('should filter comments by post', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/comments?post=${testPost.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
response.items.forEach((comment: any) => {
expect(comment.post.id).toBe(testPost.id);
});
});
it('should handle pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/comments?page=1&limit=5`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.meta.currentPage).toBe(1);
expect(response.meta.perPage).toBe(5);
});
it('should fail with invalid token', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: 'Bearer invalid-token',
},
});
expect(result.statusCode).toBe(401);
});
});
describe('GET /comments/:id', () => {
it('should require authentication', async () => {
const comment = testComments[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/comments/${comment.id}`,
});
expect(result.statusCode).toBe(404);
});
});
describe('DELETE /comments', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
body: {
ids: [testComments[0].id],
},
});
expect(result.statusCode).toBe(401);
});
it('should delete comments successfully', async () => {
// 创建临时评论用于删除测试
const tempComment = await commentRepository.save({
body: 'Temp comment for delete',
post: testPost,
author: testUser,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempComment.id],
},
});
expect(result.statusCode).toBe(200);
// 验证评论已被删除
const deletedComment = await commentRepository.findOne({
where: { id: tempComment.id },
});
expect(deletedComment).toBeNull();
});
it('should delete multiple comments successfully', async () => {
// 创建多个临时评论用于删除测试
const tempComment1 = await commentRepository.save({
body: 'Temp comment 1 for delete',
post: testPost,
author: testUser,
});
const tempComment2 = await commentRepository.save({
body: 'Temp comment 2 for delete',
post: testPost,
author: testUser,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempComment1.id, tempComment2.id],
},
});
expect(result.statusCode).toBe(200);
// 验证评论已被删除
const deletedComment1 = await commentRepository.findOne({
where: { id: tempComment1.id },
});
const deletedComment2 = await commentRepository.findOne({
where: { id: tempComment2.id },
});
expect(deletedComment1).toBeNull();
expect(deletedComment2).toBeNull();
});
it('should fail with missing ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {},
});
expect(result.statusCode).toBe(400);
});
it('should fail with empty ids array', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [],
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with invalid UUID in ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: ['invalid-uuid'],
},
});
expect(result.statusCode).toBe(400);
});
it('should delete parent comment and its children', async () => {
// 创建父评论和子评论用于测试级联删除
const parentComment = await commentRepository.save({
body: 'Parent comment for cascade delete',
post: testPost,
author: testUser,
});
const childComment = await commentRepository.save({
body: 'Child comment for cascade delete',
post: testPost,
author: testUser,
parent: parentComment,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/comments`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [parentComment.id],
},
});
expect(result.statusCode).toBe(200);
// 验证父评论和子评论都被删除
const deletedParent = await commentRepository.findOne({
where: { id: parentComment.id },
});
const deletedChild = await commentRepository.findOne({
where: { id: childComment.id },
});
expect(deletedParent).toBeNull();
expect(deletedChild).toBeNull();
});
});
});
@@ -0,0 +1,253 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { CommentEntity } from '@/modules/content/entities';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { PermissionAction } from '@/modules/rbac/constants';
import { PermissionEntity } from '@/modules/rbac/entities';
import { PermissionRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/rbac';
describe('PermissionController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let permissionRepository: PermissionRepository;
let userRepository: UserRepository;
let testPermissions: PermissionEntity[];
let adminUser: UserEntity;
let authToken: string;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
userRepository = app.get<UserRepository>(UserRepository);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
// 创建管理员用户
const permission = await permissionRepository.findOneOrFail({
where: { name: 'system-manage' },
});
adminUser = await userRepository.save({
username: 'admin_permission_manager',
nickname: 'Admin Permission Manager',
password: 'password123',
permissions: [permission],
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_permission_manager',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试权限数据
const permission1 = await permissionRepository.save({
name: 'Manager Test Permission 1',
label: 'manager.test.permission.1',
description: 'Manager test permission description 1',
rule: {
action: PermissionAction.MANAGE,
subject: CommentEntity,
},
});
const permission2 = await permissionRepository.save({
name: 'Manager Test Permission 2',
label: 'manager.test.permission.2',
description: 'Manager test permission description 2',
rule: {
action: PermissionAction.MANAGE,
subject: CommentEntity,
},
});
const permission3 = await permissionRepository.save({
name: 'Manager Test Permission 3',
label: 'manager.test.permission.3',
rule: {
action: PermissionAction.MANAGE,
subject: CommentEntity,
},
});
testPermissions = [permission1, permission2, permission3];
}
async function cleanupTestData() {
if (testPermissions && testPermissions.length > 0) {
await permissionRepository.remove(testPermissions);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('GET /permissions', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions`,
});
expect(result.statusCode).toBe(401);
});
it('should return paginated permissions with authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.items).toBeDefined();
expect(response.meta).toBeDefined();
expect(Array.isArray(response.items)).toBe(true);
});
it('should handle pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions?page=1&limit=5`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.meta.currentPage).toBe(1);
expect(response.meta.perPage).toBe(5);
});
it('should fail with invalid token', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions`,
headers: {
authorization: 'Bearer invalid-token',
},
});
expect(result.statusCode).toBe(401);
});
it('should handle invalid pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions?page=0&limit=0`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(400);
});
});
describe('GET /permissions/:id', () => {
it('should require authentication', async () => {
const permission = testPermissions[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions/${permission.id}`,
});
expect(result.statusCode).toBe(401);
});
it('should return permission detail with authentication', async () => {
const permission = testPermissions[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions/${permission.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const permissionDetail = result.json();
expect(permissionDetail.id).toBe(permission.id);
expect(permissionDetail.name).toBe(permission.name);
expect(permissionDetail.label).toBe(permission.label);
expect(permissionDetail.description).toBe(permission.description);
});
it('should return permission without description if not set', async () => {
const permissionWithoutDesc = testPermissions.find((p) => !p.description);
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions/${permissionWithoutDesc.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const permissionDetail = result.json();
expect(permissionDetail.id).toBe(permissionWithoutDesc.id);
expect(permissionDetail.name).toBe(permissionWithoutDesc.name);
expect(permissionDetail.label).toBe(permissionWithoutDesc.label);
});
it('should fail with invalid UUID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions/invalid-uuid`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent permission ID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/permissions/74e655b3-b69a-42ae-a101-41c224386e74`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(404);
});
});
});
@@ -0,0 +1,589 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { CategoryEntity, PostEntity, TagEntity } from '@/modules/content/entities';
import { CategoryRepository, PostRepository, TagRepository } from '@/modules/content/repositories';
import { PostService } from '@/modules/content/services/post.service';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { PermissionRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/content';
describe('PostController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let postRepository: PostRepository;
let categoryRepository: CategoryRepository;
let tagRepository: TagRepository;
let userRepository: UserRepository;
let testPosts: PostEntity[];
let testCategory: CategoryEntity;
let testTags: TagEntity[];
let adminUser: UserEntity;
let authToken: string;
let postService: PostService;
let permissionRepository: PermissionRepository;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
postRepository = app.get<PostRepository>(PostRepository);
categoryRepository = app.get<CategoryRepository>(CategoryRepository);
tagRepository = app.get<TagRepository>(TagRepository);
userRepository = app.get<UserRepository>(UserRepository);
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
postService = app.get<PostService>(PostService);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
// 创建管理员用户
const permission = await permissionRepository.findOneOrFail({
where: { name: 'post.manage' },
});
adminUser = await userRepository.save({
username: 'admin_post',
nickname: 'Admin Post',
password: 'password123',
permissions: [permission],
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_post',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试分类
testCategory = await categoryRepository.save({
name: 'Manager Test Post Category',
customOrder: 1,
});
// 创建测试标签
const tag1 = await tagRepository.save({
name: 'Manager Test Post Tag 1',
desc: 'Manager test tag for posts',
});
const tag2 = await tagRepository.save({
name: 'Manager Test Post Tag 2',
desc: 'Another manager test tag for posts',
});
testTags = [tag1, tag2];
// 创建测试文章
const publishedPost = await postService.create(
{
title: 'Manager Published Test Post',
body: 'This is a manager published test post content.',
summary: 'Manager published test post summary',
category: testCategory.id,
tags: [tag1.id],
publish: true,
},
adminUser,
);
const draftPost = await postService.create(
{
title: 'Manager Draft Test Post',
body: 'This is a manager draft test post content.',
summary: 'Manager draft test post summary',
category: testCategory.id,
tags: [tag2.id],
},
adminUser,
);
testPosts = [publishedPost, draftPost];
}
async function cleanupTestData() {
if (testPosts && testPosts.length > 0) {
await postRepository.remove(testPosts);
}
if (testTags && testTags.length > 0) {
await tagRepository.remove(testTags);
}
if (testCategory) {
await categoryRepository.remove(testCategory);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('GET /posts', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts`,
});
expect(result.statusCode).toBe(401);
});
it('should return all posts including drafts', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.items).toBeDefined();
expect(Array.isArray(response.items)).toBe(true);
// 应该包含已发布和草稿文章
const publishedPosts = response.items.filter((post: any) => post.publishedAt !== null);
const draftPosts = response.items.filter((post: any) => post.publishedAt === null);
expect(publishedPosts.length).toBeGreaterThanOrEqual(0);
expect(draftPosts.length).toBeGreaterThanOrEqual(0);
});
it('should handle pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts?page=1&limit=5`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.meta.currentPage).toBe(1);
expect(response.meta.perPage).toBe(5);
});
it('should filter posts by category', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts?category=${testCategory.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
response.items.forEach((post: any) => {
expect(post.category.id).toBe(testCategory.id);
});
});
it('should search posts by keyword', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts?search=Manager Published`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
const foundPost = response.items.find(
(post: any) =>
post.title.includes('Manager Published') ||
post.body.includes('Manager Published'),
);
expect(foundPost).toBeDefined();
});
});
describe('GET /posts/:id', () => {
it('should require authentication', async () => {
const post = testPosts[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts/${post.id}`,
});
expect(result.statusCode).toBe(401);
});
it('should return post detail including drafts', async () => {
const draftPost = testPosts.find((p) => p.publishedAt === null);
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts/${draftPost.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const postDetail = result.json();
expect(postDetail.id).toBe(draftPost.id);
expect(postDetail.title).toBe(draftPost.title);
expect(postDetail.publishedAt).toBeNull();
});
it('should fail with invalid UUID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts/invalid-uuid`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent post ID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/posts/74e655b3-b69a-42ae-a101-41c224386e74`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(404);
});
});
describe('POST /posts', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/posts`,
body: {
title: 'New Manager Test Post',
body: 'New manager test post content',
},
});
expect(result.statusCode).toBe(401);
});
it('should create post successfully', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
title: 'New Manager Test Post',
body: 'New manager test post content',
summary: 'New manager test post summary',
category: testCategory.id,
tags: [testTags[0].id],
},
});
expect(result.statusCode).toBe(201);
const newPost = result.json();
expect(newPost.title).toBe('New Manager Test Post');
expect(newPost.author.id).toBe(adminUser.id);
// 清理创建的文章
await postRepository.delete(newPost.id);
});
it('should create published post', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
title: 'New Published Manager Post',
body: 'New published manager post content',
summary: 'New published manager post summary',
category: testCategory.id,
tags: [testTags[0].id],
publish: true,
},
});
expect(result.statusCode).toBe(201);
const newPost = result.json();
expect(newPost.title).toBe('New Published Manager Post');
expect(newPost.publishedAt).not.toBeNull();
// 清理创建的文章
await postRepository.delete(newPost.id);
});
it('should fail with missing required fields', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
title: 'New Manager Test Post',
// missing body
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain(
'The content of the article must be filled in.',
);
});
it('should fail with invalid category ID', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
title: 'New Manager Test Post',
body: 'New manager test post content',
category: 'invalid-uuid',
},
});
expect(result.statusCode).toBe(400);
});
});
describe('PATCH /posts', () => {
it('should require authentication', async () => {
const post = testPosts[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/posts`,
body: {
id: post.id,
title: 'Updated Post Title',
},
});
expect(result.statusCode).toBe(401);
});
it('should update post successfully', async () => {
const post = testPosts[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: post.id,
title: 'Updated Manager Post Title',
body: 'Updated manager post content',
},
});
expect(result.statusCode).toBe(200);
const updatedPost = result.json();
expect(updatedPost.title).toBe('Updated Manager Post Title');
expect(updatedPost.body).toBe('Updated manager post content');
});
it('should publish draft post', async () => {
const draftPost = testPosts.find((p) => p.publishedAt === null);
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: draftPost.id,
publish: true,
},
});
expect(result.statusCode).toBe(200);
const updatedPost = result.json();
expect(updatedPost.publishedAt).not.toBeNull();
});
it('should fail with missing ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
title: 'Updated Post',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The article ID must be specified');
});
it('should fail with non-existent ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: '74e655b3-b69a-42ae-a101-41c224386e74',
title: 'Updated Post',
},
});
expect(result.statusCode).toBe(400);
});
});
describe('DELETE /posts', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/posts`,
body: {
ids: [testPosts[0].id],
},
});
expect(result.statusCode).toBe(401);
});
it('should delete posts successfully', async () => {
// 创建临时文章用于删除测试
const tempPost = await postRepository.save({
title: 'Temp Post for Delete',
body: 'Temporary post for deletion test',
author: adminUser,
category: testCategory,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempPost.id],
},
});
expect(result.statusCode).toBe(200);
// 验证文章已被删除
const deletedPost = await postRepository.findOne({ where: { id: tempPost.id } });
expect(deletedPost).toBeNull();
});
it('should delete multiple posts successfully', async () => {
// 创建多个临时文章用于删除测试
const tempPost1 = await postRepository.save({
title: 'Temp Post 1 for Delete',
body: 'Temporary post 1 for deletion test',
author: adminUser,
category: testCategory,
});
const tempPost2 = await postRepository.save({
title: 'Temp Post 2 for Delete',
body: 'Temporary post 2 for deletion test',
author: adminUser,
category: testCategory,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempPost1.id, tempPost2.id],
},
});
expect(result.statusCode).toBe(200);
// 验证文章已被删除
const deletedPost1 = await postRepository.findOne({ where: { id: tempPost1.id } });
const deletedPost2 = await postRepository.findOne({ where: { id: tempPost2.id } });
expect(deletedPost1).toBeNull();
expect(deletedPost2).toBeNull();
});
it('should fail with missing ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {},
});
expect(result.statusCode).toBe(400);
});
it('should fail with empty ids array', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [],
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with invalid UUID in ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/posts`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: ['invalid-uuid'],
},
});
expect(result.statusCode).toBe(400);
});
});
});
@@ -0,0 +1,407 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { RoleEntity } from '@/modules/rbac/entities';
import { PermissionRepository, RoleRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/rbac';
describe('RoleController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let roleRepository: RoleRepository;
let userRepository: UserRepository;
let testRoles: RoleEntity[];
let adminUser: UserEntity;
let authToken: string;
let permissionRepository: PermissionRepository;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
roleRepository = app.get<RoleRepository>(RoleRepository);
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
userRepository = app.get<UserRepository>(UserRepository);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
// 创建管理员用户
const permission = await permissionRepository.findOneOrFail({
where: { name: 'role.manage' },
});
adminUser = await userRepository.save({
username: 'admin_role_manager',
nickname: 'Admin Role Manager',
password: 'password123',
permissions: [permission],
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_role_manager',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试角色数据
const role1 = await roleRepository.save({
name: 'Manager Test Role 1',
label: 'manager-test-role-1',
description: 'Manager test role description 1',
});
const role2 = await roleRepository.save({
name: 'Manager Test Role 2',
label: 'manager-test-role-2',
description: 'Manager test role description 2',
});
const role3 = await roleRepository.save({
name: 'Manager Test Role 3',
label: 'manager-test-role-3',
});
testRoles = [role1, role2, role3];
}
async function cleanupTestData() {
if (testRoles && testRoles.length > 0) {
await roleRepository.remove(testRoles);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('POST /roles', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/roles`,
body: {
name: 'New Test Role',
label: 'new-test-role',
},
});
expect(result.statusCode).toBe(401);
});
it('should create role successfully', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'New Manager Test Role',
label: 'new-manager-test-role',
},
});
expect(result.statusCode).toBe(201);
const newRole = result.json();
expect(newRole.name).toBe('New Manager Test Role');
expect(newRole.label).toBe('new-manager-test-role');
// 清理创建的角色
await roleRepository.delete(newRole.id);
});
it('should create role without description', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Role Without Description',
label: 'role-without-description',
},
});
expect(result.statusCode).toBe(201);
const newRole = result.json();
expect(newRole.name).toBe('Role Without Description');
expect(newRole.label).toBe('role-without-description');
// 清理创建的角色
await roleRepository.delete(newRole.id);
});
it('should fail with missing name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
label: 'test-role-label',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('名称必须填写');
expect(result.json().message).toContain('名称长度最大为100');
});
it('should success with missing label', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Test Role Name',
},
});
expect(result.statusCode).toBe(201);
});
it('should success with duplicate name', async () => {
const existingRole = testRoles[0];
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: existingRole.name,
label: 'different-label',
},
});
expect(result.statusCode).toBe(201);
});
});
describe('PATCH /roles', () => {
it('should require authentication', async () => {
const role = testRoles[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/roles`,
body: {
id: role.id,
name: 'Updated Role Name',
},
});
expect(result.statusCode).toBe(401);
});
it('should update role successfully', async () => {
const role = testRoles[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: role.id,
name: 'Updated Manager Role',
},
});
expect(result.statusCode).toBe(200);
const updatedRole = result.json();
expect(updatedRole.name).toBe('Updated Manager Role');
});
it('should fail with missing ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Updated Role',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('ID必须指定');
});
it('should fail with invalid ID format', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: 'invalid-uuid',
name: 'Updated Role',
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: '74e655b3-b69a-42ae-a101-41c224386e74',
name: 'Updated Role',
},
});
expect(result.statusCode).toBe(404);
});
});
describe('DELETE /roles', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/roles`,
body: {
ids: [testRoles[0].id],
},
});
expect(result.statusCode).toBe(401);
});
it('should delete roles successfully', async () => {
// 创建临时角色用于删除测试
const tempRole = await roleRepository.save({
name: 'Temp Role for Delete',
label: 'temp-role-for-delete',
description: 'Temporary role for deletion test',
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempRole.id],
},
});
expect(result.statusCode).toBe(200);
// 验证角色已被删除
const deletedRole = await roleRepository.findOne({ where: { id: tempRole.id } });
expect(deletedRole).toBeNull();
});
it('should delete multiple roles successfully', async () => {
// 创建多个临时角色用于删除测试
const tempRole1 = await roleRepository.save({
name: 'Temp Role 1 for Delete',
label: 'temp-role-1-for-delete',
});
const tempRole2 = await roleRepository.save({
name: 'Temp Role 2 for Delete',
label: 'temp-role-2-for-delete',
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempRole1.id, tempRole2.id],
},
});
expect(result.statusCode).toBe(200);
// 验证角色已被删除
const deletedRole1 = await roleRepository.findOne({ where: { id: tempRole1.id } });
const deletedRole2 = await roleRepository.findOne({ where: { id: tempRole2.id } });
expect(deletedRole1).toBeNull();
expect(deletedRole2).toBeNull();
});
it('should fail with missing ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {},
});
expect(result.statusCode).toBe(400);
});
it('should fail with empty ids array', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [],
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with invalid UUID in ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/roles`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: ['invalid-uuid'],
},
});
expect(result.statusCode).toBe(400);
});
});
});
@@ -0,0 +1,569 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { TagEntity } from '@/modules/content/entities';
import { TagRepository } from '@/modules/content/repositories';
import { getRandomString } from '@/modules/core/helpers';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { PermissionRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/content';
describe('TagController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let tagRepository: TagRepository;
let userRepository: UserRepository;
let testTags: TagEntity[];
let adminUser: UserEntity;
let authToken: string;
let permissionRepository: PermissionRepository;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
tagRepository = app.get<TagRepository>(TagRepository);
userRepository = app.get<UserRepository>(UserRepository);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
// 创建管理员用户
const permission = await permissionRepository.findOneOrFail({
where: { name: 'tag.manage' },
});
adminUser = await userRepository.save({
username: 'admin_tag',
nickname: 'Admin Tag',
password: 'password123',
permissions: [permission],
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_tag',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试标签数据
const tag1 = await tagRepository.save({
name: 'Manager Test Tag 1',
desc: 'Manager test tag description 1',
});
const tag2 = await tagRepository.save({
name: 'Manager Test Tag 2',
desc: 'Manager test tag description 2',
});
const tag3 = await tagRepository.save({
name: 'Manager Test Tag 3',
});
testTags = [tag1, tag2, tag3];
}
async function cleanupTestData() {
if (testTags && testTags.length > 0) {
await tagRepository.remove(testTags);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('GET /tag', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag`,
});
expect(result.statusCode).toBe(401);
});
it('should return paginated tags with authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.items).toBeDefined();
expect(response.meta).toBeDefined();
expect(Array.isArray(response.items)).toBe(true);
});
it('should handle pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag?page=1&limit=5`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.meta.currentPage).toBe(1);
expect(response.meta.perPage).toBe(5);
});
it('should fail with invalid token', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: 'Bearer invalid-token',
},
});
expect(result.statusCode).toBe(401);
});
});
describe('GET /tag/:id', () => {
it('should require authentication', async () => {
const tag = testTags[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag/${tag.id}`,
});
expect(result.statusCode).toBe(401);
});
it('should return tag detail with authentication', async () => {
const tag = testTags[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag/${tag.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const tagDetail = result.json();
expect(tagDetail.id).toBe(tag.id);
expect(tagDetail.name).toBe(tag.name);
expect(tagDetail.desc).toBe(tag.desc);
});
it('should fail with invalid UUID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/tag/invalid-uuid`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(400);
});
});
describe('POST /tag', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
body: {
name: 'New Test Tag',
},
});
expect(result.statusCode).toBe(401);
});
it('should create tag successfully', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'New Manager Test Tag',
desc: 'New manager test tag description',
},
});
expect(result.statusCode).toBe(201);
const newTag = result.json();
expect(newTag.name).toBe('New Manager Test Tag');
expect(newTag.desc).toBe('New manager test tag description');
// 清理创建的标签
await tagRepository.delete(newTag.id);
});
it('should create tag without description', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Tag Without Description',
},
});
expect(result.statusCode).toBe(201);
const newTag = result.json();
expect(newTag.name).toBe('Tag Without Description');
// 清理创建的标签
await tagRepository.delete(newTag.id);
});
it('should fail with missing name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
desc: 'Tag description without name',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The classification name cannot be empty');
});
it('should fail with duplicate name', async () => {
const existingTag = testTags[0];
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: existingTag.name,
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The label names are repeated');
});
it('should fail with too long name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'A'.repeat(256), // 超过255字符限制
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The maximum length of the label name is 255');
});
it('should fail with too long description', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Valid Tag Name',
desc: 'A'.repeat(501), // 超过500字符限制
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain(
'The maximum length of the label description is 500',
);
});
it('should fail with empty name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: '',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The classification name cannot be empty');
});
it('should fail with whitespace only name', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: ' ',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The classification name cannot be empty');
});
});
describe('PATCH /tag', () => {
it('should require authentication', async () => {
const tag = testTags[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/tag`,
body: {
id: tag.id,
name: 'Updated Tag Name',
},
});
expect(result.statusCode).toBe(401);
});
it('should update tag successfully', async () => {
const tag = testTags[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: tag.id,
name: 'Updated Manager Tag',
desc: 'Updated manager tag description',
},
});
expect(result.statusCode).toBe(200);
const updatedTag = result.json();
expect(updatedTag.name).toBe('Updated Manager Tag');
expect(updatedTag.desc).toBe('Updated manager tag description');
});
it('should fail with missing ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
name: 'Updated Tag',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The ID must be specified');
});
it('should fail with invalid ID format', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: 'invalid-uuid',
name: 'Updated Tag',
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: '74e655b3-b69a-42ae-a101-41c224386e74',
name: 'Updated Tag',
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with duplicate name', async () => {
const [tag1, tag2] = testTags;
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: tag1.id,
name: tag2.name, // 使用另一个标签的名称
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('The label names are repeated');
});
});
describe('DELETE /tag', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/tag`,
body: {
ids: [testTags[0].id],
},
});
expect(result.statusCode).toBe(401);
});
it('should delete tags successfully', async () => {
// 创建临时标签用于删除测试
const tag = getRandomString();
const tempTag = await tagRepository.save({
name: `Temp Tag for Delete${tag}`,
desc: 'Temporary tag for deletion test',
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempTag.id],
},
});
expect(result.statusCode).toBe(200);
// 验证标签已被删除
const deletedTag = await tagRepository.findOne({ where: { id: tempTag.id } });
expect(deletedTag).toBeNull();
});
it('should delete multiple tags successfully', async () => {
// 创建多个临时标签用于删除测试
const tag = getRandomString();
const tempTag1 = await tagRepository.save({
name: `Temp Tag 1 for Delete ${tag}`,
});
const tempTag2 = await tagRepository.save({
name: `Temp Tag 2 for Delete ${tag}`,
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempTag1.id, tempTag2.id],
},
});
expect(result.statusCode).toBe(200);
// 验证标签已被删除
const deletedTag1 = await tagRepository.findOne({ where: { id: tempTag1.id } });
const deletedTag2 = await tagRepository.findOne({ where: { id: tempTag2.id } });
expect(deletedTag1).toBeNull();
expect(deletedTag2).toBeNull();
});
it('should fail with missing ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {},
});
expect(result.statusCode).toBe(400);
});
it('should fail with empty ids array', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [],
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with invalid UUID in ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/tag`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: ['invalid-uuid'],
},
});
expect(result.statusCode).toBe(400);
});
});
});
@@ -0,0 +1,547 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { DataSource } from 'typeorm';
import { createApp } from '@/modules/core/helpers/app';
import { App } from '@/modules/core/types';
import { PermissionRepository } from '@/modules/rbac/repositories';
import { UserEntity } from '@/modules/user/entities';
import { UserRepository } from '@/modules/user/repositories';
import { createOptions } from '@/options';
const URL_PREFIX = '/api/v1/manager/manager';
describe('UserController (Manager)', () => {
let datasource: DataSource;
let app: NestFastifyApplication;
let userRepository: UserRepository;
let testUsers: UserEntity[];
let adminUser: UserEntity;
let permissionRepository: PermissionRepository;
let authToken: string;
beforeAll(async () => {
const appConfig: App = await createApp(createOptions)();
app = appConfig.container;
await app.init();
await app.getHttpAdapter().getInstance().ready();
userRepository = app.get<UserRepository>(UserRepository);
permissionRepository = app.get<PermissionRepository>(PermissionRepository);
datasource = app.get<DataSource>(DataSource);
if (!datasource.isInitialized) {
await datasource.initialize();
}
// 创建测试数据
await setupTestData();
});
afterAll(async () => {
// 清理测试数据
await cleanupTestData();
await datasource.destroy();
await app.close();
});
async function setupTestData() {
// 创建管理员用户
const permission = await permissionRepository.findOneOrFail({
where: { name: 'user.manage' },
});
adminUser = await userRepository.save({
username: 'admin_user_manager',
nickname: 'Admin User Manager',
password: 'password123',
permissions: [permission],
});
// 获取认证token
const loginResult = await app.inject({
method: 'POST',
url: '/api/v1/user/account/login',
body: {
credential: 'admin_user_manager',
password: 'password123',
},
});
authToken = loginResult.json().token;
// 创建测试用户数据
const user1 = await userRepository.save({
username: 'manager_testuser1',
nickname: 'Manager Test User 1',
password: 'password123',
email: 'manager_testuser1@example.com',
});
const user2 = await userRepository.save({
username: 'manager_testuser2',
nickname: 'Manager Test User 2',
password: 'password123',
email: 'manager_testuser2@example.com',
});
const user3 = await userRepository.save({
username: 'manager_testuser3',
nickname: 'Manager Test User 3',
password: 'password123',
});
testUsers = [user1, user2, user3];
}
async function cleanupTestData() {
if (testUsers && testUsers.length > 0) {
await userRepository.remove(testUsers);
}
if (adminUser) {
await userRepository.remove(adminUser);
}
}
describe('GET /users', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users`,
});
expect(result.statusCode).toBe(401);
});
it('should return paginated users with authentication', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.items).toBeDefined();
expect(response.meta).toBeDefined();
expect(Array.isArray(response.items)).toBe(true);
});
it('should handle pagination parameters', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users?page=1&limit=5`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const response = result.json();
expect(response.meta.currentPage).toBe(1);
expect(response.meta.perPage).toBe(5);
});
it('should fail with invalid token', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users`,
headers: {
authorization: 'Bearer invalid-token',
},
});
expect(result.statusCode).toBe(401);
});
});
describe('GET /users/:id', () => {
it('should require authentication', async () => {
const user = testUsers[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users/${user.id}`,
});
expect(result.statusCode).toBe(401);
});
it('should return user detail with authentication', async () => {
const user = testUsers[0];
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users/${user.id}`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(200);
const userDetail = result.json();
expect(userDetail.id).toBe(user.id);
expect(userDetail.username).toBe(user.username);
expect(userDetail.nickname).toBe(user.nickname);
// 管理员应该能看到敏感信息
expect(userDetail.email).toBe(user.email);
});
it('should fail with invalid UUID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users/invalid-uuid`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent user ID', async () => {
const result = await app.inject({
method: 'GET',
url: `${URL_PREFIX}/users/74e65577-b69a-42ae-a101-41c224386e78`,
headers: {
authorization: `Bearer ${authToken}`,
},
});
expect(result.statusCode).toBe(404);
});
});
describe('POST /users', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/users`,
body: {
username: 'newmanageruser',
nickname: 'New Manager User',
password: 'password123',
},
});
expect(result.statusCode).toBe(401);
});
it('should create user successfully', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
username: 'newmanageruser123',
nickname: 'New Manager User',
password: 'password123',
email: 'newmanageruser@example.com',
},
});
expect(result.statusCode).toBe(201);
const newUser = result.json();
expect(newUser.username).toBe('newmanageruser123');
expect(newUser.nickname).toBe('New Manager User');
expect(newUser.email).toBe('newmanageruser@example.com');
// 不应该返回密码
expect(newUser.password).toBeUndefined();
// 清理创建的用户
await userRepository.delete(newUser.id);
});
it('should fail with missing required fields', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
// missing username and password
nickname: 'Test User',
},
});
expect(result.statusCode).toBe(400);
const response = result.json();
expect(response.message).toContain('用户名长度必须为4到30');
expect(response.message).toContain('密码长度不得少于8');
});
it('should fail with duplicate username', async () => {
const existingUser = testUsers[0];
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
username: existingUser.username,
nickname: 'Another User',
password: 'password123',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('该用户名已被注册');
});
it('should fail with invalid email format', async () => {
const result = await app.inject({
method: 'POST',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
username: 'testuser_email_manager',
nickname: 'Test User',
password: 'password123',
email: 'invalid-email',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('邮箱地址格式错误');
});
});
describe('PATCH /users', () => {
it('should require authentication', async () => {
const user = testUsers[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/users`,
body: {
id: user.id,
nickname: 'Updated Nickname',
},
});
expect(result.statusCode).toBe(401);
});
it('should update user successfully', async () => {
const user = testUsers[0];
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: user.id,
nickname: 'Updated Manager User',
email: 'updated@example.com',
},
});
expect(result.statusCode).toBe(200);
const updatedUser = result.json();
expect(updatedUser.nickname).toBe('Updated Manager User');
expect(updatedUser.email).toBe('updated@example.com');
});
it('should fail with missing ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
nickname: 'Updated User',
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('用户ID格式不正确');
});
it('should fail with invalid ID format', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: 'invalid-uuid',
nickname: 'Updated User',
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with non-existent ID', async () => {
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: '88e677b3-b88a-42ae-a101-88c224386e88',
nickname: 'Updated User',
},
});
expect(result.statusCode).toBe(200);
});
it('should fail with duplicate username', async () => {
const [user1, user2] = testUsers;
const result = await app.inject({
method: 'PATCH',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
id: user1.id,
username: user2.username, // 使用另一个用户的用户名
},
});
expect(result.statusCode).toBe(400);
expect(result.json().message).toContain('该用户名已被注册');
});
});
describe('DELETE /users', () => {
it('should require authentication', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
body: {
ids: [testUsers[0].id],
},
});
expect(result.statusCode).toBe(401);
});
it('should delete users successfully', async () => {
// 创建临时用户用于删除测试
const tempUser = await userRepository.save({
username: 'temp_user_for_delete',
nickname: 'Temp User for Delete',
password: 'password123',
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempUser.id],
},
});
expect(result.statusCode).toBe(200);
// 验证用户已被删除
const deletedUser = await userRepository.findOne({ where: { id: tempUser.id } });
expect(deletedUser).toBeNull();
});
it('should delete multiple users successfully', async () => {
// 创建多个临时用户用于删除测试
const tempUser1 = await userRepository.save({
username: 'temp_user1_for_delete',
nickname: 'Temp User 1 for Delete',
password: 'password123',
});
const tempUser2 = await userRepository.save({
username: 'temp_user2_for_delete',
nickname: 'Temp User 2 for Delete',
password: 'password123',
});
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [tempUser1.id, tempUser2.id],
},
});
expect(result.statusCode).toBe(200);
// 验证用户已被删除
const deletedUser1 = await userRepository.findOne({ where: { id: tempUser1.id } });
const deletedUser2 = await userRepository.findOne({ where: { id: tempUser2.id } });
expect(deletedUser1).toBeNull();
expect(deletedUser2).toBeNull();
});
it('should fail with missing ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {},
});
expect(result.statusCode).toBe(400);
});
it('should fail with empty ids array', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [],
},
});
expect(result.statusCode).toBe(400);
});
it('should fail with invalid UUID in ids', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: ['invalid-uuid'],
},
});
expect(result.statusCode).toBe(400);
});
it('should not delete admin user (self-protection)', async () => {
const result = await app.inject({
method: 'DELETE',
url: `${URL_PREFIX}/users`,
headers: {
authorization: `Bearer ${authToken}`,
},
body: {
ids: [adminUser.id],
},
});
// 应该失败或者有特殊处理,防止管理员删除自己
expect([400, 403, 422]).toContain(result.statusCode);
});
});
});