# How Unit Tests Validate Plugin Loading Across Diverse Environments in the i-have-adhd Repository

> Learn how unit tests in the i-have-adhd repository validate plugin loading across Node.js, POSIX shell, and PowerShell. Discover robust cross-platform testing strategies.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: how-to-guide
- Published: 2026-08-29

---

**The i-have-adhd repository employs a comprehensive test suite that dynamically discovers available runtimes (Node.js, POSIX shell, and PowerShell), injects isolated environment configurations, and asserts correct plugin behavior across Claude, Codex, and OpenCode platforms.**

The `ayghri/i-have-adhd` project provides an AI assistant plugin designed to operate consistently across multiple shells and AI platforms. To ensure reliable **plugin loading validation across diverse environments**, the repository maintains a robust suite of unit tests located in [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py) and [`tests/test_opencode_plugin.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_opencode_plugin.py) that verify discovery, configuration, and execution without requiring manual testing in every target environment.

## Dynamic Runtime Discovery and Execution

### Detecting Available Runtimes

The core validation logic resides in [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py), specifically within the `AlwaysOnHookTest.runtimes()` method (lines 24-42). This function dynamically detects which interpreters are present on the host system—`node`, `sh`, and `pwsh` (or `powershell`)—and constructs command arrays for each. 

```python
def runtimes(self):
    runtimes = []
    if node := shutil.which("node"):
        runtimes.append(("node", [node, self.plugin_root / "hooks" / "always-on.mjs"]))
    if sh := shutil.which("sh"):
        runtimes.append(("sh", [sh, self.plugin_root / "hooks" / "always-on.sh"]))
    if powershell := shutil.which("pwsh") or shutil.which("powershell"):
        runtimes.append(
            ("powershell", [
                powershell,
                "-NoProfile", "-ExecutionPolicy", "Bypass",
                "-File", self.plugin_root / "hooks" / "always-on.ps1",
            ])
        )
    return runtimes

```

This approach ensures tests only attempt to execute against installed runtimes, making the suite portable across Linux, macOS, and Windows environments.

### Isolating Test Environments

Before spawning subprocesses, each test constructs a temporary directory structure and copies the repository's `hooks` and `skills` folders. The tests inject environment variables—`CLAUDE_CONFIG_DIR`, `CLAUDE_PLUGIN_ROOT`, `PLUGIN_ROOT`, and `XDG_CONFIG_HOME`—pointing to these temporary locations. 

This isolation prevents tests from interfering with the user's actual configuration while accurately simulating real-world platform conventions for configuration directory resolution.

## Validating the Shared Launcher Architecture

The plugin uses a single launcher script for both Claude and Codex. The test `test_hook_uses_a_shared_claude_and_codex_launcher` reads [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) and verifies that the generated command string contains references to `process.env.CLAUDE_PLUGIN_ROOT` and `process.env.PLUGIN_ROOT`. 

It also confirms the presence of the dynamic import pattern (`await import`), ensuring the launcher behaves identically across AI platforms without code divergence. This validation guarantees that the declarative hook definition produces the correct Node.js invocation string for both assistants.

## Front-Matter Handling and Edge Cases

### YAML Stripping Accuracy

The plugin must strip valid YAML front-matter from skill files while preserving the content body. The test suite covers two critical edge cases: standard front-matter with trailing whitespace, and unclosed fence markers that should remain untouched. These assertions prevent malformed banners from breaking skill output or leaking internal configuration flags.

The tests execute the plugin against sample files and assert that `stdout` contains the expected body text while excluding front-matter keys such as `alwaysOn` or `description`.

## OpenCode Server Integration

While the always-on hooks target local shells, the OpenCode platform requires server-side execution. The [`tests/test_opencode_plugin.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_opencode_plugin.py) file contains `OpenCodePluginTest.run_plugin()` (lines 27-40), which launches the driver script `tests/opencode_plugin_driver.mjs`. 

```python

# Execute a hook and assert silent behaviour when the opt‑in flag is missing

for name, command in self.runtimes():
    with self.subTest(runtime=name):
        result = self.run_hook(command)
        self.assertEqual(0, result.returncode)
        self.assertEqual("", result.stdout)
        self.assertEqual("", result.stderr)

```

This test verifies that the same front-matter stripping logic and silent operation (when the opt-in flag is absent) function correctly within the OpenCode runtime, ensuring parity between local hooks and server implementations.

## Cross-Platform Consistency Checks

The test suite executes each discovered runtime via `subprocess.run` and asserts on `returncode`, `stdout`, and `stderr`. For silent-mode validation, `stdout` must be empty when the opt-in flag is missing. 

These checks confirm that whether the plugin runs in Node.js on Linux, PowerShell on Windows, or the OpenCode server environment, it respects configuration flags and produces identical output patterns across all supported environments.

## Summary

- **Dynamic runtime detection** in `AlwaysOnHookTest.runtimes()` ensures tests execute only against available interpreters (Node.js, sh, PowerShell).
- **Temporary directories** and injected environment variables isolate tests from user configurations while simulating `CLAUDE_CONFIG_DIR` and `XDG_CONFIG_HOME` conventions.
- **Shared launcher verification** confirms [`hooks/hooks.json`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/hooks.json) generates correct commands containing `CLAUDE_PLUGIN_ROOT` and `PLUGIN_ROOT` for both Claude and Codex platforms.
- **Front-matter handling tests** prevent malformed output by validating YAML stripping and fence preservation in skill files.
- **OpenCode-specific tests** in [`test_opencode_plugin.py`](https://github.com/ayghri/i-have-adhd/blob/main/test_opencode_plugin.py) ensure server-side plugin behavior matches local hook implementations using the `opencode_plugin_driver.mjs` driver.

## Frequently Asked Questions

### How do the tests detect which runtimes are available?

The `runtimes()` method in [`tests/test_always_on_hooks.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_always_on_hooks.py) uses `shutil.which()` to check for `node`, `sh`, and `pwsh` executables on the host system. It returns a list of tuples containing the runtime name and command array, allowing the test loop to execute only against installed interpreters while skipping unavailable ones.

### What prevents tests from modifying my actual Claude or Codex configuration?

Each test creates a temporary directory, copies the `hooks` and `skills` folders there, and sets environment variables like `CLAUDE_CONFIG_DIR` and `CLAUDE_PLUGIN_ROOT` to point to these temporary locations. This isolation ensures the plugin reads flags from the test environment rather than your real configuration directory.

### How does the test suite verify the plugin works in OpenCode specifically?

The [`tests/test_opencode_plugin.py`](https://github.com/ayghri/i-have-adhd/blob/main/tests/test_opencode_plugin.py) file launches an OpenCode driver script (`tests/opencode_plugin_driver.mjs`) that loads the plugin module. This test mirrors the local hook validations, checking silent operation and front-matter handling within the OpenCode server runtime to ensure consistent behavior across all supported AI platforms.

### What edge cases are covered for front-matter processing?

The tests validate two scenarios: standard YAML front-matter with trailing whitespace (which must be stripped) and unclosed fence markers (which must be preserved). These cases ensure the plugin correctly handles both valid metadata and malformed files without breaking skill output or exposing internal flags.