diff --git a/CLAUDE.md b/CLAUDE.md index cb60119..c17aab7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -215,6 +215,12 @@ Examples: - Mock ansible-vault subprocess calls in unit tests - Use real ansible-vault in integration tests (test fixtures) - Minimum 70% coverage enforced +- **No skips in CI**: `tests/conftest.py` rewrites any skipped test into a + setup error when `GITHUB_ACTIONS=true`. `requires_cue` / + `requires_ansible_vault` markers stay useful for local dev (graceful + fallback when the tool is missing) but never fire in CI because both + tools are installed there. New skip markers without their corresponding + CI tool will fail loudly. See #55 for the rationale. ### Commit Convention diff --git a/tests/conftest.py b/tests/conftest.py index 410f3ad..26ffbbd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,39 @@ TEST_PASSWORD = "test-vault-password-12345" +# --- CI policy: no skips --- +# +# Local dev keeps `requires_cue` / `requires_ansible_vault` markers as graceful +# fallbacks. In CI we install both tools (#54), so those markers never fire — +# unless someone adds a new skip marker without its CI tool. The hook below +# rewrites any skip report into a failure when running under GitHub Actions, +# so the gap turns into a real test failure rather than silent green. + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo[None]) -> object: + """Convert skip → failure under GITHUB_ACTIONS=true. + + Skip outcomes appear during the "setup" phase (skipif markers fire there). + Rewriting the report's outcome makes pytest count the skip as a failed + test, which propagates to a non-zero exit code through pytest's normal + accounting. + """ + outcome = yield + if os.environ.get("GITHUB_ACTIONS") != "true": + return outcome + report = outcome.get_result() + if report.skipped and report.when == "setup": + original = report.longrepr + report.outcome = "failed" + report.longrepr = ( + f"CI policy: skipped tests are forbidden under GITHUB_ACTIONS=true. " + f"Install the required tool in CI instead of skipping. " + f"Original skip reason: {original}" + ) + return outcome + + @pytest.fixture def tmp_dir(tmp_path): """Return a temporary directory as Path."""