--- id: WARDEN-WP-0003 type: workplan title: "OpsWarden Test Coverage and Code Quality" domain: custodian repo: ops-warden status: done owner: Bernd topic_slug: custodian planning_priority: medium planning_order: 3 created: "2026-05-15" updated: "2026-05-15" state_hub_workstream_id: "cb2bbf3c-848a-4af6-ba64-8361e64cd4d7" --- # WARDEN-WP-0003 — Test Coverage and Code Quality **Scope:** Close the test coverage gaps left after WARDEN-WP-0001: VaultCA has zero tests, `generate_keypair` is untested, no CLI tests exist, and no real `ssh-keygen` integration test was written. Also fix file permission enforcement (security) and add `--state-dir` override to `warden status` (usability). **Out of scope:** Functional behaviour changes (WARDEN-WP-0002), Vault cluster setup, host-side tooling. --- ## Goal After this workplan: 1. VaultCA has a full unit test suite covering success, auth failure, network failure, and role-map misconfiguration. 2. `generate_keypair` has direct unit tests alongside the existing `sign` tests. 3. A `tests/test_cli.py` covers every command's exit codes and output shape. 4. A `tests/test_integration.py` marked `@pytest.mark.integration` exercises `LocalCA.sign()` against a real `ssh-keygen` without any mocking. 5. Cert and key files written by warden are always mode 600; a scorecard check flags world-readable files. 6. `warden status --state-dir ` works without a `warden.yaml`. --- ## Reference Documents | Document | Location | |---|---| | WARDEN-WP-0001 | `workplans/WARDEN-WP-0001-initial-implementation.md` | | WARDEN-WP-0002 | `workplans/WARDEN-WP-0002-correctness-and-completeness.md` | | CertCommandInterface | `wiki/CertCommandInterface.md` | --- ## Design Decisions ### Integration tests: separate marker, not separate directory Use `@pytest.mark.integration` and skip when `ssh-keygen` is not in PATH. Tests live in `tests/test_integration.py`. The unit suite (`uv run pytest`) excludes integration tests via `pytest.ini_options addopts = "-m 'not integration'"`; `uv run pytest -m integration` runs them explicitly. This keeps CI fast while making the real-ssh-keygen path easy to invoke manually. ### File permissions: at write time, not at read time `os.chmod(path, 0o600)` is called immediately after each `path.write_text()` or `shutil.copy2()` that writes a key or cert. No deferred or scheduled chmod. The scorecard check catches files that were written by older versions of warden or by external tools. ### `warden status --state-dir`: config bypass, not config optional When `--state-dir` is provided, skip `load_config()` entirely — don't try to load a partial config. This makes the flag useful on remote machines that have received a cert via ops-bridge but have no warden installation. --- ## Tasks ### T1 — VaultCA tests ```task id: WARDEN-WP-0003-T1 state_hub_task_id: eff074ce-c027-4df5-8006-0990296592ac status: done priority: high ``` - [x] Create `tests/test_vault.py` - [x] Test `VaultCA.sign()` success: mock `httpx.post` returning a valid `signed_key`; assert `CertRecord` fields; assert cert file written to state_dir - [x] Test HTTP 403: `httpx.HTTPStatusError` → `CAError` with status code in message - [x] Test unreachable Vault: `httpx.RequestError` → `CAError` with fallback hint - [x] Test missing `VAULT_TOKEN`: `_token()` raises `CAError` before HTTP call - [x] Test missing role in `role_map`: `CAError` before HTTP call - [x] Test missing pubkey file: `CAError` before HTTP call ### T2 — LocalCA.generate_keypair tests ```task id: WARDEN-WP-0003-T2 state_hub_task_id: ddfe5331-0a3b-4783-bdf4-f5ebcdf7965c status: done priority: medium ``` - [x] Add `TestGenerateKeypair` class to `tests/test_ca.py` - [x] Test success: mock `subprocess.run`; assert privkey and pubkey paths returned - [x] Test ssh-keygen called with `-t ed25519`, `-N ""`, `-C actor_name` - [x] Test existing files are unlinked before generation (write dummy files first) - [x] Test `CAError` raised on non-zero ssh-keygen exit code - [x] Test output files land in `state_dir/keys/` ### T3 — CLI tests ```task id: WARDEN-WP-0003-T3 state_hub_task_id: 040ce3a1-0efb-4816-a2d9-357162dd1612 status: done priority: high ``` - [x] Create `tests/test_cli.py` using `typer.testing.CliRunner` - [x] `warden sign`: exits 0 and stdout is cert text (mock CA); exits 1 on unknown actor; exits 1 on config error - [x] `warden issue`: exits 1 on vault backend; exits 0 on local backend (mock CA) - [x] `warden status`: exits 0 and prints "no cert" message when state_dir empty; exits 1 when cert is expired (mock `parse_cert_metadata`) - [x] `warden scorecard`: exits 0 on clean inventory + empty state_dir; exits 1 when a check fails - [x] `warden inventory add / list / remove`: round-trip via tmp inventory file - [x] `warden log`: exits 0 with empty output when no log; `--json` is valid JSON (after WARDEN-WP-0002 T3 adds the log command) - [x] `warden cleanup --dry-run`: exits 0, no files deleted (after WARDEN-WP-0002 T2 adds cleanup) ### T4 — Real ssh-keygen integration test ```task id: WARDEN-WP-0003-T4 state_hub_task_id: 434fb008-103f-410c-85fd-e77b33e61fe4 status: done priority: medium ``` - [x] Create `tests/test_integration.py` - [x] Mark all tests `@pytest.mark.integration` - [x] Add `pytest.ini_options` to `pyproject.toml`: `addopts = "-m 'not integration'"` so unit suite skips them by default - [x] Test `LocalCA.sign()` end-to-end: generate a real CA keypair and actor keypair via subprocess ssh-keygen in tmp_path; call `LocalCA.sign()`; assert `CertRecord.valid_before > datetime.now(utc)`; assert cert file exists; assert `parse_cert_metadata()` succeeds on it without mocking - [x] Skip test if `shutil.which("ssh-keygen") is None` - [x] Document in README: `uv run pytest -m integration` to run real-CA tests ### T5 — File permissions enforcement (mode 600) ```task id: WARDEN-WP-0003-T5 state_hub_task_id: ac146fe6-d1fd-4186-91bd-6f098de72449 status: done priority: medium ``` - [x] `ca.py` `LocalCA.sign()`: call `os.chmod(dest, 0o600)` after `shutil.copy2` - [x] `ca.py` `LocalCA.generate_keypair()`: call `os.chmod(privkey, 0o600)` and `os.chmod(pubkey, 0o644)` after generation - [x] `vault.py` `VaultCA.sign()`: call `os.chmod(dest, 0o600)` after `dest.write_text` - [x] `scorecard.py`: add `check_file_permissions(state_dir)` — flag any `*-cert.pub` or `keys/*` file where `stat().st_mode & 0o044 != 0` - [x] Add `check_file_permissions` to `run_scorecard()` - [x] Update `test_ca.py`: assert `os.chmod` called with correct mode after sign and generate_keypair (patch os.chmod or check stat on actual files in tmp_path) ### T6 — warden status --state-dir override ```task id: WARDEN-WP-0003-T6 state_hub_task_id: 1c9f1987-7b11-43c1-a5e3-c2fd8d1c1589 status: done priority: low ``` - [x] `cli.py` `status()`: add `state_dir_override: Annotated[Optional[Path], typer.Option("--state-dir")] = None` - [x] When `--state-dir` is provided: use it directly, skip `_load_cfg()` entirely - [x] When absent: load config as today - [x] Add test in `test_cli.py`: invoke `warden status --state-dir ` without a config file; assert exit 0 --- ## Acceptance Criteria - [x] `uv run pytest` runs unit suite only; all pass; VaultCA and generate_keypair covered - [x] `uv run pytest -m integration` succeeds (requires ssh-keygen in PATH) - [x] `test_cli.py` covers all commands; no mocked subprocess in CLI tests where avoidable (use tmp inventory files and mocked CA) - [x] `ls -la ~/.local/state/warden/*.pub` shows mode 600 for newly signed certs - [x] Scorecard `file_permissions` check passes on a clean state dir; fails on a world-readable cert - [x] `warden status --state-dir /tmp/some-dir` runs without a `warden.yaml` - [x] All lints pass: `uv run ruff check .`