This commit is contained in:
2026-10-21 14:42:59 +08:00
commit 954236d3f9
399 changed files with 85885 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
// tests/unit/main/utils/file-check.test.ts
//
// 守 assertFileExists 的**错误分类**契约(2026-10-20,r201):
//
// fs.open('r') 失败时的错误码必须区分:
// ENOENT / 无 code → FILE_MISSING("文件不存在")
// 其它( EACCES / EPERM / EISDIR / EBUSY …)→ IO_ERROR
//
// 为什么这条分类是行为面(不是风格):调用方(notes 的"在文件夹中显示"、文献
// 的 open 路径)据此决定 UI 文案与是否可重试。旧实现一律 FILE_MISSING,
// 权限问题被错报成"文件不存在",用户不停 retry,真实根因被埋没 —— r33 修过
// 一次(r33 只分了 ENOENT / 其它两支),r96 又把"拿不到 code"这一支从
// IO_ERROR 改回 FILE_MISSING(保守:不认识 → 当成不存在)。
//
// 本文件锁住 r96 之后的三支语义 —— 源码注释里明确写着"摘 !code 分支必红":
// 1. ENOENT → FILE_MISSING
// 2. 错误对象**没有** code → FILE_MISSING(保守兜底)
// 3. EACCES 等其它 code → IO_ERROR
// 4. 空路径 → FILE_MISSING
// 5. 打开成功 → 句柄一定被 close(不泄漏 fd)
import { promises as fsPromises } from 'fs';
import { mkdtemp, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { assertFileExists } from '../../../../src/main/utils/file-check';
import { createLogger } from '../../../../src/main/utils/logger';
const log = createLogger('file-check-test');
/** 造一个带 errno code 的错误(模拟 EACCES / EPERM / EISDIR / EBUSY) */
function fsError(code: string, msg: string): NodeJS.ErrnoException {
const e = new Error(msg) as NodeJS.ErrnoException;
e.code = code;
return e;
}
/** 取 LibraryError 的 code */
function codeOf(e: unknown): string | undefined {
return (e as { code?: string }).code;
}
describe('assertFileExists — 错误分类(r201)', () => {
let tmpRoot: string;
beforeEach(async () => {
tmpRoot = await mkdtemp(join(tmpdir(), 'rkms-filecheck-'));
});
afterEach(async () => {
vi.restoreAllMocks();
await rm(tmpRoot, { recursive: true, force: true });
});
it('空路径 → FILE_MISSING(不发起 syscall)', async () => {
const spy = vi.spyOn(fsPromises, 'open');
const err = await assertFileExists('', '文件不存在', log).then(
() => null,
(e: unknown) => e,
);
expect(err).toBeInstanceOf(Error);
expect(codeOf(err)).toBe('FILE_MISSING');
expect(spy).not.toHaveBeenCalled();
});
it('文件存在 → 正常 resolve', async () => {
const p = join(tmpRoot, 'there.md');
await writeFile(p, '# hi', 'utf-8');
await expect(assertFileExists(p, '文件不存在', log)).resolves.toBeUndefined();
});
it('文件不存在(ENOENT)→ FILE_MISSING,且消息带上 errno 文本便于排查', async () => {
const p = join(tmpRoot, 'missing.md');
const err = await assertFileExists(p, '文件不存在', log).then(
() => null,
(e: unknown) => e,
);
expect(err).toBeInstanceOf(Error);
expect(codeOf(err)).toBe('FILE_MISSING');
expect((err as Error).message).toContain('文件不存在');
expect((err as Error).message).toMatch(/ENOENT|no such file/i);
});
it('无 code 的错误 → FILE_MISSING(r96 保守兜底:不认识就当不存在)', async () => {
const spy = vi
.spyOn(fsPromises, 'open')
.mockImplementationOnce(() => Promise.reject(new Error('weird failure')) as never);
const err = await assertFileExists(join(tmpRoot, 'x.md'), '文件不存在', log).then(
() => null,
(e: unknown) => e,
);
expect(codeOf(err)).toBe('FILE_MISSING');
expect(spy).toHaveBeenCalledTimes(1);
});
it('EACCES → IO_ERROR(不是 FILE_MISSING)', async () => {
vi.spyOn(fsPromises, 'open').mockImplementationOnce(
() => Promise.reject(fsError('EACCES', 'permission denied')) as never,
);
const err = await assertFileExists(join(tmpRoot, 'x.md'), '文件不存在', log).then(
() => null,
(e: unknown) => e,
);
expect(codeOf(err)).toBe('IO_ERROR');
});
it('EISDIR(路径其实是目录)→ IO_ERROR', async () => {
vi.spyOn(fsPromises, 'open').mockImplementationOnce(
() => Promise.reject(fsError('EISDIR', 'illegal operation on a directory')) as never,
);
const err = await assertFileExists(join(tmpRoot, 'dir.md'), '文件不存在', log).then(
() => null,
(e: unknown) => e,
);
expect(codeOf(err)).toBe('IO_ERROR');
});
it('打开成功 → 句柄一定被 close(不泄漏 fd)', async () => {
const close = vi.fn(async () => undefined);
const fakeFh = { close } as unknown as Awaited<ReturnType<typeof fsPromises.open>>;
vi.spyOn(fsPromises, 'open').mockImplementationOnce((() =>
Promise.resolve(fakeFh)) as unknown as typeof fsPromises.open);
await expect(
assertFileExists(join(tmpRoot, 'ok.md'), '文件不存在', log),
).resolves.toBeUndefined();
expect(close).toHaveBeenCalledTimes(1);
});
});