Skip to content
Agentmart
Submit a tool

writing-tests

How to write tests in the Onlooker ecosystem repo — bats integration tests for hooks and the node:test schema suite.

Skill by onlooker-community

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/onlooker-community-writing-tests

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

How to write tests in the Onlooker ecosystem repo — bats integration tests for hooks and the node:test schema suite. Use when adding or changing anything under test/, when writing a new hook or plugin that needs tests, or when deciding which shared helper to reach for instead of hand-rolling setup, dates, CLI stubs, project keys, or event-log assertions. Steers toward test/helpers/setup.bash and each plugin's own libs so tests stay isolated and never become time bombs.

Use cases: testing, writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/writing-tests

What's inside

Skill name
writing-tests
Description
How to write tests in the Onlooker ecosystem repo — bats integration tests for hooks and the node:test schema suite. Use when adding or changing anything under test/, when writing a new hook or plugin that needs tests, or when deciding which shared helper to reach for instead of hand-rolling setup, dates, CLI stubs, project keys, or event-log assertions. Steers toward test/helpers/setup.bash and each plugin's own libs so tests stay isolated and never become time bombs.
License
MIT
Instructions
328 lines in SKILL.md · view on GitHub

Agent Skills spec issues

  • files: referenced but missing: scripts/lib/validate-path.sh, scripts/lib/onlooker-event.mjs

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Writing tests

This repo has two test suites. Use the right one for what you're testing, and lean on the shared helpers below — most fragility in this codebase has come from tests hand-rolling things a helper already does.

- **bats** (`test/bats/*.bats`) — bash hooks and end-to-end plugin behavior. This is where almost all tests live.
- **node:test** (`test/node/*.test.mjs`) — schema mapping, manifest, and reference validation. Pure data-in/data-out, no hooks.

## Run the suite

```bash
npm test               # bats + node schema suite (what most changes need)
npm run test:bats      # just bats  (all of test/bats, or the files you pass after --)
npm run test:schema    # just node  (node --test test/node/*.test.mjs)
npm run test:ci        # shellcheck + bats + schema + biome + manifest/reference lint
```

