---
description: Go tests, tags, and commands for this repo
globs: **/*_test.go, **/*.go
alwaysApply: true
---

# Testing (Go)

## Commands

- Express run (what agents run before finishing): **`make test`**
  - Runs **`make test-opencode-rules`**, **`make ui-build`**, **`make ui-test`** (the SPA's vitest suite), then one **`go test -tags=http,ui,scheduler,memory,cli,gateway,swarm ./...`** - the whole tree with every optional module compiled in, which is what the shipped binary contains. Minutes, not tens of minutes.
- Tag matrix (CI's job, not a local step): **`make test-matrix`** walks every combination in **`TEST_TAG_SETS`** (**`Makefile`**) - the untagged build with its stubs, single tags, pairs, the shipped set. **`.github/workflows/tests-on-pr.yaml`** reads that list through **`make print-test-tag-sets`** and runs one job per combination with **`fail-fast: false`**, so a broken stub is named by the job that failed. Locally, a build-tag boundary change (a **`_stub.go`**, an **`Available`** const, a **`//go:build`** line) is checked with that one combination: **`go test -tags=<set> ./...`**. The untagged build is also compiled by **`make lint`**, so a signature a stub no longer satisfies is caught before the push.
- Targeted run while iterating: **`go test ./path/to/pkg -run TestName -count=1`**
- **Touching a file behind `//go:build windows`, or any signature it shares with `_other.go` / the rest of the tree: also run `make check-windows` and `make lint-windows`.** `make test` and `make lint` run on the host, so the Windows half of **`internal/platform`** (and anything else with a per-OS file) is never compiled by them. `check-windows` cross-builds and `go vet`s every non-`ui` tag combination, test files included. CI runs both (**`.github/workflows/tests-on-pr.yaml`**), plus **`go test`** on a real `windows-latest` runner for **`internal/platform`**, **`internal/bgtask`**, **`internal/tools/shell`**, **`internal/update`**, **`internal/hooks`**, **`internal/rules`** (drive-letter glob anchoring) and the console TUI (**`external/cli`**, `-tags=cli`) — the packages whose behavior differs by OS. The same packages run on `macos-latest` (job **`test-macos`**), which also builds the console and drives the binary through a real pty with **`examples/cli/cli_e2e_startup.py`**; the `cli` job of the Linux test matrix runs that script too, and its `gateway` job runs **`examples/gateway/tg_e2e_offline.sh`**: the built binary against the fake Bot API of **`cmd/tgfake`**, no Telegram and no model. The Go suite never opens a pty, so this is the only check of the console's terminal path.
- HTTP server core is **`//go:build http`**. Embedded UI tests compile with **`go test -tags=http,ui ./external/httpserver`**. SPA-free **`http`** build uses **`//go:build http && !ui`** handlers under **`external/httpserver`**. Session memory REST uses **`//go:build http && memory`** (**`memory_http.go`**); without **`memory`**, **`memory_http_stub.go`** registers no routes.

## Conventions

- Prefer table-driven tests where it clarifies cases.
- Keep tests deterministic; avoid real network unless the test is explicitly integration-style and documented.
- New HTTP behavior belongs in **`external/httpserver/server_test.go`** (and related files) with **`http` build tag parity.

## BDD feature specs (`features/`)

- **Happy-path behavior** of a feature (and the reproduction of a bug) is an executable Gherkin
  spec in the **repo-root `features/`** directory, run by a godog harness (e.g.
  **`external/httpserver/bdd_remote_test.go`**, **`bdd_workspace_test.go`**) whose
  **`Options.Paths`** points at **`../../features/<name>.feature`**.
- **Edge / boundary / error cases** are ordinary **unit tests** next to the code, **not** scenarios
  in `features/`. Keep `.feature` files to the correct-behavior story.
- Feature suites run under the tag that owns the behavior (e.g. **`-tags http`**) and are part of
  **`make test`**. Step definitions may use a stub runner to stay deterministic and LLM-free.

## References

@Makefile
@code-style.mdc
@architecture.mdc
