add tests

This commit is contained in:
bot
2026-06-20 09:49:39 -04:00
parent 86ed0297a7
commit 525dde2b3c
34 changed files with 7427 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
"""
Live will-executor / welist query tests.
These tests contact the real servers documented in the project spec:
* the default will-executor ``https://we.bitcoin-after.life`` (``/bitcoin``)
* the welist ``https://welist.bitcoin-after.life`` (``/data/bitcoin``)
They are automatically **skipped** when the machine is offline or the servers
are unreachable, so the suite never fails spuriously in a sandbox / CI without
internet.
Two things are verified for each endpoint:
1. The live response has the documented JSON shape.
2. The plugin's own parsing logic (``Willexecutors.get_info_task`` /
``Willexecutors.download_list``) consumes that live response correctly.
To test the parsing without needing a running Electrum daemon, the live
JSON is fetched once with ``urllib`` and then fed back through the plugin
by mocking only the transport layer.
"""
import json
import os
import sys
import urllib.error
import urllib.request
import pytest
from unittest.mock import MagicMock, patch
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
from bal.core.willexecutors import Willexecutors
WE_URL = "https://we.bitcoin-after.life"
WE_INFO_URL = "https://we.bitcoin-after.life/bitcoin/info"
WELIST_URL = "https://welist.bitcoin-after.life"
WELIST_DATA_URL = "https://welist.bitcoin-after.life/data/bitcoin?page=0&limit=100"
HTTP_TIMEOUT = 8
def _fetch_json(url):
"""Fetch and JSON-decode ``url``; skip the test on any network problem."""
req = urllib.request.Request(
url, headers={"User-Agent": "BalPluginTest"}
)
try:
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT) as resp:
raw = resp.read().decode("utf-8")
except (urllib.error.URLError, OSError, TimeoutError) as e:
pytest.skip(f"network unavailable for {url}: {e}")
try:
return json.loads(raw)
except json.JSONDecodeError as e:
pytest.fail(f"{url} returned non-JSON: {e}: {raw[:200]!r}")
# ------------------------------------------------------------------ #
# Will-executor /info endpoint
# ------------------------------------------------------------------ #
def test_live_willexecutor_info_shape():
"""The live /bitcoin/info reply has the documented fields."""
data = _fetch_json(WE_INFO_URL)
assert isinstance(data, dict)
for key in ("address", "base_fee", "info"):
assert key in data, f"missing '{key}' in info reply: {data}"
assert isinstance(data["base_fee"], int)
assert data["base_fee"] > 0
assert isinstance(data["address"], str) and data["address"]
def test_live_willexecutor_info_parsed_by_plugin():
"""Feed the live info reply through ``get_info_task`` and check it parses."""
data = _fetch_json(WE_INFO_URL)
we = {"url": WE_URL, "selected": True, "status": "New"}
# The plugin reaches the server via send_request -> Network transport. We
# make the transport return exactly what the live server returned, so we
# exercise the real parsing path without a running Electrum daemon.
with patch("bal.core.willexecutors.Network.get_instance",
return_value=MagicMock()), \
patch("bal.core.willexecutors.Network.send_http_on_proxy",
return_value=data):
out = Willexecutors.get_info_task(WE_URL, we, max_retries=0)
assert out["status"] == 200
assert out["base_fee"] == data["base_fee"]
assert out["address"] == data["address"]
assert out["info"] == data["info"]
# ------------------------------------------------------------------ #
# Welist /data/bitcoin endpoint
# ------------------------------------------------------------------ #
def test_live_welist_shape():
"""The live welist reply maps URLs to will-executor descriptors."""
data = _fetch_json(WELIST_DATA_URL)
assert isinstance(data, dict)
# Drop the optional bookkeeping keys before inspecting executors.
executors = {k: v for k, v in data.items() if k not in ("status", "url")}
assert executors, "welist returned no will-executors"
for url, we in executors.items():
assert url.startswith("http"), f"bad executor url: {url}"
assert isinstance(we, dict)
assert "base_fee" in we, f"executor {url} missing base_fee"
assert "url" in we
def test_live_welist_contains_default_executor():
"""The default will-executor should be listed in the welist."""
data = _fetch_json(WELIST_DATA_URL)
assert WE_URL in data, f"{WE_URL} not present in welist: {list(data)[:5]}"
def test_live_welist_parsed_by_plugin():
"""Feed the live welist reply through ``download_list`` and check parsing.
``download_list`` normalises each executor entry (sets url / status /
selected / address) and must not crash on the real payload.
"""
data = _fetch_json(WELIST_DATA_URL)
with patch("bal.core.willexecutors.Network.get_instance",
return_value=MagicMock()), \
patch("bal.core.willexecutors.Network.send_http_on_proxy",
return_value=data):
out = Willexecutors.download_list({}, WELIST_URL)
assert isinstance(out, dict)
executors = {k: v for k, v in out.items() if k not in ("status", "url")}
assert executors, "download_list produced no executors"
for url, we in executors.items():
# initialize_willexecutor must have stamped the normalised fields.
assert we["url"] == url
assert "selected" in we
assert "address" in we
assert "status" in we
# ------------------------------------------------------------------ #
if __name__ == "__main__":
import inspect
for name, obj in sorted(globals().items()):
if name.startswith("test_") and inspect.isfunction(obj):
try:
obj()
print(f" [OK] {name}")
except Exception as e: # pragma: no cover
print(f" [FAIL/SKIP] {name}: {e}")
print("[done] live willexecutor tests")