mineracks-ckbunker-hsm-sign/tests/conftest.py
mineracks 9d380f5013 Initial import: CKBunker HSM validation harness
WebSocket client + CLI harness + pytest suite that exercises each axis of
a CKBunker + Coldcard Mk4 policy and asserts the expected outcomes, including
the critical negative test that a large PSBT without TOTP is rejected with
a specific 'rule #1: need user(s) confirmation' reason.

Configuration via .env / YAML / CLI flags, two pre-crafted test PSBTs as
fixtures (generation guide in fixtures/README.md), dashboard counter
scraper as sanity check, design rationale in docs/.
2026-04-14 10:50:04 +10:00

83 lines
2.1 KiB
Python

"""Pytest fixtures.
Each test module imports `client_session` and/or `cfg` from here. Running
`pytest` against a CKBunker deployment picks up configuration from the same
sources as the CLI harness — .env first, then `config.yaml` if present.
"""
from __future__ import annotations
import asyncio
from pathlib import Path
import pytest
from ckbunker_hsm_sign import Client, load_config
from ckbunker_hsm_sign.config import Config
@pytest.fixture(scope="session")
def cfg() -> Config:
yaml_path = Path("config.yaml")
return load_config(
yaml_path=yaml_path if yaml_path.exists() else None,
dotenv_path=Path(".env") if Path(".env").exists() else None,
)
@pytest.fixture(scope="session")
def client(cfg: Config) -> Client:
return Client(
base_url=cfg.url,
cf_access_client_id=cfg.cf_client_id,
cf_access_client_secret=cfg.cf_client_secret,
totp_secret=cfg.totp_secret,
user=cfg.user,
verbose=cfg.verbose,
)
@pytest.fixture
def event_loop():
"""Give each test its own event loop — WebSockets don't love being shared."""
loop = asyncio.new_event_loop()
yield loop
loop.close()
def _read_psbt(path: Path) -> bytes:
import base64
raw = path.read_bytes()
if raw[:5] == b"psbt\xff":
return raw
try:
decoded = base64.b64decode(raw.strip())
if decoded[:5] == b"psbt\xff":
return decoded
except Exception:
pass
try:
decoded = bytes.fromhex(raw.strip().decode("ascii"))
if decoded[:5] == b"psbt\xff":
return decoded
except Exception:
pass
pytest.skip(f"{path} is not a valid PSBT")
return b"" # unreachable
@pytest.fixture
def small_psbt(cfg: Config) -> bytes:
path = Path(cfg.small_psbt_path)
if not path.exists():
pytest.skip(f"{path} not found — see fixtures/README.md")
return _read_psbt(path)
@pytest.fixture
def large_psbt(cfg: Config) -> bytes:
path = Path(cfg.large_psbt_path)
if not path.exists():
pytest.skip(f"{path} not found — see fixtures/README.md")
return _read_psbt(path)