From 6918eef08013e37de899bb8097c8f50c9c22a214 Mon Sep 17 00:00:00 2001 From: reginaldalfret Date: Thu, 29 Oct 2026 00:57:14 +0530 Subject: [PATCH] test: ensure cross-platform environment isolation in isolateHome Stub both HOME and USERPROFILE in isolateHome to ensure Node's os.homedir() is properly redirected to the isolated temporary directory across all operating systems. On Windows, os.homedir() prioritizes USERPROFILE over HOME, which previously led to tests writing to the host user's actual ~/.cursor configuration. Also: - Add unit tests for isolateHome in packages/testing - Enable vitest runner in @spotify-confidence/testing - Skip POSIX permission bit checks on Windows in store.test.ts - Normalize path resolution assertions for --dir in CLI tests Signed-off-by: reginaldalfret --- packages/cli/__tests__/commands/mcp.test.ts | 4 +-- .../cli/__tests__/commands/plugin.test.ts | 8 ++++-- .../__tests__/auth/credentials/store.test.ts | 5 ++-- .../testing/__tests__/home-scaffold.test.ts | 26 +++++++++++++++++++ .../testing/__tests__/overlay-env.test.ts | 1 + packages/testing/package.json | 1 + .../testing/src/scaffold/home-scaffold.ts | 5 ++-- 7 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 packages/testing/__tests__/home-scaffold.test.ts diff --git a/packages/cli/__tests__/commands/mcp.test.ts b/packages/cli/__tests__/commands/mcp.test.ts index 919c29d7..faeb7784 100644 --- a/packages/cli/__tests__/commands/mcp.test.ts +++ b/packages/cli/__tests__/commands/mcp.test.ts @@ -229,7 +229,7 @@ describe('--dir', () => { await run(['mcp', 'status', '--ide', 'claude', '--dir', '/tmp/my-project', '--json']); - expect(mockIntegration.detectMcpStatuses).toHaveBeenCalledWith('/tmp/my-project'); + expect(mockIntegration.detectMcpStatuses).toHaveBeenCalledWith(resolve('/tmp/my-project')); }); it('passes --dir through to auth refresh', async () => { @@ -239,7 +239,7 @@ describe('--dir', () => { await run(['mcp', 'auth', '--ide', 'claude', '--dir', '/tmp/my-project']); expect(mockIntegration.connectMcpServer).toHaveBeenCalledWith( - expect.objectContaining({ projectDir: '/tmp/my-project' }), + expect.objectContaining({ projectDir: resolve('/tmp/my-project') }), ); }); }); diff --git a/packages/cli/__tests__/commands/plugin.test.ts b/packages/cli/__tests__/commands/plugin.test.ts index 845069c5..11e2ce8a 100644 --- a/packages/cli/__tests__/commands/plugin.test.ts +++ b/packages/cli/__tests__/commands/plugin.test.ts @@ -124,7 +124,7 @@ describe('plugin update', () => { await run(['plugin', 'update', '--ide', 'claude', '--dir', '/tmp/my-project']); - expect(mockUpdatePlugin).toHaveBeenCalledWith('claude', '/tmp/my-project', 'project'); + expect(mockUpdatePlugin).toHaveBeenCalledWith('claude', resolve('/tmp/my-project'), 'project'); }); it('passes --scope to the updater', async () => { @@ -163,7 +163,11 @@ describe('plugin uninstall', () => { await run(['plugin', 'uninstall', '--ide', 'claude', '--dir', '/tmp/my-project']); - expect(mockUninstallPlugin).toHaveBeenCalledWith('claude', '/tmp/my-project', 'project'); + expect(mockUninstallPlugin).toHaveBeenCalledWith( + 'claude', + resolve('/tmp/my-project'), + 'project', + ); }); it('passes --scope to the uninstaller', async () => { diff --git a/packages/core/__tests__/auth/credentials/store.test.ts b/packages/core/__tests__/auth/credentials/store.test.ts index 2090ce08..129a1f31 100644 --- a/packages/core/__tests__/auth/credentials/store.test.ts +++ b/packages/core/__tests__/auth/credentials/store.test.ts @@ -1,5 +1,6 @@ import { existsSync, statSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; +import { isWindows } from '@spotify-confidence/shared-kernel'; import { prepareAuthTokens } from '@spotify-confidence/testing/auth'; import { getConfigDir } from '@config/index.js'; import { @@ -20,7 +21,7 @@ describe('writeCredentials / readCredentials', () => { expect(sut).toEqual(creds); }); - it('creates the config directory with 0700 permissions', () => { + it.skipIf(isWindows)('creates the config directory with 0700 permissions', () => { using _auth = prepareAuthTokens('none'); writeCredentials({ accessToken: 'tok' }); @@ -30,7 +31,7 @@ describe('writeCredentials / readCredentials', () => { expect(mode).toBe(0o700); }); - it('writes the credentials file with 0600 permissions', () => { + it.skipIf(isWindows)('writes the credentials file with 0600 permissions', () => { using _auth = prepareAuthTokens('none'); writeCredentials({ accessToken: 'tok' }); diff --git a/packages/testing/__tests__/home-scaffold.test.ts b/packages/testing/__tests__/home-scaffold.test.ts new file mode 100644 index 00000000..56122b93 --- /dev/null +++ b/packages/testing/__tests__/home-scaffold.test.ts @@ -0,0 +1,26 @@ +import { homedir } from 'node:os'; +import { describe, it, expect } from 'vitest'; +import { isolateHome } from '../src/scaffold/home-scaffold.js'; + +describe('isolateHome', () => { + it('stubs both HOME and USERPROFILE to isolate os.homedir() across platforms', () => { + const fakeDir = process.platform === 'win32' ? 'C:\\fake\\home' : '/fake/home'; + { + using _home = isolateHome(fakeDir); + expect(process.env.HOME).toBe(fakeDir); + expect(process.env.USERPROFILE).toBe(fakeDir); + expect(homedir()).toBe(fakeDir); + } + }); + + it('restores previous environment when disposed', () => { + const origHome = process.env.HOME; + const origUserProfile = process.env.USERPROFILE; + { + using _home = isolateHome('/temporary/test/dir'); + expect(process.env.HOME).toBe('/temporary/test/dir'); + } + expect(process.env.HOME).toBe(origHome); + expect(process.env.USERPROFILE).toBe(origUserProfile); + }); +}); diff --git a/packages/testing/__tests__/overlay-env.test.ts b/packages/testing/__tests__/overlay-env.test.ts index 1816d674..c97203b3 100644 --- a/packages/testing/__tests__/overlay-env.test.ts +++ b/packages/testing/__tests__/overlay-env.test.ts @@ -1,3 +1,4 @@ +import { describe, it, expect } from 'vitest'; import { overlayEnv } from '../src/env/overlay-env.js'; describe('overlayEnv', () => { diff --git a/packages/testing/package.json b/packages/testing/package.json index 576f46bb..b77ef9a5 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -19,6 +19,7 @@ "node-pty": "1.2.0-beta.14" }, "scripts": { + "test": "vitest run", "typecheck": "tsc --noEmit" }, "devDependencies": { diff --git a/packages/testing/src/scaffold/home-scaffold.ts b/packages/testing/src/scaffold/home-scaffold.ts index 688923e5..70bea2b3 100644 --- a/packages/testing/src/scaffold/home-scaffold.ts +++ b/packages/testing/src/scaffold/home-scaffold.ts @@ -1,8 +1,8 @@ import { vi } from 'vitest'; /** - * Stubs the `HOME` environment variable to `dir`, isolating the test - * from real user config files (`~/.cursor/`, `~/.claude.json`, etc.). + * Stubs `HOME` and `USERPROFILE` environment variables to `dir`, isolating the test + * from real user config files (`~/.cursor/`, `~/.claude.json`, etc.) across platforms. * Supports `Symbol.dispose` for automatic cleanup via `vi.unstubAllEnvs`. * * @example @@ -13,6 +13,7 @@ import { vi } from 'vitest'; */ export function isolateHome(dir: string) { vi.stubEnv('HOME', dir); + vi.stubEnv('USERPROFILE', dir); return { [Symbol.dispose]() {