Quellcode durchsuchen

fix: 修复统一文件模块问题

- 修复 vitest.config.ts 删除不必要的别名配置
- 修复 unified-file.entity.ts 完整的类定义
- 修复 unified-file.service.ts 完整的 Service 实现
- 修复 unified-file.schema.ts 字段名与 Entity 一致
- 修复集成测试删除不必要的 UserEntityMt 依赖
- 修复单元测试 mock 问题

测试结果:8/8 通过

🤖 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 vor 2 Wochen
Ursprung
Commit
aade00ef59

+ 114 - 1
packages/unified-file-module/src/entities/unified-file.entity.ts

@@ -2,4 +2,117 @@ import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
 import process from 'node:process';
 import { MinioService } from '../services/minio.service';
 
-@Entity('unified_file')
+@Entity('unified_file')
+export class UnifiedFile {
+  @PrimaryGeneratedColumn({
+    name: 'id',
+    type: 'int',
+    unsigned: true,
+    comment: '文件ID'
+  })
+  id!: number;
+
+  @Column({
+    name: 'file_name',
+    type: 'varchar',
+    length: 255,
+    comment: '文件名'
+  })
+  fileName!: string;
+
+  @Column({
+    name: 'file_path',
+    type: 'varchar',
+    length: 512,
+    comment: '文件存储路径'
+  })
+  filePath!: string;
+
+  @Column({
+    name: 'file_size',
+    type: 'int',
+    unsigned: true,
+    nullable: true,
+    comment: '文件大小,单位字节'
+  })
+  fileSize!: number | null;
+
+  @Column({
+    name: 'mime_type',
+    type: 'varchar',
+    length: 100,
+    nullable: true,
+    comment: 'MIME类型'
+  })
+  mimeType!: string | null;
+
+  @Column({
+    name: 'description',
+    type: 'text',
+    nullable: true,
+    comment: '文件描述'
+  })
+  description!: string | null;
+
+  @Column({
+    name: 'created_by',
+    type: 'int',
+    unsigned: true,
+    nullable: true,
+    comment: '创建者ID'
+  })
+  createdBy!: number | null;
+
+  @Column({
+    name: 'updated_by',
+    type: 'int',
+    unsigned: true,
+    nullable: true,
+    comment: '更新者ID'
+  })
+  updatedBy!: number | null;
+
+  @Column({
+    name: 'status',
+    type: 'int',
+    default: 1,
+    comment: '状态: 1=启用, 0=禁用'
+  })
+  @Index('idx_unified_file_status')
+  status!: number;
+
+  @Column({
+    name: 'created_at',
+    type: 'timestamp',
+    default: () => 'CURRENT_TIMESTAMP',
+    comment: '创建时间'
+  })
+  @Index('idx_unified_file_created_at')
+  createdAt!: Date;
+
+  @Column({
+    name: 'updated_at',
+    type: 'timestamp',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+    comment: '更新时间'
+  })
+  updatedAt!: Date;
+
+  // 获取完整的文件URL(MinIO预签名URL)
+  get fullUrl(): Promise<string> {
+    const minioService = new MinioService();
+    const bucketName = process.env.MINIO_BUCKET_NAME || 'd8dai';
+
+    return new Promise((resolve, reject) => {
+      minioService.getPresignedFileUrl(bucketName, this.filePath)
+        .then(url => {
+          resolve(url);
+        })
+        .catch(error => {
+          console.error('获取文件预签名URL失败:', error);
+          reject(error);
+        });
+    });
+  }
+}

+ 26 - 38
packages/unified-file-module/src/schemas/unified-file.schema.ts

@@ -5,40 +5,36 @@ export const UnifiedFileSchema = z.object({
     description: '文件ID',
     example: 1
   }),
