Skip to content

fix(tests): eliminate user-scope pollution and global module mocks in tests#401

Merged
rhuanbarreto merged 5 commits into
mainfrom
fix/vscode-settings-test-pollution
Jun 10, 2026
Merged

fix(tests): eliminate user-scope pollution and global module mocks in tests#401
rhuanbarreto merged 5 commits into
mainfrom
fix/vscode-settings-test-pollution

Conversation

@rhuanbarreto

@rhuanbarreto rhuanbarreto commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Why the v0.44.0 release failed

The Release and Validate runs on the chore(release): 0.44.0 merge failed on two tests in tests/helpers/vscode-settings.test.ts — the same tree had passed CI 13 minutes earlier. Root cause is an order-dependent test-pollution flake:

  1. init-project.test.ts wrote the runner's REAL VS Code user settings. The "vscode returns auto-installed" test drives the real configureVscodeSettingsaddMarketplaceToUserSettings, writing the marketplace URL into ~/.config/Code/User/settings.json (and into developers' real settings on local runs).
  2. vscode-settings.test.ts's HOME override is silently ineffective on Linux. Bun caches os.homedir(), so the tests actually read/wrote the real settings file — passing only while it didn't exist.
  3. Bun's test-file execution order is non-deterministic across runs. Passing run: vscode-settings ran before init-project. Failing run: after — reading the polluted file (the failure diff shows exactly the stray archgate/vscode.git entry).

No v0.44.0 tag/release was created — merging this re-triggers the Release workflow and publishes 0.44.0.

Commit 1 — fix the failing tests (test-only, no production changes)

  • init-project.test.ts: spy out configureVscodeSettings (ARCH-005 first-party spyOn pattern) so initProject never writes user-scope state.
  • vscode-settings.test.ts: replace HOME env overrides with spyOn(os, "homedir").mockReturnValue(tempDir) — verified the spy intercepts the named import inside vscode-settings.ts. APPDATA env override stays for the Windows branch (reads Bun.env at call time).
  • restoreEnv() helper deletes originally-unset env keys instead of Object.assign stringifying undefined.
  • ARCH-005 updated with both rules.

Commit 2 — sweep all other tests for the same pattern

Audited every test overriding HOME/USERPROFILE/APPDATA/XDG_* against what production actually reads. Env overrides are safe for all paths.ts-based resolvers (read Bun.env at call time) — no changes needed for clean/upgrade/auth/binary-upgrade/credential-store/sentry/telemetry/update-check/opencode tests. Fixed the rest:

  • session-context.test.ts / session-context-cursor.test.ts: wrote real ~/.claude/projects and ~/.cursor/projects as a documented workaround for homedir caching → now spyOn(os, "homedir") into mkdtemp dirs.
  • session-context-copilot.test.ts: wrote real ~/.copilot/session-state → now Bun.env.HOME override (resolver reads Bun.env at call time).
  • plugin-install.test.ts (worst case): installCursorPlugin/installOpencodePlugin tests mocked fetch and Bun.spawn but not the filesystem prep — every run created dirs under and deleted archgate-* agents/skills from the developer's real ~/.cursor and ~/.config/opencode. Now redirects HOME/XDG_CONFIG_HOME to a mkdtemp dir file-wide.

Commit 3 — remove the last first-party mock.module

plugin-install.test.ts globally replaced the whole platform module (process-global, not undone by mock.restore()) — the same order-dependent flake class, explicitly forbidden by ARCH-005. Converted to per-test spyOn(platform, "resolveCommand"). Full suite passes, confirming nothing depended on the leak.

Validation

  • bun run validate green after each commit (1281 tests, 39/39 ADR checks, knip, build check)
  • Reviewer skill: APPROVED, 0 violations
  • Pre-existing warning (unrelated, already on main): ARCH-010 src/helpers/plugin-install.ts:170 uses .text() + JSON.parse instead of .json()

Summary by CodeRabbit

  • Documentation

    • Clarified testing standards and contributor guidance: added explicit prohibition on tests writing real user-scoped state and guidance to keep production behavior unchanged while using mocks/spies for home-directory resolution.
  • Tests

    • Redirects user-scoped writes to per-test temporary locations and consistently mocks/restores home-directory resolution.
    • Improved per-test temp-home setup, environment restore/cleanup, and spy restoration to reduce cross-test pollution and flaky failures.

The v0.44.0 release failed on an order-dependent test flake:

- init-project.test.ts ("vscode returns auto-installed") exercised the
  real configureVscodeSettings with mocked credentials, writing the
  marketplace URL into the runner's REAL ~/.config/Code/User/settings.json
  (and developers' real VS Code settings on local runs).
- vscode-settings.test.ts overrode HOME per-test, but Bun caches
  os.homedir() on Linux, so the override was silently ignored and the
  tests read/wrote the real (now polluted) settings file.
- Bun's test-file execution order is non-deterministic across runs, so
  the failure only appeared when init-project.test.ts happened to run
  first - the same tree passed CI on the release PR and failed on main
  13 minutes later.

Fixes (test-only, no production changes):

- init-project.test.ts: spy out configureVscodeSettings so initProject
  never writes user-scope state.
- vscode-settings.test.ts: replace HOME env overrides with
  spyOn(os, "homedir").mockReturnValue(tempDir) - verified the spy
  reaches named imports in other modules. Keep the APPDATA env override
  for the Windows branch, which reads Bun.env at call time.
- vscode-settings.test.ts: restore saved env by deleting originally
  unset keys instead of Object.assign stringifying undefined.
- ARCH-005: capture both rules (mock os.homedir(), never write real
  user-scope state in tests).

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 75a5ccc
Status: ✅  Deploy successful!
Preview URL: https://a2874e3d.archgate-cli.pages.dev
Branch Preview URL: https://fix-vscode-settings-test-pol.archgate-cli.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2fc95f80-8141-4339-b8bd-3b83a8934428

📥 Commits

Reviewing files that changed from the base of the PR and between 9fb0be8 and 75a5ccc.

📒 Files selected for processing (1)
  • tests/helpers/plugin-install-cleanup.test.ts

📝 Walkthrough

Walkthrough

Adds policy and refactors multiple test suites to avoid writing real user-scoped state by mocking os.homedir() or redirecting HOME to per-test temp directories; updates helpers, assertions, and environment restore logic accordingly.

Changes

Test Isolation via os.homedir() Mocking

Layer / File(s) Summary
Testing Policy and Standards Documentation
.archgate/adrs/ARCH-005-testing-standards.md, .claude/agent-memory/archgate-developer/MEMORY.md, .claude/agent-memory/archgate-developer/feedback_no_prod_changes_for_tests.md
Documents the "no production semantics changes for tests" policy and requires tests that redirect home/user-scope paths to mock os.homedir() via spyOn (restore in afterEach) and avoid writing real user-scope state.
VS Code Settings Test Refactor with Mocking
tests/helpers/vscode-settings.test.ts
Switches to import * as os, adds restoreEnv helper, reworks configureVscodeSettings and addMarketplaceToUserSettings suites to mock os.homedir() with spies (restored in teardown), and updates path expectations to use os.homedir().
Init Project Test Integration with VS Code Settings Spy
tests/helpers/init-project.test.ts
Imports vscodeSettings and updates the "vscode returns auto-installed" test to stub/spy configureVscodeSettings, assert it was called with tempDir and a "vscode.git"-containing arg, and restore the spy.
Plugin-install Tests Redirect HOME to Temp
tests/helpers/plugin-install.test.ts
Adds temp dir creation/cleanup, snapshots and overrides Bun.env.HOME (clears XDG_CONFIG_HOME) in beforeEach, sets up per-test spies for platform resolution, and restores originals in afterEach, ensuring plugin install writes occur inside the temp HOME.
Copilot Session Tests: Temp HOME
tests/helpers/session-context-copilot.test.ts
Creates per-test temp HOME, sets Bun.env.HOME to it, computes stateDir under redirected home, stops tracking createdDirs, and removes the temp home tree in teardown.
Cursor Session Tests: Mock os.homedir()
tests/helpers/session-context-cursor.test.ts
Switches imports to support spyOn, mocks os.homedir() to return a temp home created under os.tmpdir(), builds transcriptsDir under mocked home, restores spy and removes temp home in teardown.
Session Context Tests: Temp HOME and homedir Spy
tests/helpers/session-context.test.ts
Adjusts imports to os namespace and spyOn, creates temp home per test and mocks os.homedir() for the happy-path setup, then restores spy and removes temp home in teardown.
Plugin-install-cleanup: per-test platform spy and env redirection
tests/helpers/plugin-install-cleanup.test.ts
Replaces global module mocks with per-test spyOn(platform.resolveCommand) and redirects user-scope dirs via Bun.env.HOME/Bun.env.XDG_CONFIG_HOME, restoring them after each test.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • archgate/cli#400: Related changes to plugin-install cleanup and extraction-to-config-dir behavior affecting the same cleanup tests.

Poem

🐰 A hop through mocked homedirs tonight,
Temp HOMEs and spies keep tests polite,
No real settings scattered on disk,
Teardown tidy, quick, and brisk,
Rabbity cheers for isolated light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: eliminating user-scope test pollution and global module mocks in tests, which aligns with the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/vscode-settings-test-pollution

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.2% (6789 / 7529)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 88.1% 2078 / 2359
src/engine/ 93.1% 1379 / 1481
src/formats/ 100.0% 141 / 141
src/helpers/ 89.9% 3191 / 3548

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026
Sweep of all test files overriding HOME/APPDATA/XDG_* envs, auditing
each against what the production code actually reads:

- env overrides are SAFE for consumers of paths.ts resolvers
  (archgateHomeDir, opencodeConfigDir, opencodeDbPath, cursorUserDir,
  copilotSessionStateDir) and upgrade.ts/install-info.ts - all read
  Bun.env.HOME / USERPROFILE / XDG_* at call time. No changes needed
  for clean, upgrade, auth, binary-upgrade, credential-store, sentry,
  telemetry*, update-check, opencode-settings, plugin-install-cleanup,
  session-context-opencode tests.

- session-context.test.ts / session-context-cursor.test.ts worked
  around Bun's homedir() caching by writing into the REAL
  ~/.claude/projects and ~/.cursor/projects with unique names. Now
  spy os.homedir() into a mkdtemp tempHome instead (readClaudeCode-
  Session/readCursorSession call homedir() at call time).

- session-context-copilot.test.ts created real ~/.copilot/session-state
  and tracked created dirs for cleanup. Now overrides Bun.env.HOME to a
  mkdtemp tempHome (copilotSessionStateDir reads Bun.env at call time).

- plugin-install.test.ts was the worst case: installCursorPlugin /
  installOpencodePlugin tests mocked fetch and Bun.spawn but NOT the
  filesystem prep in installEditorPluginBundle - every test run
  created dirs under and DELETED archgate-* agents/skills from the
  developer's real ~/.cursor and ~/.config/opencode. Now redirects
  Bun.env.HOME + XDG_CONFIG_HOME to a mkdtemp tempHome file-wide.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026
…-install tests

mock.module("../../src/helpers/platform") is process-global: it replaces
the whole platform module (only resolveCommand exported, everything else
undefined) for every test file in the run and is not undone by
mock.restore(). ARCH-005 forbids mock.module on first-party modules for
exactly this reason - it is the same order-dependent flake class as the
user-scope pollution fixed in the previous commits.

Convert to the documented pattern: import * as platform + per-test
spyOn(platform, "resolveCommand") installed in beforeEach and restored
via mock.restore() in afterEach. The spy reaches the named import inside
plugin-install.ts through Bun's live bindings.

Full suite passes, confirming no other test file depended on the leaked
global mock.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@rhuanbarreto rhuanbarreto changed the title fix(tests): stop vscode-settings tests from touching real user settings fix(tests): eliminate user-scope pollution and global module mocks in tests Jun 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/helpers/plugin-install.test.ts (1)

117-127: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit restoration of mockResolveCommand spy.

spawnSpy.mockRestore() is called on line 119, but mockResolveCommand (created in beforeEach on line 99) is never restored. For consistency with the spawnSpy pattern and to ensure complete test isolation, explicitly restore the spy.

🔧 Proposed fix
 afterEach(() => {
   globalThis.fetch = originalFetch;
   spawnSpy.mockRestore();
+  mockResolveCommand.mockRestore();
   mock.restore();
 
   if (savedHome === undefined) delete Bun.env.HOME;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/plugin-install.test.ts` around lines 117 - 127, The afterEach
cleanup forgets to restore the mockResolveCommand spy created in beforeEach, so
add an explicit restore for mockResolveCommand (e.g., call
mockResolveCommand.mockRestore()) alongside the existing spawnSpy.mockRestore()
in the afterEach block to ensure the spy is removed and tests remain isolated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/helpers/plugin-install.test.ts`:
- Around line 117-127: The afterEach cleanup forgets to restore the
mockResolveCommand spy created in beforeEach, so add an explicit restore for
mockResolveCommand (e.g., call mockResolveCommand.mockRestore()) alongside the
existing spawnSpy.mockRestore() in the afterEach block to ensure the spy is
removed and tests remain isolated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c470d6f9-e971-4912-819e-eb85c797d047

📥 Commits

Reviewing files that changed from the base of the PR and between a851fa8 and 2945079.

📒 Files selected for processing (1)
  • tests/helpers/plugin-install.test.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026
mock.restore() already restores all spyOn spies, but restore it
explicitly for symmetry with spawnSpy and clearer intent.
(CodeRabbit review feedback on PR #401.)

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026
…tall-cleanup

CI failed after the previous commit removed plugin-install.test.ts's
mock.module("platform"): plugin-install-cleanup.test.ts ALSO globally
mocked the platform module (and paths). On Linux file order the cleanup
file runs first, its leaked module mock is what plugin-install's new
spyOn wrapped and "restored" to, and platform.test.ts / doctor.test.ts
then saw a bare mock returning undefined instead of the real
resolveCommand. The two competing mock.modules had been masking each
other; removing only one exposed the other.

Convert the last first-party mock.modules to the ARCH-005 patterns:

- platform: per-test spyOn(platform, "resolveCommand") in beforeEach,
  restored in afterEach.
- paths: no mock at all - cursorUserDir(), opencodeConfigDir(), and
  internalPath() read Bun.env.HOME / XDG_CONFIG_HOME at call time, so
  the existing tempDir env override (now including HOME, saved and
  restored per-test) isolates the real resolvers. Drops the
  cursorDirOverride indirection.
- env restore now deletes originally-unset keys instead of assigning
  undefined.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@rhuanbarreto rhuanbarreto merged commit 0cff642 into main Jun 10, 2026
27 checks passed
@rhuanbarreto rhuanbarreto deleted the fix/vscode-settings-test-pollution branch June 10, 2026 10:03
@archgatebot archgatebot Bot mentioned this pull request Jun 10, 2026
rhuanbarreto pushed a commit that referenced this pull request Jun 10, 2026
# archgate

## [0.45.0](v0.43.0...v0.45.0)
(2026-06-10)

### Features

* **cursor:** automated plugin install via tarball download
([#395](#395))
([41eb37e](41eb37e))

### Bug Fixes

* **opencode:** extract to config dir and clean stale files on install
([#400](#400))
([51b41e7](51b41e7))
* **tests:** eliminate user-scope pollution and global module mocks in
tests ([#401](#401))
([0cff642](0cff642))

---
This PR was generated with
[simple-release](https://github.com/TrigenSoftware/simple-release).

<details>
<summary>📄 Cheatsheet</summary>
<br>



You can configure the bot's behavior through a pull request comment
using the `!simple-release/set-options` command.

### Command Format

````md
!simple-release/set-options

```json
{
  "bump": {},
  "publish": {}
}
```
````

### Useful Parameters

#### Bump

| Parameter | Type | Description |
|-----------|------|-------------|
| `version` | `string` | Force set specific version |
| `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type
|
| `prerelease` | `string` | Pre-release identifier (e.g., "alpha",
"beta") |
| `firstRelease` | `boolean` | Whether this is the first release |
| `skip` | `boolean` | Skip version bump |
| `byProject` | `Record<string, object>` | Per-project bump options for
monorepos |

#### Publish

| Parameter | Type | Description |
|-----------|------|-------------|
| `skip` | `boolean` | Skip publishing |
| `access` | `'public' \| 'restricted'` | Package access level |
| `tag` | `string` | Tag for npm publication |

### Usage Examples

#### Force specific version

````md
!simple-release/set-options

```json
{
  "bump": {
    "version": "2.0.0"
  }
}
```
````

#### Force major bump

````md
!simple-release/set-options

```json
{
  "bump": {
    "as": "major"
  }
}
```
````

#### Create alpha pre-release

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "alpha"
  }
}
```
````

#### Publish with specific access and tag

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "beta"
  },
  "publish": {
    "access": "public",
    "tag": "beta"
  }
}
```
````

### Access Restrictions

The command can only be used by users with permissions:
- repository owner
- organization member
- collaborator

### Notes

- The last comment with `!simple-release/set-options` command takes
priority
- JSON must be valid, otherwise the command will be ignored
- Parameters apply only to the current release execution
- The command can be updated by editing the comment or adding a new one


</details>

<!--
  Please do not edit this comment.
  simple-release-pull-request: true
  simple-release-branch-from: release
  simple-release-branch-to: main
-->

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant