前段

Change-Id: I718d4d07ea03c6d2b6bcbd4d426c5d1af2201bf4
diff --git a/src/api/auth.js b/src/api/auth.js
new file mode 100644
index 0000000..ad845bb
--- /dev/null
+++ b/src/api/auth.js
@@ -0,0 +1,37 @@
+// src/api/auth.js

+import axios from 'axios';

+

+// 创建并导出 axios 实例

+export const api = axios.create({

+  baseURL: 'http://localhost:8088',

+  timeout: 5000,

+});

+

+// 请求拦截器

+api.interceptors.request.use(config => {

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

+  if (token) {

+    config.headers.Authorization = `Bearer ${token}`;

+  }

+  return config;

+});

+

+export const login = async (username, password) => {

+  const response = await api.post('/user/login', null, {

+    params: { username, password }

+  });

+  if (response.data.code === 200 && response.data.data.token) {

+    localStorage.setItem('token', response.data.data.token);

+  }

+  return response;

+};

+

+export const register = (username, password, code) => {

+  return api.post('/user/regist', null, {

+    params: { username, password, code }

+  });

+};

+

+export const getUserInfo = (token) => {

+  return api.get('/user/info', { params: { token } });

+};
\ No newline at end of file
diff --git a/src/api/auth.test.js b/src/api/auth.test.js
new file mode 100644
index 0000000..c9ace4d
--- /dev/null
+++ b/src/api/auth.test.js
@@ -0,0 +1,114 @@
+import MockAdapter from 'axios-mock-adapter';

+import { api, login, register, getUserInfo } from './auth';

+

+describe('auth API', () => {

+  let mockAxios;

+

+  beforeEach(() => {

+    // 确保使用我们导出的 api 实例

+    mockAxios = new MockAdapter(api);

+    localStorage.clear();

+  });

+

+  afterEach(() => {

+    mockAxios.restore();

+  });

+

+  describe('login', () => {

+    it('should send login request with username and password', async () => {

+        const mockResponse = {

+          code: 200,

+          data: { 

+            token: 'mock-token',

+            // 确保响应结构与实际API一致

+            userInfo: { username: 'testuser' }

+          },

+          message: '登录成功'

+        };

+        

+        mockAxios.onPost('/user/login').reply(200, mockResponse);

+    

+        const response = await login('testuser', 'testpass');

+        

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

+        // 检查token是否存入localStorage

+        expect(localStorage.getItem('token')).toBe('mock-token');

+      });

+    

+

+    it('should handle login failure', async () => {

+      mockAxios.onPost('/user/login').reply(401);

+      

+      await expect(login('wronguser', 'wrongpass')).rejects.toThrow();

+    });

+  });

+

+  describe('register', () => {

+    it('should send register request with username, password and code', async () => {

+      const mockResponse = {

+        code: 200,

+        message: '注册成功'

+      };

+      

+      mockAxios.onPost('/user/regist').reply(200, mockResponse);

+

+      const response = await register('newuser', 'newpass', 'invite123');

+      

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

+    });

+

+    it('should handle registration failure', async () => {

+      mockAxios.onPost('/user/regist').reply(400);

+      

+      await expect(register('newuser', 'newpass', 'wrongcode')).rejects.toThrow();

+    });

+  });

+

+  describe('getUserInfo', () => {

+    it('should send request with token to get user info', async () => {

+      const mockResponse = {

+        code: 200,

+        data: { username: 'testuser', role: 'user' }

+      };

+      

+      mockAxios.onGet('/user/info').reply(200, mockResponse);

+

+      const response = await getUserInfo('test-token');

+      

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

+    });

+

+    it('should handle unauthorized request', async () => {

+      mockAxios.onGet('/user/info').reply(401);

+      

+      await expect(getUserInfo('invalid-token')).rejects.toThrow();

+    });

+  });

+

+  describe('request interceptor', () => {

+    it('should add Authorization header when token exists', async () => {

+      localStorage.setItem('token', 'test-token');

+      const mockResponse = { data: 'success' };

+      

+      mockAxios.onGet('/test').reply((config) => {

+        expect(config.headers.Authorization).toBe('Bearer test-token');

+        return [200, mockResponse];

+      });

+

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

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

+    });

+

+    it('should not add Authorization header when token does not exist', async () => {

+      const mockResponse = { data: 'success' };

+      

+      mockAxios.onGet('/test').reply((config) => {

+        expect(config.headers.Authorization).toBeUndefined();

+        return [200, mockResponse];

+      });

+

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

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

+    });

+  });

+});
\ No newline at end of file
diff --git a/src/api/helpComment.js b/src/api/helpComment.js
new file mode 100644
index 0000000..e711587
--- /dev/null
+++ b/src/api/helpComment.js
@@ -0,0 +1,13 @@
+import { api } from './auth';