-  name: z.string().max(255).openapi({
+  fileName: z.string().max(255).openapi({
     description: '文件名称',
     example: 'banner.jpg'
   }),
-  type: z.string().max(50).nullable().openapi({
-    description: '文件类型/MIME类型',
-    example: 'image/jpeg'
+  filePath: z.string().max(512).openapi({
+    description: '文件存储路径',
+    example: 'unified/uuid-banner.jpg'
   }),
-  size: z.number().int().positive().nullable().openapi({
+  fileSize: z.number().int().positive().nullable().openapi({
     description: '文件大小(字节)',
     example: 102400
   }),
-  path: z.string().max(512).openapi({
-    description: '文件存储路径',
-    example: 'unified/uuid-banner.jpg'
+  mimeType: z.string().max(100).nullable().openapi({
+    description: 'MIME类型',
+    example: 'image/jpeg'
   }),
   description: z.string().nullable().openapi({
     description: '文件描述',
     example: '首页轮播图'
   }),
-  uploadUserId: z.number().int().positive().openapi({
-    description: '上传用户ID',
+  createdBy: z.number().int().positive().nullable().openapi({
+    description: '创建者ID',
     example: 1
   }),
-  uploadTime: z.coerce.date<Date>().openapi({
-    description: '上传时间',
-    example: '2024-01-15T10:30:00Z'
-  }),
-  lastUpdated: z.date().nullable().openapi({
-    description: '最后更新时间',
-    example: '2024-01-16T14:20:00Z'
+  updatedBy: z.number().int().positive().nullable().openapi({
+    description: '更新者ID',
+    example: 1
   }),
   status: z.number().int().openapi({
-    description: '状态 0禁用 1启用',
+    description: '状态: 1=启用, 0=禁用',
     example: 1
   }),
   createdAt: z.coerce.date<Date>().openapi({
@@ -48,46 +44,38 @@ export const UnifiedFileSchema = z.object({
   updatedAt: z.coerce.date<Date>().openapi({
     description: '更新时间',
     example: '2024-01-16T14:20:00Z'
-  }),
-  createdBy: z.number().int().positive().nullable().openapi({
-    description: '创建用户ID',
-    example: 1
-  }),
-  updatedBy: z.number().int().positive().nullable().openapi({
-    description: '更新用户ID',
-    example: 1
   })
 });
 
 export const CreateUnifiedFileDto = z.object({
-  name: z.string().min(1).max(255).openapi({
+  fileName: z.string().min(1).max(255).openapi({
     description: '文件名称',
     example: 'banner.jpg'
   }),
-  type: z.string().max(50).optional().openapi({
-    description: '文件类型/MIME类型',
-    example: 'image/jpeg'
+  filePath: z.string().max(512).openapi({
+    description: '文件存储路径',
+    example: 'unified/uuid-banner.jpg'
   }),
-  size: z.coerce.number<number>().int().positive().optional().openapi({
+  fileSize: z.coerce.number<number>().int().positive().optional().openapi({
     description: '文件大小(字节)',
     example: 102400
   }),
-  path: z.string().max(512).openapi({
-    description: '文件存储路径',
-    example: 'unified/uuid-banner.jpg'
+  mimeType: z.string().max(100).optional().openapi({
+    description: 'MIME类型',
+    example: 'image/jpeg'
   }),
   description: z.string().optional().openapi({
     description: '文件描述',
     example: '首页轮播图'
   }),
-  uploadUserId: z.number().int().positive().openapi({
-    description: '上传用户ID',
+  createdBy: z.number().int().positive().optional().openapi({
+    description: '创建者ID',
     example: 1
   })
 });
 
 export const UpdateUnifiedFileDto = z.object({
-  name: z.string().max(255).optional().openapi({
+  fileName: z.string().max(255).optional().openapi({
     description: '文件名称',
     example: 'banner_v2.jpg'
   }),
@@ -96,7 +84,7 @@ export const UpdateUnifiedFileDto = z.object({
     example: '首页轮播图(更新版)'
   }),
   status: z.number().int().min(0).max(1).optional().openapi({
-    description: '状态 0禁用 1启用',
+    description: '状态: 1=启用, 0=禁用',
     example: 1
   })
 });

+ 337 - 1
packages/unified-file-module/src/services/unified-file.service.ts

@@ -1 +1,337 @@
-import { GenericCrudService } from '@d8d/shared-crud';
+import { GenericCrudService } from '@d8d/shared-crud';
+import { DataSource } from 'typeorm';
+import { UnifiedFile } from '../entities/unified-file.entity';
+import { MinioService } from './minio.service';
+import { v4 as uuidv4 } from 'uuid';
+import { logger } from '@d8d/shared-utils';
+
+export class UnifiedFileService extends GenericCrudService<UnifiedFile> {
+  private readonly minioService: MinioService;
+
+  constructor(dataSource: DataSource) {
+    super(dataSource, UnifiedFile);
+    this.minioService = new MinioService();
+  }
+
+  /**
+   * 覆盖创建方法,设置默认值
+   */
+  override async create(data: Partial<UnifiedFile>, userId?: string | number): Promise<UnifiedFile> {
+    const fileData = {
+      ...data,
+      status: data.status ?? 1,
+      createdAt: new Date(),
+      updatedAt: new Date()
+    };
+
+    return super.create(fileData, userId);
+  }
+
+  /**
+   * 覆盖更新方法,自动设置 updatedAt
+   */
+  override async update(id: number, data: Partial<UnifiedFile>, userId?: string | number): Promise<UnifiedFile | null> {
+    const updateData = {
+      ...data,
+      updatedAt: new Date()
+    };
+
+    return super.update(id, updateData, userId);
+  }
+
+  /**
+   * 覆盖删除方法,实现软删除
+   */
+  override async delete(id: number, userId?: string | number): Promise<boolean> {
+    const file = await this.getById(id);
+    if (!file) {
+      return false;
+    }
+
+    // 软删除:设置 status = 0
+    await this.update(id, { status: 0 }, userId);
+    return true;
+  }
+
+  /**
+   * 创建文件记录并生成预签名上传URL
+   */
+  async createFile(data: Partial<UnifiedFile>) {
+    try {
+      // 生成唯一文件存储路径
+      const fileKey = `unified/${uuidv4()}-${data.fileName}`;
+      // 生成MinIO上传策略
+      const uploadPolicy = await this.minioService.generateUploadPolicy(fileKey);
+
+      // 准备文件记录数据
+      const fileData = {
+        ...data,
+        filePath: fileKey,
+        status: 1,
+        createdAt: new Date(),
+        updatedAt: new Date()
+      };
+
+      // 保存文件记录到数据库
+      const savedFile = await this.create(fileData as UnifiedFile);
+
+      // 返回文件记录和上传策略
+      return {
+        file: savedFile,
+        uploadPolicy
+      };
+    } catch (error) {
+      logger.error('Failed to create file:', error);
+      throw new Error('文件创建失败');
+    }
+  }
+
+  /**
+   * 删除文件记录及对应的MinIO文件
+   */
+  async deleteFile(id: number) {
+    // 获取文件记录
+    const file = await this.getById(id);
+    if (!file) {
+      throw new Error('文件不存在');
+    }
+
+    try {
+      // 验证文件是否存在于MinIO
+      const fileExists = await this.minioService.objectExists(this.minioService.bucketName, file.filePath);
+      if (!fileExists) {
+        logger.error(`File not found in MinIO: ${this.minioService.bucketName}/${file.filePath}`);
+      } else {
+        // 从MinIO删除文件
+        await this.minioService.deleteObject(this.minioService.bucketName, file.filePath);
+      }
+
+      // 软删除数据库记录
+      await this.delete(id);
+
+      return true;
+    } catch (error) {
+      logger.error('Failed to delete file:', error);
+      throw new Error('文件删除失败');
+    }
+  }
+
+  /**
+   * 获取文件访问URL
+   */
+  async getFileUrl(id: number) {
+    const file = await this.getById(id);
+    if (!file) {
+      throw new Error('文件不存在');
+    }
+
+    return this.minioService.getPresignedFileUrl(this.minioService.bucketName, file.filePath);
+  }
+
+  /**
+   * 保存文件记录并将文件内容直接上传到MinIO(支持自定义存储路径)
+   */
+  async saveFileWithCustomPath(
+    fileData: {
+      fileName: string;
+      fileSize: number;
+      mimeType: string;
+      createdBy?: number;
+      [key: string]: any;
+    },
+    fileContent: Buffer,
+    customPath?: string,
+    contentType?: string
+  ) {
+    try {
+      logger.db('Starting saveFileWithCustomPath process:', {
+        filename: fileData.fileName,
+        size: fileData.fileSize,
+        mimeType: fileData.mimeType,
+        customPath: customPath || 'auto-generated'
+      });
+
+      // 使用自定义路径或生成唯一文件存储路径
+      const fileKey = customPath || `unified/${uuidv4()}-${fileData.fileName}`;
+
+      // 确保存储桶存在
+      await this.minioService.ensureBucketExists();
+
+      // 直接上传文件内容到MinIO
+      const fileUrl = await this.minioService.createObject(
+        this.minioService.bucketName,
+        fileKey,
+        fileContent,
+        contentType || fileData.mimeType
+      );
+
+      // 准备文件记录数据
+      const completeFileData = {
+        ...fileData,
+        filePath: fileKey,
+        status: 1
+      };
+
+      // 保存文件记录到数据库
+      const savedFile = await this.create(completeFileData as any);
+
+      logger.db('File saved with custom path successfully:', {
+        fileId: savedFile.id,
+        filename: savedFile.fileName,
+        size: savedFile.fileSize,
+        path: fileKey,
+        url: fileUrl
+      });
+
+      return {
+        file: savedFile,
+        url: fileUrl
+      };
+    } catch (error) {
+      logger.error('Failed to save file with custom path:', error);
+      throw new Error(`文件保存失败: ${error instanceof Error ? error.message : '未知错误'}`);
+    }
+  }
+
+  /**
+   * 从URL下载文件并保存到MinIO
+   */
+  async downloadAndSaveFromUrl(
+    url: string,
+    fileData: {
+      createdBy?: number;
+      mimeType?: string;
+      customFileName?: string;
+      customPath?: string;
+      [key: string]: any;
+    },
+    options?: {
+      timeout?: number;
+      retries?: number;
+    }
+  ) {
+    try {
+      const axios = require('axios');
+
+      logger.db('Starting downloadAndSaveFromUrl process:', {
+        url,
+        customFileName: fileData.customFileName,
+        customPath: fileData.customPath
+      });
+
+      // 下载文件
+      const response = await axios.get(url, {
+        responseType: 'arraybuffer',
+        timeout: options?.timeout || 30000,
+        maxRedirects: 5,
+        headers: {
+          'User-Agent': 'Mozilla/5.0 (compatible; FileDownloader/1.0)'
+        }
+      });
+
+      const buffer = Buffer.from(response.data);
+
+      // 从URL或响应头中获取文件名
+      let fileName = fileData.customFileName;
+      if (!fileName) {
+        const contentDisposition = response.headers['content-disposition'];
+        if (contentDisposition) {
+          const filenameMatch = contentDisposition.match(/filename[*]?=(?:utf-8'')?(.+)/i);
+          if (filenameMatch) {
+            fileName = decodeURIComponent(filenameMatch[1].replace(/['"]/g, ''));
+          }
+        }
+
+        if (!fileName) {
+          const urlPath = new URL(url).pathname;
+          fileName = urlPath.split('/').pop() || `file_${Date.now()}`;
+        }
+      }
+
+      // 确保文件有扩展名
+      if (!fileName.includes('.') && fileData.mimeType) {
+        const ext = this.getExtensionFromMimeType(fileData.mimeType);
+        if (ext) {
+          fileName += `.${ext}`;
+        }
+      }
+
+      // 确定MIME类型
+      let mimeType = fileData.mimeType || response.headers['content-type'];
+      if (!mimeType || mimeType === 'application/octet-stream') {
+        mimeType = this.inferMimeType(fileName);
+      }
+
+      // 保存文件
+      const saveResult = await this.saveFileWithCustomPath(
+        {
+          ...fileData,
+          fileName,
+          fileSize: buffer.length,
+          mimeType
+        },
+        buffer,
+        fileData.customPath,
+        mimeType
+      );
+
+      logger.db('Download and save completed successfully:', {
+        fileId: saveResult.file.id,
+        fileName,
+        size: buffer.length,
+        url: saveResult.url
+      });
+
+      return saveResult;
+    } catch (error) {
+      logger.error('Failed to download and save file from URL:', {
+        url,
+        error: error instanceof Error ? error.message : '未知错误'
+      });
+      throw new Error(`从URL下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
+    }
+  }
+
+  /**
+   * 根据MIME类型获取文件扩展名
+   */
+  private getExtensionFromMimeType(mimeType: string): string | null {
+    const mimeMap: Record<string, string> = {
+      'image/jpeg': 'jpg',
+      'image/png': 'png',
+      'image/gif': 'gif',
+      'image/webp': 'webp',
+      'image/svg+xml': 'svg',
+      'application/pdf': 'pdf',
+      'text/plain': 'txt',
+      'application/json': 'json',
+      'application/xml': 'xml',
+      'video/mp4': 'mp4',
+      'audio/mp3': 'mp3'
+    };
+    return mimeMap[mimeType] || null;
+  }
+
+  /**
+   * 根据文件名推断MIME类型
+   */
+  private inferMimeType(fileName: string): string {
+    const ext = fileName.toLowerCase().split('.').pop();
+    const extMap: Record<string, string> = {
+      'jpg': 'image/jpeg',
+      'jpeg': 'image/jpeg',
+      'png': 'image/png',
+      'gif': 'image/gif',
+      'webp': 'image/webp',
+      'svg': 'image/svg+xml',
+      'pdf': 'application/pdf',
+      'txt': 'text/plain',
+      'json': 'application/json',
+      'xml': 'application/xml',
+      'mp4': 'video/mp4',
+      'mp3': 'audio/mp3',
+      'wav': 'audio/wav'
+    };
+    return extMap[ext || ''] || 'application/octet-stream';
+  }
+}

+ 73 - 25
packages/unified-file-module/tests/integration/unified-files.integration.test.ts

@@ -2,41 +2,29 @@ import { describe, it, expect, beforeEach } from 'vitest';
 import { testClient } from 'hono/testing';
 import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
 import { JWTUtil } from '@d8d/shared-utils';
-import { UserEntityMt, RoleMt } from '@d8d/core-module-mt/user-module-mt';
 import unifiedFilesAdminRoutes from '../../src/routes/admin/unified-files.admin.routes';
 import { UnifiedFile } from '../../src/entities/unified-file.entity';
 
-setupIntegrationDatabaseHooksWithEntities([UserEntityMt, RoleMt, UnifiedFile]);
+// 设置集成测试钩子 - 只需要 UnifiedFile 实体
+setupIntegrationDatabaseHooksWithEntities([UnifiedFile]);
 
 describe('统一文件模块集成测试', () => {
-  describe('管理员路由', () => {
+  describe('管理员路由(超级管理员专用)', () => {
     let adminClient: ReturnType<typeof testClient<typeof unifiedFilesAdminRoutes>>;
     let superAdminToken: string;
     let regularUserToken: string;
-    let testUser: UserEntityMt;
 
     beforeEach(async () => {
       adminClient = testClient(unifiedFilesAdminRoutes);
 
-      const dataSource = await IntegrationTestDatabase.getDataSource();
-      const userRepository = dataSource.getRepository(UserEntityMt);
-      const fileRepository = dataSource.getRepository(UnifiedFile);
-
-      testUser = userRepository.create({
-        username: 'test_user_' + Date.now(),
-        password: 'test_password',
-        nickname: '测试用户',
-        registrationSource: 'web',
-        tenantId: 1
-      });
-      await userRepository.save(testUser);
-
+      // 生成超级管理员token (ID=1)
       superAdminToken = JWTUtil.generateToken({
         id: 1,
         username: 'admin',
         roles: [{ name: 'admin' }]
       });
 
+      // 生成普通用户token (ID=2)
       regularUserToken = JWTUtil.generateToken({
         id: 2,
         username: 'user',
@@ -50,7 +38,7 @@ describe('统一文件模块集成测试', () => {
           query: { page: 1, pageSize: 10 }
         }, {
           headers: {
-            'Authorization': 'Bearer ' + superAdminToken
+            'Authorization': `Bearer ${superAdminToken}`
           }
         });
 
@@ -71,22 +59,29 @@ describe('统一文件模块集成测试', () => {
           query: { page: 1, pageSize: 10 }
         }, {
           headers: {
-            'Authorization': 'Bearer ' + regularUserToken
+            'Authorization': `Bearer ${regularUserToken}`
           }
         });
 
         expect(response.status).toBe(403);
       });
+
+      it('应该拒绝未认证用户访问', async () => {
+        const response = await adminClient.$get({
+          query: { page: 1, pageSize: 10 }
+        });
+
+        expect(response.status).toBe(401);
+      });
     });
 
     describe('POST /admin/unified-files', () => {
       it('应该允许超级管理员创建文件记录', async () => {
         const newFile = {
-          name: 'test-banner.jpg',
-          path: 'unified/test-banner.jpg',
-          type: 'image/jpeg',
-          size: 102400,
-          uploadUserId: 1,
+          fileName: 'test-banner.jpg',
+          filePath: 'unified/test-banner.jpg',
+          fileSize: 102400,
+          mimeType: 'image/jpeg',
           status: 1
         };
 
@@ -94,13 +89,66 @@ describe('统一文件模块集成测试', () => {
           json: newFile
         }, {
           headers: {
-            'Authorization': 'Bearer ' + superAdminToken
+            'Authorization': `Bearer ${superAdminToken}`
           }
         });
 
         console.debug('创建文件响应状态:', response.status);
         expect([200, 201]).toContain(response.status);
       });
+
+      it('应该拒绝普通用户创建文件', async () => {
+        const newFile = {
+          fileName: 'test-banner.jpg',
+          filePath: 'unified/test-banner.jpg',
+          fileSize: 102400,
+          mimeType: 'image/jpeg'
+        };
+
+        const response = await adminClient.$post({
+          json: newFile
+        }, {
+          headers: {
+            'Authorization': `Bearer ${regularUserToken}`
+          }
+        });
+
+        expect(response.status).toBe(403);
+      });
+    });
+
+    describe('DELETE /admin/unified-files/:id', () => {
+      it('应该允许超级管理员软删除文件', async () => {
+        const dataSource = await IntegrationTestDatabase.getDataSource();
+        const fileRepository = dataSource.getRepository(UnifiedFile);
+
+        const testFile = fileRepository.create({
+          fileName: 'to-delete.jpg',
+          filePath: 'unified/to-delete.jpg',
+          fileSize: 51200,
+          mimeType: 'image/jpeg',
+          status: 1
+        });
+        await fileRepository.save(testFile);
+
+        const response = await adminClient[':id'].$delete({
+          param: { id: testFile.id }
+        }, {
+          headers: {
+            'Authorization': `Bearer ${superAdminToken}`
+          }
+        });
+
+        console.debug('删除文件响应状态:', response.status);
+        expect(response.status).toBe(200);
+
+        // 验证软删除
+        const deletedFile = await fileRepository.findOne({
+          where: { id: testFile.id }
+        });
+        expect(deletedFile).toBeDefined();
+        expect(deletedFile?.status).toBe(0);
+      });
     });
   });
 });

+ 22 - 11
packages/unified-file-module/tests/unit/unified-file.service.unit.test.ts

@@ -19,17 +19,24 @@ describe('UnifiedFileService', () => {
   describe('create', () => {
     it('should create file record with status=1', async () => {
       const fileData: Partial<UnifiedFile> = {
-        name: 'test.jpg',
-        path: 'unified/test.jpg',
-        uploadUserId: 1
+        fileName: 'test.jpg',
+        filePath: 'unified/test.jpg',
+        createdBy: 1
       };
 
+      const mockEntity = { ...fileData, id: 1, status: 1, createdAt: new Date(), updatedAt: new Date() };
+
       const repository = {
-        create: vi.fn().mockReturnValue(fileData),
-        save: vi.fn().mockResolvedValue(fileData)
+        create: vi.fn().mockReturnValue(mockEntity),
+        save: vi.fn().mockResolvedValue(mockEntity),
+        update: vi.fn().mockResolvedValue(undefined),
+        findOne: vi.fn().mockResolvedValue(mockEntity)
       };
       (mockDataSource.getRepository as any).mockReturnValue(repository);
 
+      // 设置 repository 到 service 实例
+      service['repository'] = repository as any;
+
       const result = await service.create(fileData, 1);
 
       expect(repository.create).toHaveBeenCalled();
@@ -41,15 +48,19 @@ describe('UnifiedFileService', () => {
     it('should set status to 0 instead of physical delete', async () => {
       const fileData: Partial<UnifiedFile> = {
         id: 1,
-        name: 'test.jpg',
-        path: 'unified/test.jpg',
+        fileName: 'test.jpg',
+        filePath: 'unified/test.jpg',
         status: 1,
-        uploadUserId: 1
+        createdBy: 1
       };
 
+      const updatedData = { ...fileData, status: 0, updatedAt: new Date() };
+
       const repository = {
-        findOne: vi.fn().mockResolvedValue(fileData),
-        save: vi.fn().mockResolvedValue({ ...fileData, status: 0 })
+        create: vi.fn(),
+        save: vi.fn().mockResolvedValue(updatedData),
+        update: vi.fn().mockResolvedValue({ affected: 1 }),
+        findOne: vi.fn().mockResolvedValue(fileData)
       };
       (mockDataSource.getRepository as any).mockReturnValue(repository);
 
@@ -58,7 +69,7 @@ describe('UnifiedFileService', () => {
       const result = await service.delete(1, 1);
 
       expect(result).toBe(true);
-      expect(fileData.status).toBe(0);
+      expect(repository.update).toHaveBeenCalledWith(1, expect.objectContaining({ status: 0 }));
     });
   });
 });