-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(tests): stop a quota test from deleting the real OpenCodex home #4681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import { beforeEach, describe, expect, test } from "bun:test"; | ||
| import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; | ||
| import { afterAll, beforeEach, describe, expect, test } from "bun:test"; | ||
| import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { getConfigDir } from "../../src/config"; | ||
| import type { QuotaResetEvent } from "../../src/quota/reset-detector"; | ||
|
|
@@ -15,6 +16,36 @@ import { | |
| swapLastObservedWindows, | ||
| } from "../../src/quota/reset-seen-store"; | ||
|
|
||
| /** | ||
| * This file owns its config directory instead of inheriting one. | ||
| * | ||
| * Every case here resolves the process-global config home, and one of them DELETES it to | ||
| * force a write failure. That is bounded only while OPENCODEX_HOME points at a sandbox, and | ||
| * the preload that normally guarantees it does not cover every way this file can be run: Bun | ||
| * resolves `bunfig.toml` — and therefore its `preload = ["./tests/preload.ts"]` — from the | ||
| * CURRENT WORKING DIRECTORY. A run started outside the repository loads no preload, leaves | ||
| * OPENCODEX_HOME unset and the guard disarmed, and `getConfigDir()` then resolves the | ||
| * developer's real `~/.opencodex`. | ||
| * | ||
| * On 2026-09-15 exactly that invocation ran this file and deleted a live home. auth.json, | ||
| * codex-accounts.json, the service tokens and a 372MB usage ledger went with it; every OAuth | ||
| * login on the machine was gone, and only an unrelated three-week-old copy made any of it | ||
| * recoverable. The write guard could not help: `assertNotRealHomeUnderTest` covers | ||
| * writers, and `rmSync` is not one. | ||
| * | ||
| * Pinning the home here is what makes the deletion below safe under EITHER invocation. The | ||
| * previous value is restored afterwards because Bun reuses one process for several files. | ||
| */ | ||
| const PREVIOUS_OPENCODEX_HOME = process.env.OPENCODEX_HOME; | ||
| const ISOLATED_HOME = mkdtempSync(join(tmpdir(), "quota-reset-seen-store-")); | ||
| process.env.OPENCODEX_HOME = ISOLATED_HOME; | ||
|
|
||
| afterAll(() => { | ||
| if (PREVIOUS_OPENCODEX_HOME === undefined) delete process.env.OPENCODEX_HOME; | ||
| else process.env.OPENCODEX_HOME = PREVIOUS_OPENCODEX_HOME; | ||
| rmSync(ISOLATED_HOME, { recursive: true, force: true }); | ||
|
Comment on lines
+43
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this file is launched from outside the repository alongside another test file, the final Useful? React with 👍 / 👎. |
||
| }); | ||
|
|
||
| const DAY = 24 * 60 * 60_000; | ||
| /** | ||
| * Real wall clock, not a fixed constant. | ||
|
|
@@ -75,7 +106,10 @@ describe("quota reset claim store", () => { | |
| // still reported a durable claim and the next start re-notified. | ||
| // atomicWriteFile writes a sibling temp file in the config dir, so replacing that | ||
| // directory with a regular file makes the real write fail without touching the module. | ||
| const configDir = getConfigDir(); | ||
| // This file's OWN directory, named directly: the store resolves the same path, and a | ||
| // destructive call must never be able to follow a config home it did not create. | ||
| const configDir = ISOLATED_HOME; | ||
| expect(getConfigDir()).toBe(configDir); | ||
| rmSync(configDir, { recursive: true, force: true }); | ||
| writeFileSync(configDir, "not a directory"); | ||
| try { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Detect formatted destructive calls.
This guard analyzes one line at a time and excludes every line that contains a quote. A future test can bypass it with a normal multiline call such as
rmSync(\n getConfigDir(),\n { recursive: true, force: true },\n). A directrenameSync(getConfigDir(), join(tmpdir(), "backup"))call also bypasses the check because the destination contains a quote.If that test starts outside the repository,
getConfigDir()can resolve the developer's real home. Parse the TypeScript source and inspect destructive-call argument expressions instead of filtering lines. Add regression cases for multiline calls and quoted destination arguments.Also applies to: 569-576
🤖 Prompt for AI Agents