Jelajahi Sumber

🗑️ chore(server): 删除重复的单元测试文件

- 删除file.service.test.ts(已在file-module包中实现)
- 删除minio.service.test.ts(已在file-module包中实现)
- 删除user.service.test.ts(已在user-module包中实现)

这些测试在各目的业务模块包中已有完整实现,避免重复测试。

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 bulan lalu
induk
melakukan
7cd8fff076

+ 0 - 424
packages/server/tests/unit/modules/file.service.test.ts

@@ -1,424 +0,0 @@
-import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
-import { DataSource } from 'typeorm';
-import { FileService } from '@/modules/files/file.service';
-import { File } from '@/modules/files/file.entity';
-import { MinioService } from '@/modules/files/minio.service';
-import { logger } from '@/utils/logger';
-
-// Mock dependencies
-vi.mock('@/modules/files/minio.service');
-vi.mock('@/utils/logger');
-vi.mock('uuid', () => ({
-  v4: () => 'test-uuid-123'
-}));
-
-describe('FileService', () => {
-  let mockDataSource: DataSource;
-
-  beforeEach(() => {
-    mockDataSource = {
-      getRepository: vi.fn(() => ({
-        findOneBy: vi.fn(),
-        save: vi.fn()
-      }))
-    } as unknown as DataSource;
-
-  });
-
-  afterEach(() => {
-    vi.clearAllMocks();
-  });
-
-  describe('createFile', () => {
-    it('should create file with upload policy successfully', async () => {
-      const mockFileData = {
-        name: 'test.txt',
-        type: 'text/plain',
-        size: 1024,
-        uploadUserId: 1
-      };
-
-      const mockUploadPolicy = {
-        'x-amz-algorithm': 'test-algorithm',
-        'x-amz-credential': 'test-credential',
-        host: 'https://minio.example.com'
-      };
-
-      const mockSavedFile = {
-        id: 1,
-        ...mockFileData,
-        path: '1/test-uuid-123-test.txt',
-        uploadTime: new Date(),
-        createdAt: new Date(),
-        updatedAt: new Date()
-      };
-
-
-      const mockGenerateUploadPolicy = vi.fn().mockResolvedValue(mockUploadPolicy);
-      vi.mocked(MinioService).mockImplementation(() => ({
-        generateUploadPolicy: mockGenerateUploadPolicy
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-
-      // Mock GenericCrudService methods
-      vi.spyOn(fileService, 'create').mockResolvedValue(mockSavedFile as File);
-
-      const result = await fileService.createFile(mockFileData);
-
-      expect(mockGenerateUploadPolicy).toHaveBeenCalledWith('1/test-uuid-123-test.txt');
-      expect(fileService.create).toHaveBeenCalledWith(expect.objectContaining({
-        name: 'test.txt',
-        path: '1/test-uuid-123-test.txt',
-        uploadUserId: 1
-      }));
-      expect(result).toEqual({
-        file: mockSavedFile,
-        uploadPolicy: mockUploadPolicy
-      });
-    });
-
-    it('should handle errors during file creation', async () => {
-      const mockFileData = {
-        name: 'test.txt',
-        uploadUserId: 1
-      };
-
-      const mockGenerateUploadPolicy = vi.fn().mockRejectedValue(new Error('MinIO error'));
-      vi.mocked(MinioService).mockImplementation(() => ({
-        generateUploadPolicy: mockGenerateUploadPolicy
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-
-      await expect(fileService.createFile(mockFileData)).rejects.toThrow('文件创建失败');
-      expect(logger.error).toHaveBeenCalled();
-    });
-  });
-
-  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;
-
-      const mockObjectExists = vi.fn().mockResolvedValue(true);
-      const mockDeleteObject = vi.fn().mockResolvedValue(undefined);
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        objectExists: mockObjectExists,
-        deleteObject: mockDeleteObject,
-        bucketName: 'd8dai'
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-      vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
-      vi.spyOn(fileService, 'delete').mockResolvedValue(true);
-
-      const result = await fileService.deleteFile(1);
-
-      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);
-    });
-
-    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 mockObjectExists = vi.fn().mockResolvedValue(false);
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        objectExists: mockObjectExists,
-        deleteObject: vi.fn(),
-        bucketName: 'd8dai'
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-      vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
-      vi.spyOn(fileService, 'delete').mockResolvedValue(true);
-
-      const result = await fileService.deleteFile(1);
-
-      expect(mockObjectExists).toHaveBeenCalledWith('d8dai', '1/test-file.txt');
-      expect(fileService.delete).toHaveBeenCalledWith(1);
-      expect(result).toBe(true);
-      expect(logger.error).toHaveBeenCalled();
-    });
-
-    it('should throw error when file not found', async () => {
-      const fileService = new FileService(mockDataSource);
-      vi.spyOn(fileService, 'getById').mockResolvedValue(null);
-
-      await expect(fileService.deleteFile(999)).rejects.toThrow('文件不存在');
-    });
-  });
-
-  describe('getFileUrl', () => {
-    it('should return file URL successfully', async () => {
-      const mockFile = {
-        id: 1,
-        path: '1/test-file.txt'
-      } as File;
-
-      const mockPresignedUrl = 'https://minio.example.com/presigned-url';
-
-      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 () => {
-      const mockFile = {
-        id: 1,
-        path: '1/test-file.txt',
-        name: '测试文件.txt'
-      } as File;
-
-      const mockPresignedUrl = 'https://minio.example.com/download-url';
-
-      const mockGetPresignedFileDownloadUrl = vi.fn().mockResolvedValue(mockPresignedUrl);
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        getPresignedFileDownloadUrl: mockGetPresignedFileDownloadUrl,
-        bucketName: 'd8dai'
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-      vi.spyOn(fileService, 'getById').mockResolvedValue(mockFile);
-
-      const result = await fileService.getFileDownloadUrl(1);
-
-      expect(fileService.getById).toHaveBeenCalledWith(1);
-      expect(mockGetPresignedFileDownloadUrl).toHaveBeenCalledWith(
-        'd8dai',
-        '1/test-file.txt',
-        '测试文件.txt'
-      );
-      expect(result).toEqual({
-        url: mockPresignedUrl,
-        filename: '测试文件.txt'
-      });
-    });
-
-    it('should throw error when file not found', async () => {
-      const fileService = new FileService(mockDataSource);
-      vi.spyOn(fileService, 'getById').mockResolvedValue(null);
-
-      await expect(fileService.getFileDownloadUrl(999)).rejects.toThrow('文件不存在');
-    });
-  });
-
-  describe('createMultipartUploadPolicy', () => {
-    it('should create multipart upload policy successfully', async () => {
-      const mockFileData = {
-        name: 'large-file.zip',
-        type: 'application/zip',
-        uploadUserId: 1
-      };
-
-      const mockUploadId = 'upload-123';
-      const mockUploadUrls = ['url1', 'url2', 'url3'];
-      const mockSavedFile = {
-        id: 1,
-        ...mockFileData,
-        path: '1/test-uuid-123-large-file.zip',
-        uploadTime: new Date(),
-        createdAt: new Date(),
-        updatedAt: new Date()
-      } as File;
-
-      const mockCreateMultipartUpload = vi.fn().mockResolvedValue(mockUploadId);
-      const mockGenerateMultipartUploadUrls = vi.fn().mockResolvedValue(mockUploadUrls);
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        createMultipartUpload: mockCreateMultipartUpload,
-        generateMultipartUploadUrls: mockGenerateMultipartUploadUrls,
-        bucketName: 'd8dai'
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-      vi.spyOn(fileService, 'create').mockResolvedValue(mockSavedFile);
-
-      const result = await fileService.createMultipartUploadPolicy(mockFileData, 3);
-
-      expect(mockCreateMultipartUpload).toHaveBeenCalledWith('d8dai', '1/test-uuid-123-large-file.zip');
-      expect(mockGenerateMultipartUploadUrls).toHaveBeenCalledWith(
-        'd8dai',
-        '1/test-uuid-123-large-file.zip',
-        mockUploadId,
-        3
-      );
-      expect(result).toEqual({
-        file: mockSavedFile,
-        uploadId: mockUploadId,
-        uploadUrls: mockUploadUrls,
-        bucket: 'd8dai',
-        key: '1/test-uuid-123-large-file.zip'
-      });
-    });
-
-    it('should handle errors during multipart upload creation', async () => {
-      const mockFileData = {
-        name: 'large-file.zip',
-        uploadUserId: 1
-      };
-
-      const mockCreateMultipartUpload = vi.fn().mockRejectedValue(new Error('MinIO error'));
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        createMultipartUpload: mockCreateMultipartUpload,
-        bucketName: 'd8dai'
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-
-      await expect(fileService.createMultipartUploadPolicy(mockFileData, 3)).rejects.toThrow('创建多部分上传策略失败');
-      expect(logger.error).toHaveBeenCalled();
-    });
-  });
-
-  describe('completeMultipartUpload', () => {
-    it('should complete multipart upload successfully', async () => {
-      const uploadData = {
-        uploadId: 'upload-123',
-        bucket: 'd8dai',
-        key: '1/test-file.txt',
-        parts: [
-          { partNumber: 1, etag: 'etag1' },
-          { partNumber: 2, etag: 'etag2' }
-        ]
-      };
-
-      const mockFile = {
-        id: 1,
-        path: '1/test-file.txt',
-        size: 0,
-        updatedAt: new Date()
-      } as File;
-
-      const mockCompleteResult = { size: 2048 };
-      const mockFileUrl = 'https://minio.example.com/file.txt';
-
-      const mockCompleteMultipartUpload = vi.fn().mockResolvedValue(mockCompleteResult);
-      const mockGetFileUrl = vi.fn().mockReturnValue(mockFileUrl);
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        completeMultipartUpload: mockCompleteMultipartUpload,
-        getFileUrl: mockGetFileUrl
-      } as unknown as MinioService));
-
-      const mockRepository = {
-        findOneBy: vi.fn().mockResolvedValue(mockFile),
-        save: vi.fn().mockResolvedValue({ ...mockFile, size: 2048 } as File)
-      };
-
-      mockDataSource.getRepository = vi.fn().mockReturnValue(mockRepository);
-      const fileService = new FileService(mockDataSource);
-
-      const result = await fileService.completeMultipartUpload(uploadData);
-
-      expect(mockCompleteMultipartUpload).toHaveBeenCalledWith(
-        'd8dai',
-        '1/test-file.txt',
-        'upload-123',
-        [{ PartNumber: 1, ETag: 'etag1' }, { PartNumber: 2, ETag: 'etag2' }]
-      );
-      expect(mockRepository.findOneBy).toHaveBeenCalledWith({ path: '1/test-file.txt' });
-      expect(mockRepository.save).toHaveBeenCalledWith(expect.objectContaining({
-        size: 2048
-      }));
-      expect(result).toEqual({
-        fileId: 1,
-        url: mockFileUrl,
-        key: '1/test-file.txt',
-        size: 2048
-      });
-    });
-
-    it('should throw error when file record not found', async () => {
-      const uploadData = {
-        uploadId: 'upload-123',
-        bucket: 'd8dai',
-        key: '1/nonexistent.txt',
-        parts: [{ partNumber: 1, etag: 'etag1' }]
-      };
-
-      const mockCompleteMultipartUpload = vi.fn().mockResolvedValue({ size: 1024 });
-
-      vi.mocked(MinioService).mockImplementation(() => ({
-        completeMultipartUpload: mockCompleteMultipartUpload
-      } as unknown as MinioService));
-
-      const mockRepository = {
-        findOneBy: vi.fn().mockResolvedValue(null)
-      };
-
-      mockDataSource.getRepository = vi.fn().mockReturnValue(mockRepository);
-      const fileService = new FileService(mockDataSource);
-
-      await expect(fileService.completeMultipartUpload(uploadData)).rejects.toThrow('文件记录不存在');
-    });
-
-    it('should handle errors during completion', async () => {
-      const uploadData = {
-        uploadId: 'upload-123',
-        bucket: 'd8dai',
-        key: '1/test-file.txt',
-        parts: [{ partNumber: 1, etag: 'etag1' }]
-      };
-
-      const mockFile = {
-        id: 1,
-        path: '1/test-file.txt',
-        size: 0,
-        updatedAt: new Date()
-      } as File;
-
-      const mockRepository = {
-        findOneBy: vi.fn().mockResolvedValue(mockFile),
-        save: vi.fn()
-      };
-
-      const mockCompleteMultipartUpload = vi.fn().mockRejectedValue(new Error('Completion failed'));
-
-      mockDataSource.getRepository = vi.fn().mockReturnValue(mockRepository);
-      vi.mocked(MinioService).mockImplementation(() => ({
-        completeMultipartUpload: mockCompleteMultipartUpload
-      } as unknown as MinioService));
-
-      const fileService = new FileService(mockDataSource);
-
-      await expect(fileService.completeMultipartUpload(uploadData)).rejects.toThrow('完成分片上传失败');
-      expect(logger.error).toHaveBeenCalled();
-    });
-  });
-});

