完成上传下载连接,公告管理与详情页面,求种区页面,轮播图折扣显示,修改部分bug

Change-Id: I86fc294e32911cb3426a8b16f90aca371f975c11
diff --git a/src/api/announcement.js b/src/api/announcement.js
new file mode 100644
index 0000000..6213d10
--- /dev/null
+++ b/src/api/announcement.js
@@ -0,0 +1,28 @@
+import { api } from './auth';
+
+// 获取所有公告
+export const getAnnouncements = () => {
+  return api.get('/announcement/list');
+};
+
+
+export const postAnnouncement = (data) => {
+  // 创建 FormData 对象
+  const formData = new FormData();
+  formData.append('title', data.title);
+  formData.append('content', data.content);
+
+  return api.post('/announcement/create', formData);
+}
+
+
+// 获取最新公告
+export const getLatestAnnouncements = () => {
+  return api.get('/announcement/latest');
+};
+
+// 获取公告详情
+export const getAnnouncementDetail = (id) => {
+  return api.get(`/announcement/${id}`);
+};
+
diff --git a/src/api/announcement.test.js b/src/api/announcement.test.js
new file mode 100644
index 0000000..bae9482
--- /dev/null
+++ b/src/api/announcement.test.js
@@ -0,0 +1,139 @@
+import MockAdapter from 'axios-mock-adapter';
+import { api } from './auth';
+import { getAnnouncements, getLatestAnnouncements, getAnnouncementDetail,postAnnouncement } from './announcement';
+
+describe('公告API', () => {
+  let mockAxios;
+
+  beforeEach(() => {
+    mockAxios = new MockAdapter(api);
+  });
+
+  afterEach(() => {
+    mockAxios.restore();
+  });
+
+describe('postAnnouncement - 发布公告', () => {
+  it('应该成功发布公告', async () => {
+    const mockData = {
+      title: '测试公告',
+      content: '测试内容'
+    };
+    
+    const mockResponse = {
+      code: 200,
+      message: '公告发布成功',
+      data: {
+        id: 123,
+        title: mockData.title,
+        content: mockData.content,
+        createTime: new Date().toISOString()
+      }
+    };
+  
+
+    mockAxios.onPost('/announcement/create').reply(200, mockResponse);
+
+    const response = await postAnnouncement(mockData);
+    expect(response.data).toEqual(mockResponse);
+    expect(response.data.code).toBe(200);
+    expect(response.data.data.title).toBe(mockData.title);
+    expect(response.data.data.content).toBe(mockData.content);
+  });
+});
+
+  describe('getAnnouncements - 获取所有公告', () => {
+    it('应该成功获取公告列表', async () => {
+      const mockResponse = {
+        code: 200,
+        data: {
+          announcements: [
+            { id: 1, title: '公告1', content: '内容1', createTime: '2023-01-01T00:00:00Z' },
+            { id: 2, title: '公告2', content: '内容2', createTime: '2023-01-01T00:00:00Z' }
+          ]
+        }
+      };
+      
+      mockAxios.onGet('/announcement/list').reply(200, mockResponse);
+
+      const response = await getAnnouncements();
+      expect(response.data).toEqual(mockResponse);
+      expect(response.data.data.announcements).toHaveLength(2);
+    });
+
+    
+  });
+
+  describe('getLatestAnnouncements - 获取最新公告', () => {
+    it('应该成功获取最新公告', async () => {
+      const mockResponse = {
+        code: 200,
+        data: {
+          announcements: [
+            { id: 1, title: '最新公告', content: '最新内容' }
+          ]
+        }
+      };
+      
+      mockAxios.onGet('/announcement/latest').reply(200, mockResponse);
+
+      const response = await getLatestAnnouncements();
+      expect(response.data).toEqual(mockResponse);
+      expect(response.data.data.announcements).toHaveLength(1);
+    });
+
+    it('应该正确处理服务器错误', async () => {
+      mockAxios.onGet('/announcement/latest').reply(500);
+      
+      await expect(getLatestAnnouncements()).rejects.toThrow();
+    });
+  });
+
+  describe('getAnnouncementDetail - 获取公告详情', () => {
+    it('应该成功获取公告详情', async () => {
+      const announcementId = 123;
+      const mockResponse = {
+        code: 200,
+        data: {
+          announcement: {
+            id: announcementId,
+            title: '详细公告',
+            content: '详细内容',
+            createTime: '2023-01-01T00:00:00Z'
+          }
+        }
+      };
+      
+      mockAxios.onGet(`/announcement/${announcementId}`).reply(200, mockResponse);
+
+      const response = await getAnnouncementDetail(announcementId);
+      expect(response.data).toEqual(mockResponse);
+      expect(response.data.data.announcement.id).toBe(announcementId);
+    });
+
+    it('应该处理公告不存在的情况', async () => {
+      const announcementId = 999;
+      const mockResponse = {
+        code: 404,
+        message: '公告不存在'
+      };
+      
+      mockAxios.onGet(`/announcement/${announcementId}`).reply(404, mockResponse);
+
+      try {
+        await getAnnouncementDetail(announcementId);
+    } catch (error) {
+        expect(error.response.status).toBe(404);
+        expect(error.response.data.code).toBe(404);
+        expect(error.response.data.message).toBe('公告不存在');
+    }
+    });
+
+    it('应该正确处理网络错误', async () => {
+      const announcementId = 456;
+      mockAxios.onGet(`/announcement/${announcementId}`).networkError();
+      
+      await expect(getAnnouncementDetail(announcementId)).rejects.toThrow();
+    });
+  });
+});
\ No newline at end of file
diff --git a/src/api/helpComment.js b/src/api/helpComment.js
index c1d00c0..2ff9ade 100644
--- a/src/api/helpComment.js
+++ b/src/api/helpComment.js
@@ -1,14 +1,14 @@
 import { api } from './auth';

 

