创作中心模块包含首页展示、个人中心、帖子审核。

“首页展示”支持广告轮播展示、推广帖子优先展示、分页显示所有帖子、导航栏便捷标签筛选帖子、全局标题模糊搜索帖子、点击帖子“查看更多”进入帖子详情页。帖子详情页展示帖子封面图片、作者时间、详细内容(可以插入种子链接对种子进行介绍与推广)等基本信息、对帖子点赞收藏举报评论回复、查看相关推荐帖子。相关推荐会推荐当前帖子作者的其他帖子(最多推荐5篇),还会推荐具有相似标签的其他帖子,两者总共最多推荐9篇帖子。

“个人中心”包含“我的中心”和“我的收藏”。
“我的中心”中可以管理已经成功发布的帖子(编辑、删除帖子),还可以发布新帖子。发布新帖子时除了填写帖子基本信息以外,帖子标签支持下拉多项选择,用户还可以选择帖子推广项目并进行支付。设置了多种推广项目,包含广告轮播推广、帖子置顶展示、限时优先展示、分类页首条展示。系统后台执行自动定时任务,每小时对帖子的推广时效性进行检查,如超出推广时限,则取消帖子的推广显示特权。用户点击发布帖子后帖子处于待审核状态,需要管理员审核通过才能正常发布在首页展示页面。编辑帖子时用户可以追加帖子推广,但如果帖子处于推广状态,则禁止修改推广项目。
“我的收藏”中可以便捷查看所有已收藏的帖子。

“帖子审核”包含“帖子发布管理”和“帖子举报管理”。“帖子审核”板块具有权限管理,只有管理员界面能够进入。
“帖子发布管理”对所有待审核帖子进行处理,支持预览待审核帖子详细内容,批准通过和拒绝通过选项。
“帖子举报管理”对所有用户的举报请求进行人工审核,如果举报内容属实,则将帖子下架处理,如果举报内容不属实,驳回举报请求。所有举报请求的处理结果均留存显示,方便后续再次审查。

