























@@ -52,30 +52,97 @@ describe("media store", () => {
5252segment: string;
5353run: (store: typeof import("./store.js"), home: string) => Promise<{ path: string }>;
5454}) {
55-await withTempStore(async (store, home) => {
56-const originalWriteFile = fs.writeFile.bind(fs);
57-let injectedEnoent = false;
58-vi.spyOn(fs, "writeFile").mockImplementation(async (...args) => {
59-const [filePath] = args;
60-if (
61-!injectedEnoent &&
62-typeof filePath === "string" &&
63-filePath.includes(`${path.sep}${params.segment}${path.sep}`)
64-) {
65-injectedEnoent = true;
66-await fs.rm(path.dirname(filePath), { recursive: true, force: true });
67-const err = new Error("missing dir") as NodeJS.ErrnoException;
68-err.code = "ENOENT";
69-throw err;
70-}
71-return await originalWriteFile(...args);
55+const mockKey = `./store.js?scope=retry-pruned-write-${params.segment}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
56+let injectedEnoent = false;
57+vi.doMock("../infra/file-store.js", async (importOriginal) => {
58+const actual = await importOriginal<typeof import("../infra/file-store.js")>();
59+return {
60+ ...actual,
61+fileStore: (options: Parameters<typeof actual.fileStore>[0]) => {
62+const actualStore = actual.fileStore(options);
63+return {
64+ ...actualStore,
65+write: async (...args: Parameters<typeof actualStore.write>) => {
66+const [relativePath] = args;
67+if (!injectedEnoent && relativePath.includes(`${params.segment}${path.sep}`)) {
68+injectedEnoent = true;
69+await fs.rm(path.dirname(actualStore.path(relativePath)), {
70+recursive: true,
71+force: true,
72+});
73+const err = new Error("missing dir") as NodeJS.ErrnoException;
74+err.code = "ENOENT";
75+throw err;
76+}
77+return await actualStore.write(...args);
78+},
79+};
80+},
81+};
82+});
83+84+try {
85+const storeWithMock = await importFreshModule<typeof import("./store.js")>(
86+import.meta.url,
87+mockKey,
88+);
89+await withTempStore(async (_store, home) => {
90+const saved = await params.run(storeWithMock, home);
91+const savedStat = await fs.stat(saved.path);
92+expect(injectedEnoent).toBe(true);
93+expect(savedStat.isFile()).toBe(true);
7294});
95+} finally {
96+vi.doUnmock("../infra/file-store.js");
97+}
98+}
739974-const saved = await params.run(store, home);
75-const savedStat = await fs.stat(saved.path);
76-expect(injectedEnoent).toBe(true);
77-expect(savedStat.isFile()).toBe(true);
100+async function expectFailedBufferWriteCase() {
101+const mockKey = `./store.js?scope=failed-buffer-write-${Date.now()}-${Math.random().toString(36).slice(2)}`;
102+const attemptedRelPaths: string[] = [];
103+vi.doMock("../infra/file-store.js", async (importOriginal) => {
104+const actual = await importOriginal<typeof import("../infra/file-store.js")>();
105+return {
106+ ...actual,
107+fileStore: (options: Parameters<typeof actual.fileStore>[0]) => {
108+const actualStore = actual.fileStore(options);
109+return {
110+ ...actualStore,
111+write: async (...args: Parameters<typeof actualStore.write>) => {
112+const [relativePath] = args;
113+if (relativePath.includes(`failed-buffer${path.sep}`)) {
114+attemptedRelPaths.push(relativePath);
115+const err = new Error("no space left on device") as NodeJS.ErrnoException;
116+err.code = "ENOSPC";
117+throw err;
118+}
119+return await actualStore.write(...args);
120+},
121+};
122+},
123+};
78124});
125+126+try {
127+const storeWithMock = await importFreshModule<typeof import("./store.js")>(
128+import.meta.url,
129+mockKey,
130+);
131+await withTempStore(async (_store) => {
132+const mediaDir = await storeWithMock.ensureMediaDir();
133+await expect(
134+storeWithMock.saveMediaBuffer(Buffer.from("voice"), "audio/ogg", "failed-buffer"),
135+).rejects.toMatchObject({ code: "ENOSPC" });
136+137+const failedDir = path.join(mediaDir, "failed-buffer");
138+const entries = await fs.readdir(failedDir).catch(() => []);
139+expect(attemptedRelPaths).toHaveLength(1);
140+expect(path.basename(attemptedRelPaths[0] ?? "")).toMatch(/^[^/\\]+\.ogg$/);
141+expect(entries).toEqual([]);
142+});
143+} finally {
144+vi.doUnmock("../infra/file-store.js");
145+}
79146}
8014781148async function expectSavedOriginalFilenameCase(params: {
@@ -310,35 +377,7 @@ describe("media store", () => {
310377{
311378name: "does not leave final media artifacts when buffer writes fail",
312379run: async () => {
313-await withTempStore(async (store) => {
314-const mediaDir = await store.ensureMediaDir();
315-const originalWriteFile = fs.writeFile.bind(fs);
316-const attemptedPaths: string[] = [];
317-vi.spyOn(fs, "writeFile").mockImplementation(async (...args) => {
318-const [filePath] = args;
319-if (
320-typeof filePath === "string" &&
321-filePath.includes(`${path.sep}failed-buffer${path.sep}`)
322-) {
323-attemptedPaths.push(filePath);
324-await originalWriteFile(filePath, Buffer.alloc(0), args[2]);
325-const err = new Error("no space left on device") as NodeJS.ErrnoException;
326-err.code = "ENOSPC";
327-throw err;
328-}
329-return await originalWriteFile(...args);
330-});
331-332-await expect(
333-store.saveMediaBuffer(Buffer.from("voice"), "audio/ogg", "failed-buffer"),
334-).rejects.toMatchObject({ code: "ENOSPC" });
335-336-const failedDir = path.join(mediaDir, "failed-buffer");
337-const entries = await fs.readdir(failedDir).catch(() => []);
338-expect(attemptedPaths).toHaveLength(1);
339-expect(path.basename(attemptedPaths[0] ?? "")).toMatch(/^\..+\.tmp$/);
340-expect(entries).toEqual([]);
341-});
380+await expectFailedBufferWriteCase();
342381},
343382},
344383{
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。