+

+export const likePostComment = (commentId) => {

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

+};

+

+export const getCommentReplies = (commentId) => {

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

+};

+

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

+  return api.post(`/help/comments/${commentId}/replies`, replyData);

+};
\ No newline at end of file
diff --git a/src/api/helpComment.test.js b/src/api/helpComment.test.js
new file mode 100644
index 0000000..2b7be4c
--- /dev/null
+++ b/src/api/helpComment.test.js
@@ -0,0 +1,45 @@
+import MockAdapter from 'axios-mock-adapter';

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

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

+

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

+  let mockAxios;

+

+  beforeEach(() => {

+    mockAxios = new MockAdapter(api);

+  });

+

+  afterEach(() => {

+    mockAxios.restore();

+  });

+

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

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

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

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

+

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

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

+    });

+  });

+

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

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

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

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

+

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

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

+    });

+  });

+

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

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

+      const testData = { content: '测试回复', author: 'user1' };

+      mockAxios.onPost('/help/comments/789/replies', testData).reply(200, { code: 200 });

+

+      const response = await addCommentReply('789', testData);

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

+    });

+  });

+});
\ No newline at end of file
diff --git a/src/api/helpPost.js b/src/api/helpPost.js
new file mode 100644
index 0000000..426e92b
--- /dev/null
+++ b/src/api/helpPost.js
@@ -0,0 +1,28 @@
+// src/api/helpPost.js

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

+

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

+  return api.post('/help/posts', { 

+    title, 

+    content, 

+    authorId 

+  });

+};

+

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

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

+    params: { page, size }

+  });

+};

+

+export const getPostDetail = (postId) => {

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

+};

+

+export const likePost = (postId) => {

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

+};

+

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

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

+};
\ No newline at end of file
diff --git a/src/api/helpPost.test.js b/src/api/helpPost.test.js
new file mode 100644
index 0000000..e163090
--- /dev/null
+++ b/src/api/helpPost.test.js
@@ -0,0 +1,60 @@
+import MockAdapter from 'axios-mock-adapter';

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

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

+

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

+  let mockAxios;

+

+  beforeEach(() => {

+    mockAxios = new MockAdapter(api);

+  });

+

+  afterEach(() => {

+    mockAxios.restore();

+  });

+

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

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

+      const postData = {

+        title: '测试标题',

+        content: '测试内容',

+        authorId: 'user123'

+      };

+      mockAxios.onPost('/help/posts', postData).reply(201, { code: 201 });

+

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

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

+    });

+  });

+

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

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

+      const page = 2, size = 10;

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

+        code: 200,

+        data: []

+      });

+

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

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

+    });

+  });

+

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

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

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

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

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

+    });

+  });

+

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

+    it('应该正确发送评论数据', async () => {

+      const comment = { content: '测试评论', author: 'user1' };

+      mockAxios.onPost('/help/posts/post456/comments', comment).reply(200, { code: 200 });

+      

+      const response = await addPostComment('post456', comment);

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

+    });

+  });

+});
\ No newline at end of file
diff --git a/src/api/torrent.js b/src/api/torrent.js
new file mode 100644
index 0000000..7118436
--- /dev/null
+++ b/src/api/torrent.js
@@ -0,0 +1,48 @@
+// src/api/torrent.js

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

+

+export const createTorrent = (torrentName, description, username, category, region, resolution, subtitle, filePath) => {

+  return api.post('/torrent', { 

+    torrentName, 

+    description,

+    username,

+    category,

+    region,

+    resolution,

+    subtitle,

+    filePath

+  });

+};

+

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

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

+    params: { page, size }

+  });

+};

+

+export const getTorrentDetail = (torrentId) => {

+  return api.get(`/torrent/${torrentId}`).then(response => {

+    // 确保数据结构一致

+    if (response.data && response.data.data) {

+      return {

+        ...response,

+        data: {

+          ...response.data,

+          data: {

+            torrent: response.data.data.post || response.data.data.torrent,

+            comments: response.data.data.comments || []

+          }

+        }

+      };

+    }

+    return response;

+  });

+};

+

+export const likeTorrent = (torrentId) => {

+  return api.post(`/torrent/${torrentId}/like`);

+};

+