-export const likePostComment = (commentId) => {

+export const likeHelpPostComment = (commentId) => {

   return api.post(`/help/comments/${commentId}/like`);

 };

 

-export const getCommentReplies = (commentId) => {

+export const getHelpCommentReplies = (commentId) => {

   return api.get(`/help/comments/${commentId}/replies`);

 };

 

-export const addCommentReply = (commentId, replyData) => {

+export const addHelpCommentReply = (commentId, replyData) => {

   const formData = new FormData();

   formData.append('authorId', replyData.authorId);

   formData.append('content', replyData.content);

@@ -21,7 +21,7 @@
   return api.post(`/help/comments/${commentId}/replies`, formData);

 };

 

-export const deleteComment = (commentId, authorId) => {

+export const deleteHelpComment = (commentId, authorId) => {

   return api.delete(`/help/comments/${commentId}`, {

     params: { authorId }

   });

diff --git a/src/api/helpComment.test.js b/src/api/helpComment.test.js
index a4a7d99..99efd8c 100644
--- a/src/api/helpComment.test.js
+++ b/src/api/helpComment.test.js
@@ -1,6 +1,6 @@
 import MockAdapter from 'axios-mock-adapter';

 import { api } from './auth'; // 添加api导入

-import { likePostComment, getCommentReplies, addCommentReply, deleteComment } from './helpComment';

+import { likeHelpPostComment, getHelpCommentReplies, addHelpCommentReply, deleteHelpComment } from './helpComment';

 

 describe('求助帖评论API', () => {

   let mockAxios;

@@ -13,27 +13,27 @@
     mockAxios.restore();

   });

 

-  describe('likePostComment - 点赞求助帖评论', () => {

+  describe('likeHelpPostComment - 点赞求助帖评论', () => {

     it('应该成功发送点赞请求', async () => {

       const mockResponse = { code: 200, message: '点赞成功' };

       mockAxios.onPost('/help/comments/123/like').reply(200, mockResponse);

 

-      const response = await likePostComment('123');

+      const response = await likeHelpPostComment('123');

       expect(response.data).toEqual(mockResponse);

     });

   });

 

-  describe('getCommentReplies - 获取评论回复', () => {

+  describe('getHelpCommentReplies - 获取评论回复', () => {

     it('应该返回正确的回复数据结构', async () => {

       const mockData = [{ id: '1', content: '回复内容' }];

       mockAxios.onGet('/help/comments/456/replies').reply(200, { code: 200, data: mockData });

 

-      const response = await getCommentReplies('456');

+      const response = await getHelpCommentReplies('456');

       expect(response.data.data).toEqual(mockData);

     });

   });

 

-  describe('addCommentReply - 添加评论回复', () => {

+  describe('addHelpCommentReply - 添加评论回复', () => {

     it('应该正确发送回复内容(无图片)', async () => {

       const commentId = '789';

       const replyData = {

@@ -49,7 +49,7 @@
         return [200, { code: 200 }];

       });

 

-      const response = await addCommentReply(commentId, replyData);

+      const response = await addHelpCommentReply(commentId, replyData);

       expect(response.status).toBe(200);

     });

     it('应该正确处理带图片的回复', async () => {

@@ -66,11 +66,11 @@
         return [200, { code: 200 }];

       });

 

-      const response = await addCommentReply(commentId, replyData);

+      const response = await addHelpCommentReply(commentId, replyData);

       expect(response.status).toBe(200);

     });

   });

-  describe('deleteComment - 删除评论', () => {

+  describe('deleteHelpComment - 删除评论', () => {

     it('应该正确发送删除请求', async () => {

       const commentId = '101112';

       const authorId = 'user1';

@@ -80,7 +80,7 @@
         return [200, { code: 200 }];

       });

 

-      const response = await deleteComment(commentId, authorId);

+      const response = await deleteHelpComment(commentId, authorId);

       expect(response.status).toBe(200);

     });

   });

diff --git a/src/api/helpPost.js b/src/api/helpPost.js
index 92d6402..68b37d9 100644
--- a/src/api/helpPost.js
+++ b/src/api/helpPost.js
@@ -1,7 +1,7 @@
 // src/api/helpPost.js

 import { api } from './auth'; // 复用已有的axios实例

 

-export const createPost = (title, content, authorId, selectedImage) => {

+export const createHelpPost = (title, content, authorId, selectedImage) => {

   // 创建 FormData 对象

   const formData = new FormData();

   formData.append('title', title);

@@ -16,44 +16,36 @@
   return api.post('/help/posts', formData);

 };

 

-export const getPosts = (page = 1, size = 5) => {

+export const getHelpPosts = (page = 1, size = 5) => {

   return api.get('/help/posts', {

     params: { page, size }

   });

 };

 

-export const getPostDetail = (postId) => {

+export const getHelpPostDetail = (postId) => {

   return api.get(`/help/posts/${postId}`);

 };

 

-export const likePost = (postId, data) => {

+export const likeHelpPost = (postId, data) => {

   return api.post(`/help/posts/${postId}/like`, null, {

     params: data

   });

 };

 

-export const addPostComment = (postId, commentData) => {

-  // 创建FormData对象来处理文件上传

-  const formData = new FormData();

-  formData.append('authorId', commentData.authorId);

-  formData.append('content', commentData.content);

+export const addHelpPostComment = (postId, commentData) => {

   

-  // 如果有图片,添加到formData

-  if (commentData.commentImage) {

-    formData.append('image', commentData.commentImage);

-  }

 

-  return api.post(`/help/posts/${postId}/comments`, formData);

+  return api.post(`/help/posts/${postId}/comments`, commentData);

 };

 

-export const deletePost = (postId, authorId) => {

+export const deleteHelpPost = (postId, authorId) => {

   return api.delete(`/help/posts/${postId}`, {

     params: { authorId }

   });

 };

 

 

-export const searchPosts = (keyword, page = 1, size = 5) => {

+export const searchHelpPosts = (keyword, page = 1, size = 5) => {

   return api.get('/help/posts/search', {

     params: { keyword, page, size }

   });

diff --git a/src/api/helpPost.test.js b/src/api/helpPost.test.js
index b445b4d..53e9e01 100644
--- a/src/api/helpPost.test.js
+++ b/src/api/helpPost.test.js
@@ -1,6 +1,6 @@
 import MockAdapter from 'axios-mock-adapter';

 import { api } from './auth'; // 添加api导入

-import { createPost, getPosts, getPostDetail, likePost, addPostComment,deletePost } from './helpPost';

+import { createHelpPost, getHelpPosts, getHelpPostDetail, likeHelpPost, addHelpPostComment,deleteHelpPost,searchHelpPosts } from './helpPost';

 

 describe('求助帖API', () => {

   let mockAxios;

@@ -13,7 +13,7 @@
     mockAxios.restore();

   });

 

-  describe('createPost - 创建求助帖', () => {

+  describe('createHelpPost - 创建求助帖', () => {

     it('应该正确发送无图片帖子数据', async () => {

       const postData = {

         title: '测试标题',

@@ -30,7 +30,7 @@
         return [201, { code: 201 }];

       });

 

-      const response = await createPost(postData.title, postData.content, postData.authorId);

+      const response = await createHelpPost(postData.title, postData.content, postData.authorId);

       expect(response.status).toBe(201);

     });

   });

@@ -48,7 +48,7 @@
       return [201, { code: 201 }];

     });

 

-    const response = await createPost(

+    const response = await createHelpPost(

       postData.title, 

       postData.content, 

       postData.authorId, 

@@ -58,7 +58,7 @@
   });

 

 

-  describe('getPosts - 获取求助帖列表', () => {

+  describe('getHelpPosts - 获取求助帖列表', () => {

     it('应该支持分页参数', async () => {

       const page = 2, size = 10;

       mockAxios.onGet('/help/posts', { params: { page, size } }).reply(200, {

@@ -66,57 +66,21 @@
         data: []

       });

 

-      const response = await getPosts(page, size);

+      const response = await getHelpPosts(page, size);

       expect(response.status).toBe(200);

     });

   });

 

-  describe('likePost - 点赞求助帖', () => {

+  describe('likeHelpPost - 点赞求助帖', () => {

     it('应该正确发送点赞请求', async () => {

       mockAxios.onPost('/help/posts/post123/like').reply(200, { code: 200 });

-      const response = await likePost('post123');

+      const response = await likeHelpPost('post123');

       expect(response.status).toBe(200);

     });

   });

 

-  describe('addPostComment - 添加帖子评论', () => {

-    it('应该正确发送评论数据(无图片)', async () => {

-      const postId = 'post456';

-      const commentData = {

-        authorId: 'user1',

-        content: '测试评论'

-      };

-      

-      mockAxios.onPost(`/help/posts/${postId}/comments`).reply(config => {

-        const data = config.data;

-        expect(data.get('authorId')).toBe(commentData.authorId);

-        expect(data.get('content')).toBe(commentData.content);

-        expect(data.has('image')).toBe(false);

-        return [200, { code: 200 }];

-      });

-      

-      const response = await addPostComment('post456', commentData);

-      expect(response.status).toBe(200);

-    });

-    it('应该正确处理带图片的评论', async () => {

-      const postId = 'post456';

-      const commentData = {

-        authorId: 'user1',

-        content: '测试评论',

-        commentImage: new File(['content'], 'comment.jpg')

-      };

-      

-      mockAxios.onPost(`/help/posts/${postId}/comments`).reply(config => {

-        const data = config.data;

-        expect(data.get('image')).toBeInstanceOf(File);

-        return [200, { code: 200 }];

-      });

-

-      const response = await addPostComment(postId, commentData);

-      expect(response.status).toBe(200);

-    });

-  });

-  describe('deletePost - 删除帖子', () => {

+  

+  describe('deleteHelpPost - 删除帖子', () => {

     it('应该正确发送删除请求', async () => {

       const postId = 'post789';

       const authorId = 'user1';

@@ -126,8 +90,28 @@
         return [200, { code: 200 }];

       });

 

-      const response = await deletePost(postId, authorId);

+      const response = await deleteHelpPost(postId, authorId);

       expect(response.status).toBe(200);

     });

   });

+  describe('searchHelpPosts - 搜索求助帖', () => {

+      it('应该正确发送搜索请求', async () => {

+        const keyword = '测试';

+        const page = 1, size = 5;

+        

+        mockAxios.onGet('/help/posts/search', {

+          params: { keyword, page, size }

+        }).reply(200, {

+          code: 200,

+          data: {

+            records: [],

+            total: 0

+          }

+        });

+  

+        const response = await searchHelpPosts(keyword, page, size);

+        expect(response.status).toBe(200);

+        expect(response.data.data).toBeDefined();

+      });

+    });

 });
\ No newline at end of file
diff --git a/src/api/personal.test.js b/src/api/personal.test.js
new file mode 100644
index 0000000..3a929e8
--- /dev/null
+++ b/src/api/personal.test.js
@@ -0,0 +1,354 @@
+// src/api/__tests__/personal.test.js
+import { 
+  getUserInfo, 
+  formatFileSize, 
+  getDownloadQuota, 
+  getDownloadProgress, 
+  getUserTorrents, 
+  deleteTorrent, 
+  generateInviteCode, 
+  getUserInviteCodes, 
+  exchangeUpload, 
+  updatePassword 
+} from './personal';
+import { api } from './auth';
+
+// 模拟整个 auth 模块
+jest.mock('./auth');
+
+describe('Personal API', () => {
+  beforeEach(() => {
+    // 在每个测试前重置模拟
+    jest.clearAllMocks();
+  });
+
+  describe('getUserInfo', () => {
+    it('should return formatted user info when API call succeeds', async () => {
+      const mockUserData = {
+        username: 'testuser',
+        level: 3,
+        registTime: '2023-01-01T00:00:00Z',
+        magicPoints: 100,
+        upload: 1024,
+        download: 512,
+        shareRate: 2.0
+      };
+
+      api.get.mockResolvedValue({
+        data: {
+          code: 200,
+          data: mockUserData,
+          message: 'success'
+        }
+      });
+
+      const result = await getUserInfo();
+      
+      expect(api.get).toHaveBeenCalledWith('/user/userInfo');
+      expect(result).toEqual({
+        username: 'testuser',
+        level: 3,
+        registTime: '2023-01-01',
+        magicPoints: 100,
+        upload: 1024,
+        download: 512,
+        shareRate: '2.00'
+      });
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.get.mockResolvedValue({
+        data: {
+          code: 500,
+          message: 'Internal server error'
+        }
+      });
+
+      await expect(getUserInfo()).rejects.toThrow('Internal server error');
+    });
+  });
+
+  describe('formatFileSize', () => {
+    it('should format bytes correctly', () => {
+      expect(formatFileSize(500)).toBe('500 B');
+      expect(formatFileSize(1024)).toBe('1.00 KB');
+      expect(formatFileSize(2048)).toBe('2.00 KB');
+      expect(formatFileSize(1024 * 1024)).toBe('1.00 MB');
+      expect(formatFileSize(1024 * 1024 * 2.5)).toBe('2.50 MB');
+      expect(formatFileSize(1024 * 1024 * 1024)).toBe('1.00 GB');
+      expect(formatFileSize(1024 * 1024 * 1024 * 3.7)).toBe('3.70 GB');
+    });
+  });
+
+  describe('getDownloadQuota', () => {
+    it('should return download quota data when API call succeeds', async () => {
+      const mockData = {
+        total: 1073741824, // 1GB in bytes
+        used: 536870912,   // 512MB
+        remaining: 536870912
+      };
+
+      api.get.mockResolvedValue({
+        data: {
+          code: 200,
+          data: mockData,
+          message: 'success'
+        }
+      });
+
+      const result = await getDownloadQuota();
+      
+      expect(api.get).toHaveBeenCalledWith('/user/allowDownload');
+      expect(result).toEqual(mockData);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.get.mockResolvedValue({
+        data: {
+          code: 403,
+          message: 'Forbidden'
+        }
+      });
+
+      await expect(getDownloadQuota()).rejects.toThrow('Forbidden');
+    });
+  });
+
+  describe('getDownloadProgress', () => {
+    it('should return download progress when API call succeeds', async () => {
+      const mockProgresses = [
+        { id: 1, name: 'file1', progress: 50 },
+        { id: 2, name: 'file2', progress: 75 }
+      ];
+
+      api.get.mockResolvedValue({
+        data: {
+          code: 200,
+          data: { progresses: mockProgresses },
+          message: 'success'
+        }
+      });
+
+      const result = await getDownloadProgress();
+      
+      expect(api.get).toHaveBeenCalledWith('/torrent/getProgress');
+      expect(result).toEqual(mockProgresses);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.get.mockResolvedValue({
+        data: {
+          code: 404,
+          message: 'Not found'
+        }
+      });
+
+      await expect(getDownloadProgress()).rejects.toThrow('Not found');
+    });
+  });
+
+  describe('getUserTorrents', () => {
+    it('should return user torrents with pagination when API call succeeds', async () => {
+      const mockResponse = {
+        records: [
+          { 
+            torrent: { id: 1, name: 'torrent1', size: 1024 },
+            downloadCount: 10,
+            formattedSize: '1 KB'
+          }
+        ],
+        total: 1
+      };
+
+      api.get.mockResolvedValue({
+        data: {
+          code: 200,
+          data: mockResponse,
+          message: 'success'
+        }
+      });
+
+      const result = await getUserTorrents(1, 5);
+      
+      expect(api.get).toHaveBeenCalledWith('/torrent/get/torrentMyself', {
+        params: { page: 1, size: 5 }
+      });
+      expect(result).toEqual({
+        records: [
+          {
+            id: 1,
+            name: 'torrent1',
+            size: 1024,
+            downloadCount: 10,
+            formattedSize: '1 KB'
+          }
+        ],
+        total: 1
+      });
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.get.mockResolvedValue({
+        data: {
+          code: 500,
+          message: 'Server error'
+        }
+      });
+
+      await expect(getUserTorrents()).rejects.toThrow('Server error');
+    });
+  });
+
+  describe('deleteTorrent', () => {
+    it('should successfully delete torrent when API call succeeds', async () => {
+      const mockResponse = {
+        code: 200,
+        message: 'Deleted successfully'
+      };
+
+      api.delete.mockResolvedValue({
+        data: mockResponse
+      });
+
+      const result = await deleteTorrent(123);
+      
+      expect(api.delete).toHaveBeenCalledWith('/torrent/deleteTorrent/123');
+      expect(result).toEqual(mockResponse);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.delete.mockResolvedValue({
+        data: {
+          code: 404,
+          message: 'Torrent not found'
+        }
+      });
+
+      await expect(deleteTorrent(123)).rejects.toThrow('Torrent not found');
+    });
+  });
+
+  describe('generateInviteCode', () => {
+    it('should return generated invite code when API call succeeds', async () => {
+      const mockCode = 'ABCD-EFGH-IJKL';
+
+      api.post.mockResolvedValue({
+        data: {
+          code: 200,
+          data: { inviteCode: mockCode },
+          message: 'success'
+        }
+      });
+
+      const result = await generateInviteCode();
+      
+      expect(api.post).toHaveBeenCalledWith('/invitecode/generate');
+      expect(result).toBe(mockCode);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.post.mockResolvedValue({
+        data: {
+          code: 403,
+          message: 'Permission denied'
+        }
+      });
+
+      await expect(generateInviteCode()).rejects.toThrow('Permission denied');
+    });
+  });
+
+  describe('getUserInviteCodes', () => {
+    it('should return user invite codes when API call succeeds', async () => {
+      const mockCodes = ['CODE1', 'CODE2'];
+
+      api.get.mockResolvedValue({
+        data: {
+          code: 200,
+          data: { inviteCode: mockCodes },
+          message: 'success'
+        }
+      });
+
+      const result = await getUserInviteCodes();
+      
+      expect(api.get).toHaveBeenCalledWith('/invitecode/userInviteCode');
+      expect(result).toEqual(mockCodes);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.get.mockResolvedValue({
+        data: {
+          code: 500,
+          message: 'Server error'
+        }
+      });
+
+      await expect(getUserInviteCodes()).rejects.toThrow('Server error');
+    });
+  });
+
+  describe('exchangeUpload', () => {
+    it('should successfully exchange magic points for upload when API call succeeds', async () => {
+      const mockResponse = {
+        code: 200,
+        message: 'Exchange successful'
+      };
+
+      api.post.mockResolvedValue({
+        data: mockResponse
+      });
+
+      const result = await exchangeUpload(100);
+      
+      expect(api.post).toHaveBeenCalledWith('/user/exchangeUpload', {
+        magicPoint: 100
+      });
+      expect(result).toEqual(mockResponse);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.post.mockResolvedValue({
+        data: {
+          code: 400,
+          message: 'Not enough points'
+        }
+      });
+
+      await expect(exchangeUpload(100)).rejects.toThrow('Not enough points');
+    });
+  });
+
+  describe('updatePassword', () => {
+    it('should successfully update password when API call succeeds', async () => {
+      const mockResponse = {
+        code: 200,
+        message: 'Password updated'
+      };
+
+      api.put.mockResolvedValue({
+        data: mockResponse
+      });
+
+      const result = await updatePassword('oldPass', 'newPass');
+      
+      expect(api.put).toHaveBeenCalledWith('/user/password', {
+        oldPassword: 'oldPass',
+        newPassword: 'newPass'
+      });
+      expect(result).toEqual(mockResponse);
+    });
+
+    it('should throw error when API call fails', async () => {
+      api.put.mockResolvedValue({
+        data: {
+          code: 401,
+          message: 'Invalid old password'
+        }
+      });
+
+      await expect(updatePassword('wrongPass', 'newPass'))
+        .rejects.toThrow('Invalid old password');
+    });
+  });
+});
\ No newline at end of file
diff --git a/src/api/requestComment.js b/src/api/requestComment.js
new file mode 100644
index 0000000..f7e9b43
--- /dev/null
+++ b/src/api/requestComment.js
@@ -0,0 +1,28 @@
+import { api } from './auth';
+
+export const likeRequestPostComment = (commentId) => {
+  return api.post(`/request/comments/${commentId}/like`);
+};
+
+export const getRequestCommentReplies = (commentId) => {
+  return api.get(`/request/comments/${commentId}/replies`);
+};
+
+export const addRequestCommentReply = (commentId, replyData) => {
+  const formData = new FormData();
+  formData.append('authorId', replyData.authorId);
+  formData.append('content', replyData.content);
+
+  // 如果有图片,添加到formData
+  if (replyData.image) {
+    formData.append('image', replyData.image);
+  }
+
+  return api.post(`/request/comments/${commentId}/replies`, formData);
+};
+
+export const deleteRequestComment = (commentId, authorId) => {
+  return api.delete(`/request/comments/${commentId}`, {
+    params: { authorId }
+  });
+};
\ No newline at end of file
diff --git a/src/api/requestComment.test.js b/src/api/requestComment.test.js
new file mode 100644
index 0000000..2f50a3c
--- /dev/null
+++ b/src/api/requestComment.test.js
@@ -0,0 +1,97 @@
+import MockAdapter from 'axios-mock-adapter';
+import { api } from './auth';
+import {
+  likeRequestPostComment,
+  getRequestCommentReplies,
+  addRequestCommentReply,
+  deleteRequestComment
+} from './requestComment';
+
+describe('求种帖评论API', () => {
+  let mockAxios;
+
+  beforeEach(() => {
+    mockAxios = new MockAdapter(api);
+  });
+
+  afterEach(() => {
+    mockAxios.restore();
+  });
+
+  describe('likeRequestPostComment - 点赞求种帖评论', () => {
+    it('应该成功发送点赞请求', async () => {
+      const commentId = 'reqc123';
+      const mockResponse = { code: 200, message: '点赞成功' };
+      mockAxios.onPost(`/request/comments/${commentId}/like`).reply(200, mockResponse);
+
+      const response = await likeRequestPostComment(commentId);
+      expect(response.data).toEqual(mockResponse);
+    });
+  });
+
+  describe('getRequestCommentReplies - 获取评论回复', () => {
+    it('应该返回正确的回复数据结构', async () => {
+      const commentId = 'reqc456';
+      const mockData = [{ id: '1', content: '求种回复内容' }];
+      mockAxios.onGet(`/request/comments/${commentId}/replies`)
+        .reply(200, { code: 200, data: mockData });
+
+      const response = await getRequestCommentReplies(commentId);
+      expect(response.data.data).toEqual(mockData);
+    });
+  });
+
+  describe('addRequestCommentReply - 添加评论回复', () => {
+    it('应该正确发送回复内容(无图片)', async () => {
+      const commentId = 'reqc789';
+      const replyData = {
+        authorId: 'user1',
+        content: '测试求种回复'
+      };
+      
+      mockAxios.onPost(`/request/comments/${commentId}/replies`).reply(config => {
+        const data = config.data;
+        expect(data.get('authorId')).toBe(replyData.authorId);
+        expect(data.get('content')).toBe(replyData.content);
+        expect(data.has('image')).toBe(false);
+        return [200, { code: 200 }];
+      });
+
+      const response = await addRequestCommentReply(commentId, replyData);
+      expect(response.status).toBe(200);
+    });
+
+    it('应该正确处理带图片的回复', async () => {
+      const commentId = 'reqc789';
+      const replyData = {
+        authorId: 'user1',
+        content: '测试求种回复',
+        image: new File(['content'], 'reply.jpg')
+      };
+      
+      mockAxios.onPost(`/request/comments/${commentId}/replies`).reply(config => {
+        const data = config.data;
+        expect(data.get('image')).toBeInstanceOf(File);
+        return [200, { code: 200 }];
+      });
+
+      const response = await addRequestCommentReply(commentId, replyData);
+      expect(response.status).toBe(200);
+    });
+  });
+
+  describe('deleteRequestComment - 删除评论', () => {
+    it('应该正确发送删除请求', async () => {
+      const commentId = 'reqc101';
+      const authorId = 'user1';
+      
+      mockAxios.onDelete(`/request/comments/${commentId}`).reply(config => {
+        expect(config.params).toEqual({ authorId });
+        return [200, { code: 200 }];
+      });
+
+      const response = await deleteRequestComment(commentId, authorId);
+      expect(response.status).toBe(200);
+    });
+  });
+});
\ No newline at end of file
diff --git a/src/api/requestPost.js b/src/api/requestPost.js
new file mode 100644
index 0000000..224e3e9
--- /dev/null
+++ b/src/api/requestPost.js
@@ -0,0 +1,52 @@
+// src/api/requestPost.js
+import { api } from './auth'; // 复用已有的axios实例
+
+export const createRequestPost = (title, content, authorId, selectedImage) => {
+  // 创建 FormData 对象
+  const formData = new FormData();
+  formData.append('title', title);
+  formData.append('content', content);
+  formData.append('authorId', authorId);
+  
+  // 如果有图片,添加到 FormData
+  if (selectedImage) {
+    formData.append('image', selectedImage);
+  }
+
+  return api.post('/request/posts', formData);
+};
+
+export const getRequestPosts = (page = 1, size = 5) => {
+  return api.get('/request/posts', {
+    params: { page, size }
+  });
+};
+
+export const getRequestPostDetail = (postId) => {
+  return api.get(`/request/posts/${postId}`);
+};
+
+export const likeRequestPost = (postId, data) => {
+  return api.post(`/request/posts/${postId}/like`, null, {
+    params: data
+  });
+};
+
+export const addRequestPostComment = (postId, commentData) => {
+  
+
+  return api.post(`/request/posts/${postId}/comments`, commentData);
+};
+
+export const deleteRequestPost = (postId, authorId) => {
+  return api.delete(`/request/posts/${postId}`, {
+    params: { authorId }
+  });
+};
+
+
+export const searchRequestPosts = (keyword, page = 1, size = 5) => {
+  return api.get('/request/posts/search', {
+    params: { keyword, page, size }
+  });
+};
\ No newline at end of file
diff --git a/src/api/requestPost.test.js b/src/api/requestPost.test.js
new file mode 100644
index 0000000..a3bccc6
--- /dev/null
+++ b/src/api/requestPost.test.js
@@ -0,0 +1,147 @@
+import MockAdapter from 'axios-mock-adapter';
+import { api } from './auth';
+import {
+  createRequestPost,
+  getRequestPosts,
+  getRequestPostDetail,
+  likeRequestPost,
+  addRequestPostComment,
+  deleteRequestPost,
+  searchRequestPosts
+} from './requestPost';
+
+describe('求种帖API', () => {
+  let mockAxios;
+
+  beforeEach(() => {
+    mockAxios = new MockAdapter(api);
+  });
+
+  afterEach(() => {
+    mockAxios.restore();
+  });
+
+  describe('createRequestPost - 创建求种帖', () => {
+    it('应该正确发送无图片帖子数据', async () => {
+      const postData = {
+        title: '求种测试标题',
+        content: '求种测试内容',
+        authorId: 'user123'
+      };
+      
+      mockAxios.onPost('/request/posts').reply(config => {
+        const data = config.data;
+        expect(data.get('title')).toBe(postData.title);
+        expect(data.get('content')).toBe(postData.content);
+        expect(data.get('authorId')).toBe(postData.authorId);
+        expect(data.has('image')).toBe(false);
+        return [201, { code: 201 }];
+      });
+
+      const response = await createRequestPost(
+        postData.title,
+        postData.content,
+        postData.authorId
+      );
+      expect(response.status).toBe(201);
+    });
+
+    it('应该正确处理带图片的帖子', async () => {
+      const postData = {
+        title: '求种测试标题',
+        content: '求种测试内容',
+        authorId: 'user123',
+        selectedImage: new File(['content'], 'request.jpg')
+      };
+      
+      mockAxios.onPost('/request/posts').reply(config => {
+        const data = config.data;
+        expect(data.get('image')).toBeInstanceOf(File);
+        return [201, { code: 201 }];
+      });
+
+      const response = await createRequestPost(
+        postData.title,
+        postData.content,
+        postData.authorId,
+        postData.selectedImage
+      );
+      expect(response.status).toBe(201);
+    });
+  });
+
+  describe('getRequestPosts - 获取求种帖列表', () => {
+    it('应该支持分页参数', async () => {
+      const page = 2, size = 10;
+      mockAxios.onGet('/request/posts', { params: { page, size } })
+        .reply(200, {
+          code: 200,
+          data: {
+            records: [],
+            total: 0
+          }
+        });
+
+      const response = await getRequestPosts(page, size);
+      expect(response.status).toBe(200);
+      expect(response.data.data).toBeDefined();
+    });
+  });
+
+  describe('getRequestPostDetail - 获取求种帖详情', () => {
+    it('应该正确获取帖子详情', async () => {
+      const postId = 'req123';
+      mockAxios.onGet(`/request/posts/${postId}`)
+        .reply(200, {
+          code: 200,
+          data: {
+            post: {
+              id: postId,
+              title: '测试求种帖'
+            }
+          }
+        });
+
+      const response = await getRequestPostDetail(postId);
+      expect(response.status).toBe(200);
+      expect(response.data.data.post.id).toBe(postId);
+    });
+  });
+
+
+
+  describe('deleteRequestPost - 删除求种帖', () => {
+    it('应该正确发送删除请求', async () => {
+      const postId = 'req101';
+      const authorId = 'user1';
+      
+      mockAxios.onDelete(`/request/posts/${postId}`, {
+        params: { authorId }
+      }).reply(200, { code: 200 });
+
+      const response = await deleteRequestPost(postId, authorId);
+      expect(response.status).toBe(200);
+    });
+  });
+
+  describe('searchRequestPosts - 搜索求种帖', () => {
+    it('应该正确发送搜索请求', async () => {
+      const keyword = '测试';
+      const page = 1, size = 5;
+      
+      mockAxios.onGet('/request/posts/search', {
+        params: { keyword, page, size }
+      }).reply(200, {
+        code: 200,
+        data: {
+          records: [],
+          total: 0
+        }
+      });
+
+      const response = await searchRequestPosts(keyword, page, size);
+      expect(response.status).toBe(200);
+      expect(response.data.data).toBeDefined();
+    });
+  });
+});
\ No newline at end of file
diff --git a/src/api/torrent.js b/src/api/torrent.js
index 8a00e54..4a623d7 100644
--- a/src/api/torrent.js
+++ b/src/api/torrent.js
@@ -1,26 +1,74 @@
 // src/api/torrent.js

 import { api } from './auth'; // 复用已有的axios实例

 

-export const createTorrent = (torrentData, file) => {

+/**

+ * 创建并上传一个种子

+ * @param {File} file 种子文件 (.torrent)

+ * @param {Object} bodyObj 包含种子信息的对象,格式如下:

+ * {

+ *   torrentName: "测试下载",

+ *   description: "A high-quality 1080p version of Example Movie.",

+ *   category: "电影",

+ *   region: "USA",

+ *   resolution: "1080p",

+ *   subtitle: "English",

+ *   filePath: "D:/大学/大三_下/torrentFrom/[电影天堂www.dytt89.com]两杆大烟枪BD中英双字.mp4.torrent"

+ * }

+ */

+export async function createTorrent(file, bodyObj) {

   const formData = new FormData();

-  const token = localStorage.getItem('token');

+  formData.append('file', file);

 

-  // 添加文件

-  if (file) {

-    formData.append('file', file);

+  // 关键修改:将JSON字符串转换为Blob对象,并设置正确的Content-Type

+  formData.append('body', new Blob([JSON.stringify(bodyObj)], {

+    type: 'application/json'

+  }));

+

+  try {

+    const response = await api.post('/torrent', formData, {

+      headers: {

+      }

+    });

+    return response.data;

+  } catch (error) {

+    console.error('上传种子失败:', error);

+    throw error;

   }

-  // 通过这个方式就可以指定 ContentType 了

-  formData.append('body', JSON.stringify(torrentData))

-

-  console.log(formData);

-  return api.post('/torrent', formData, {

-    headers: {

-      'Content-Type': 'multipart/form-data',

-      'Authorization': `${token}`

-    },

-  });

 };

 

+/**

+ * 下载种子文件

+ * @param {number} torrentId 种子ID

+ * @param {string} downloadPath 下载路径

+ */

+export async function downloadTorrent(torrentId, downloadPath) {

+  try {

+    const response = await api.get(`/torrent/downloadTorrent`, {

+      params: {

+        id: torrentId,

+        downloadPath: downloadPath

+      }

+    });

+    return response.data;

+  } catch (error) {

+    console.error('下载种子失败:', error);

+    throw error;

+  }

+}

+

+/**

+ * 获取下载进度

+ */

+export async function getDownloadProgress() {

+  try {

+    const response = await api.get('/torrent/getProgress');

+    return response.data;

+  } catch (error) {

+    console.error('获取下载进度失败:', error);

+    throw error;

+  }

+}

+

 export const getTorrents = (page = 1, size = 5) => {

   return api.get('/torrent', {

     params: { page, size }

@@ -54,6 +102,61 @@
   return api.post(`/torrent/${torrentId}/comments`, commentData);

 };

 

+export const deleteTorrent = (torrentId) => {

+  return api.delete(`/torrent/deleteTorrent/${torrentId}`);

+};

+

+// // 上传种子接口

+// export const uploadTorrent = async (torrentFile, torrentData, onProgress) => {

+//   // 1. 基础验证

+//   if (!torrentFile || !torrentFile.name.endsWith('.torrent')) {

+//     throw new Error('请选择有效的.torrent文件');

+//   }

+

+//   // 2. 构建FormData

+//   const formData = new FormData();

+//   formData.append('file', torrentFile);

+  

+//   // 确保JSON内容使用正确的Content-Type

+//   const metadataBlob = new Blob(

+//     [JSON.stringify(torrentData)], 

+//     { type: 'application/json' }

+//   );

+//   formData.append('body', metadataBlob);

+

+//   // 3. 获取认证token(根据你的实际存储方式调整)

+//   const token = localStorage.getItem('token') || '';

+

+//   try {

+//     const response = await api.post('/api/torrents', formData, {

+//       headers: {

+//         'Authorization': `Bearer ${token}`,

+//       },

+//       onUploadProgress: (progressEvent) => {

+//         if (onProgress && progressEvent.total) {

+//           const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);

+//           onProgress(percent);

+//         }

+//       }

+//     });

+

+//     return response.data;

+//   } catch (error) {

+//     console.error('上传失败:', error);

+    

+//     // 增强错误处理

+//     let errorMessage = '上传失败';

+//     if (error.response) {

+//       errorMessage = error.response.data?.message || 

+//                      `服务器错误: ${error.response.status}`;

+//     } else if (error.request) {

+//       errorMessage = '网络错误,请检查连接';

+//     }

+

+//     throw new Error(errorMessage);

+//   }

+// };

+

 export const searchTorrents = (keyword, page = 1, size = 5) => {

   return api.get('/torrent/search', {

     params: { keyword, page, size }

diff --git a/src/api/torrent.test.js b/src/api/torrent.test.js
index 6515bdc..6cd481e 100644
--- a/src/api/torrent.test.js
+++ b/src/api/torrent.test.js
@@ -22,22 +22,6 @@
     localStorage.clear();

   });

 

-  describe('createTorrent - 创建种子', () => {

-    it('应该正确发送包含文件和数据的表单请求', async () => {

-      const mockFile = new File(['test'], 'test.torrent');

-      const torrentData = { name: '测试种子', description: '测试描述' };

-      const mockResponse = { code: 200, message: '创建成功' };

-  

-      mockAxios.onPost('/torrent').reply((config) => {

-        expect(config.headers['Content-Type']).toBe('multipart/form-data');

-        expect(config.headers['Authorization']).toBe('Bearer test-token'); // 修改为包含Bearer

-        return [200, mockResponse];

-      });

-  

-      const response = await createTorrent(torrentData, mockFile);

-      expect(response.data).toEqual(mockResponse);

-    });

-  });

 

   describe('getTorrents - 获取种子列表', () => {

     it('应该发送带分页参数的请求', async () => {