Преглед на файлове

✅ test(files): 完善文件服务测试用例并优化代码结构

- 实现deleteFile测试用例,覆盖文件存在、文件不存在和MinIO文件不存在三种场景
- 实现getFileUrl测试用例,验证URL生成功能和错误处理
- 重构file.service.ts中deleteFile方法的代码结构,将文件存在性检查移至try块外部
- 修复测试用例中MinioService的模拟方式,确保测试隔离性和准确性
yourname преди 2 месеца
родител
ревизия
ba3ee2b24f
променени са 2 файла, в които са добавени 90 реда и са изтрити 65 реда
  1. 82 57
      src/server/modules/files/__tests__/file.service.test.ts
  2. 8 8
      src/server/modules/files/file.service.ts

+ 82 - 57
src/server/modules/files/__tests__/file.service.test.ts

@@ -93,79 +93,104 @@ describe('FileService', () => {
     });
   });
 
-  // describe('deleteFile', () => {
-  //   it('should delete file successfully when file exists', async () => {
-  //     const mockFile = {
-  //       id: 1,
-  //       path: '1/test-file.txt',
-  //       name: 'test-file.txt'
-  //     } as File;
+  describe('deleteFile', () => {
+    it('should delete file successfully when file exists', async () => {
+      const mockFile = {
+        id: 1,
+        path: '1/test-file.txt',
+        name: 'test-file.txt'
+      } as File;
 
-  //     vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
-  //     vi.mocked(mockMinioService.objectExists).mockResolvedValue(true);
-  //     vi.mocked(mockMinioService.deleteObject).mockResolvedValue(undefined);
-  //     vi.spyOn(fileService, 'delete').mockResolvedValue(undefined);
+      const mockObjectExists = vi.fn().mockResolvedValue(true);
+      const mockDeleteObject = vi.fn().mockResolvedValue(undefined);
 
-  //     const result = await fileService.deleteFile(1);
+      vi.mocked(MinioService).mockImplementation(() => ({
+        objectExists: mockObjectExists,
+        deleteObject: mockDeleteObject,
+        bucketName: 'd8dai'
+      } as unknown as MinioService));
 
-  //     expect(fileService.getById).toHaveBeenCalledWith(1);
-  //     expect(mockMinioService.objectExists).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
-  //     expect(mockMinioService.deleteObject).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
-  //     expect(fileService.delete).toHaveBeenCalledWith(1);
-  //     expect(result).toBe(true);
-  //   });
+      const fileService = new FileService(mockDataSource);
+      vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
+      vi.spyOn(fileService, 'delete').mockResolvedValue(true);
 
-  //   it('should delete database record even when MinIO file not found', async () => {
-  //     const mockFile = {
-  //       id: 1,
-  //       path: '1/test-file.txt',
-  //       name: 'test-file.txt'
-  //     } as File;
+      const result = await fileService.deleteFile(1);
 
-  //     vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
-  //     vi.mocked(mockMinioService.objectExists).mockResolvedValue(false);
-  //     vi.spyOn(fileService, 'delete').mockResolvedValue(undefined);
+      expect(fileService.getById).toHaveBeenCalledWith(1);
+      expect(mockObjectExists).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
+      expect(mockDeleteObject).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
+      expect(fileService.delete).toHaveBeenCalledWith(1);
+      expect(result).toBe(true);
+    });
 
-  //     const result = await fileService.deleteFile(1);
+    it('should delete database record even when MinIO file not found', async () => {
+      const mockFile = {
+        id: 1,
+        path: '1/test-file.txt',
+        name: 'test-file.txt'
+      } as File;
 
-  //     expect(mockMinioService.deleteObject).not.toHaveBeenCalled();
-  //     expect(fileService.delete).toHaveBeenCalledWith(1);
-  //     expect(result).toBe(true);
-  //     expect(logger.error).toHaveBeenCalled();
-  //   });
+      const mockObjectExists = vi.fn().mockResolvedValue(false);
 
-  //   it('should throw error when file not found', async () => {
-  //     vi.spyOn(fileService, 'getById').mockResolvedValue(null);
+      vi.mocked(MinioService).mockImplementation(() => ({
+        objectExists: mockObjectExists,
+        deleteObject: vi.fn(),
+        bucketName: 'd8dai'
+      } as unknown as MinioService));
 
-  //     await expect(fileService.deleteFile(999)).rejects.toThrow('文件不存在');
-  //   });
-  // });
+      const fileService = new FileService(mockDataSource);
+      vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
+      vi.spyOn(fileService, 'delete').mockResolvedValue(true);
 
-  // describe('getFileUrl', () => {
-  //   it('should return file URL successfully', async () => {
-  //     const mockFile = {
-  //       id: 1,
-  //       path: '1/test-file.txt'
-  //     } as File;
+      const result = await fileService.deleteFile(1);
 
-  //     const mockPresignedUrl = 'https://minio.example.com/presigned-url';
+      expect(mockObjectExists).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
+      expect(fileService.delete).toHaveBeenCalledWith(1);
+      expect(result).toBe(true);
+      expect(logger.error).toHaveBeenCalled();
+    });
 
-  //     vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
-  //     vi.mocked(mockMinioService.getPresignedFileUrl).mockResolvedValue(mockPresignedUrl);
+    it('should throw error when file not found', async () => {
+      const fileService = new FileService(mockDataSource);
+      vi.spyOn(fileService, 'getById').mockResolvedValue(null);
 
-  //     const result = await fileService.getFileUrl(1);
+      await expect(fileService.deleteFile(999)).rejects.toThrow('文件不存在');
+    });
+  });
 
