Testing philosophy
What CI runs
Section titled “What CI runs”.github/workflows/ci.yml, on every PR and every push to main:
| Job | What |
|---|---|
lint | bin/rubocop -f github --parallel |
security | bin/brakeman --no-pager -q |
verify_lockfile | bundle lock then git diff --exit-code Gemfile.lock |
test | bin/rails test — Postgres 16 + Redis 7 service containers |
retention_logic | ruby scripts/ghcr_retention_test.rb (pure Ruby, no Rails boot) |
docs_site | Builds this documentation site |
What CI does not run
Section titled “What CI does not run”Tests that skip themselves
Section titled “Tests that skip themselves”Several tests skip when a credential or file is absent — which in CI means they never run at all:
| Test | Skips when |
|---|---|
preregistered_oauth_config_test.rb:189 | ”OAuth credentials not available (CI environment)“ |
secrets_loader_test.rb:158 | ”Credentials key not available (CI environment)“ |
references_config_test.rb:79 | ”references directory not found” |
air_catalog_ref_rewriter_test.rb:190,198 | ”air.production.json not present” / “no github:// catalogs to pin” |
That last pair means the catalog-pinning feature has zero CI coverage — the code path exists, the tests exist, and neither runs.
Known-flaky tests
Section titled “Known-flaky tests”Four open issues, all CI flakes:
- #10
ClaudeModelConfigurationAuditTest— a globalFile.stubraces background threads. Noted as having turnedmainred. - #5
SessionsControllerTest#test_should_refresh_all…intermittently reports “Test is missing assertions” (the body exits before any assert). - #3
Zeitwerk::NameError: GoodJob::Job— GoodJob’s model intermittently fails to autoload. - #2
CleanupOrphanedSessionsJobTestuses a globalassert_no_enqueued_jobs, which under the parallel suite sees other tests’ jobs.
The catalog coupling — read this before you debug
Section titled “The catalog coupling — read this before you debug”Contract tests
Section titled “Contract tests”The one solid piece of test architecture here. Runtimes are enforced structurally rather than by convention:
test/contracts/runtime_cli_adapter_contract_test.rbasserts every registered adapter (ClaudeCliAdapter,CodexRuntimeAdapter, and their mocks) has keyword-set-identicalexecuteandresumesignatures — checked viainstance_method(:execute).parameters, so a renamed kwarg fails the build rather than failing at spawn time.test/contracts/runtime_mcp_credential_writer_contract_test.rbdoes the same for credential writers.
Running tests
Section titled “Running tests”bin/rails test test/models/session_test.rb # targeted — do this locallybin/rails test # everything (let CI do this)bin/rubocopbin/brakemanThe convention in AGENTS.md: run targeted tests locally, let CI run the full suite.
The philosophy, such as it is
Section titled “The philosophy, such as it is”The old docs/TESTING_PHILOSOPHY.md was 417 lines. The parts that survive contact with the actual
suite:
- Mock at the boundary, not in the middle.
MockClaudeCliAdapter/MockCodexRuntimeAdapterexist so tests never spawn a real CLI, and they are held to the same contract test as the real ones. FileSystemAdapterandProcessManagerare injected, so process and filesystem behavior can be faked without stubbing globals. (Issue #10 is a test that reached for a globalFile.stubanyway, and now flakes.)- The state machine is tested as a state machine — its transitions and guards, down to the individual states.
What it does not have is meaningful end-to-end coverage of the thing Zimmer does: spawn a real agent against a real repo. That path is covered by running it.