"""End-to-end coverage for the #170 public TOML validation settings.""" from __future__ import annotations import json from pathlib import Path import socket import subprocess import sys import tempfile from urllib.parse import urlparse def free_port() -> int: with socket.socket() as sock: sock.bind(("127.0.0.1", 0)) return int(sock.getsockname()[1]) def toml_string(path: Path) -> str: return json.dumps(str(path)) def run_checked(args: list[str], *, timeout: int = 120) -> subprocess.CompletedProcess[str]: result = subprocess.run(args, capture_output=True, text=True, timeout=timeout) if result.returncode != 0: raise AssertionError( f"command failed ({result.returncode}): {' '.join(args)}\n" f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" ) return result def wait_for_server(server: subprocess.Popen[str]) -> None: assert server.stdout is not None line = server.stdout.readline().strip() if line != "ready": raise AssertionError(f"RRDP fixture server did not become ready: {line!r}") def stop_process(process: subprocess.Popen[str] | None) -> None: if process is None: return if process.poll() is None: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() process.wait() def write_config( path: Path, *, fixture: Path, output: Path, state: Path, ccr: Path, wrapper: Path, mirror: Path, sync_preference: str, rrdp_mode: str, validation_time: str, resource_mode: str, scope: str, ) -> None: path.write_text( f'''config-version = 1 mode = "validate" out = {toml_string(output)} rrdp-state-dir = {toml_string(state)} rrdp-sync-mode = "{rrdp_mode}" ccr-out = {toml_string(ccr)} http-timeout-secs = 2 http-root-cert = [{toml_string(fixture / "certs/rrdp-ca.pem")}] require-complete = true parallel-max-repo-sync-workers-global = 2 parallel-phase2-object-workers = 2 log-level = "info" log-format = "json" resource-validation-mode = "{resource_mode}" validation-time = "{validation_time}" sync-preference = "{sync_preference}" rsync-timeout-secs = 47 rsync-command = {toml_string(wrapper)} rsync-mirror-root = {toml_string(mirror)} rsync-scope = "{scope}" [strict] name = true cms-der = true signed-attrs = true [[anchors]] id = "custom" tal = {toml_string(fixture / "tal/custom.tal")} ta = {toml_string(fixture / "ta/custom-ta.cer")} ''', encoding="utf-8", ) def make_rsync_wrapper(path: Path, fixture: Path, log: Path) -> None: repository = fixture / "cases" / "baseline-v1" / "repository" path.write_text( f'''#!/usr/bin/env python3 import json import shutil import sys from pathlib import Path from urllib.parse import urlparse repository = Path({str(repository)!r}) log = Path({str(log)!r}) args = sys.argv[1:] with log.open("a", encoding="utf-8") as stream: stream.write(json.dumps(args) + "\\n") if len(args) < 2: raise SystemExit("rsync wrapper expected source and destination") source_uri, destination_text = args[-2:] destination = Path(destination_text) parts = [part for part in urlparse(source_uri).path.split("/") if part] if not parts or parts[0] != "custom": raise SystemExit(f"unexpected fixture rsync source: {{source_uri}}") relative = Path(*parts[1:]) if len(parts) > 1 else Path() source = repository / relative if not source.exists(): raise SystemExit(f"fixture rsync source does not exist: {{source}}") destination.mkdir(parents=True, exist_ok=True) if source.is_file(): shutil.copy2(source, destination / source.name) else: for item in source.iterdir(): target = destination / item.name if item.is_dir(): shutil.copytree(item, target, dirs_exist_ok=True) else: shutil.copy2(item, target) ''', encoding="utf-8", ) path.chmod(0o755) def read_json(path: Path) -> dict: return json.loads(path.read_text(encoding="utf-8")) def assert_successful_output(output: Path, expected_validation_time: str) -> None: summary = read_json(output / "summary.json") assert summary["resultStatus"] == "success", summary assert summary["vrps"] == 1, summary assert (output / "result-complete.json").is_file() assert len((output / "vrps.csv").read_text(encoding="utf-8").splitlines()) == 2 timing = read_json(output / "analysis" / "timing.json") assert timing["meta"]["validation_time_utc_rfc3339"] == expected_validation_time, timing def run(binary: str) -> None: support = Path(__file__).resolve().parent with tempfile.TemporaryDirectory(prefix="panda-rpki-public-config-") as tmp: base = Path(tmp) fixture = base / "fixture" port = free_port() run_checked( [ sys.executable, str(support / "generate_repository.py"), "--rrdp-host", "127.0.0.1", "--rrdp-port", str(port), "--rsync-host", "127.0.0.1", "--strict-names", "--output", str(fixture), ] ) active = fixture / "active" active.symlink_to("cases/baseline-v1/http") wrapper_log = base / "rsync-wrapper.log" wrapper = base / "rsync-wrapper.py" make_rsync_wrapper(wrapper, fixture, wrapper_log) server = subprocess.Popen( [ sys.executable, str(support / "serve_repository.py"), str(active), str(port), str(fixture / "certs/rrdp-server.pem"), str(fixture / "certs/rrdp-server.key"), ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) try: wait_for_server(server) fixed_input = "2026-06-01T08:00:00.250+08:00" fixed_effective = "2026-06-01T00:00:00Z" rrdp_output = base / "rrdp-output" rrdp_state = base / "rrdp-state" rrdp_config = base / "rrdp.toml" write_config( rrdp_config, fixture=fixture, output=rrdp_output, state=rrdp_state, ccr=rrdp_output / "result.ccr", wrapper=wrapper, mirror=base / "rrdp-mirror", sync_preference="rrdp-then-rsync", rrdp_mode="snapshot", validation_time=fixed_input, resource_mode="validation-update-03", scope="publication-point", ) run_checked([binary, "validate", "--config", str(rrdp_config)]) assert_successful_output(rrdp_output, fixed_effective) assert not wrapper_log.exists() or not wrapper_log.read_text(encoding="utf-8").strip(), ( "RRDP success path unexpectedly invoked the rsync command" ) finally: stop_process(server) rsync_output = base / "rsync-output" rsync_state = base / "rsync-state" rsync_mirror = base / "rsync-mirror" rsync_config = base / "rsync.toml" write_config( rsync_config, fixture=fixture, output=rsync_output, state=rsync_state, ccr=rsync_output / "result.ccr", wrapper=wrapper, mirror=rsync_mirror, sync_preference="rsync-only", rrdp_mode="auto", validation_time="2026-06-01T00:00:00Z", resource_mode="rfc6487", scope="publication-point", ) # The RRDP server is stopped. A successful run therefore proves that # sync-preference came from public TOML and reached the scheduler. run_checked([binary, "validate", "--config", str(rsync_config)]) assert_successful_output(rsync_output, "2026-06-01T00:00:00Z") calls = [ json.loads(line) for line in wrapper_log.read_text(encoding="utf-8").splitlines() if line.strip() ] assert calls, "rsync-only validation did not invoke configured rsync command" assert all("--timeout" in call and call[call.index("--timeout") + 1] == "47" for call in calls), calls sources = [call[-2] for call in calls] assert any("/custom/root/" in source for source in sources), sources assert any("/custom/child/" in source for source in sources), sources assert all("/custom/" in source and source.rstrip("/") != "rsync://127.0.0.1:873/custom" for source in sources), sources mirror_files = [path for path in rsync_mirror.rglob("*") if path.is_file()] assert mirror_files, f"configured rsync mirror remained empty: {rsync_mirror}" print( "Public TOML E2E passed: strict/resource/time + RRDP path + " "rsync-only command/timeout/mirror/scope" ) if __name__ == "__main__": run(str(Path(sys.argv[1]).resolve()))