+export const addTorrentComment = (torrentId, commentData) => {

+  return api.post(`/torrent/${torrentId}/comments`, commentData);

+};
\ No newline at end of file
diff --git a/src/api/torrent.test.js b/src/api/torrent.test.js
new file mode 100644
index 0000000..c4de6c5
--- /dev/null
+++ b/src/api/torrent.test.js
@@ -0,0 +1,58 @@
+import MockAdapter from 'axios-mock-adapter';

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

+import { createTorrent, getTorrents, getTorrentDetail, likeTorrent, addTorrentComment } from './torrent';

+

+describe('种子资源API', () => {

+  let mockAxios;

+

+  beforeEach(() => {

+    mockAxios = new MockAdapter(api);

+  });

+

+  afterEach(() => {

+    mockAxios.restore();

+  });

+

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

+    it('应该发送完整的种子数据', async () => {

+      const torrentData = {

+        torrentName: '测试种子',

+        description: '描述内容',

+        username: 'user1',

+        category: '电影',

+        region: '美国',

+        resolution: '1080p',

+        subtitle: '中文字幕',

+        filePath: '/path/to/file'

+      };

+      mockAxios.onPost('/torrent', torrentData).reply(201, { code: 201 });

+

+      const response = await createTorrent(...Object.values(torrentData));

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

+    });

+  });

+

+  describe('getTorrentDetail - 获取种子详情', () => {

+    it('应该规范化返回的数据结构', async () => {

+      const mockData = {

+        data: {

+          post: { id: '123', name: '测试种子' },

+          comments: [{ id: '1', content: '评论1' }]

+        }

+      };

+      mockAxios.onGet('/torrent/123').reply(200, mockData);

+

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

+      expect(response.data.data.torrent.name).toBe('测试种子');

+      expect(response.data.data.comments).toHaveLength(1);

+    });

+  });

+

+  describe('likeTorrent - 点赞种子', () => {

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

+      mockAxios.onPost('/torrent/t123/like').reply(200, { code: 200 });

+      const response = await likeTorrent('t123');

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

+    });

+  });

+});
\ No newline at end of file
diff --git a/src/api/torrentComment.js b/src/api/torrentComment.js
new file mode 100644
index 0000000..f15ce4b
--- /dev/null
+++ b/src/api/torrentComment.js
@@ -0,0 +1,13 @@
+import { api } from './auth';

+

+export const likeTorrentComment = (commentId) => {

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

+};

+

+export const getCommentReplies = (commentId) => {

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

+};

+

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

+  return api.post(`/torrent/comments/${commentId}/replies`, replyData);

+};
\ No newline at end of file
diff --git a/src/api/torrentComment.test.js b/src/api/torrentComment.test.js
new file mode 100644
index 0000000..585b0d3
--- /dev/null
+++ b/src/api/torrentComment.test.js
@@ -0,0 +1,60 @@
+import MockAdapter from 'axios-mock-adapter';

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

+import { likeTorrentComment, getCommentReplies, addCommentReply } from './torrentComment';

+

+describe('种子评论API', () => {

+  let mockAxios;

+

+  beforeEach(() => {

+    mockAxios = new MockAdapter(api);

+  });

+

+  afterEach(() => {

+    mockAxios.restore();

+  });

+

+  describe('likeTorrentComment - 点赞种子评论', () => {

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

+      const commentId = '123';

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

+      

+      mockAxios.onPost(`/torrent/comments/${commentId}/like`).reply(200, mockResponse);

+

+      const response = await likeTorrentComment(commentId);

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

+    });

+

+    it('应该处理点赞失败的情况', async () => {

+      mockAxios.onPost('/torrent/comments/123/like').reply(500);

+      await expect(likeTorrentComment('123')).rejects.toThrow();

+    });

+  });

+

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

+    it('应该成功获取回复列表', async () => {

+      const commentId = '456';

+      const mockResponse = {

+        code: 200,

+        data: [{ id: '1', content: '回复1' }, { id: '2', content: '回复2' }]

+      };

+      

+      mockAxios.onGet(`/torrent/comments/${commentId}/replies`).reply(200, mockResponse);

+

+      const response = await getCommentReplies(commentId);

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

+    });

+  });

+

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

+    it('应该成功添加回复', async () => {

+      const commentId = '789';

+      const replyData = { content: '测试回复' };

+      const mockResponse = { code: 200, data: { id: '3', ...replyData } };

+      

+      mockAxios.onPost(`/torrent/comments/${commentId}/replies`, replyData).reply(200, mockResponse);

+

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

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

+    });

+  });

+});
\ No newline at end of file