179 lines
6.5 KiB
Python
179 lines
6.5 KiB
Python
"""
|
|
Tests for ``bal.core.checkalive`` (pure, GUI-free).
|
|
|
|
Covers the CheckAliveError exception and the BASIC/ADVANCED date_to_check
|
|
policy (``resolve_date_to_check`` / ``check_alive_expired``).
|
|
|
|
Run:
|
|
source /home/steal/devel/bal/electrum/env/bin/activate
|
|
python3 tests/test_core_checkalive.py
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, __file__.rsplit("/", 2)[0])
|
|
|
|
from bal.core.checkalive import ( # noqa: E402 (path insert above)
|
|
CheckAliveError,
|
|
check_alive_expired,
|
|
resolve_date_to_check,
|
|
)
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# CheckAliveError
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
|
def test_check_alive_error_default():
|
|
err = CheckAliveError(1000000)
|
|
assert err.timestamp_to_check == 1000000
|
|
|
|
|
|
def test_check_alive_error_str():
|
|
err = CheckAliveError(1000000)
|
|
s = str(err)
|
|
assert "Check alive expired" in s
|
|
assert "1970" in s
|
|
|
|
|
|
def test_check_alive_error_subclass():
|
|
assert issubclass(CheckAliveError, Exception)
|
|
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# resolve_date_to_check
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
|
def test_basic_mode_uses_now():
|
|
fake_now = 1_800_000_000.0
|
|
assert resolve_date_to_check(True, {}, now=fake_now) == fake_now
|
|
|
|
|
|
def test_advanced_mode_uses_threshold_absolute():
|
|
threshold = time.time() + 5 * 86400
|
|
settings = {"threshold": threshold}
|
|
result = resolve_date_to_check(False, settings)
|
|
assert abs(result - threshold) < 1
|
|
|
|
|
|
def test_advanced_mode_parses_relative_threshold():
|
|
# A relative threshold means "N days BEFORE the delivery": it resolves
|
|
# against the stored locktime (backwards), not forward from now.
|
|
from datetime import datetime, timedelta
|
|
|
|
fake_now = 1_800_000_000.0
|
|
locktime = fake_now + 90 * 86400
|
|
settings = {"threshold": "30d", "locktime": locktime}
|
|
result = resolve_date_to_check(False, settings, now=fake_now)
|
|
# date_to_check = (locktime, midnight-normalised) - 30 days.
|
|
expected = (datetime.fromtimestamp(locktime)
|
|
.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
- timedelta(days=30)).timestamp()
|
|
assert abs(result - expected) < 1
|
|
# 90d delivery with a 30d window: the window starts 60 days after now.
|
|
assert result > fake_now
|
|
|
|
|
|
def test_advanced_mode_relative_threshold_anchored_to_locktime():
|
|
"""A relative threshold never drifts with the clock: re-resolving it a day
|
|
later, with the same fixed absolute locktime, yields the same date."""
|
|
fake_now = 1_800_000_000.0
|
|
locktime = fake_now + 90 * 86400
|
|
settings = {"threshold": "30d", "locktime": locktime}
|
|
first = resolve_date_to_check(False, settings, now=fake_now)
|
|
# Next day: same stored settings (the fixed delivery), a later clock.
|
|
second = resolve_date_to_check(False, settings, now=fake_now + 86400)
|
|
assert first == second
|
|
|
|
|
|
def test_advanced_mode_relative_threshold_with_relative_locktime():
|
|
"""A relative locktime is resolved against 'now' first, then the relative
|
|
threshold counts N days back from it (matches the settings widget)."""
|
|
from datetime import datetime
|
|
|
|
from bal.core.plugin_base import BalTimestamp
|
|
|
|
fake_now = 1_800_000_000.0
|
|
settings = {"threshold": "30d", "locktime": "90d"}
|
|
result = resolve_date_to_check(False, settings, now=fake_now)
|
|
# Recompute the expected value with the same resolution rules:
|
|
# locktime = now + 90d (midnight-normalised), threshold = locktime - 30d.
|
|
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now))
|
|
expected = BalTimestamp("30d").to_date(locktime_dt, reverse=True).timestamp()
|
|
assert abs(result - expected) < 1
|
|
assert result > fake_now
|
|
|
|
|
|
def test_advanced_mode_relative_threshold_no_locktime_falls_back():
|
|
# Without a locktime reference, fall back to the legacy forward resolution.
|
|
settings = {"threshold": "30d"}
|
|
result = resolve_date_to_check(False, settings)
|
|
assert result > time.time()
|
|
|
|
|
|
def test_advanced_mode_relative_locktime_anchored_to_built_tx():
|
|
"""A RELATIVE stored locktime is anchored to the built will's frozen
|
|
delivery date (built_locktime), not to "now": an unchanged will must not
|
|
read as expired as the clock advances (the karen7 daily-invalidate bug)."""
|
|
frozen = 1817438400 # frozen tx locktime (2027-08-05), built 2026-08-05
|
|
settings = {"threshold": "30d", "locktime": "2y"}
|
|
# On build day the frozen delivery is authoritative: date_to_check is
|
|
# frozen - 30d and NEVER drifts, however much later the clock gets.
|
|
first = resolve_date_to_check(
|
|
False, settings, now=1_800_000_000.0, built_locktime=frozen
|
|
)
|
|
assert abs(first - (frozen - 30 * 86400)) < 1
|
|
later = resolve_date_to_check(
|
|
False, settings, now=1_800_000_000.0 + 10 * 86400, built_locktime=frozen
|
|
)
|
|
assert first == later
|
|
# The check window must start BEFORE the frozen delivery (never expired).
|
|
assert first < frozen
|
|
|
|
|
|
def test_advanced_mode_relative_locktime_without_built_tx_falls_back():
|
|
"""Without a built will there is no anchor: keeps the legacy now-based
|
|
resolution (a moving target, used only before the first build)."""
|
|
from datetime import datetime
|
|
|
|
from bal.core.plugin_base import BalTimestamp
|
|
|
|
fake_now = 1_800_000_000.0
|
|
settings = {"threshold": "30d", "locktime": "90d"}
|
|
result = resolve_date_to_check(False, settings, now=fake_now)
|
|
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now))
|
|
expected = BalTimestamp("30d").to_date(locktime_dt, reverse=True).timestamp()
|
|
assert abs(result - expected) < 1
|
|
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# check_alive_expired
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
|
def test_basic_mode_never_expired():
|
|
assert check_alive_expired(True, 1_000_000_000.0) is False
|
|
assert check_alive_expired(True, time.time() - 10_000) is False
|
|
|
|
|
|
def test_advanced_expired_when_past():
|
|
assert check_alive_expired(False, time.time() - 10_000) is True
|
|
|
|
|
|
def test_advanced_not_expired_when_future():
|
|
assert check_alive_expired(False, time.time() + 10_000) is False
|
|
|
|
|
|
# ------------------------------------------------------------------ #
|
|
# Main
|
|
# ------------------------------------------------------------------ #
|
|
|
|
if __name__ == "__main__":
|
|
for name in sorted(dir()):
|
|
if name.startswith("test_"):
|
|
globals()[name]()
|
|
print(f" [OK] {name}")
|
|
print("[OK] All core checkalive tests passed")
|