files.integration.test.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { describe, it, expect, beforeEach, vi } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import {
  4. IntegrationTestDatabase,
  5. setupIntegrationDatabaseHooks,
  6. TestDataFactory
  7. } from '../utils/integration-test-db';
  8. import { fileApiRoutes } from '../../src/api';
  9. import { AuthService } from '@d8d/auth-module';
  10. import { UserService } from '@d8d/user-module';
  11. import { MinioService } from '@d8d/file-module';
  12. // Mock MinIO service to avoid real connections in tests
  13. vi.mock('@d8d/file-module', async (importOriginal) => {
  14. const actual = await importOriginal();
  15. return {
  16. ...actual,
  17. MinioService: vi.fn(() => ({
  18. bucketName: 'd8dai',
  19. ensureBucketExists: vi.fn().mockResolvedValue(true),
  20. objectExists: vi.fn().mockResolvedValue(false),
  21. deleteObject: vi.fn().mockResolvedValue(true),
  22. generateUploadPolicy: vi.fn().mockResolvedValue({
  23. 'x-amz-algorithm': 'AWS4-HMAC-SHA256',
  24. 'x-amz-credential': 'test-credential',
  25. 'x-amz-date': '20250101T120000Z',
  26. policy: 'test-policy',
  27. 'x-amz-signature': 'test-signature',
  28. host: 'https://minio.example.com',
  29. key: 'test-key',
  30. bucket: 'd8dai'
  31. }),
  32. getPresignedFileUrl: vi.fn().mockResolvedValue('https://minio.example.com/presigned-url'),
  33. getPresignedFileDownloadUrl: vi.fn().mockResolvedValue('https://minio.example.com/download-url'),
  34. createMultipartUpload: vi.fn().mockResolvedValue('test-upload-id'),
  35. generateMultipartUploadUrls: vi.fn().mockResolvedValue(['https://minio.example.com/part1', 'https://minio.example.com/part2']),
  36. completeMultipartUpload: vi.fn().mockResolvedValue({
  37. size: 104857600
  38. }),
  39. createObject: vi.fn().mockResolvedValue('https://minio.example.com/d8dai/test-file'),
  40. getFileUrl: vi.fn().mockReturnValue('https://minio.example.com/d8dai/test-file')
  41. }))
  42. };
  43. });
  44. // 设置集成测试钩子
  45. setupIntegrationDatabaseHooks()
  46. describe('文件API连通性测试', () => {
  47. let client: ReturnType<typeof testClient<typeof fileApiRoutes>>['api']['v1'];
  48. let testToken: string;
  49. beforeEach(async () => {
  50. // 创建测试客户端
  51. client = testClient(fileApiRoutes).api.v1;
  52. // 创建测试用户并生成token
  53. const dataSource = await IntegrationTestDatabase.getDataSource();
  54. const userService = new UserService(dataSource);
  55. const authService = new AuthService(userService);
  56. // 确保admin用户存在
  57. const user = await authService.ensureAdminExists();
  58. // 生成admin用户的token
  59. testToken = authService.generateToken(user);
  60. });
  61. describe('文件上传策略API连通性', () => {
  62. it('应该成功响应文件上传策略请求', async () => {
  63. const fileData = {
  64. name: 'test.txt',
  65. type: 'text/plain',
  66. size: 1024,
  67. path: '/uploads/test.txt',
  68. description: 'Test file'
  69. };
  70. const response = await client.files['upload-policy'].$post({
  71. json: fileData
  72. },
  73. {
  74. headers: {
  75. 'Authorization': `Bearer ${testToken}`
  76. }
  77. });
  78. expect(response.status).toBe(200);
  79. });
  80. it('应该拒绝无认证令牌的请求', async () => {
  81. const fileData = {
  82. name: 'test.txt',
  83. type: 'text/plain',
  84. size: 1024,
  85. path: '/uploads/test.txt'
  86. };
  87. const response = await client.files['upload-policy'].$post({
  88. json: fileData
  89. });
  90. expect(response.status).toBe(401);
  91. });
  92. });
  93. describe('文件URL生成API连通性', () => {
  94. it('应该成功响应文件URL生成请求', async () => {
  95. const dataSource = await IntegrationTestDatabase.getDataSource();
  96. if (!dataSource) throw new Error('Database not initialized');
  97. const testFile = await TestDataFactory.createTestFile(dataSource, {
  98. name: 'testfile_url.txt',
  99. type: 'text/plain',
  100. size: 1024,
  101. path: 'testfile_url.txt'
  102. });
  103. const response = await client.files[':id']['url'].$get({
  104. param: { id: testFile.id }
  105. },
  106. {
  107. headers: {
  108. 'Authorization': `Bearer ${testToken}`
  109. }
  110. });
  111. expect(response.status).toBe(200);
  112. });
  113. it('应该返回404当文件不存在时', async () => {
  114. const response = await client.files[':id']['url'].$get({
  115. param: { id: 999999 }
  116. },
  117. {
  118. headers: {
  119. 'Authorization': `Bearer ${testToken}`
  120. }
  121. });
  122. expect(response.status).toBe(404);
  123. });
  124. });
  125. describe('文件下载URL生成API连通性', () => {
  126. it('应该成功响应文件下载URL生成请求', async () => {
  127. const dataSource = await IntegrationTestDatabase.getDataSource();
  128. if (!dataSource) throw new Error('Database not initialized');
  129. const testFile = await TestDataFactory.createTestFile(dataSource, {
  130. name: 'testfile_download.txt',
  131. type: 'text/plain',
  132. size: 1024,
  133. path: 'testfile_download.txt'
  134. });
  135. const response = await client.files[':id']['download'].$get({
  136. param: { id: testFile.id }
  137. },
  138. {
  139. headers: {
  140. 'Authorization': `Bearer ${testToken}`
  141. }
  142. });
  143. expect(response.status).toBe(200);
  144. });
  145. it('应该返回404当文件不存在时', async () => {
  146. const response = await client.files[':id']['download'].$get({
  147. param: { id: 999999 }
  148. },
  149. {
  150. headers: {
  151. 'Authorization': `Bearer ${testToken}`
  152. }
  153. });
  154. expect(response.status).toBe(404);
  155. });
  156. });
  157. describe('文件CRUD API连通性', () => {
  158. it('应该成功响应文件列表请求', async () => {
  159. const dataSource = await IntegrationTestDatabase.getDataSource();
  160. if (!dataSource) throw new Error('Database not initialized');
  161. await TestDataFactory.createTestFile(dataSource, {
  162. name: 'file1.txt',
  163. type: 'text/plain',
  164. size: 1024,
  165. path: 'file1.txt'
  166. });
  167. const response = await client.files.$get({
  168. query: {}
  169. },
  170. {
  171. headers: {
  172. 'Authorization': `Bearer ${testToken}`
  173. }
  174. });
  175. expect(response.status).toBe(200);
  176. });
  177. it('应该成功响应单个文件详情请求', async () => {
  178. const dataSource = await IntegrationTestDatabase.getDataSource();
  179. if (!dataSource) throw new Error('Database not initialized');
  180. const testFile = await TestDataFactory.createTestFile(dataSource, {
  181. name: 'testfile_detail.txt',
  182. type: 'text/plain',
  183. size: 1024,
  184. path: 'testfile_detail.txt'
  185. });
  186. const response = await client.files[':id'].$get({
  187. param: { id: testFile.id }
  188. },
  189. {
  190. headers: {
  191. 'Authorization': `Bearer ${testToken}`
  192. }
  193. });
  194. expect(response.status).toBe(200);
  195. });
  196. });
  197. describe('多部分上传API连通性', () => {
  198. it('应该成功响应多部分上传策略请求', async () => {
  199. const multipartData = {
  200. fileKey: 'large-file.zip',
  201. totalSize: 1024 * 1024 * 100, // 100MB
  202. partSize: 1024 * 1024 * 20, // 20MB
  203. name: 'large-file.zip',
  204. type: 'application/zip'
  205. };
  206. const response = await client.files['multipart-policy'].$post({
  207. json: multipartData
  208. },
  209. {
  210. headers: {
  211. 'Authorization': `Bearer ${testToken}`
  212. }
  213. });
  214. expect(response.status).toBe(200);
  215. });
  216. });
  217. });