---
issue: 35
issue_title: "Align #21 test files with updated mock-cleanup and node:* default-export rules"
---

# Align #21 test files with updated mock-cleanup and node:\* default-export rules

## Problem Statement

Two AGENTS.md testing rules were added during the #21 retro, but the three test files that were written as part of #21 still violate them:

1. `vi.fn()` stubs are reset via `vi.clearAllMocks()` in `afterEach` rather than via
   explicit `.mockReset()` (or `.mockClear()`) calls on named module-scope references
   in `beforeEach`.
   `vi.restoreAllMocks()` only handles `vi.spyOn()` spies — `vi.clearAllMocks()` is the
   wrong hook for `vi.fn()` stubs, and reset-in-`afterEach` fires after the test body
   has already completed, so a leaked state from test N can pollute test N+1 before
   `afterEach` runs.
2. `tests/external-directory.test.ts` mocks `node:os` without a `default` export, creating
   a latent "No default export defined on the mock" failure for any future import path
   that uses the default form of that module.

## Goals

- Replace `vi.clearAllMocks()` in `afterEach` with explicit `beforeEach` + `.mockReset()`
  (or `.mockClear()` where the default implementation must be preserved) on named stub
  references in the three affected test files.
- Add the `default` mirror export to the `node:os` mock factory in
  `tests/external-directory.test.ts`.
- Pass `npx vitest run` and `npm run build` with no regressions.

## Non-Goals

- Changes to production source files.
- Extending test coverage beyond what is needed for the cleanup.
- Updating any other test files that were not written in #21.
- Schema, config, or documentation changes.

## Background

### Affected files

All four files were created in #21.
No permission surface or runtime behavior is involved — this is test-infrastructure hygiene.

| File                               | Violation                                                                                                                                                      |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tests/bash-filter.test.ts`        | `vi.clearAllMocks()` in `afterEach`; two `vi.fn()` stubs (`compileWildcardPatterns`, `findCompiledWildcardMatch`) cleaned up globally rather than individually |
| `tests/permission-prompts.test.ts` | `vi.clearAllMocks()` in `afterEach`; `mockedFormatToolInput` reset globally                                                                                    |
| `tests/tool-input-preview.test.ts` | `vi.clearAllMocks()` in `afterEach`; `mockedStringify` reset globally                                                                                          |
| `tests/external-directory.test.ts` | `node:os` mock factory missing `default` key; `vi.restoreAllMocks()` in `afterEach` is correct as-is (no `vi.fn()` stubs to clean up there)                    |

### Current pattern (all three mock-cleanup files)

```typescript
afterEach(() => {
  vi.clearAllMocks();    // wrong hook for vi.fn() stubs
  vi.restoreAllMocks();  // correct for vi.spyOn() spies
});
```

### Target pattern

```typescript
beforeEach(() => {
  mockedX.mockReset();   // or mockClear() when default implementation must survive
});
afterEach(() => {
  vi.restoreAllMocks();  // unchanged
});
```

### `compileWildcardPatterns` nuance in `bash-filter.test.ts`

The `vi.mock()` factory for `wildcard-matcher` supplies a non-trivial default implementation for `compileWildcardPatterns` (it transforms the patterns object into a compiled array).
Several tests assert `.toHaveBeenCalledWith(...)` on that function but rely on the default implementation being in place.
`mockReset()` would wipe the implementation and cause those tests to fail.
`mockClear()` clears call history while preserving the implementation — it is the correct choice here.
`findCompiledWildcardMatch` has no default implementation and each test that needs a return value calls `.mockReturnValue()` explicitly, so `mockReset()` is safe.

A module-scope `vi.mocked()` reference should be extracted for `compileWildcardPatterns` so it can be addressed explicitly in `beforeEach`.

## Design Overview

No new types, modules, or config changes.
All changes are confined to four test files.

### `tests/bash-filter.test.ts`

1. Extract `const mockedCompilePatterns = vi.mocked(compileWildcardPatterns);` at module scope.
2. `beforeEach`: call `mockedCompilePatterns.mockClear()` and `mockedFindMatch.mockReset()`.
3. `afterEach`: keep only `vi.restoreAllMocks()`.
4. Remove `vi.clearAllMocks()`.

### `tests/permission-prompts.test.ts`

1. `beforeEach`: call `mockedFormatToolInput.mockReset()`.
   (`mockedFormatToolInput` is already module-scope.)
2. `afterEach`: keep only `vi.restoreAllMocks()`.
3. Remove `vi.clearAllMocks()`.

### `tests/tool-input-preview.test.ts`

1. `beforeEach`: call `mockedStringify.mockReset()`.
   (`mockedStringify` is already module-scope.)
2. `afterEach`: keep only `vi.restoreAllMocks()`.
3. Remove `vi.clearAllMocks()`.

### `tests/external-directory.test.ts`

Replace the `node:os` mock factory:

```typescript
// Before
vi.mock("node:os", () => ({
  homedir: vi.fn(() => "/mock/home"),
}));