+ 0 - 424
packages/server/tests/unit/modules/minio.service.test.ts

@@ -1,424 +0,0 @@
-import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
-import { MinioService } from '@/modules/files/minio.service';
-import { Client } from 'minio';
-import { logger } from '@/utils/logger';
-
-// Mock dependencies
-vi.mock('minio');
-vi.mock('@/utils/logger');
-
-// Mock process.env using vi.stubEnv for proper isolation
-beforeEach(() => {
-  vi.stubEnv('MINIO_HOST', 'localhost');
-  vi.stubEnv('MINIO_PORT', '9000');
-  vi.stubEnv('MINIO_USE_SSL', 'false');
-  vi.stubEnv('MINIO_ACCESS_KEY', 'minioadmin');
-  vi.stubEnv('MINIO_SECRET_KEY', 'minioadmin');
-  vi.stubEnv('MINIO_BUCKET_NAME', 'test-bucket');
-});
-
-afterEach(() => {
-  vi.unstubAllEnvs();
-});
-
-describe('MinioService', () => {
-  let minioService: MinioService;
-  let mockClient: Client;
-
-  beforeEach(() => {
-    mockClient = new Client({} as any);
-    (Client as any).mockClear();
-    (Client as any).mockImplementation(() => mockClient);
-
-    minioService = new MinioService();
-  });
-
-  afterEach(() => {
-    vi.clearAllMocks();
-  });
-
-  describe('constructor', () => {
-    it('should initialize with correct configuration', () => {
-      expect(Client).toHaveBeenCalledWith({
-        endPoint: 'localhost',
-        port: 9000,
-        useSSL: false,
-        accessKey: 'minioadmin',
-        secretKey: 'minioadmin'
-      });
-      expect(minioService.bucketName).toBe('test-bucket');
-    });
-  });
-
-  describe('setPublicReadPolicy', () => {
-    it('should set public read policy successfully', async () => {
-      const mockPolicy = JSON.stringify({
-        Version: '2012-10-17',
-        Statement: [
-          {
-            Effect: 'Allow',
-            Principal: { AWS: '*' },
-            Action: ['s3:GetObject'],
-            Resource: ['arn:aws:s3:::test-bucket/*']
-          },
-          {
-            Effect: 'Allow',
-            Principal: { AWS: '*' },
-            Action: ['s3:ListBucket'],
-            Resource: ['arn:aws:s3:::test-bucket']
-          }
-        ]
-      });
-
-      vi.mocked(mockClient.setBucketPolicy).mockResolvedValue(undefined);
-
-      await minioService.setPublicReadPolicy();
-
-      expect(mockClient.setBucketPolicy).toHaveBeenCalledWith('test-bucket', mockPolicy);
-      expect(logger.db).toHaveBeenCalledWith('Bucket policy set to public read for: test-bucket');
-    });
-
-    it('should handle errors when setting policy', async () => {
-      const error = new Error('Policy error');
-      vi.mocked(mockClient.setBucketPolicy).mockRejectedValue(error);
-
-      await expect(minioService.setPublicReadPolicy()).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith('Failed to set bucket policy for test-bucket:', error);
-    });
-  });
-
-  describe('ensureBucketExists', () => {
-    it('should create bucket if not exists', async () => {
-      vi.mocked(mockClient.bucketExists).mockResolvedValue(false);
-      vi.mocked(mockClient.makeBucket).mockResolvedValue(undefined);
-      vi.spyOn(minioService, 'setPublicReadPolicy').mockResolvedValue(undefined);
-
-      const result = await minioService.ensureBucketExists();
-
-      expect(mockClient.bucketExists).toHaveBeenCalledWith('test-bucket');
-      expect(mockClient.makeBucket).toHaveBeenCalledWith('test-bucket');
-      expect(minioService.setPublicReadPolicy).toHaveBeenCalledWith('test-bucket');
-      expect(result).toBe(true);
-      expect(logger.db).toHaveBeenCalledWith('Created new bucket: test-bucket');
-    });
-
-    it('should return true if bucket already exists', async () => {
-      vi.mocked(mockClient.bucketExists).mockResolvedValue(true);
-
-      const result = await minioService.ensureBucketExists();
-
-      expect(mockClient.bucketExists).toHaveBeenCalledWith('test-bucket');
-      expect(mockClient.makeBucket).not.toHaveBeenCalled();
-      expect(result).toBe(true);
-    });
-
-    it('should handle errors during bucket check', async () => {
-      const error = new Error('Bucket check failed');
-      vi.mocked(mockClient.bucketExists).mockRejectedValue(error);
-
-      await expect(minioService.ensureBucketExists()).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith('Failed to ensure bucket exists: test-bucket', error);
-    });
-  });
-
-  describe('generateUploadPolicy', () => {
-    it('should generate upload policy successfully', async () => {
-      const fileKey = 'test-file.txt';
-      const mockPolicy = {
-        setBucket: vi.fn(),
-        setKey: vi.fn(),
-        setExpires: vi.fn()
-      };
-      const mockFormData = {
-        'x-amz-algorithm': 'AWS4-HMAC-SHA256',
-        'x-amz-credential': 'credential',
-        'x-amz-date': '20250101T120000Z',
-        policy: 'policy-string',
-        'x-amz-signature': 'signature'
-      };
-
-      vi.spyOn(minioService, 'ensureBucketExists').mockResolvedValue(true);
-      vi.mocked(mockClient.newPostPolicy).mockReturnValue(mockPolicy as any);
-      vi.mocked(mockClient.presignedPostPolicy).mockResolvedValue({
-        postURL: 'https://minio.example.com',
-        formData: mockFormData
-      });
-
-      const result = await minioService.generateUploadPolicy(fileKey);
-
-      expect(minioService.ensureBucketExists).toHaveBeenCalled();
-      expect(mockClient.newPostPolicy).toHaveBeenCalled();
-      expect(mockPolicy.setBucket).toHaveBeenCalledWith('test-bucket');
-      expect(mockPolicy.setKey).toHaveBeenCalledWith(fileKey);
-      expect(mockPolicy.setExpires).toHaveBeenCalledWith(expect.any(Date));
-      expect(mockClient.presignedPostPolicy).toHaveBeenCalledWith(mockPolicy);
-      expect(result).toEqual({
-        'x-amz-algorithm': 'AWS4-HMAC-SHA256',
-        'x-amz-credential': 'credential',
-        'x-amz-date': '20250101T120000Z',
-        'x-amz-security-token': undefined,
-        policy: 'policy-string',
-        'x-amz-signature': 'signature',
-        host: 'https://minio.example.com',
-        key: fileKey,
-        bucket: 'test-bucket'
-      });
-    });
-  });
-
-  describe('getFileUrl', () => {
-    it('should generate correct file URL without SSL', () => {
-      const url = minioService.getFileUrl('test-bucket', 'file.txt');
-      expect(url).toBe('http://localhost:9000/test-bucket/file.txt');
-    });
-
-    it('should generate correct file URL with SSL', async () => {
-      // Create new instance with SSL by temporarily overriding env vars
-      vi.stubEnv('MINIO_USE_SSL', 'true');
-      vi.stubEnv('MINIO_PORT', '443');
-
-      const sslService = new MinioService();
-      const url = sslService.getFileUrl('test-bucket', 'file.txt');
-      expect(url).toBe('https://localhost:443/test-bucket/file.txt');
-    });
-  });
-
-  describe('getPresignedFileUrl', () => {
-    it('should generate presigned URL successfully', async () => {
-      const mockUrl = 'https://minio.example.com/presigned-url';
-      vi.mocked(mockClient.presignedGetObject).mockResolvedValue(mockUrl);
-
-      const result = await minioService.getPresignedFileUrl('test-bucket', 'file.txt', 3600);
-
-      expect(mockClient.presignedGetObject).toHaveBeenCalledWith('test-bucket', 'file.txt', 3600);
-      expect(result).toBe(mockUrl);
-      expect(logger.db).toHaveBeenCalledWith(
-        'Generated presigned URL for test-bucket/file.txt, expires in 3600s'
-      );
-    });
-
-    it('should handle errors during URL generation', async () => {
-      const error = new Error('URL generation failed');
-      vi.mocked(mockClient.presignedGetObject).mockRejectedValue(error);
-
-      await expect(minioService.getPresignedFileUrl('test-bucket', 'file.txt')).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith(
-        'Failed to generate presigned URL for test-bucket/file.txt:',
-        error
-      );
-    });
-  });
-
-  describe('getPresignedFileDownloadUrl', () => {
-    it('should generate download URL with content disposition', async () => {
-      const mockUrl = 'https://minio.example.com/download-url';
-      vi.mocked(mockClient.presignedGetObject).mockResolvedValue(mockUrl);
-
-      const result = await minioService.getPresignedFileDownloadUrl(
-        'test-bucket',
-        'file.txt',
-        '测试文件.txt',
-        1800
-      );
-
-      expect(mockClient.presignedGetObject).toHaveBeenCalledWith(
-        'test-bucket',
-        'file.txt',
-        1800,
-        {
-          'response-content-disposition': 'attachment; filename="%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt"',
-          'response-content-type': 'application/octet-stream'
-        }
-      );
-      expect(result).toBe(mockUrl);
-      expect(logger.db).toHaveBeenCalledWith(
-        'Generated presigned download URL for test-bucket/file.txt, filename: 测试文件.txt'
-      );
-    });
-  });
-
-  describe('createMultipartUpload', () => {
-    it('should create multipart upload successfully', async () => {
-      const mockUploadId = 'upload-123';
-      vi.mocked(mockClient.initiateNewMultipartUpload).mockResolvedValue(mockUploadId);
-
-      const result = await minioService.createMultipartUpload('test-bucket', 'large-file.zip');
-
-      expect(mockClient.initiateNewMultipartUpload).toHaveBeenCalledWith(
-        'test-bucket',
-        'large-file.zip',
-        {}
-      );
-      expect(result).toBe(mockUploadId);
-      expect(logger.db).toHaveBeenCalledWith(
-        'Created multipart upload for large-file.zip with ID: upload-123'
-      );
-    });
-
-    it('should handle errors during multipart upload creation', async () => {
-      const error = new Error('Upload creation failed');
-      vi.mocked(mockClient.initiateNewMultipartUpload).mockRejectedValue(error);
-
-      await expect(minioService.createMultipartUpload('test-bucket', 'file.zip')).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith(
-        'Failed to create multipart upload for file.zip:',
-        error
-      );
-    });
-  });
-
-  describe('generateMultipartUploadUrls', () => {
-    it('should generate multipart upload URLs', async () => {
-      const mockUrls = ['url1', 'url2', 'url3'];
-      vi.mocked(mockClient.presignedUrl)
-        .mockResolvedValueOnce('url1')
-        .mockResolvedValueOnce('url2')
-        .mockResolvedValueOnce('url3');
-
-      const result = await minioService.generateMultipartUploadUrls(
-        'test-bucket',
-        'large-file.zip',
-        'upload-123',
-        3
-      );
-
-      expect(mockClient.presignedUrl).toHaveBeenCalledTimes(3);
-      expect(mockClient.presignedUrl).toHaveBeenNthCalledWith(
-        1,
-        'put',
-        'test-bucket',
-        'large-file.zip',
-        3600,
-        { uploadId: 'upload-123', partNumber: '1' }
-      );
-      expect(result).toEqual(mockUrls);
-    });
-  });
-
-  describe('completeMultipartUpload', () => {
-    it('should complete multipart upload successfully', async () => {
-      const parts = [
-        { ETag: 'etag1', PartNumber: 1 },
-        { ETag: 'etag2', PartNumber: 2 }
-      ];
-      const mockStat = { size: 2048 };
-
-      vi.mocked(mockClient.completeMultipartUpload).mockResolvedValue({ etag: 'etag123', versionId: null });
-      vi.mocked(mockClient.statObject).mockResolvedValue(mockStat as any);
-
-      const result = await minioService.completeMultipartUpload(
-        'test-bucket',
-        'large-file.zip',
-        'upload-123',
-        parts
-      );
-
-      expect(mockClient.completeMultipartUpload).toHaveBeenCalledWith(
-        'test-bucket',
-        'large-file.zip',
-        'upload-123',
-        [{ part: 1, etag: 'etag1' }, { part: 2, etag: 'etag2' }]
-      );
-      expect(mockClient.statObject).toHaveBeenCalledWith('test-bucket', 'large-file.zip');
-      expect(result).toEqual({ size: 2048 });
-      expect(logger.db).toHaveBeenCalledWith(
-        'Completed multipart upload for large-file.zip with ID: upload-123'
-      );
-    });
-
-    it('should handle errors during completion', async () => {
-      const error = new Error('Completion failed');
-      vi.mocked(mockClient.completeMultipartUpload).mockRejectedValue(error);
-
-      await expect(minioService.completeMultipartUpload(
-        'test-bucket',
-        'file.zip',
-        'upload-123',
-        [{ ETag: 'etag1', PartNumber: 1 }]
-      )).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith(
-        'Failed to complete multipart upload for file.zip:',
-        error
-      );
-    });
-  });
-
-  describe('createObject', () => {
-    it('should create object successfully', async () => {
-      const fileContent = Buffer.from('test content');
-      const mockUrl = 'http://localhost:9000/test-bucket/file.txt';
-
-      vi.spyOn(minioService, 'ensureBucketExists').mockResolvedValue(true);
-      vi.mocked(mockClient.putObject).mockResolvedValue({ etag: 'etag123', versionId: null });
-      vi.spyOn(minioService, 'getFileUrl').mockReturnValue(mockUrl);
-
-      const result = await minioService.createObject(
-        'test-bucket',
-        'file.txt',
-        fileContent,
-        'text/plain'
-      );
-
-      expect(minioService.ensureBucketExists).toHaveBeenCalledWith('test-bucket');
-      expect(mockClient.putObject).toHaveBeenCalledWith(
-        'test-bucket',
-        'file.txt',
-        fileContent,
-        fileContent.length,
-        { 'Content-Type': 'text/plain' }
-      );
-      expect(result).toBe(mockUrl);
-      expect(logger.db).toHaveBeenCalledWith('Created object: test-bucket/file.txt');
-    });
-  });
-
-  describe('objectExists', () => {
-    it('should return true when object exists', async () => {
-      vi.mocked(mockClient.statObject).mockResolvedValue({} as any);
-
-      const result = await minioService.objectExists('test-bucket', 'file.txt');
-      expect(result).toBe(true);
-    });
-
-    it('should return false when object not found', async () => {
-      const error = new Error('Object not found');
-      vi.mocked(mockClient.statObject).mockRejectedValue(error);
-
-      const result = await minioService.objectExists('test-bucket', 'nonexistent.txt');
-      expect(result).toBe(false);
-    });
-
-    it('should rethrow other errors', async () => {
-      const error = new Error('Permission denied');
-      vi.mocked(mockClient.statObject).mockRejectedValue(error);
-
-      await expect(minioService.objectExists('test-bucket', 'file.txt')).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith(
-        'Error checking existence of object test-bucket/file.txt:',
-        error
-      );
-    });
-  });
-
-  describe('deleteObject', () => {
-    it('should delete object successfully', async () => {
-      vi.mocked(mockClient.removeObject).mockResolvedValue(undefined);
-
-      await minioService.deleteObject('test-bucket', 'file.txt');
-
-      expect(mockClient.removeObject).toHaveBeenCalledWith('test-bucket', 'file.txt');
-      expect(logger.db).toHaveBeenCalledWith('Deleted object: test-bucket/file.txt');
-    });
-
-    it('should handle errors during deletion', async () => {
-      const error = new Error('Deletion failed');
-      vi.mocked(mockClient.removeObject).mockRejectedValue(error);
-
-      await expect(minioService.deleteObject('test-bucket', 'file.txt')).rejects.toThrow(error);
-      expect(logger.error).toHaveBeenCalledWith(
-        'Failed to delete object test-bucket/file.txt:',
-        error
-      );
-    });
-  });
-});

