import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { AppDataSource } from '@d8d/shared-utils'; import { setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util'; import { FileMt } from '../../src/entities/file.entity'; import { FileServiceMt } from '../../src/services/file.service.mt'; import { UserEntityMt } from '@d8d/user-module-mt/entities'; describe('文件模块多租户数据隔离测试', () => { let fileService: FileServiceMt; const tenant1UserId = 1; const tenant2UserId = 2; // 设置数据库钩子 setupIntegrationDatabaseHooksWithEntities([FileMt, UserEntityMt]); beforeEach(async () => { // 创建文件服务实例 fileService = new FileServiceMt(AppDataSource); // 清理文件数据 const fileRepository = AppDataSource.getRepository(FileMt); await fileRepository.delete({}); }); describe('文件创建租户隔离', () => { it('应该为租户1创建文件并设置正确的租户ID', async () => { const fileData = { name: 'tenant1_file.pdf', type: 'application/pdf', size: 1024, path: 'test/path', uploadUserId: tenant1UserId, description: '租户1的文件' }; const result = await fileService.createFile(fileData, 1); expect(result.file.tenantId).toBe(1); expect(result.file.name).toBe('tenant1_file.pdf'); expect(result.file.uploadUserId).toBe(tenant1UserId); }); it('应该为租户2创建文件并设置正确的租户ID', async () => { const fileData = { name: 'tenant2_file.pdf', type: 'application/pdf', size: 2048, path: 'test/path', uploadUserId: tenant2UserId, description: '租户2的文件' }; const result = await fileService.createFile(fileData, 2); expect(result.file.tenantId).toBe(2); expect(result.file.name).toBe('tenant2_file.pdf'); expect(result.file.uploadUserId).toBe(tenant2UserId); }); }); describe('文件查询租户隔离', () => { beforeEach(async () => { // 创建测试文件 const fileRepository = AppDataSource.getRepository(FileMt); // 租户1的文件 await fileRepository.save([ fileRepository.create({ name: 'tenant1_file1.pdf', type: 'application/pdf', size: 1024, path: 'tenant1/path1', uploadUserId: tenant1UserId, tenantId: 1, uploadTime: new Date() }), fileRepository.create({ name: 'tenant1_file2.jpg', type: 'image/jpeg', size: 2048, path: 'tenant1/path2', uploadUserId: tenant1UserId, tenantId: 1, uploadTime: new Date() }) ]); // 租户2的文件 await fileRepository.save([ fileRepository.create({ name: 'tenant2_file1.pdf', type: 'application/pdf', size: 3072, path: 'tenant2/path1', uploadUserId: tenant2UserId, tenantId: 2, uploadTime: new Date() }) ]); }); it('应该只返回租户1的文件列表', async () => { const files = await fileService.findAll({ tenantId: 1 }); expect(files).toHaveLength(2); expect(files.every(file => file.tenantId === 1)).toBe(true); expect(files.some(file => file.name === 'tenant1_file1.pdf')).toBe(true); expect(files.some(file => file.name === 'tenant1_file2.jpg')).toBe(true); }); it('应该只返回租户2的文件列表', async () => { const files = await fileService.findAll({ tenantId: 2 }); expect(files).toHaveLength(1); expect(files[0].tenantId).toBe(2); expect(files[0].name).toBe('tenant2_file1.pdf'); }); it('应该正确获取租户1的特定文件', async () => { const fileRepository = AppDataSource.getRepository(FileMt); const tenant1File = await fileRepository.findOneBy({ tenantId: 1, name: 'tenant1_file1.pdf' }); if (tenant1File) { const file = await fileService.getById(tenant1File.id, { tenantId: 1 }); expect(file).toBeDefined(); expect(file?.tenantId).toBe(1); expect(file?.name).toBe('tenant1_file1.pdf'); } }); it('租户1不应该访问租户2的文件', async () => { const fileRepository = AppDataSource.getRepository(FileMt); const tenant2File = await fileRepository.findOneBy({ tenantId: 2, name: 'tenant2_file1.pdf' }); if (tenant2File) { const file = await fileService.getById(tenant2File.id, { tenantId: 1 }); expect(file).toBeNull(); } }); }); describe('文件更新租户隔离', () => { let tenant1File: FileMt; beforeEach(async () => { // 创建租户1的测试文件 const fileRepository = AppDataSource.getRepository(FileMt); tenant1File = fileRepository.create({ name: 'original_name.pdf', type: 'application/pdf', size: 1024, path: 'tenant1/original', uploadUserId: tenant1UserId, tenantId: 1, uploadTime: new Date() }); await fileRepository.save(tenant1File); }); it('应该允许租户1更新自己的文件', async () => { const updateData = { name: 'updated_name.pdf', description: '更新后的描述' }; const updatedFile = await fileService.update(tenant1File.id, updateData, { tenantId: 1 }); expect(updatedFile.name).toBe('updated_name.pdf'); expect(updatedFile.description).toBe('更新后的描述'); expect(updatedFile.tenantId).toBe(1); }); it('不应该允许租户2更新租户1的文件', async () => { const updateData = { name: 'hacked_name.pdf' }; await expect(fileService.update(tenant1File.id, updateData, { tenantId: 2 })) .rejects.toThrow(); }); }); describe('文件删除租户隔离', () => { let tenant1File: FileMt; let tenant2File: FileMt; beforeEach(async () => { // 创建测试文件 const fileRepository = AppDataSource.getRepository(FileMt); tenant1File = fileRepository.create({ name: 'tenant1_delete_test.pdf', type: 'application/pdf', size: 1024, path: 'tenant1/delete_test', uploadUserId: tenant1UserId, tenantId: 1, uploadTime: new Date() }); tenant2File = fileRepository.create({ name: 'tenant2_delete_test.pdf', type: 'application/pdf', size: 2048, path: 'tenant2/delete_test', uploadUserId: tenant2UserId, tenantId: 2, uploadTime: new Date() }); await fileRepository.save([tenant1File, tenant2File]); }); it('应该允许租户1删除自己的文件', async () => { const result = await fileService.delete(tenant1File.id, { tenantId: 1 }); expect(result).toBe(true); // 验证文件已被删除 const fileRepository = AppDataSource.getRepository(FileMt); const deletedFile = await fileRepository.findOneBy({ id: tenant1File.id }); expect(deletedFile).toBeNull(); }); it('不应该允许租户2删除租户1的文件', async () => { await expect(fileService.delete(tenant1File.id, { tenantId: 2 })) .rejects.toThrow(); // 验证文件仍然存在 const fileRepository = AppDataSource.getRepository(FileMt); const existingFile = await fileRepository.findOneBy({ id: tenant1File.id }); expect(existingFile).toBeDefined(); }); it('应该允许租户2删除自己的文件', async () => { const result = await fileService.delete(tenant2File.id, { tenantId: 2 }); expect(result).toBe(true); // 验证文件已被删除 const fileRepository = AppDataSource.getRepository(FileMt); const deletedFile = await fileRepository.findOneBy({ id: tenant2File.id }); expect(deletedFile).toBeNull(); }); }); describe('文件存储路径租户隔离', () => { it('应该为租户1的文件生成包含租户ID的存储路径', async () => { const fileData = { name: 'tenant1_path_test.pdf', type: 'application/pdf', size: 1024, uploadUserId: tenant1UserId, description: '租户1路径测试文件' }; const result = await fileService.createFile(fileData, 1); expect(result.file.path).toContain('tenants/1/'); expect(result.file.path).toContain(tenant1UserId.toString()); expect(result.file.path).toContain('tenant1_path_test.pdf'); }); it('应该为租户2的文件生成包含租户ID的存储路径', async () => { const fileData = { name: 'tenant2_path_test.pdf', type: 'application/pdf', size: 1024, uploadUserId: tenant2UserId, description: '租户2路径测试文件' }; const result = await fileService.createFile(fileData, 2); expect(result.file.path).toContain('tenants/2/'); expect(result.file.path).toContain(tenant2UserId.toString()); expect(result.file.path).toContain('tenant2_path_test.pdf'); }); }); });