#!/usr/bin/env python3 import json import subprocess import sys import tempfile import unittest from pathlib import Path import yaml ROOT = Path(__file__).resolve().parents[1] CATALOG = ROOT / "scripts" / "catalog.py" def write_fixture(root: Path, *, complete: bool = True) -> None: tester = root / ".pouch" / "tester" (tester / "cases").mkdir(parents=True) (tester / "runs").mkdir() (tester / "catalog.yaml").write_text( yaml.safe_dump( { "version": 2, "id_pattern": "^TC-[A-Z]+-[0-9]+$", "categories": [{"id": "CLIENT"}], "risks": ["readonly", "mutating", "destructive"], "default_max_risk": "readonly", "client_suites": { "client-smoke": {"default": True}, "client-regression": {"default": False}, "safety": {"default": False}, }, "sessions": {"browser": {"kind": "browser", "reuse": "run"}}, }, sort_keys=False, ), encoding="utf-8", ) case = { "id": "TC-CLIENT-001", "category": "CLIENT", "title": "journey", "what": "client journey", "lifecycle": "active", "tag": "smoke", "risk": "readonly", "driver": "browser", "session": "browser", "client_suite": "client-smoke", "platform": ["android", "ios"], "journey": { "goal": "goal", "entry_state": "logged_out", "actions": ["start", "login"], "assertions": ["visible"], "final_state": "done", "failure_policy": "stop", }, "fixture": {"account": "test-role"}, "cleanup": {"steps": ["cleanup"]}, "how": {"steps": ["start", "login"]}, "expected": [{"kind": "visible-text", "value": "done"}], } if not complete: case.pop("fixture") case.pop("cleanup") (tester / "cases" / "client.yaml").write_text( yaml.safe_dump({"category": "CLIENT", "cases": [case]}, sort_keys=False), encoding="utf-8", ) class CatalogJourneyTests(unittest.TestCase): def run_catalog(self, root: Path, *args: str): return subprocess.run( [sys.executable, str(CATALOG), *args, "--root", str(root)], text=True, capture_output=True, ) def test_valid_client_catalog_and_suite_filter(self): with tempfile.TemporaryDirectory() as temp: root = Path(temp) write_fixture(root) valid = self.run_catalog(root, "validate") self.assertEqual(valid.returncode, 0, valid.stderr) selected = self.run_catalog(root, "select", "--suite", "client-smoke") self.assertEqual(selected.returncode, 0, selected.stderr) self.assertEqual(json.loads(selected.stdout)["count"], 1) def test_missing_fixture_or_cleanup_blocks_before_results(self): with tempfile.TemporaryDirectory() as temp: root = Path(temp) write_fixture(root, complete=False) selected = self.run_catalog(root, "select", "--suite", "client-smoke") self.assertNotEqual(selected.returncode, 0) payload = json.loads(selected.stdout) self.assertEqual(payload["preflight"]["failure_class"], "contract") self.assertTrue(any("fixture 缺失" in error for error in payload["preflight"]["errors"])) self.assertEqual(payload["preflight"]["status"], "blocked") if __name__ == "__main__": unittest.main()