# Testing Standards ## Philosophy - **TDD mandatory**: Write a failing test first, then the minimal implementation. The cycle is Red → Green → Refactor. - Test behavior, not implementation details. - Tests must run without a live server or external services. - A change is not complete until `make test` (and `make test-integration` where applicable) passes. ## Go Tests (apiserver / xcecli) **Co-located** alongside source: `jobs.go` → `jobs_test.go`. Integration tests use descriptive names: `integration_test.go`, `host_control_test.go`. **Table-driven** for validation/filter logic: ```go tests := []struct { name string input SomeRequest wantErr bool }{ {"valid request", validReq, false}, {"missing field", missingReq, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := preCheckRequest(tt.input) if (err != nil) != tt.wantErr { t.Errorf(...) } }) } ``` **State isolation** — reset global store state between tests: ```go func resetRBACState() { /* clear in-memory stores */ } func TestPermissions(t *testing.T) { t.Cleanup(resetRBACState) // ... } ``` **Fixtures**: use `t.TempDir()` for file-system state; never rely on absolute paths or external services. **Build targets**: - `make test` — unit tests only (`CGO_ENABLED=0`) - `make test-backend` — CGO-backed backend tests (requires nix dev shell + build dir) - `make test-integration` — full regression suite (requires `XCE_BIN` and `SRV_URL` env vars) ## C Tests (controller / scheduler / lib) **Location**: `testsuite/` directory inside each C component. **Note**: `src/worker` has no `testsuite/` directory and no meson `test()` registrations today — it currently carries no C test coverage of its own. **Structure**: one `.c` test program per unit: ``` src/controller/testsuite/ test.c ← test runner harness test_ha_cluster.c ← tests for ha.c test_persist_wal.c ← WAL-specific tests test_e2e_checkpoint.c ← end-to-end checkpoint flow test_p1_integration.c ← P1 HA integration tier ``` File naming convention: `test_.c`. E2E tests: `test_e2e_.c`. Integration tiers: `test_p1_*.c`, `test_p2_*.c`, `test_p3_hardening.c`. **Test-only symbols** are guarded to prevent production bloat: ```c #ifdef XCE_TESTING extern void reset_ha_state(ha_state_t *s); #endif ``` **Add a test case in `testsuite/` before modifying any C implementation.** ## Verifying a Test Run Actually Passed **Meson's aggregate `Ok:N Fail:0` is not sufficient evidence.** Meson can report a suite `Fail:0` while Unity sub-groups inside it fail — meson only tracks the process exit code of each *meson test unit*, not each Unity `RUN_TEST`. Always cross-check the **standalone test binary's own stdout** (`controller_unit_test`, `xcelib_test`, etc., run directly) for its authoritative `:FAIL:` count / `N Tests M Failures` summary before treating a run as green. This was the root cause of a masked-failure bug in the Unity harness itself (nested `UNITY_BEGIN()`/`UNITY_END()` across 83 `controller/testsuite/*.c` files swallowed the real exit code — fixed 2026-07-17) and is now the standard verification step for every `/kiro-validate-impl` pass. ## Coverage Targets - Go: 80% minimum on business logic (controllers, services, store) - C: every new function in a daemon must have at least one test case in `testsuite/` - Integration: all public REST endpoints must have at least one integration test case in `src/frontend/tests/regression/` ## What Not to Mock - Mock **external dependencies only** (network calls, file I/O that crosses component boundaries) - Do **not** mock the system under test - In-memory stores (`store/`) are real — use them directly in unit tests - CGO bridge (`backend/`) is covered by `make test-backend` — do not stub it in unit tests