diff --git a/test/commands/issue/utils.test.ts b/test/commands/issue/utils.test.ts index ad3ac173..08c50a5d 100644 --- a/test/commands/issue/utils.test.ts +++ b/test/commands/issue/utils.test.ts @@ -15,7 +15,12 @@ import { DEFAULT_SENTRY_URL } from "../../../src/lib/constants.js"; import { setAuthToken } from "../../../src/lib/db/auth.js"; import { CONFIG_DIR_ENV_VAR } from "../../../src/lib/db/index.js"; import { setOrgRegion } from "../../../src/lib/db/regions.js"; -import { cleanupTestDir, createTestConfigDir } from "../../helpers.js"; +import { + cleanupTestDir, + createTestConfigDir, + lockConfigDir, + lockFetch, +} from "../../helpers.js"; describe("buildCommandHint", () => { test("suggests /ID for numeric IDs", () => { @@ -49,13 +54,22 @@ describe("buildCommandHint", () => { let testConfigDir: string; let originalFetch: typeof globalThis.fetch; +let savedConfigDir: string | undefined; +let unlockEnv: (() => void) | undefined; +let unlockFetchFn: (() => void) | undefined; beforeEach(async () => { + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; // Use isolateProjectRoot to prevent DSN detection from scanning the real project testConfigDir = await createTestConfigDir("test-issue-utils-", { isolateProjectRoot: true, }); process.env[CONFIG_DIR_ENV_VAR] = testConfigDir; + + // Lock the env var so concurrent test files cannot change it during our test. + // This prevents the DB singleton from auto-invalidating mid-test. + unlockEnv = lockConfigDir(testConfigDir); + originalFetch = globalThis.fetch; await setAuthToken("test-token"); // Pre-populate region cache for orgs used in tests to avoid region resolution API calls @@ -66,14 +80,23 @@ beforeEach(async () => { }); afterEach(async () => { + // Unlock fetch first (if locked by this test) + unlockFetchFn?.(); + unlockFetchFn = undefined; globalThis.fetch = originalFetch; + // Unlock env var, then restore + unlockEnv?.(); + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } await cleanupTestDir(testConfigDir); }); describe("resolveOrgAndIssueId", () => { test("throws for numeric ID (org cannot be resolved)", async () => { - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -100,7 +123,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); // Numeric IDs don't have org context, so resolveOrgAndIssueId should throw await expect( @@ -113,8 +136,7 @@ describe("resolveOrgAndIssueId", () => { }); test("resolves explicit org prefix (org/ISSUE-ID)", async () => { - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -140,7 +162,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); const result = await resolveOrgAndIssueId({ issueArg: "my-org/PROJECT-ABC", @@ -165,8 +187,7 @@ describe("resolveOrgAndIssueId", () => { "" ); - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -192,7 +213,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); const result = await resolveOrgAndIssueId({ issueArg: "f-g", @@ -205,8 +226,7 @@ describe("resolveOrgAndIssueId", () => { }); test("resolves explicit org prefix with project-suffix (e.g., 'org1/dashboard-4y')", async () => { - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -233,7 +253,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); const result = await resolveOrgAndIssueId({ issueArg: "org1/dashboard-4y", @@ -249,8 +269,7 @@ describe("resolveOrgAndIssueId", () => { const { setDefaults } = await import("../../../src/lib/db/defaults.js"); await setDefaults("my-org", "my-project"); - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -276,7 +295,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); const result = await resolveOrgAndIssueId({ issueArg: "G", @@ -310,8 +329,7 @@ describe("resolveOrgAndIssueId", () => { ); await clearProjectAliases(); - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -371,7 +389,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); const result = await resolveOrgAndIssueId({ issueArg: "craft-g", @@ -389,8 +407,7 @@ describe("resolveOrgAndIssueId", () => { ); await clearProjectAliases(); - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -430,7 +447,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); await expect( resolveOrgAndIssueId({ @@ -449,8 +466,7 @@ describe("resolveOrgAndIssueId", () => { await setOrgRegion("org2", DEFAULT_SENTRY_URL); - // @ts-expect-error - partial mock - globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => { + unlockFetchFn = lockFetch(async (input, init) => { const req = new Request(input, init); const url = req.url; @@ -506,7 +522,7 @@ describe("resolveOrgAndIssueId", () => { return new Response(JSON.stringify({ detail: "Not found" }), { status: 404, }); - }; + }); await expect( resolveOrgAndIssueId({ @@ -521,11 +537,12 @@ describe("resolveOrgAndIssueId", () => { const { setDefaults } = await import("../../../src/lib/db/defaults.js"); await setDefaults("my-org", "my-project"); - // @ts-expect-error - partial mock - globalThis.fetch = async () => - new Response(JSON.stringify({ detail: "Unauthorized" }), { - status: 401, - }); + unlockFetchFn = lockFetch( + async () => + new Response(JSON.stringify({ detail: "Unauthorized" }), { + status: 401, + }) + ); // Auth errors should propagate await expect( @@ -541,11 +558,12 @@ describe("resolveOrgAndIssueId", () => { const { setDefaults } = await import("../../../src/lib/db/defaults.js"); await setDefaults("my-org", "my-project"); - // @ts-expect-error - partial mock - globalThis.fetch = async () => - new Response(JSON.stringify({ detail: "Internal Server Error" }), { - status: 500, - }); + unlockFetchFn = lockFetch( + async () => + new Response(JSON.stringify({ detail: "Internal Server Error" }), { + status: 500, + }) + ); // Server errors should propagate await expect( diff --git a/test/helpers.ts b/test/helpers.ts index c2f27312..25f9a865 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -73,3 +73,107 @@ type FetchMockFn = export function mockFetch(fn: FetchMockFn): typeof fetch { return fn as unknown as typeof fetch; } + +/** + * Module-level lock state for SENTRY_CONFIG_DIR. + * + * We replace process.env with a Proxy that intercepts reads, writes, + * and deletes of SENTRY_CONFIG_DIR. When locked, the proxy returns the + * locked value and silently ignores mutations. When unlocked, all + * operations pass through to the real env object. + * + * A Proxy is used instead of Object.defineProperty because: + * - configurable:true descriptors can be removed by `delete` (other + * test files do `delete process.env[CONFIG_DIR_ENV_VAR]` in afterEach) + * - configurable:false descriptors cause `delete` to throw in Bun, + * breaking other test files + * + * The Proxy is installed once and persists for the process lifetime. + * Lock/unlock just toggles the module-level state variables. + */ +let configDirLocked = false; +let configDirLockedValue: string | undefined; +let envProxyInstalled = false; + +function installEnvProxy(): void { + if (envProxyInstalled) return; + const realEnv = process.env; + process.env = new Proxy(realEnv, { + get(target, prop, receiver) { + if (prop === "SENTRY_CONFIG_DIR" && configDirLocked) { + return configDirLockedValue; + } + return Reflect.get(target, prop, receiver); + }, + set(target, prop, value) { + if (prop === "SENTRY_CONFIG_DIR" && configDirLocked) { + return true; // Silently ignore + } + return Reflect.set(target, prop, value); + }, + deleteProperty(target, prop) { + if (prop === "SENTRY_CONFIG_DIR" && configDirLocked) { + return true; // Silently ignore + } + return Reflect.deleteProperty(target, prop); + }, + }); + envProxyInstalled = true; +} + +/** + * Lock SENTRY_CONFIG_DIR so concurrent test files cannot change it. + * + * Bun runs test files concurrently in a single process, sharing + * `process.env`. Between any `await` point inside our test, another + * file's beforeEach/afterEach can mutate the env var. The DB singleton + * auto-invalidates when the config dir changes, so even a momentary + * mutation causes getDatabase() to open the wrong DB and lose data. + * + * Uses a Proxy on process.env that intercepts get/set/delete of the + * config dir env var. When locked, returns the locked value and ignores + * mutations. When unlocked, all operations pass through normally. + * + * @param configDir - The config directory path to lock + * @returns Unlock function to call in afterEach + */ +export function lockConfigDir(configDir: string): () => void { + installEnvProxy(); + configDirLocked = true; + configDirLockedValue = configDir; + + return () => { + configDirLocked = false; + }; +} + +/** + * Lock globalThis.fetch to a mock handler so concurrent test files + * cannot replace it between async boundaries. + * + * Uses configurable: true since concurrent test files use assignment + * (not delete) for fetch, and other test files (e.g. api-client) need + * to assign their own mock via globalThis.fetch = ... without going + * through this lock. + * + * @param fn - The fetch mock implementation + * @returns Unlock function to call in afterEach + */ +export function lockFetch( + fn: (input: RequestInfo | URL, init?: RequestInit) => Promise +): () => void { + Object.defineProperty(globalThis, "fetch", { + get() { + return fn; + }, + set() { + // Silently ignore writes from other test files + }, + configurable: true, + enumerable: true, + }); + + return () => { + delete (globalThis as Record).fetch; + }; +} diff --git a/test/lib/api-client.multiregion.test.ts b/test/lib/api-client.multiregion.test.ts index 90ab2a5a..4b40b6fb 100644 --- a/test/lib/api-client.multiregion.test.ts +++ b/test/lib/api-client.multiregion.test.ts @@ -23,8 +23,10 @@ import { cleanupTestDir, createTestConfigDir } from "../helpers.js"; let testConfigDir: string; let originalFetch: typeof globalThis.fetch; +let savedConfigDir: string | undefined; beforeEach(async () => { + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; testConfigDir = await createTestConfigDir("test-multiregion-"); process.env[CONFIG_DIR_ENV_VAR] = testConfigDir; @@ -42,6 +44,11 @@ afterEach(async () => { // Restore original fetch globalThis.fetch = originalFetch; closeDatabase(); + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } await cleanupTestDir(testConfigDir); }); diff --git a/test/lib/api-client.seer.test.ts b/test/lib/api-client.seer.test.ts index 5b6a528f..3cde7e43 100644 --- a/test/lib/api-client.seer.test.ts +++ b/test/lib/api-client.seer.test.ts @@ -18,8 +18,10 @@ import { cleanupTestDir, createTestConfigDir } from "../helpers.js"; // Test config directory let testConfigDir: string; let originalFetch: typeof globalThis.fetch; +let savedConfigDir: string | undefined; beforeEach(async () => { + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; testConfigDir = await createTestConfigDir("test-seer-api-"); process.env[CONFIG_DIR_ENV_VAR] = testConfigDir; @@ -35,6 +37,11 @@ beforeEach(async () => { afterEach(async () => { // Restore original fetch globalThis.fetch = originalFetch; + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } await cleanupTestDir(testConfigDir); }); diff --git a/test/lib/api-client.test.ts b/test/lib/api-client.test.ts index 18838a09..bbd3aa6b 100644 --- a/test/lib/api-client.test.ts +++ b/test/lib/api-client.test.ts @@ -9,26 +9,29 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { buildSearchParams, rawApiRequest } from "../../src/lib/api-client.js"; import { setAuthToken } from "../../src/lib/db/auth.js"; import { CONFIG_DIR_ENV_VAR } from "../../src/lib/db/index.js"; -import { cleanupTestDir, createTestConfigDir } from "../helpers.js"; +import { + cleanupTestDir, + createTestConfigDir, + lockConfigDir, +} from "../helpers.js"; // Test config directory let testConfigDir: string; let originalFetch: typeof globalThis.fetch; - -/** - * Tracks requests made during a test - */ -type RequestLog = { - url: string; - method: string; - authorization: string | null; - isRetry: boolean; -}; +let savedConfigDir: string | undefined; +let savedClientId: string | undefined; +let unlockEnv: (() => void) | undefined; beforeEach(async () => { + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; + savedClientId = process.env.SENTRY_CLIENT_ID; testConfigDir = await createTestConfigDir("test-api-"); process.env[CONFIG_DIR_ENV_VAR] = testConfigDir; + // Lock the env var so concurrent test files cannot change it during our test. + // This prevents the DB singleton from auto-invalidating mid-test. + unlockEnv = lockConfigDir(testConfigDir); + // Set required env var for OAuth refresh process.env.SENTRY_CLIENT_ID = "test-client-id"; @@ -43,6 +46,19 @@ afterEach(async () => { // Restore original fetch globalThis.fetch = originalFetch; + // Unlock env var first, then restore + unlockEnv?.(); + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } + if (savedClientId !== undefined) { + process.env.SENTRY_CLIENT_ID = savedClientId; + } else { + delete process.env.SENTRY_CLIENT_ID; + } + await cleanupTestDir(testConfigDir); }); @@ -51,6 +67,9 @@ afterEach(async () => { * Uses rawApiRequest which goes to control silo (no region resolution needed). * * The `apiRequestHandler` is called for each API request. + * + * The mock re-pins itself on every call so that another test file's + * afterEach cannot replace it between async boundaries. */ function createMockFetch( requests: RequestLog[], @@ -64,7 +83,13 @@ function createMockFetch( ): typeof globalThis.fetch { let apiRequestCount = 0; - return async (input: RequestInfo | URL, init?: RequestInit) => { + const mock = async ( + input: RequestInfo | URL, + init?: RequestInit + ): Promise => { + // Re-pin this mock — another test file's afterEach may have replaced it + globalThis.fetch = mock as unknown as typeof fetch; + const req = new Request(input, init); requests.push({ url: req.url, @@ -96,6 +121,7 @@ function createMockFetch( apiRequestCount += 1; return apiRequestHandler(req, apiRequestCount); }; + return mock as unknown as typeof globalThis.fetch; } describe("401 retry behavior", () => { diff --git a/test/lib/config.test.ts b/test/lib/config.test.ts index e2b747f5..0218ac9b 100644 --- a/test/lib/config.test.ts +++ b/test/lib/config.test.ts @@ -38,6 +38,7 @@ import { setCachedProject, setCachedProjectByDsnKey, } from "../../src/lib/db/project-cache.js"; +import { lockConfigDir } from "../helpers.js"; /** * Test isolation: Each test gets its own config directory within @@ -48,12 +49,18 @@ import { * are still running. The parent test directory is cleaned up on * process exit by preload.ts. */ -const testBaseDir = process.env[CONFIG_DIR_ENV_VAR]!; +let savedConfigDir: string | undefined; +let unlockEnv: (() => void) | undefined; beforeEach(() => { // Close any previous database connection closeDatabase(); + // Read the base dir at hook time (not module scope) to avoid race conditions + // with other test files that may modify the env var during parallel execution + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; + const testBaseDir = process.env[CONFIG_DIR_ENV_VAR]!; + // Create a unique subdirectory for this test const testConfigDir = join( testBaseDir, @@ -61,13 +68,22 @@ beforeEach(() => { ); mkdirSync(testConfigDir, { recursive: true }); process.env[CONFIG_DIR_ENV_VAR] = testConfigDir; + + // Lock the env var so concurrent test files cannot change it during our test. + // This prevents the DB singleton from auto-invalidating mid-test. + unlockEnv = lockConfigDir(testConfigDir); }); afterEach(() => { // Close database to release file handles closeDatabase(); - // Note: We don't delete the test directory here because tests run in parallel. - // The parent test directory is cleaned up on process exit by preload.ts. + // Unlock env var first, then restore + unlockEnv?.(); + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } }); describe("auth token management", () => { @@ -565,7 +581,7 @@ describe("getDbPath", () => { test("returns the database file path", () => { const path = getDbPath(); expect(path).toContain("cli.db"); - expect(path).toContain(testBaseDir); + expect(path).toContain(process.env[CONFIG_DIR_ENV_VAR]!); }); }); diff --git a/test/lib/db/concurrent.test.ts b/test/lib/db/concurrent.test.ts index 45b8dabc..b18d4c76 100644 --- a/test/lib/db/concurrent.test.ts +++ b/test/lib/db/concurrent.test.ts @@ -19,7 +19,6 @@ import { import { getCachedProject } from "../../../src/lib/db/project-cache.js"; const WORKER_SCRIPT = join(import.meta.dir, "concurrent-worker.ts"); -const TEST_BASE_DIR = process.env[CONFIG_DIR_ENV_VAR]!; type WorkerResult = { workerId: string; @@ -89,11 +88,14 @@ async function spawnWorkersConcurrently( describe("concurrent database access", () => { let testConfigDir: string; + let savedConfigDir: string | undefined; beforeEach(async () => { closeDatabase(); + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; + const testBaseDir = process.env[CONFIG_DIR_ENV_VAR]!; testConfigDir = join( - TEST_BASE_DIR, + testBaseDir, `concurrent-${Date.now()}-${Math.random().toString(36).slice(2)}` ); mkdirSync(testConfigDir, { recursive: true }); @@ -109,6 +111,11 @@ describe("concurrent database access", () => { afterEach(() => { closeDatabase(); + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } }); test("multiple processes can write DSN cache entries simultaneously", async () => { diff --git a/test/lib/db/install-info.test.ts b/test/lib/db/install-info.test.ts index 7278e608..8b5146bb 100644 --- a/test/lib/db/install-info.test.ts +++ b/test/lib/db/install-info.test.ts @@ -12,15 +12,21 @@ import { import { cleanupTestDir, createTestConfigDir } from "../../helpers.js"; let testConfigDir: string; +let savedConfigDir: string | undefined; beforeEach(async () => { + savedConfigDir = process.env.SENTRY_CONFIG_DIR; testConfigDir = await createTestConfigDir("test-install-info-"); process.env.SENTRY_CONFIG_DIR = testConfigDir; }); afterEach(async () => { closeDatabase(); - delete process.env.SENTRY_CONFIG_DIR; + if (savedConfigDir !== undefined) { + process.env.SENTRY_CONFIG_DIR = savedConfigDir; + } else { + delete process.env.SENTRY_CONFIG_DIR; + } await cleanupTestDir(testConfigDir); }); diff --git a/test/lib/db/project-cache.test.ts b/test/lib/db/project-cache.test.ts index eb06d8bb..afc4f2a3 100644 --- a/test/lib/db/project-cache.test.ts +++ b/test/lib/db/project-cache.test.ts @@ -16,8 +16,10 @@ import { import { cleanupTestDir, createTestConfigDir } from "../../helpers.js"; let testConfigDir: string; +let savedConfigDir: string | undefined; beforeEach(async () => { + savedConfigDir = process.env.SENTRY_CONFIG_DIR; testConfigDir = await createTestConfigDir("test-project-cache-"); process.env.SENTRY_CONFIG_DIR = testConfigDir; }); @@ -25,7 +27,11 @@ beforeEach(async () => { afterEach(async () => { // Close database to release file handles before cleanup closeDatabase(); - delete process.env.SENTRY_CONFIG_DIR; + if (savedConfigDir !== undefined) { + process.env.SENTRY_CONFIG_DIR = savedConfigDir; + } else { + delete process.env.SENTRY_CONFIG_DIR; + } await cleanupTestDir(testConfigDir); }); diff --git a/test/lib/dsn/cache.test.ts b/test/lib/dsn/cache.test.ts index 2611ef46..9c97cb3b 100644 --- a/test/lib/dsn/cache.test.ts +++ b/test/lib/dsn/cache.test.ts @@ -18,15 +18,23 @@ import { CONFIG_DIR_ENV_VAR } from "../../../src/lib/db/index.js"; // Use a unique test config directory const TEST_CONFIG_DIR = join(homedir(), ".sentry-cli-test-cache"); +let savedConfigDir: string | undefined; describe("DSN Cache", () => { beforeEach(() => { // Set up test config directory + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; process.env[CONFIG_DIR_ENV_VAR] = TEST_CONFIG_DIR; mkdirSync(TEST_CONFIG_DIR, { recursive: true }); }); afterEach(() => { + // Restore config dir + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } // Clean up test config directory try { rmSync(TEST_CONFIG_DIR, { recursive: true, force: true }); diff --git a/test/lib/dsn/detector.test.ts b/test/lib/dsn/detector.test.ts index a1c9b818..c3aae62f 100644 --- a/test/lib/dsn/detector.test.ts +++ b/test/lib/dsn/detector.test.ts @@ -39,9 +39,13 @@ const TEST_CONFIG_DIR = join(homedir(), ".sentry-cli-test-detector-config"); describe("DSN Detector (New Module)", () => { let testDir: string; + let savedConfigDir: string | undefined; + let savedSentryDsn: string | undefined; beforeEach(async () => { testDir = createTempDir(); + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; + savedSentryDsn = process.env.SENTRY_DSN; process.env[CONFIG_DIR_ENV_VAR] = TEST_CONFIG_DIR; mkdirSync(TEST_CONFIG_DIR, { recursive: true }); // Clear any cached DSN for the test directory @@ -51,7 +55,17 @@ describe("DSN Detector (New Module)", () => { }); afterEach(() => { - delete process.env.SENTRY_DSN; + // Restore env vars + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } + if (savedSentryDsn !== undefined) { + process.env.SENTRY_DSN = savedSentryDsn; + } else { + delete process.env.SENTRY_DSN; + } cleanupDir(testDir); cleanupDir(TEST_CONFIG_DIR); }); diff --git a/test/lib/region.test.ts b/test/lib/region.test.ts index 493027c0..158fbebb 100644 --- a/test/lib/region.test.ts +++ b/test/lib/region.test.ts @@ -16,10 +16,14 @@ import { } from "../../src/lib/region.js"; import { getSentryBaseUrl } from "../../src/lib/sentry-urls.js"; -const testBaseDir = process.env[CONFIG_DIR_ENV_VAR]!; +let savedConfigDir: string | undefined; +let savedSentryUrl: string | undefined; beforeEach(async () => { closeDatabase(); + savedConfigDir = process.env[CONFIG_DIR_ENV_VAR]; + savedSentryUrl = process.env.SENTRY_URL; + const testBaseDir = process.env[CONFIG_DIR_ENV_VAR]!; const testConfigDir = join( testBaseDir, `region-resolve-${Math.random().toString(36).slice(2)}` @@ -34,7 +38,16 @@ beforeEach(async () => { afterEach(() => { closeDatabase(); - delete process.env.SENTRY_URL; + if (savedConfigDir !== undefined) { + process.env[CONFIG_DIR_ENV_VAR] = savedConfigDir; + } else { + delete process.env[CONFIG_DIR_ENV_VAR]; + } + if (savedSentryUrl !== undefined) { + process.env.SENTRY_URL = savedSentryUrl; + } else { + delete process.env.SENTRY_URL; + } }); describe("getSentryBaseUrl", () => { diff --git a/test/lib/version-check.test.ts b/test/lib/version-check.test.ts index 6f52af54..7622d962 100644 --- a/test/lib/version-check.test.ts +++ b/test/lib/version-check.test.ts @@ -48,8 +48,10 @@ describe("shouldSuppressNotification", () => { describe("getUpdateNotification", () => { let testConfigDir: string; let savedNoUpdateCheck: string | undefined; + let savedConfigDir: string | undefined; beforeEach(async () => { + savedConfigDir = process.env.SENTRY_CONFIG_DIR; testConfigDir = await createTestConfigDir("test-version-notif-"); process.env.SENTRY_CONFIG_DIR = testConfigDir; // Save and clear the env var to test real implementation @@ -58,7 +60,11 @@ describe("getUpdateNotification", () => { }); afterEach(async () => { - delete process.env.SENTRY_CONFIG_DIR; + if (savedConfigDir !== undefined) { + process.env.SENTRY_CONFIG_DIR = savedConfigDir; + } else { + delete process.env.SENTRY_CONFIG_DIR; + } // Restore the env var if (savedNoUpdateCheck !== undefined) { process.env.SENTRY_CLI_NO_UPDATE_CHECK = savedNoUpdateCheck; @@ -117,8 +123,10 @@ describe("abortPendingVersionCheck", () => { describe("maybeCheckForUpdateInBackground", () => { let testConfigDir: string; let savedNoUpdateCheck: string | undefined; + let savedConfigDir: string | undefined; beforeEach(async () => { + savedConfigDir = process.env.SENTRY_CONFIG_DIR; testConfigDir = await createTestConfigDir("test-version-bg-"); process.env.SENTRY_CONFIG_DIR = testConfigDir; // Save and clear the env var to test real implementation @@ -129,7 +137,11 @@ describe("maybeCheckForUpdateInBackground", () => { afterEach(async () => { // Abort any pending check to clean up abortPendingVersionCheck(); - delete process.env.SENTRY_CONFIG_DIR; + if (savedConfigDir !== undefined) { + process.env.SENTRY_CONFIG_DIR = savedConfigDir; + } else { + delete process.env.SENTRY_CONFIG_DIR; + } // Restore the env var if (savedNoUpdateCheck !== undefined) { process.env.SENTRY_CLI_NO_UPDATE_CHECK = savedNoUpdateCheck;