diff --git a/tests/usage/usage-filter-index.test.ts b/tests/usage/usage-filter-index.test.ts index 3272cbd3d51..e79ba16a10c 100644 --- a/tests/usage/usage-filter-index.test.ts +++ b/tests/usage/usage-filter-index.test.ts @@ -1075,7 +1075,11 @@ describe("filtered usage index availability", () => { expect(existsSync(`${indexPath}-wal`)).toBe(false); }); - test("an unwritable home degrades to the streaming read", async () => { + // chmod cannot make a Windows directory unwritable, so there this would build the index and + // assert nothing about permissions. The degrade itself is the platform-neutral catch-all in + // readUsageFilterCandidates, which the vanishing-after-refresh case below reaches on every + // platform; contention and the foreign file cover the busy and explicit fallbacks. + test.skipIf(process.platform === "win32")("an unwritable home degrades to the streaming read", async () => { const rows = Array.from({ length: 5 }, (_, index) => syntheticRow(index)); writeRows(rows); const expected = rows.filter(row => row.provider === "openai").length; @@ -1130,13 +1134,25 @@ describe("filtered usage index availability", () => { } }); - test("a projection that vanishes before the read is not recreated", async () => { + /** Delete the projection at one point of a filtered query and expect a streaming answer. */ + async function expectVanishedProjectionNotRecreated(hooks: (remove: (dbPath: string) => void) => + Parameters[0]): Promise { const rows = Array.from({ length: 5 }, (_, index) => syntheticRow(index)); writeRows(rows); const indexPath = usageFilterIndexPath(testDir); - setUsageFilterIndexTestHooksForTests({ - afterPublish: ({ dbPath }) => { rmSync(dbPath, { force: true }); }, - }); + let removed = false; + setUsageFilterIndexTestHooksForTests(hooks(dbPath => { + const existed = existsSync(dbPath); + // A scanner can hold a just-closed file on Windows for a moment; that is not the subject. + for (let attempt = 0; ; attempt += 1) { + try { rmSync(dbPath, { force: true }); break; } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (attempt >= 4 || (code !== "EBUSY" && code !== "EPERM")) throw error; + Bun.sleepSync(25); + } + } + removed = existed && !existsSync(dbPath); + })); try { const result = await getFilteredUsageAggregate({ provider: "openai" }); expect(summary(result).summary.requests) @@ -1145,7 +1161,19 @@ describe("filtered usage index availability", () => { } finally { setUsageFilterIndexTestHooksForTests(null); } + // Without this the streaming fallback would satisfy every assertion even if the hook never ran. + expect(removed).toBe(true); expect(existsSync(indexPath)).toBe(false); + } + + // Windows refuses to delete a file SQLite holds open: there the projection can only vanish once + // the refresh has closed it, which the next case covers on every platform. + test.skipIf(process.platform === "win32")("a projection that vanishes before the read is not recreated", async () => { + await expectVanishedProjectionNotRecreated(remove => ({ afterPublish: ({ dbPath }) => remove(dbPath) })); + }); + + test("a projection that vanishes after the refresh closed it is not recreated by the read", async () => { + await expectVanishedProjectionNotRecreated(remove => ({ afterRefresh: ({ dbPath }) => remove(dbPath) })); }); test("a home change during a refresh never mixes installations", async () => {