// After
vi.mock("node:os", () => {
  const homedir = vi.fn(() => "/mock/home");
  return {
    homedir,
    default: { homedir },
  };
});
```

No `beforeEach` changes are needed here — `homedir` is not referenced by a module-scope `vi.mocked()` variable, and the `afterEach` `vi.restoreAllMocks()` is correct as-is.

## Module-Level Changes

| File                               | Change                                                                                                   |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `tests/bash-filter.test.ts`        | Extract `mockedCompilePatterns`; add `beforeEach` with `mockClear`/`mockReset`; replace `afterEach` body |
| `tests/permission-prompts.test.ts` | Add `beforeEach` with `mockReset`; replace `afterEach` body                                              |
| `tests/tool-input-preview.test.ts` | Add `beforeEach` with `mockReset`; replace `afterEach` body                                              |
| `tests/external-directory.test.ts` | Refactor `node:os` mock factory to include `default` key                                                 |

No source files, schemas, config, or documentation are modified.

## TDD Order

Because this issue is a test-file refactor with no production code changes, each cycle is: verify tests still pass (green) → make the change → verify still green → commit.

1. Verify baseline: `npx vitest run` passes for all four test files.
   Commit message: *(no commit — baseline only)*
2. Refactor `tests/bash-filter.test.ts`: extract `mockedCompilePatterns`, add `beforeEach` with `mockClear`/`mockReset`, drop `vi.clearAllMocks()`.
   Commit message: `test: use beforeEach mockReset/mockClear in bash-filter tests (#35)`
3. Refactor `tests/permission-prompts.test.ts`: add `beforeEach` with `mockReset`, drop `vi.clearAllMocks()`.
   Commit message: `test: use beforeEach mockReset in permission-prompts tests (#35)`
4. Refactor `tests/tool-input-preview.test.ts`: add `beforeEach` with `mockReset`, drop `vi.clearAllMocks()`.
   Commit message: `test: use beforeEach mockReset in tool-input-preview tests (#35)`
5. Refactor `tests/external-directory.test.ts`: extract `homedir` into a named variable and return it with a `default` mirror in the `node:os` mock factory.
   Commit message: `test: add default export to node:os mock in external-directory tests (#35)`
6. Final verification: `npx vitest run` and `npm run build` both pass.
   Commit message: *(included in step 5 commit or a follow-up if build reveals type issues)*

## Risks and Mitigations

| Risk                                                                                                        | Mitigation                                                                                                                             |
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `mockReset()` on `compileWildcardPatterns` wipes the default implementation, breaking tests that rely on it | Use `mockClear()` for that stub; confirmed safe because it only clears call history                                                    |
| Removing `vi.clearAllMocks()` leaves state leak between tests if a stub is missed                           | The `beforeEach` hooks are exhaustive — every `vi.fn()` in each file is listed; run `npx vitest run` after each file change to confirm |
| Could this silently weaken a permission?                                                                    | No. No production code or policy logic is modified. These are test-only changes.                                                       |
| `default` key addition to `node:os` mock could break existing tests that rely on named imports only         | Adding `default` is purely additive; tests using `import { homedir } from "node:os"` are unaffected                                    |

## Open Questions

- None.
  The changes are fully specified by the issue and the AGENTS.md rules.
