"""Shared helper utilities for the fpcf conformance test suite. This module contains plain importable helpers (no pytest fixtures — those live in ``conftest.py``). Together with ``conformance/README.md`` ("Module API contract"), these tests are the executable contract for the ``fpcf`` package API. """ from __future__ import annotations import copy import json from pathlib import Path from typing import Any, Iterable #: Default deterministic seed used across the test suite (64 hex chars). DEFAULT_SEED = "5eed" * 16 # --------------------------------------------------------------------------- # JSON / file helpers # --------------------------------------------------------------------------- def load_json(path: Path) -> Any: with open(path, "rb") as fh: return json.loads(fh.read().decode("utf-8")) def write_json(path: Path, obj: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) with open(path, "w", encoding="utf-8") as fh: json.dump(obj, fh, ensure_ascii=False) def tree_bytes(root: Path) -> dict[str, bytes]: """Map of relative path -> file bytes for every file under ``root``.""" out: dict[str, bytes] = {} for p in sorted(root.rglob("*")): if p.is_file(): out[p.relative_to(root).as_posix()] = p.read_bytes() return out # --------------------------------------------------------------------------- # Manifest / vector helpers # --------------------------------------------------------------------------- def manifest_entries(manifest: dict) -> list[dict]: entries = manifest["vectors"] assert isinstance(entries, list) and entries, "manifest has no vectors" return entries def vector_payload(vectors_dir: Path, entry: dict) -> Any: return load_json(vectors_dir / entry["file"]) def is_single_op(payload: Any) -> bool: """A single-operation vector payload is one signed envelope.""" return isinstance(payload, dict) and "body" in payload and "ops" not in payload def is_log(payload: Any) -> bool: """A log vector payload is ``{"ops": [envelope, ...], ...}``.""" return isinstance(payload, dict) and isinstance(payload.get("ops"), list) def vectors_where( vectors_dir: Path, manifest: dict, *, expect: str, predicate, ) -> list[tuple[dict, Any]]: out: list[tuple[dict, Any]] = [] for entry in manifest_entries(manifest): if entry["expect"] != expect: continue payload = vector_payload(vectors_dir, entry) if predicate(payload): out.append((entry, payload)) return out def valid_single_ops(vectors_dir: Path, manifest: dict) -> list[tuple[dict, dict]]: return vectors_where(vectors_dir, manifest, expect="valid", predicate=is_single_op) def invalid_single_ops(vectors_dir: Path, manifest: dict) -> list[tuple[dict, dict]]: return vectors_where(vectors_dir, manifest, expect="invalid", predicate=is_single_op) def valid_logs(vectors_dir: Path, manifest: dict) -> list[tuple[dict, dict]]: return vectors_where(vectors_dir, manifest, expect="valid", predicate=is_log) def invalid_logs(vectors_dir: Path, manifest: dict) -> list[tuple[dict, dict]]: return vectors_where(vectors_dir, manifest, expect="invalid", predicate=is_log) # --------------------------------------------------------------------------- # Tampering helpers # --------------------------------------------------------------------------- def _flipped(s: str) -> str: """Return ``s`` with its last character deterministically changed.""" if not s: return "X" last = s[-1] repl = "A" if last not in ("A", "a") else "B" return s[:-1] + repl def _tamper_first_string(value: Any) -> tuple[Any, bool]: """Return a copy of ``value`` with the first string leaf changed.""" if isinstance(value, str): return _flipped(value), True if isinstance(value, dict): for k in sorted(value): new, changed = _tamper_first_string(value[k]) if changed: out = dict(value) out[k] = new return out, True return value, False if isinstance(value, list): for i, item in enumerate(value): new, changed = _tamper_first_string(item) if changed: out = list(value) out[i] = new return out, True return value, False return value, False def tamper_body(op: dict) -> dict: """Return a deep copy of ``op`` whose body no longer matches its sig/id.""" bad = copy.deepcopy(op) body = bad["body"] assert isinstance(body, dict), "envelope body must be an object" body["x_tampered"] = True return bad def tamper_sig(op: dict) -> dict: """Return a deep copy of ``op`` with a corrupted signature.""" bad = copy.deepcopy(op) new_sig, changed = _tamper_first_string(bad["sig"]) assert changed, "could not find a string leaf in sig to tamper" bad["sig"] = new_sig return bad def tamper_op_id(op: dict) -> dict: """Return a deep copy of ``op`` with a corrupted op_id.""" bad = copy.deepcopy(op) assert isinstance(bad["op_id"], str) bad["op_id"] = _flipped(bad["op_id"]) return bad # --------------------------------------------------------------------------- # Runner / CLI adapters # --------------------------------------------------------------------------- def report_counts(report: Any) -> tuple[int, int, int]: """Normalise a runner report (object or dict) to (total, passed, failed).""" if isinstance(report, dict): src = report else: src = { k: getattr(report, k) for k in ("total", "passed", "failed") if hasattr(report, k) } total = src.get("total") passed = src.get("passed") failed = src.get("failed") if total is None and passed is not None and failed is not None: total = passed + failed assert total is not None and passed is not None and failed is not None, ( "runner report must expose total/passed/failed" ) return int(total), int(passed), int(failed) def run_cli(*argv: str) -> int: """Invoke fpcf.cli.main in-process and normalise the exit code.""" from fpcf import cli try: rc = cli.main(list(argv)) except SystemExit as exc: # argparse exits like this on usage errors rc = exc.code if rc is None: return 0 if isinstance(rc, int): return rc return 1 def assert_all(items: Iterable, message: str) -> list: """Materialise an iterable and assert it is non-empty.""" out = list(items) assert out, message return out