""" 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.")