-  //     expect(fileService.getById).toHaveBeenCalledWith(1);
-  //     expect(mockMinioService.getPresignedFileUrl).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
-  //     expect(result).toBe(mockPresignedUrl);
-  //   });
+  describe('getFileUrl', () => {
+    it('should return file URL successfully', async () => {
+      const mockFile = {
+        id: 1,
+        path: '1/test-file.txt'
+      } as File;
 
-  //   it('should throw error when file not found', async () => {
-  //     vi.spyOn(fileService, 'getById').mockResolvedValue(null);
+      const mockPresignedUrl = 'https://minio.example.com/presigned-url';
 
-  //     await expect(fileService.getFileUrl(999)).rejects.toThrow('文件不存在');
-  //   });
-  // });
+      const mockGetPresignedFileUrl = vi.fn().mockResolvedValue(mockPresignedUrl);
+
+      vi.mocked(MinioService).mockImplementation(() => ({
+        getPresignedFileUrl: mockGetPresignedFileUrl,
+        bucketName: 'd8dai'
+      } as unknown as MinioService));
+
+      const fileService = new FileService(mockDataSource);
+      vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
+
+      const result = await fileService.getFileUrl(1);
+
+      expect(fileService.getById).toHaveBeenCalledWith(1);
+      expect(mockGetPresignedFileUrl).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
+      expect(result).toBe(mockPresignedUrl);
+    });
+
+    it('should throw error when file not found', async () => {
+      const fileService = new FileService(mockDataSource);
+      vi.spyOn(fileService, 'getById').mockResolvedValue(null);
+
+      await expect(fileService.getFileUrl(999)).rejects.toThrow('文件不存在');
+    });
+  });
 
   // describe('getFileDownloadUrl', () => {
   //   it('should return download URL with filename', async () => {

+ 8 - 8
src/server/modules/files/file.service.ts

@@ -51,13 +51,13 @@ export class FileService extends GenericCrudService<File> {
    * 删除文件记录及对应的MinIO文件
    */
   async deleteFile(id: number) {
+    // 获取文件记录
+    const file = await this.getById(id);
+    if (!file) {
+      throw new Error('文件不存在');
+    }
+
     try {
-      // 获取文件记录
-      const file = await this.getById(id);
-      if (!file) {
-        throw new Error('文件不存在');
-      }
-      
       // 验证文件是否存在于MinIO
       const fileExists = await this.minioService.objectExists(this.minioService.bucketName, file.path);
       if (!fileExists) {
@@ -67,10 +67,10 @@ export class FileService extends GenericCrudService<File> {
         // 从MinIO删除文件
         await this.minioService.deleteObject(this.minioService.bucketName, file.path);
       }
-      
+
       // 从数据库删除记录
       await this.delete(id);
-      
+
       return true;
     } catch (error) {
       logger.error('Failed to delete file:', error);