This commit is contained in:
2026-09-12 14:15:26 +08:00
commit 9c06d3f4be
99 changed files with 41853 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
// shared/friendly-fs-error.js —— 渲染端 + 主进程共用的 errno → 中文提示
//
// 覆盖 P0Round 4 收尾前 renderer 端有三份独立 mappingsrc/app.js /
// src/file-ops.js / main/file-ops.js用户看到的 toast 文案 EROFS / ENAMETOOLONG
// / ENOTDIR / ENOTEMPTY 不一致。本测试钉死 shared 模块的 mapping 文案main /
// preload / renderer 三处全依赖此单一事实源。每条 case 必须有断言 —— 防止未来
// 重构时改坏 mapping用户会看到误导的"未知错误")。
//
// shared/friendly-fs-error.js 是 CJSmodule.exports = { friendlyFsError }
// ESM 解析器不支持 named import from CJS —— vitest 在 node 环境下走 require 链路。
import { describe, it, expect } from 'vitest';
const { friendlyFsError } = require('../../shared/friendly-fs-error.js');
describe('friendlyFsError —— 已知 errno 映射', () => {
it('EACCES → 权限 / 占用提示', () => {
expect(friendlyFsError('EACCES')).toMatch(/占用|权限/);
});
it('EPERM → 权限 / 占用提示(与 EACCES 共用文案)', () => {
expect(friendlyFsError('EPERM')).toBe(friendlyFsError('EACCES'));
});
it('ENOSPC → 磁盘空间不足', () => {
expect(friendlyFsError('ENOSPC')).toBe('磁盘空间不足');
});
it('EROFS → 只读文件系统', () => {
expect(friendlyFsError('EROFS')).toBe('只读文件系统,无法写入');
});
it('EIO → 磁盘 I/O 错误', () => {
expect(friendlyFsError('EIO')).toBe('磁盘 I/O 错误');
});
it('EBUSY → 文件被其他程序占用', () => {
expect(friendlyFsError('EBUSY')).toBe('文件被其他程序占用');
});
it('ENAMETOOLONG → 路径过长', () => {
expect(friendlyFsError('ENAMETOOLONG')).toBe('路径过长');
});
it('ENOTDIR → 父目录不是目录', () => {
expect(friendlyFsError('ENOTDIR')).toBe('父目录不是目录');
});
it('EISDIR → 目标路径是文件夹', () => {
expect(friendlyFsError('EISDIR')).toBe('目标路径是文件夹,无法写入');
});
it('ENOTEMPTY → 目标文件夹不为空', () => {
expect(friendlyFsError('ENOTEMPTY')).toBe('目标文件夹不为空');
});
});
describe('friendlyFsError —— fallback 行为', () => {
it('未知 errno + 有 fallback → 返回 fallback', () => {
expect(friendlyFsError('EEXIST', '文件已存在')).toBe('文件已存在');
});
it('未知 errno + 无 fallback → 返回「未知错误」', () => {
expect(friendlyFsError('EEXIST')).toBe('未知错误');
});
it('未知 errno + fallback=空串 → 走 fallback空串本身', () => {
// 注意:空串是合法 fallback —— caller 故意传空串时不应被「未知错误」盖掉。
// 但代码实现是 `fallback || '未知错误'`,所以空串会被替换为「未知错误」。
// 这是有意的(避免空 toast但要把行为钉死。
expect(friendlyFsError('EEXIST', '')).toBe('未知错误');
});
it('undefined code → 走 default', () => {
expect(friendlyFsError(undefined, 'fallback')).toBe('fallback');
});
it('null code → 走 default', () => {
expect(friendlyFsError(null, 'fallback')).toBe('fallback');
});
it('空字符串 code → 走 default', () => {
expect(friendlyFsError('', 'fallback')).toBe('fallback');
});
});
describe('friendlyFsError —— 业务码K1-R4 audit 合约)', () => {
// audit fix (K1-R4):所有 file:* / shell:* IPC 错误返回都有 code 字段。
// 业务码PATH_NOT_ALLOWED / SYMLINK_NOT_ALLOWED / FILE_TOO_LARGE 等)不在
// errno 白名单里renderer 一律走 fallback而 renderer 8 个调用点现在传
// result.message中文而不是 result.error业务码字符串做 fallback
// 这样业务码错误也能给用户友好提示。
it('PATH_NOT_ALLOWED → 走 fallback中文化 message', () => {
expect(friendlyFsError('PATH_NOT_ALLOWED', '路径不在数据目录内')).toBe('路径不在数据目录内');
});
it('SYMLINK_NOT_ALLOWED → 走 fallback', () => {
expect(friendlyFsError('SYMLINK_NOT_ALLOWED', '不允许在符号链接目录下创建文件')).toBe('不允许在符号链接目录下创建文件');
});
it('FILE_TOO_LARGE → 走 fallback', () => {
expect(friendlyFsError('FILE_TOO_LARGE', '文件超过 50 MB 上限')).toBe('文件超过 50 MB 上限');
});
it('FILE_CHANGED_EXTERNALLY → 走 fallback', () => {
expect(friendlyFsError('FILE_CHANGED_EXTERNALLY', '文件在外部被修改')).toBe('文件在外部被修改');
});
it('NOT_A_FILE → 走 fallback', () => {
expect(friendlyFsError('NOT_A_FILE', '该路径是文件夹')).toBe('该路径是文件夹');
});
});