120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
"""
|
|
Tests for the "BASIC mode dynamic Check Alive" fix (Group I).
|
|
|
|
Background (reported by the owner): with the plugin left in BASIC mode (the
|
|
default), the "Check Alive" (threshold) field is hidden from the user and
|
|
stays stuck at its old/default value (roughly "today + 11 months", i.e. one
|
|
year minus the default 30-day margin). If the user then anticipates the
|
|
delivery time (locktime) to something earlier than that stale threshold - e.g.
|
|
"in 1 month" - two different checks that compare locktime against
|
|
``date_to_check`` (the resolved threshold) would incorrectly treat the will as
|
|
"expired"/"invalid", even though the whole Check Alive concept is supposed to
|
|
be inert in BASIC mode.
|
|
|
|
The fix (``BalWindow.compute_date_to_check``) makes ``date_to_check`` track
|
|
the delivery time LIVE in BASIC mode, placed a fixed 2-hour margin before it,
|
|
so it is always < locktime by construction. In ADVANCED mode nothing changes:
|
|
the stored threshold is used as-is.
|
|
|
|
These tests call the real production method directly (no GUI/Electrum wallet
|
|
needed - it is a plain ``@staticmethod``), so they exercise the exact code
|
|
used at runtime rather than a re-implementation.
|
|
|
|
Run:
|
|
PYTHONPATH=electrum-src python3 -m pytest tests/test_group_i_basic_checkalive.py -q
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
|
|
|
from bal.gui.qt.window import BalWindow # noqa: E402 (path insert above)
|
|
|
|
|
|
OFFSET = BalWindow.BASIC_MODE_CHECK_ALIVE_OFFSET_SECONDS
|
|
|
|
|
|
def test_offset_is_two_hours():
|
|
"""Owner-approved margin: exactly 2 hours."""
|
|
assert OFFSET == 2 * 60 * 60
|
|
|
|
|
|
def _relative_days_to_midnight_timestamp(days):
|
|
"""Reproduce Util.parse_locktime_string's own normalisation: "now + N
|
|
days", truncated to midnight. Used to compute the expected value in
|
|
tests without duplicating the parsing logic itself."""
|
|
from datetime import datetime, timedelta
|
|
|
|
now = datetime.now()
|
|
return (
|
|
(now + timedelta(days=days))
|
|
.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
.timestamp()
|
|
)
|
|
|
|
|
|
def test_basic_mode_relative_locktime_one_year():
|
|
"""BASIC + default "1y" delivery: date_to_check tracks it, 2h earlier."""
|
|
result = BalWindow.compute_date_to_check(True, "1y", "30d")
|
|
expected_locktime = _relative_days_to_midnight_timestamp(365)
|
|
assert abs(result - (expected_locktime - OFFSET)) < 5
|
|
|
|
|
|
def test_basic_mode_anticipated_delivery_stays_consistent():
|
|
"""BASIC + delivery anticipated to 1 month: date_to_check follows it,
|
|
NOT the old ~11-month threshold - this is the exact bug scenario
|
|
reported by the owner."""
|
|
stale_threshold = "30d" # would resolve close to the OLD 1-year locktime
|
|
anticipated_locktime = "30d" # user moved delivery to ~1 month from now
|
|
result = BalWindow.compute_date_to_check(
|
|
True, anticipated_locktime, stale_threshold
|
|
)
|
|
locktime_ts = _relative_days_to_midnight_timestamp(30)
|
|
# date_to_check must be (delivery - 2h), always strictly before delivery.
|
|
assert result < locktime_ts
|
|
assert abs((locktime_ts - OFFSET) - result) < 5
|
|
|
|
|
|
def test_basic_mode_date_to_check_always_before_locktime():
|
|
"""Regression guard for the reported bug: whatever the delivery date is
|
|
(even very close to "now"), date_to_check must stay before it."""
|
|
for relative_locktime in ("1d", "7d", "30d", "90d", "365d"):
|
|
result = BalWindow.compute_date_to_check(True, relative_locktime, "30d")
|
|
parsed_locktime = _relative_days_to_midnight_timestamp(
|
|
int(relative_locktime[:-1])
|
|
)
|
|
assert result < parsed_locktime, (
|
|
f"date_to_check ({result}) should be before locktime "
|
|
f"({parsed_locktime}) for locktime={relative_locktime}"
|
|
)
|
|
|
|
|
|
def test_advanced_mode_uses_stored_threshold_unchanged():
|
|
"""ADVANCED mode: behaviour must stay exactly as before this fix - the
|
|
stored threshold is used as-is, regardless of the locktime value."""
|
|
absolute_threshold = time.time() + 5 * 86400 # arbitrary user-chosen value
|
|
result = BalWindow.compute_date_to_check(False, "30d", absolute_threshold)
|
|
assert abs(result - absolute_threshold) < 1
|
|
|
|
|
|
def test_basic_mode_falls_back_on_unparsable_locktime():
|
|
"""If the locktime can't be parsed for any reason, BASIC mode must not
|
|
crash: it falls back to the stored threshold, same as ADVANCED."""
|
|
absolute_threshold = time.time() + 5 * 86400
|
|
result = BalWindow.compute_date_to_check(
|
|
True, {"not": "a valid locktime"}, absolute_threshold
|
|
)
|
|
assert abs(result - absolute_threshold) < 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_offset_is_two_hours()
|
|
test_basic_mode_relative_locktime_one_year()
|
|
test_basic_mode_anticipated_delivery_stays_consistent()
|
|
test_basic_mode_date_to_check_always_before_locktime()
|
|
test_advanced_mode_uses_stored_threshold_unchanged()
|
|
test_basic_mode_falls_back_on_unparsable_locktime()
|
|
print("All test_group_i_basic_checkalive tests passed.")
|