+ 0 - 144
packages/server/tests/unit/modules/user.service.test.ts

@@ -1,144 +0,0 @@
-import { UserService } from '@/modules/users/user.service';
-import { UserEntity as User } from '@/modules/users/user.entity';
-import * as bcrypt from 'bcrypt';
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
-
-// Mock TypeORM 数据源和仓库
-vi.mock('typeorm', async (importOriginal) => {
-  const actual = await importOriginal() as any
-  return {
-    ...actual,
-    DataSource: vi.fn().mockImplementation(() => ({
-      getRepository: vi.fn()
-    })),
-    Repository: vi.fn()
-  }
-});
-
-// Mock bcrypt
-vi.mock('bcrypt', () => ({
-  hash: vi.fn().mockResolvedValue('hashed_password'),
-  compare: vi.fn().mockResolvedValue(true)
-}));
-
-describe('UserService', () => {
-  let userService: UserService;
-  let mockDataSource: any;
-  let mockUserRepository: any;
-  let mockRoleRepository: any;
-
-  beforeEach(() => {
-    // 创建模拟的仓库实例
-    mockUserRepository = {
-      create: vi.fn(),
-      save: vi.fn(),
-      findOne: vi.fn(),
-      update: vi.fn(),
-      delete: vi.fn(),
-      createQueryBuilder: vi.fn(),
-      find: vi.fn(),
-      findByIds: vi.fn()
-    } as any;
-
-    mockRoleRepository = {
-      findByIds: vi.fn()
-    } as any;
-
-    // 创建模拟的数据源
-    mockDataSource = {
-      getRepository: vi.fn()
-    } as any;
-
-    // 设置数据源返回模拟的仓库
-    mockDataSource.getRepository
-      .mockReturnValueOnce(mockUserRepository)
-      .mockReturnValueOnce(mockRoleRepository);
-
-    userService = new UserService(mockDataSource);
-  });
-
-  afterEach(() => {
-    vi.clearAllMocks();
-  });
-
-  describe('createUser', () => {
-    it('应该成功创建用户并哈希密码', async () => {
-      const userData = {
-        username: 'testuser',
-        password: 'password123',
-        email: 'test@example.com'
-      };
-
-      const mockUser = { id: 1, ...userData, password: 'hashed_password' } as User;
-
-      mockUserRepository.create.mockReturnValue(mockUser);
-      mockUserRepository.save.mockResolvedValue(mockUser);
-
-      const result = await userService.createUser(userData);
-
-      expect(bcrypt.hash).toHaveBeenCalledWith('password123', 10);
-      expect(mockUserRepository.create).toHaveBeenCalledWith({
-        ...userData,
-        password: 'hashed_password'
-      });
-      expect(mockUserRepository.save).toHaveBeenCalledWith(mockUser);
-      expect(result).toEqual(mockUser);
-    });
-
-    it('应该在创建用户失败时抛出错误', async () => {
-      const userData = { username: 'testuser', password: 'password123' };
-      const error = new Error('Database error');
-
-      mockUserRepository.create.mockImplementation(() => {
-        throw error;
-      });
-
-      await expect(userService.createUser(userData)).rejects.toThrow('Failed to create user');
-    });
-  });
-
-  describe('getUserById', () => {
-    it('应该通过ID成功获取用户', async () => {
-      const mockUser = { id: 1, username: 'testuser' } as User;
-      mockUserRepository.findOne.mockResolvedValue(mockUser);
-
-      const result = await userService.getUserById(1);
-
-      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
-        where: { id: 1 },
-        relations: ['roles', 'avatarFile']
-      });
-      expect(result).toEqual(mockUser);
-    });
-
-    it('应该在用户不存在时返回null', async () => {
-      mockUserRepository.findOne.mockResolvedValue(null);
-
-      const result = await userService.getUserById(999);
-
-      expect(result).toBeNull();
-    });
-  });
-
-  // getUsersWithPagination 方法已移除,使用通用CRUD服务替代
-
-  describe('verifyPassword', () => {
-    it('应该验证密码正确', async () => {
-      const user = { password: 'hashed_password' } as User;
-
-      const result = await userService.verifyPassword(user, 'password123');
-
-      expect(bcrypt.compare).toHaveBeenCalledWith('password123', 'hashed_password');
-      expect(result).toBe(true);
-    });
-
-    it('应该验证密码错误', async () => {
-      (vi.mocked(bcrypt.compare) as any).mockResolvedValueOnce(false);
-      const user = { password: 'hashed_password' } as User;
-
-      const result = await userService.verifyPassword(user, 'wrong_password');
-
-      expect(result).toBe(false);
-    });
-  });
-});