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/.
83 lines
2.1 KiB
Python
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)
|