import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"; import { mkdir, access, rm } from "node:fs/promises"; import { join } from "node:path"; const TMP_DIR = join(import.meta.dir, "__tmp_cleanup_test__"); async function dirExists(path: string): Promise { try { await access(path); return true; } catch { return false; } } describe("retryRm", () => { describe("正常删除", () => { afterEach(async () => { if (await dirExists(TMP_DIR)) { await rm(TMP_DIR, { recursive: true, force: true }); } }); it("删除存在的目录", async () => { const { retryRm } = await import("./cleanup.ts"); await mkdir(TMP_DIR, { recursive: true }); await retryRm(TMP_DIR); expect(await dirExists(TMP_DIR)).toBe(false); }); it("删除不存在的路径不报错(force:true)", async () => { const { retryRm } = await import("./cleanup.ts"); const nonExist = join(import.meta.dir, "__non_exist__"); await expect(retryRm(nonExist)).resolves.toBeUndefined(); }); }); describe("EBUSY 重试后成功", () => { let callCount: number; beforeEach(() => { callCount = 0; mock.module("node:fs/promises", () => ({ rm: mock(async () => { callCount++; if (callCount === 1) { const err: any = new Error("EBUSY"); err.code = "EBUSY"; throw err; } }), })); }); afterEach(() => { mock.restore(); }); it("EBUSY 后重试成功", async () => { const { retryRm } = await import("./cleanup.ts"); await retryRm("/some/path", { maxRetries: 3, baseDelay: 10 }); expect(callCount).toBe(2); }); }); describe("重试耗尽后抛出错误", () => { let callCount: number; beforeEach(() => { callCount = 0; mock.module("node:fs/promises", () => ({ rm: mock(async () => { callCount++; const err: any = new Error("EBUSY"); err.code = "EBUSY"; throw err; }), })); }); afterEach(() => { mock.restore(); }); it("maxRetries 耗尽后抛出最后一个错误", async () => { const { retryRm } = await import("./cleanup.ts"); await expect(retryRm("/some/path", { maxRetries: 2, baseDelay: 10 })).rejects.toThrow( "EBUSY", ); expect(callCount).toBe(3); }); }); describe("非 EBUSY/EPERM 错误不重试", () => { let callCount: number; beforeEach(() => { callCount = 0; mock.module("node:fs/promises", () => ({ rm: mock(async () => { callCount++; const err: any = new Error("ENOENT: no such file or directory"); err.code = "ENOENT"; throw err; }), })); }); afterEach(() => { mock.restore(); }); it("ENOENT 错误直接抛出不重试", async () => { const { retryRm } = await import("./cleanup.ts"); await expect(retryRm("/some/path")).rejects.toThrow("ENOENT"); expect(callCount).toBe(1); }); }); });