Skip to main content

guides/testing-and-conformance.md

# Testing And Conformance

`CliSubprocessCore` sits on the boundary between subprocess ownership and
provider-specific parsing, so it needs both low-level transport tests and
higher-level conformance tests. This guide describes the expected test layers
for the repo and for downstream custom profiles.

## Testing Layers

The repo is structured around four layers of confidence:

- pure data tests for commands, events, payloads, runtime state, and option
  validation
- raw transport tests for startup, IO, buffering, stderr dispatch, interrupt,
  close, and force-close behavior
- provider profile tests for command construction and stdout/stderr fixture
  decoding
- session tests that prove the runtime emits normalized, sequenced
  `CliSubprocessCore.Event` values from mock CLIs

Each layer should stay focused. Transport tests should not assert provider
semantics, and provider profile tests should not re-test raw subprocess
ownership.

## Raw Transport Edge Cases

The transport suite should cover at least these scenarios:

- large stdout lines that fit within the configured buffer
- oversized stdout fragments that emit a structured overflow error and recover
  at the next newline
- stderr-only flows where subscribers and the stderr ring buffer still receive
  data
- interrupt and close races that must not hang callers
- subscriber churn, including unsubscribe and monitor-based cleanup
- post-exit flush behavior for queued stdout lines and trailing fragments

The current suite exercises those cases through shell fixtures created inside
the test process.

## Provider Profile Tests

Provider profile tests should stay deterministic and fixture-driven:

- keep provider stdout fixtures in `test/fixtures/provider_profiles/*.jsonl`
- assert emitted `CliSubprocessCore.Event.kind` values and payload structs
- verify provider session id extraction when the source CLI exposes one
- verify command construction for required inputs and common option flags

These tests are the fastest way to catch schema drift in provider CLI output.

## Schema Conformance Expectations

For core-owned dynamic boundaries such as `CliSubprocessCore.Event`,
`CliSubprocessCore.Payload.*`, `CliSubprocessCore.ModelRegistry.Model`,
`CliSubprocessCore.ModelRegistry.Selection`, and
`CliSubprocessCore.ModelInput`, the repo should keep explicit tests for:

- minimal valid parse into the public struct
- missing stable-field failures with a core-local error shape
- forward-compatible unknown-field preservation where the boundary is intended
  to evolve
- round-trip projection through `to_map/1` when the boundary is re-encodable
- ownership boundaries proving the core does not absorb provider-native
  app-server or control-protocol schemas

## Session Integration Tests

Session tests should verify behavior that only exists once transport and
profiles are combined:

- `:run_started` is emitted first
- runtime sequences are monotonic and gap-free
- provider metadata is preserved on emitted events
- stderr is normalized into `:stderr` events
- terminal success and failure become `:result` or `:error`
- subscriber management behaves correctly while the session is live

Use small mock shell scripts instead of real provider binaries so the tests
stay hermetic and fast.

## Conformance Checklist For New Profiles

Before treating a profile as first-party quality, confirm that it:

- implements `CliSubprocessCore.ProviderProfile`
- returns a valid `CliSubprocessCore.Command` from `build_invocation/1`
- emits only normalized `CliSubprocessCore.Payload.*` structs
- preserves provider-native data in `event.raw` when useful for debugging
- sets `provider_session_id` when the CLI exposes a stable session identifier
- follows the built-in terminal-exit pattern: one `:result` with
  `status: :completed` on success and one `:error` on failure
- behaves correctly under interrupt, stderr-only, and partial-line exit cases

That checklist is intentionally stricter than "the parser seems to work." The
goal is a stable shared runtime surface, not one-off provider adapters.

## Repo-Local Quality Gate

During active development, use narrower loops before you rerun the full gate.

Useful subsets:

```bash
mix test test/cli_subprocess_core/transport
mix test test/cli_subprocess_core/transport_test.exs test/cli_subprocess_core/raw_session_test.exs test/cli_subprocess_core/channel_test.exs
mix test test/cli_subprocess_core/provider_profile_test.exs test/cli_subprocess_core/provider_profiles_test.exs test/cli_subprocess_core/session_test.exs
```

When changing model resolution or provider backend selection, use the repo-local
workflow helper as well. It refreshes the extracted transport substrate before
recompiling dependent workspace repos:

```bash
./scripts/model_selection_ci.sh test --tag sdk
./scripts/model_selection_ci.sh all --repo cli_subprocess_core
```

The full repo gate is:

```bash
mix format --check-formatted
mix compile --warnings-as-errors
mix test
mix credo --strict
mix dialyzer
mix docs
mix hex.build
```

Expected result:

- no formatting drift
- no compilation warnings
- no failing tests
- no Credo issues
- no Dialyzer findings
- successful documentation generation
- successful package build

When changing transport or session behavior, rerun the full gate rather than
only the targeted tests. Those layers are shared by every provider profile.