You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求适用于Cypress的Node.js文件验证NPM包及实现方案

解决方案:React文件上传表单的Cypress合法性验证(防扩展名篡改)

需求概述

需为React文件上传表单编写Cypress测试,验证上传文件的真实合法性,防止篡改扩展名的非法文件(如将.flv/.ico改为.mp4/.jpg)。合法文件范围:

  • 媒体:音频(mp3、ogg)、视频(mp4、ogg)、图片(png、jpg、jpeg、bmp)
  • 文档:PDF、TXT、DOC、DOCX、XLS、XLSX、CSV、PPT、PPTX、XML、JSON

已实现图片有效性验证(使用image-size),现需补充音视频、文档的真实类型验证,要求采用商用友好许可的NPM包。


推荐NPM包(商用许可)

1. 通用文件类型检测(优先推荐):file-type

  • 许可:MIT(完全商用友好)
  • 功能:通过文件签名(Magic Number)检测真实类型,支持几乎所有目标格式(音视频、图片、文档)
  • 优势:单包覆盖所有场景,无需分模块集成

2. 音视频专项验证

  • 音频:music-metadata(MIT许可):深度解析音频元数据,验证文件完整性
  • 视频:video-metadata-extractor(MIT许可):提取视频编码、分辨率等元数据,确认文件有效性

3. 文档专项验证

  • office-file-validator(MIT许可):针对Office文档(DOCX/XLSX/PPTX)做深度有效性校验,适合对Office文件有严格要求的场景

Cypress集成步骤(以file-type为例)

1. 安装依赖

npm install file-type --save-dev

2. 在Cypress中封装自定义命令(可选)

在cypress/support/commands.js中添加:

import { fileTypeFromBuffer } from 'file-type';

Cypress.Commands.add('verifyFileRealType', (filePath, expectedExtensions) => {
  cy.readFile(filePath, null).then((buffer) => {
    return fileTypeFromBuffer(buffer).then((fileType) => {
      // 匹配预期扩展名(忽略大小写)
      const isMatch = expectedExtensions.some(ext => 
        fileType?.ext.toLowerCase() === ext.toLowerCase()
      );
      expect(isMatch).to.be.true;
    });
  });
});

完整测试用例示例

1. 合法文件测试

describe('文件上传合法性验证 - 合法文件', () => {
  const validFiles = [
    { filePath: 'fixtures/valid/sample.jpg', expectedExts: ['jpg', 'jpeg'] },
    { filePath: 'fixtures/valid/sample.mp3', expectedExts: ['mp3'] },
    { filePath: 'fixtures/valid/sample.mp4', expectedExts: ['mp4'] },
    { filePath: 'fixtures/valid/sample.pdf', expectedExts: ['pdf'] },
    { filePath: 'fixtures/valid/sample.docx', expectedExts: ['docx'] }
  ];

  validFiles.forEach(({ filePath, expectedExts }) => {
    it(`验证合法文件:${filePath}`, () => {
      cy.visit('/upload-form'); // 替换为你的上传表单页面路径
      const field = 'input[type="file"]';
      const filename = filePath.split('/').pop();

      // 上传文件
      cy.get(field).attachFile({ filePath, fileName: filename });

      // 验证真实文件类型(通过自定义命令)
      cy.verifyFileRealType(filePath, expectedExts);

      // 可选:验证前端显示的mimetype(辅助检查)
      cy.get(field).then((input) => {
        const file = input[0].files[0];
        // 可根据预期扩展名映射对应的mimetype列表
        const expectedMimeTypes = getExpectedMimeTypes(expectedExts);
        expect(file.type).to.be.oneOf(expectedMimeTypes);
      });
    });
  });
});

// 辅助函数:根据扩展名获取对应的MIME类型
function getExpectedMimeTypes(exts) {
  const mimeMap = {
    'jpg': 'image/jpeg',
    'jpeg': 'image/jpeg',
    'png': 'image/png',
    'bmp': 'image/bmp',
    'mp3': 'audio/mpeg',
    'ogg': ['audio/ogg', 'video/ogg'],
    'mp4': 'video/mp4',
    'pdf': 'application/pdf',
    'txt': 'text/plain',
    'doc': 'application/msword',
    'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'xls': 'application/vnd.ms-excel',
    'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'csv': 'text/csv',
    'ppt': 'application/vnd.ms-powerpoint',
    'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'xml': 'application/xml',
    'json': 'application/json'
  };
  
  return exts.flatMap(ext => mimeMap[ext.toLowerCase()] || []);
}

2. 非法文件测试(篡改扩展名)

describe('文件上传合法性验证 - 非法文件(篡改扩展名)', () => {
  const invalidFiles = [
    { filePath: 'fixtures/invalid/sample.ico.jpg', expectedExts: ['jpg'] }, // 实际是ICO,改名为JPG
    { filePath: 'fixtures/invalid/sample.flv.mp4', expectedExts: ['mp4'] }, // 实际是FLV,改名为MP4
    { filePath: 'fixtures/invalid/sample.txt.docx', expectedExts: ['docx'] } // 实际是TXT,改名为DOCX
  ];

  invalidFiles.forEach(({ filePath, expectedExts }) => {
    it(`拦截非法文件:${filePath}`, () => {
      cy.visit('/upload-form');
      const field = 'input[type="file"]';
      const filename = filePath.split('/').pop();

      cy.get(field).attachFile({ filePath, fileName: filename });

      // 验证真实类型不匹配预期
      cy.readFile(filePath, null).then((buffer) => {
        return fileTypeFromBuffer(buffer).then((fileType) => {
          const isMatch = expectedExts.some(ext => 
            fileType?.ext.toLowerCase() === ext.toLowerCase()
          );
          expect(isMatch).to.be.false;
        });
      });
    });
  });
});

注意事项

  1. 若使用music-metadata验证音频,集成方式类似:读取文件buffer后调用parseBuffer方法,检查返回的元数据是否符合预期
  2. 对于大文件,建议在测试中使用压缩后的样本,避免Cypress测试超时
  3. 确保Cypress配置中允许读取fixture文件,默认配置已支持

内容的提问来源于stack exchange,提问作者Ramesh Moorthy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 06:06:16