- runner/harness/http.py: canonical Phase-2 recipe-test HTTP API (vendored from
recipe-maintainer/utils/tests/helpers.py): http_get/http_post, retry variants,
wait_for_http, assert_converges. JSON-parsing, header support, form/JSON POST
bodies, transport-failure -> status=0. Self-contained (cc-ci does not import
recipe-maintainer at runtime per DECISIONS Phase 2).
- harness.discovery.custom_tests now also recurses into
tests/<recipe>/{functional,playwright}/test_*.py (Phase 2 §4.1 layout) while
excluding lifecycle test_<op>.py names and honoring the HC2 repo-local gate.
- Unit tests:
tests/unit/test_http.py — in-process http.server fixture; deterministic
proofs of parsing/retry/convergence semantics, no network egress.
tests/unit/test_discovery_phase2.py — functional/+playwright/ recursion
+ HC2 gate still applies to subdirs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
78 lines
3.5 KiB
Python
78 lines
3.5 KiB
Python
"""Unit tests for Phase-2 discovery additions (plan §4.1).
|
|
|
|
Proves the `custom_tests` discovery recurses into the per-recipe `functional/` + `playwright/`
|
|
subdirs as well as the top-level dir, while still excluding lifecycle `test_<op>.py` names and
|
|
honouring the HC2 repo-local approval gate.
|
|
|
|
Run with: `cc-ci-run -m pytest tests/unit`. Located under tests/unit/ so the orchestrator never
|
|
picks these up as overlays/custom tests.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
|
from harness import discovery # noqa: E402
|
|
|
|
|
|
def _approve(tmp_path, *recipes):
|
|
f = tmp_path / "approved.txt"
|
|
f.write_text("".join(f"{r}\n" for r in recipes))
|
|
os.environ["CCCI_REPO_LOCAL_APPROVED_FILE"] = str(f)
|
|
|
|
|
|
def teardown_function():
|
|
os.environ.pop("CCCI_REPO_LOCAL_APPROVED_FILE", None)
|
|
|
|
|
|
def test_custom_tests_recurses_functional_and_playwright(tmp_path, monkeypatch):
|
|
"""A Phase-2 cc-ci recipe layout: functional/test_*.py + playwright/test_*.py + top-level
|
|
test_*.py — all are discovered as custom tests; the lifecycle names are excluded."""
|
|
# Point cc-ci's per-recipe dir at a fake recipe in tmp_path
|
|
fake_recipe = "ccci-phase2-fixture"
|
|
fake_dir = tmp_path / "tests" / fake_recipe
|
|
(fake_dir / "functional").mkdir(parents=True)
|
|
(fake_dir / "playwright").mkdir()
|
|
# legitimate custom tests at multiple levels
|
|
(fake_dir / "test_sso_smoke.py").write_text("# top-level cross-cutting\n")
|
|
(fake_dir / "functional" / "test_health_check.py").write_text("# parity port\n")
|
|
(fake_dir / "functional" / "test_content_roundtrip.py").write_text("# recipe-specific\n")
|
|
(fake_dir / "playwright" / "test_login_flow.py").write_text("# UI flow\n")
|
|
# lifecycle name in functional/ should be ignored (defensive)
|
|
(fake_dir / "functional" / "test_install.py").write_text("# misfiled lifecycle name\n")
|
|
|
|
# Patch the cc-ci dir resolver to point at our fixture
|
|
monkeypatch.setattr(discovery, "cc_ci_dir", lambda r: str(tmp_path / "tests" / r))
|
|
|
|
customs = discovery.custom_tests(fake_recipe, None)
|
|
names = sorted((src, os.path.basename(p)) for src, p in customs)
|
|
|
|
# Top-level + functional/ + playwright/ all discovered; lifecycle name excluded
|
|
assert ("cc-ci", "test_sso_smoke.py") in names
|
|
assert ("cc-ci", "test_health_check.py") in names
|
|
assert ("cc-ci", "test_content_roundtrip.py") in names
|
|
assert ("cc-ci", "test_login_flow.py") in names
|
|
assert ("cc-ci", "test_install.py") not in names
|
|
|
|
|
|
def test_custom_tests_repo_local_subdirs_gated(tmp_path, monkeypatch):
|
|
"""HC2 gate still applies to functional/playwright subdirs under repo-local: not approved -> the
|
|
repo-local subdir contents are ignored even if they exist."""
|
|
fake_recipe = "ccci-phase2-fixture"
|
|
monkeypatch.setattr(discovery, "cc_ci_dir", lambda r: str(tmp_path / "cc-ci" / r))
|
|
(tmp_path / "cc-ci" / fake_recipe).mkdir(parents=True)
|
|
|
|
rl = tmp_path / "repo"
|
|
(rl / "functional").mkdir(parents=True)
|
|
(rl / "functional" / "test_repo_local_specific.py").write_text("# repo-local custom\n")
|
|
|
|
_approve(tmp_path) # empty allowlist → default-deny
|
|
assert discovery.custom_tests(fake_recipe, str(rl)) == []
|
|
|
|
_approve(tmp_path, fake_recipe) # approved → repo-local subdir honored
|
|
customs = discovery.custom_tests(fake_recipe, str(rl))
|
|
names = {(src, os.path.basename(p)) for src, p in customs}
|
|
assert ("repo-local", "test_repo_local_specific.py") in names
|