Run a single bats file while iterating: `npm run test:bats -- test/bats/<name>.bats`. Files you pass replace the default target. The runner refuses overlapping targets (say, a file plus the directory it's in) and fails any run that executed fewer tests than it planned, because under `-j` bats would otherwise drop a duplicated file and exit 0 (ONL-109). Bare `bats test/bats/<name>.bats` also works, but it skips `ONLOOKER_VALIDATE=1`, so a negative schema test fails locally even though it passes in CI. `bats` comes from mise (it is on `PATH`), not `npx`.

## Golden rules

1. Every bats test starts by isolating the filesystem — source `setup.bash` and call `setup_test_env`. Never touch the real `$HOME` or `~/.onlooker`.
2. Always reference `$ONLOOKER_DIR` / `$ONLOOKER_EVENTS_LOG`, never a literal `~/.onlooker`.
3. Never hardcode a date that feeds a relative window. Use `relative_iso_days_ago`.
4. Resolve project keys, emit events, and generate ULIDs through the plugin's own libs — don't reimplement them.
5. Assert behavior through the **event log** and on-disk artifacts, the same surfaces real consumers read.

## bats tests

### Isolate the environment first

Every test file's `setup()` sources the shared helper and calls `setup_test_env`, which repoints `HOME`, `ONLOOKER_DIR`, `CLAUDE_HOME`, and `CLAUDE_PLUGIN_ROOT` into `$BATS_TEST_TMPDIR` and severs git from your global config:

```bash
setup() {
  source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
  setup_test_env

  PLUGIN_ROOT="${REPO_ROOT}/plugins/<plugin>"
  export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
  export ONLOOKER_ECOSYSTEM_ROOT="$REPO_ROOT"
}
```

If your hook needs the resolved Onlooker paths (`$ONLOOKER_EVENTS_LOG`, the session/metrics dirs) materialized, call `load_validate_path` instead — it runs `setup_test_env`, sources `scripts/lib/validate-path.sh`, and `mkdir -p`s the standard directories.

`setup_test_env` exports (read `test/helpers/setup.bash` for the authoritative list):

| Variable | Points at |
|----------|-----------|
| `HOME` | `$BATS_TEST_TMPDIR/home` (isolated) |
| `ONLOOKER_DIR` | `$HOME/.onlooker` |
| `ONLOOKER_EVENTS_LOG` | `$ONLOOKER_DIR/logs/onlooker-events.jsonl` (after `validate-path.sh` is sourced) |
| `CLAUDE_HOME` | `$HOME/.claude` |
| `REPO_ROOT` | the repo root (set when `setup.bash` is sourced) |

### Dates: use `relative_iso_days_ago`, never a literal

A hardcoded ISO date is a **time bomb** when the code under test computes a window from "now" (for example librarian's "now − `bootstrap_lookback_days`" scan window). The fixture passes today and silently fails once wall-clock now drifts past the threshold. Date such fixtures relative to now:

```bash
# yesterday, UTC — comfortably inside a 14-day lookback window
created_at=$(relative_iso_days_ago 1)
```

`relative_iso_days_ago N` lives in `test/helpers/setup.bash` and returns an ISO-8601 UTC timestamp N days in the past (0 = now, negative = future). It uses `python3` for portable date math, since `date -d` (GNU) and `date -v` (BSD/macOS) disagree. For a plain "now" timestamp, `date -u +%Y-%m-%dT%H:%M:%SZ` is fine and portable; only the *offset* math needs the helper.

### Resolve project keys through the plugin lib

Artifacts are partitioned by a project key. Don't recompute the SHA — source the vendored `onlooker-project-key.sh` and call its function (`onlooker_project_key <repo-root>`):

```bash
source "${PLUGIN_ROOT}/scripts/lib/onlooker-project-key.sh"
PROJECT_KEY=$(onlooker_project_key "$PROJECT_REPO")
ARTIFACT_DIR="${ONLOOKER_DIR}/<plugin>/${PROJECT_KEY}"
```

For key resolution to succeed the test needs a git context — stand up a throwaway repo in `setup()`:

```bash
PROJECT_REPO="${BATS_TEST_TMPDIR}/repo"
mkdir -p "$PROJECT_REPO"
git -C "$PROJECT_REPO" init -q
git -C "$PROJECT_REPO" config user.email t@example.com
git -C "$PROJECT_REPO" config user.name "Test"
git -C "$PROJECT_REPO" remote add origin git@github.com:org/fixture.git
```

### Stub the `claude` CLI on `PATH`

Hooks that classify or judge shell out to `claude`. Stub it deterministically — branch on the prompt so each case returns a known response — and prepend the stub dir to `PATH`:

```bash
STUB_BIN="${BATS_TEST_TMPDIR}/bin"
mkdir -p "$STUB_BIN"
cat > "${STUB_BIN}/claude" <<'STUB'
#!/usr/bin/env bash
prompt=$(cat)
if [[ "$prompt" == *"some-marker"* ]]; then
  printf '%s' '{"type":"feedback","title":"...","body":"...","confidence":0.84}'
else
  printf '%s' '{"type":null,"title":"","body":"","confidence":0.2}'
fi
STUB
chmod +x "${STUB_BIN}/claude"
export PATH="${STUB_BIN}:${PATH}"
```

Same pattern stubs any external CLI a hook depends on (`curl`, `git`, …). Keep the responses minimal but schema-valid.

### Build hook input with `jq`

Hooks read a JSON payload on stdin. Build it with `jq -cn` and feed it in — don't hand-concatenate JSON strings:

```bash
_hook_input() {
  jq -cn --arg cwd "$PROJECT_REPO" --arg sid "sess-test" \
    '{cwd: $cwd, session_id: $sid, hook_event_name: "SessionEnd"}'
}

From onlooker-community/ecosystem, MIT.

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 21, 2026 (8 days ago)
Last release
Oct 24, 2026 (5 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from onlooker-community

Cite this page

writing-tests skill. Agent Mart. https://agentmart.dev/skills/onlooker-community-writing-tests (last updated 2026-10-24)