寻求适用于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; }); }); }); }); });
注意事项
- 若使用
music-metadata验证音频,集成方式类似:读取文件buffer后调用parseBuffer方法,检查返回的元数据是否符合预期 - 对于大文件,建议在测试中使用压缩后的样本,避免Cypress测试超时
- 确保Cypress配置中允许读取fixture文件,默认配置已支持
内容的提问来源于stack exchange,提问作者Ramesh Moorthy
相关产品推荐
相关产品推荐