Change-Id: If822351183e9d55a5a56ff5cf1e13b313fdbe231
diff --git a/tests/README.md b/tests/README.md
new file mode 100644
index 0000000..e8ab63b
--- /dev/null
+++ b/tests/README.md
@@ -0,0 +1,145 @@
+# 单元测试文档
+
+## 概述
+
+本项目包含完整的前端和后端单元测试,确保代码质量和功能正确性。
+
+## 前端测试
+
+### 测试框架
+- Jest: 测试运行器和断言库
+- @testing-library/react: React组件测试工具
+- @testing-library/jest-dom: DOM断言扩展
+
+### 测试覆盖范围
+
+#### 服务层测试 (services/)
+- **post.test.ts**: 帖子相关API接口测试
+  - 帖子列表获取
+  - 帖子详情获取
+  - 评论功能
+  - 收藏和点赞
+  - 帖子发布和管理
+  - 文件上传
+  - 审核功能
+  - 举报功能
+  - 错误处理
+
+### 运行测试
+
+```bash
+# 运行所有测试
+npm run test
+
+# 监听模式运行测试
+npm run test:watch
+
+# 生成覆盖率报告
+npm run test:coverage
+
+# CI环境运行测试
+npm run test:ci
+```
+
+### 测试配置
+
+测试配置文件:
+- `jest.config.ts`: Jest主配置
+- `tests/setup.ts`: 测试环境设置
+
+## 后端测试
+
+### 测试框架
+- JUnit 5: 测试框架
+- Mockito: Mock框架
+- Spring Boot Test: Spring测试支持
+
+### 测试覆盖范围
+
+#### 控制器测试
+- **PostCenterControllerTest**: 帖子中心控制器测试
+  - 帖子列表API
+  - 帖子详情API
+  - 评论API
+  - 收藏和点赞API
+  - 帖子发布API
+  - 文件上传API
+  - 帖子管理API
+  - 举报API
+
+#### 服务层测试
+- **PostServiceTest**: 帖子服务测试
+- **PostCommentServiceTest**: 评论服务测试
+- **PostTagServiceTest**: 标签服务测试
+
+### 运行测试
+
+```bash
+# Maven运行测试
+mvn test
+
+# 生成测试报告
+mvn surefire-report:report
+
+# 运行特定测试类
+mvn test -Dtest=PostCenterControllerTest
+```
+
+## 测试最佳实践
+
+### 1. 测试命名规范
+- 测试方法名应清晰描述测试场景
+- 使用中文描述测试目的
+- 格式:`test[功能名]_[场景]`
+
+### 2. 测试结构
+- **Arrange**: 准备测试数据
+- **Act**: 执行被测试方法
+- **Assert**: 验证结果
+
+### 3. Mock使用
+- 只Mock外部依赖
+- 验证重要的方法调用
+- 使用合理的测试数据
+
+### 4. 覆盖率要求
+- 行覆盖率: ≥80%
+- 分支覆盖率: ≥80%
+- 函数覆盖率: ≥80%
+
+## CI/CD集成
+
+### Jenkins配置
+测试在以下情况自动运行:
+1. 代码提交到Gerrit
+2. 合并请求创建
+3. 定期构建
+
+### 测试报告
+- 前端: 生成HTML覆盖率报告
+- 后端: 生成Surefire测试报告
+- 集成到Jenkins构建结果
+
+## 故障排除
+
+### 常见问题
+1. **测试超时**: 增加超时时间或优化测试逻辑
+2. **Mock失效**: 检查Mock配置和方法签名
+3. **异步测试**: 使用适当的异步测试工具
+
+### 调试技巧
+1. 使用`console.log`输出调试信息
+2. 运行单个测试文件进行调试
+3. 检查测试数据和期望结果
+
+## 维护指南
+
+### 添加新测试
+1. 为新功能编写对应测试
+2. 确保测试覆盖正常和异常情况
+3. 更新测试文档
+
+### 更新现有测试
+1. 功能变更时同步更新测试
+2. 保持测试数据的有效性
+3. 定期重构测试代码 
\ No newline at end of file
diff --git a/tests/services/post.test.ts b/tests/services/post.test.ts
new file mode 100644
index 0000000..8481bf6
--- /dev/null
+++ b/tests/services/post.test.ts
@@ -0,0 +1,711 @@
+/**
+ * 帖子服务单元测试
+ * 
+ * 测试覆盖所有帖子相关的API接口
+ * 包括:帖子列表、详情、评论、收藏、点赞、发布、审核、举报等功能
+ */
+
+import * as postService from '../../src/services/post';
+
+// Mock request module
+jest.mock('@umijs/max', () => ({
+  request: jest.fn(),
+}));
+
+const { request } = require('@umijs/max');
+
+describe('Post Service', () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  // 帖子列表相关测试
+  describe('getPostList', () => {
+    it('应该成功获取帖子列表', async () => {
+      const mockResponse = {
+        code: 200,
+        rows: [
+          {
+            postId: 1,
+            title: '测试帖子',
+            content: '测试内容',
+            author: 'testuser',
+            views: 100,
+            comments: 5,
+            favorites: 10,
+            likes: 15,
+            tags: 'tag1,tag2',
+            publishTime: '2024-01-01 12:00:00',
+            status: '1'
+          }
+        ],
+        total: 1
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPostList({ pageNum: 1, pageSize: 10 });
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/list', {
+        method: 'GET',
+        params: { pageNum: 1, pageSize: 10 },
+      });
+    });
+
+    it('应该处理带标签筛选的帖子列表请求', async () => {
+      const mockResponse = { code: 200, rows: [], total: 0 };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      await postService.getPostList({ pageNum: 1, pageSize: 10, tags: '日剧' });
+      
+      expect(request).toHaveBeenCalledWith('/api/post-center/list', {
+        method: 'GET',
+        params: { pageNum: 1, pageSize: 10, tags: '日剧' },
+      });
+    });
+
+    it('应该处理搜索关键词的请求', async () => {
+      const mockResponse = { code: 200, rows: [], total: 0 };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      await postService.getPostList({ pageNum: 1, pageSize: 10, title: '搜索关键词' });
+      
+      expect(request).toHaveBeenCalledWith('/api/post-center/list', {
+        method: 'GET',
+        params: { pageNum: 1, pageSize: 10, title: '搜索关键词' },
+      });
+    });
+  });
+
+  // 帖子详情相关测试
+  describe('getPostDetail', () => {
+    it('应该成功获取帖子详情', async () => {
+      const mockResponse = {
+        code: 200,
+        data: {
+          post: {
+            postId: 1,
+            title: '测试帖子',
+            content: '测试内容',
+            author: 'testuser',
+            views: 100,
+            comments: 5,
+            favorites: 10,
+            likes: 15,
+            tags: 'tag1,tag2',
+            publishTime: '2024-01-01 12:00:00',
+            status: '1'
+          },
+          tags: [],
+          comments: [],
+          recommendedPosts: [],
+          favorited: false
+        }
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPostDetail(1);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/1', {
+        method: 'GET',
+      });
+    });
+
+    it('应该处理获取不存在帖子的情况', async () => {
+      const mockResponse = { code: 404, msg: '帖子不存在' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPostDetail(999);
+      
+      expect(result).toEqual(mockResponse);
+    });
+  });
+
+  // 评论相关测试
+  describe('addComment', () => {
+    it('应该成功添加评论', async () => {
+      const mockResponse = { code: 200, msg: '评论成功', data: { commentId: 1 } };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const commentData = {
+        postId: 1,
+        content: '这是一条测试评论',
+        parentId: 0
+      };
+      
+      const result = await postService.addComment(commentData);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/comment', {
+        method: 'POST',
+        data: commentData,
+      });
+    });
+
+    it('应该处理空评论内容', async () => {
+      const mockResponse = { code: 400, msg: '评论内容不能为空' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.addComment({ postId: 1, content: '' });
+      
+      expect(result).toEqual(mockResponse);
+    });
+  });
+
+  // 收藏相关测试
+  describe('toggleFavorite', () => {
+    it('应该成功收藏帖子', async () => {
+      const mockResponse = { code: 200, msg: '收藏成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.toggleFavorite(1, true);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/favorite/1', {
+        method: 'POST',
+        params: { favorite: true },
+      });
+    });
+
+    it('应该成功取消收藏帖子', async () => {
+      const mockResponse = { code: 200, msg: '取消收藏成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.toggleFavorite(1, false);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/favorite/1', {
+        method: 'POST',
+        params: { favorite: false },
+      });
+    });
+  });
+
+  // 点赞相关测试
+  describe('toggleLike', () => {
+    it('应该成功点赞帖子', async () => {
+      const mockResponse = { code: 200, msg: '点赞成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.toggleLike(1, true);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/like/1', {
+        method: 'POST',
+        params: { like: true },
+      });
+    });
+
+    it('应该成功取消点赞帖子', async () => {
+      const mockResponse = { code: 200, msg: '取消点赞成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.toggleLike(1, false);
+      
+      expect(result).toEqual(mockResponse);
+    });
+  });
+
+  // 评论点赞测试
+  describe('toggleCommentLike', () => {
+    it('应该成功点赞评论', async () => {
+      const mockResponse = { code: 200, msg: '点赞成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.toggleCommentLike(1, true);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/comment/like/1', {
+        method: 'POST',
+        params: { like: true },
+      });
+    });
+  });
+
+  // 发布帖子测试
+  describe('publishPost', () => {
+    it('应该成功发布帖子', async () => {
+      const mockResponse = { code: 200, msg: '发布成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const postData = {
+        title: '新帖子标题',
+        content: '新帖子内容',
+        summary: '新帖子摘要',
+        tags: 'tag1,tag2',
+        promotionPlan: 1
+      };
+      
+      const result = await postService.publishPost(postData);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/publish', {
+        method: 'POST',
+        data: postData,
+      });
+    });
+
+    it('应该处理发布帖子失败的情况', async () => {
+      const mockResponse = { code: 400, msg: '标题不能为空' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.publishPost({
+        title: '',
+        content: '内容',
+        summary: '摘要',
+        tags: 'tag1'
+      });
+      
+      expect(result).toEqual(mockResponse);
+    });
+  });
+
+  // 个人帖子列表测试
+  describe('getMyPosts', () => {
+    it('应该成功获取我的帖子列表', async () => {
+      const mockResponse = {
+        code: 200,
+        rows: [
+          {
+            postId: 1,
+            title: '我的帖子',
+            author: 'currentUser',
+            status: '1'
+          }
+        ],
+        total: 1
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getMyPosts({ pageNum: 1, pageSize: 10 });
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/my-posts', {
+        method: 'GET',
+        params: { pageNum: 1, pageSize: 10 },
+      });
+    });
+  });
+
+  // 收藏列表测试
+  describe('getMyFavorites', () => {
+    it('应该成功获取我的收藏列表', async () => {
+      const mockResponse = {
+        code: 200,
+        rows: [
+          {
+            postId: 1,
+            title: '收藏的帖子',
+            author: 'otherUser'
+          }
+        ],
+        total: 1
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getMyFavorites({ pageNum: 1, pageSize: 10 });
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/my-favorites', {
+        method: 'GET',
+        params: { pageNum: 1, pageSize: 10 },
+      });
+    });
+  });
+
+  // 更新帖子测试
+  describe('updatePost', () => {
+    it('应该成功更新帖子', async () => {
+      const postData: API.Post.PostInfo = {
+        postId: 1,
+        title: '更新的标题',
+        content: '更新的内容',
+        author: 'testuser',
+        views: 0,
+        comments: 0,
+        favorites: 0,
+        likes: 0,
+        tags: 'tag1,tag2',
+        publishTime: new Date().toISOString(),
+        status: '1'
+      };
+      
+      const mockResponse = { code: 200, msg: '更新成功', data: null };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.updatePost(postData);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/update', {
+        method: 'PUT',
+        data: postData,
+      });
+    });
+  });
+
+  // 删除帖子测试
+  describe('deletePost', () => {
+    it('应该成功删除帖子', async () => {
+      const mockResponse = { code: 200, msg: '删除成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.deletePost(1);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/delete/1', {
+        method: 'DELETE',
+      });
+    });
+  });
+
+  // 标签相关测试
+  describe('tag operations', () => {
+    it('应该成功获取热门标签', async () => {
+      const mockResponse = {
+        code: 200,
+        data: [
+          { tagId: 1, tagName: '日剧', postCount: 10 },
+          { tagId: 2, tagName: '电影', postCount: 8 }
+        ]
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getHotTags();
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/tags/hot', {
+        method: 'GET',
+      });
+    });
+
+    it('应该成功获取可用标签', async () => {
+      const mockResponse = {
+        code: 200,
+        data: [
+          { tagId: 1, tagName: '日剧', tagColor: 'blue' },
+          { tagId: 2, tagName: '电影', tagColor: 'green' }
+        ]
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getAvailableTags();
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/tags/available', {
+        method: 'GET',
+      });
+    });
+
+    it('应该成功根据标签获取帖子', async () => {
+      const mockResponse = {
+        code: 200,
+        rows: [{ postId: 1, title: '日剧帖子' }],
+        total: 1
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPostsByTag(1);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/bytag/1', {
+        method: 'GET',
+      });
+    });
+  });
+
+  // 图片上传测试
+  describe('image operations', () => {
+    it('应该成功上传图片', async () => {
+      const mockResponse = {
+        code: 200,
+        data: {
+          url: '/images/123456_test.jpg',
+          filename: '123456_test.jpg',
+          originalName: 'test.jpg',
+          size: '1024'
+        }
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const formData = new FormData();
+      formData.append('file', new Blob(['test'], { type: 'image/jpeg' }));
+      
+      const result = await postService.uploadImage(formData);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/upload', {
+        method: 'POST',
+        data: formData,
+      });
+    });
+
+    it('应该成功删除图片', async () => {
+      const mockResponse = { code: 200, msg: '删除成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.deleteImage('test.jpg');
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/upload', {
+        method: 'DELETE',
+        params: { filename: 'test.jpg' },
+      });
+    });
+  });
+
+  // 推广相关测试
+  describe('promotion operations', () => {
+    it('应该成功获取推广帖子', async () => {
+      const mockResponse = {
+        code: 200,
+        data: [
+          {
+            postId: 1,
+            title: '推广帖子',
+            promotionPlanId: 1
+          }
+        ]
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPromotionPosts();
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/promotion', {
+        method: 'GET',
+      });
+    });
+
+    it('应该成功获取推广计划', async () => {
+      const mockResponse = {
+        code: 200,
+        data: [
+          {
+            id: 1,
+            name: '首页推荐',
+            description: '帖子显示在首页推荐位置',
+            price: 50.00,
+            duration: 7
+          }
+        ]
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPromotionPlans();
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/promotion-plans', {
+        method: 'GET',
+      });
+    });
+
+    it('应该成功创建支付记录', async () => {
+      const mockResponse = {
+        code: 200,
+        data: {
+          paymentId: 1,
+          postId: 1,
+          planId: 1,
+          amount: 50.00,
+          paymentStatus: 'pending'
+        }
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const paymentData = {
+        postId: 1,
+        planId: 1,
+        amount: 50.00
+      };
+      
+      const result = await postService.createPayment(paymentData);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/payment', {
+        method: 'POST',
+        data: paymentData,
+      });
+    });
+
+    it('应该成功获取推广状态', async () => {
+      const mockResponse = {
+        code: 200,
+        data: {
+          hasPromotion: true,
+          promotionPlanId: 1
+        }
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPromotionStatus(1);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/promotion-status/1', {
+        method: 'GET',
+      });
+    });
+
+    it('应该成功确认支付', async () => {
+      const mockResponse = { code: 200, msg: '支付成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.confirmPayment(1);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/payment/confirm/1', {
+        method: 'POST',
+      });
+    });
+
+    it('应该成功取消支付', async () => {
+      const mockResponse = { code: 200, msg: '支付已取消' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.cancelPayment(1);
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/payment/cancel/1', {
+        method: 'POST',
+      });
+    });
+  });
+
+  // 举报相关测试
+  describe('report operations', () => {
+    it('应该成功举报帖子', async () => {
+      const mockResponse = { code: 200, msg: '举报提交成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.reportPost(1, '内容不当');
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post-center/report/1', {
+        method: 'POST',
+        data: { reason: '内容不当' },
+      });
+    });
+  });
+
+  // 管理员功能测试
+  describe('admin operations', () => {
+    it('应该成功获取待审核帖子', async () => {
+      const mockResponse = {
+        code: 200,
+        rows: [
+          {
+            postId: 1,
+            title: '待审核帖子',
+            status: '0'
+          }
+        ],
+        total: 1
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getReviewPosts({ status: '0' });
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post/review/list', {
+        method: 'GET',
+        params: { status: '0' },
+      });
+    });
+
+    it('应该成功审核帖子', async () => {
+      const mockResponse = { code: 200, msg: '审核成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.reviewPost(1, 'approve', '内容合规');
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post/review/1', {
+        method: 'PUT',
+        data: { action: 'approve', reason: '内容合规' },
+      });
+    });
+
+    it('应该成功下架帖子', async () => {
+      const mockResponse = { code: 200, msg: '下架成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.takeDownPost(1, '违规内容');
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post/takedown/1', {
+        method: 'PUT',
+        data: { reason: '违规内容' },
+      });
+    });
+
+    it('应该成功获取举报列表', async () => {
+      const mockResponse = {
+        code: 200,
+        rows: [
+          {
+            reportId: 1,
+            postId: 1,
+            reportReason: '内容不当',
+            status: '0'
+          }
+        ],
+        total: 1
+      };
+      
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getReportList({});
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post/report/list', {
+        method: 'GET',
+        params: {},
+      });
+    });
+
+    it('应该成功处理举报', async () => {
+      const mockResponse = { code: 200, msg: '举报处理成功' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.handleReport(1, 'approve', 1, '举报属实');
+      
+      expect(result).toEqual(mockResponse);
+      expect(request).toHaveBeenCalledWith('/api/post/report/handle/1', {
+        method: 'PUT',
+        data: { action: 'approve', postId: 1, reason: '举报属实' },
+      });
+    });
+  });
+
+  // 错误处理测试
+  describe('error handling', () => {
+    it('应该处理网络错误', async () => {
+      const error = new Error('Network Error');
+      (request as jest.Mock).mockRejectedValue(error);
+      
+      await expect(postService.getPostList({})).rejects.toThrow('Network Error');
+    });
+
+    it('应该处理服务器错误响应', async () => {
+      const mockResponse = { code: 500, msg: '服务器内部错误' };
+      (request as jest.Mock).mockResolvedValue(mockResponse);
+      
+      const result = await postService.getPostList({});
+      
+      expect(result).toEqual(mockResponse);
+    });
+  });
+}); 
\ No newline at end of file
diff --git a/tests/setup.ts b/tests/setup.ts
new file mode 100644
index 0000000..96ffc69
--- /dev/null
+++ b/tests/setup.ts
@@ -0,0 +1,53 @@
+import '@testing-library/jest-dom';
+
+// 全局测试配置
+global.console = {
+  ...console,
+  // 在测试中禁用console.log以减少噪音
+  log: jest.fn(),
+  debug: jest.fn(),
+  info: jest.fn(),
+  warn: jest.fn(),
+  error: jest.fn(),
+};
+
+// Mock window.matchMedia
+Object.defineProperty(window, 'matchMedia', {
+  writable: true,
+  value: jest.fn().mockImplementation(query => ({
+    matches: false,
+    media: query,
+    onchange: null,
+    addListener: jest.fn(), // deprecated
+    removeListener: jest.fn(), // deprecated
+    addEventListener: jest.fn(),
+    removeEventListener: jest.fn(),
+    dispatchEvent: jest.fn(),
+  })),
+});
+
+// Mock ResizeObserver
+global.ResizeObserver = jest.fn().mockImplementation(() => ({
+  observe: jest.fn(),
+  unobserve: jest.fn(),
+  disconnect: jest.fn(),
+}));
+
+// Mock IntersectionObserver
+global.IntersectionObserver = jest.fn().mockImplementation(() => ({
+  observe: jest.fn(),
+  unobserve: jest.fn(),
+  disconnect: jest.fn(),
+}));
+
+// Mock localStorage
+const localStorageMock: Storage = {
+  getItem: jest.fn(),
+  setItem: jest.fn(),
+  removeItem: jest.fn(),
+  clear: jest.fn(),
+  length: 0,
+  key: jest.fn(),
+};
+global.localStorage = localStorageMock;
+global.sessionStorage = localStorageMock; 
\ No newline at end of file