Documentation
¶
Overview ¶
Package testutil provides shared test contracts that every platform adapter runs against. The goal is consistency: an adapter that passes SyncContract, HooksContract, and MCPContract behaves like every other adapter at the scopes and capabilities it advertises, so users and callers can rely on uniform semantics.
Usage pattern from an adapter's *_test.go file:
func TestClaudeContracts(t *testing.T) {
testutil.SyncContract(t, func() platform.Platform { return &Platform{} })
testutil.HooksContract(t, func() platform.Platform { return &Platform{} })
testutil.MCPContract(t, func() platform.Platform { return &Platform{} })
}
The factory is re-invoked for each sub-test so a fresh adapter sees a clean filesystem rooted at the per-subtest t.TempDir().
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CatalogContract ¶ added in v0.6.0
CatalogContract verifies invariants every ModelCatalog must hold, regardless of adapter. An adapter ships an empty catalog (Cursor) by returning a SliceCatalog with no entries — the contract still passes; it just runs fewer per-entry checks.
Invariants:
- List(ctx) succeeds.
- Every entry's ID is non-empty and resolves to itself.
- Every alias is non-empty and resolves to its owning entry's ID.
- No two entries share an alias (alias-uniqueness).
- No alias collides with another entry's literal ID.
- Resolve("") returns false (empty input is never a hit).
- Resolve("definitely-not-a-model-xyzzy-zzz") returns false.
The uniqueness invariant is what keeps resolution mathematically deterministic — a release that adds a new model but forgets to demote the old entry's bare alias (e.g. leaves "opus" pointing at two rows) fails this contract immediately.
func HooksContract ¶
HooksContract exercises Install / Remove / Status. Skipped for adapters whose Capabilities().Hooks is false.
Contract:
- fresh state: Install writes entries; Status reports installed+up-to-date
- re-install: no duplicate entries; Status still installed
- Remove: entries gone; Status reports not installed
- unsupported scope: each method returns ErrUnsupportedScope
func MCPContract ¶
MCPContract mirrors HooksContract for MCP registration. Skipped for adapters whose Capabilities().MCP is false.
func SpawnContract ¶
SpawnContract exercises Platform.Spawn against the invariants every adapter that advertises Spawn must satisfy:
- the returned Cmd's binary basename matches the adapter's claim
- the prompt appears as a discrete argv element exactly once, immediately after a single platform-specific injection flag
- extraArgs survive verbatim, in order, after the prompt
- stdio is left nil so the caller can wire it
Adapters that report Spawn=false must instead return ErrUnsupportedByPlatform.
expectedBinary is the basename the adapter's Spawn should resolve to (e.g. "claude"); injectionFlag is the platform's prompt-injection flag (e.g. "--append-system-prompt"). Both are nil-safe when the adapter reports Spawn=false (the contract only exercises the unsupported branch in that case).
func SyncContract ¶
SyncContract exercises Platform.Sync against the common invariants every text-based adapter must satisfy:
- fresh project: Sync creates the target file and writes the prompt
- existing non-managed content: prompt is appended, content kept
- re-sync with identical prompt: no-op (mtime unchanged)
- user edits outside markers survive a subsequent Sync
- prompt swap: managed section is replaced in place
Adapters that don't support Sync at project scope (none today) should declare that in Capabilities; the contract skips them automatically.