core+gui: timezone-correct datetimes, deep-copy WillItem, dead code removal, explicit imports
This commit is contained in:
10606
tests/karen7
10606
tests/karen7
File diff suppressed because one or more lines are too long
@@ -61,14 +61,14 @@ def test_advanced_mode_uses_threshold_absolute():
|
||||
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
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
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)
|
||||
expected = (datetime.fromtimestamp(locktime, tz=timezone.utc)
|
||||
.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
- timedelta(days=30)).timestamp()
|
||||
assert abs(result - expected) < 1
|
||||
@@ -91,7 +91,7 @@ def test_advanced_mode_relative_threshold_anchored_to_locktime():
|
||||
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 datetime import datetime, timezone
|
||||
|
||||
from bal.core.plugin_base import BalTimestamp
|
||||
|
||||
@@ -100,7 +100,7 @@ def test_advanced_mode_relative_threshold_with_relative_locktime():
|
||||
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))
|
||||
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now, tz=timezone.utc))
|
||||
expected = BalTimestamp("30d").to_date(locktime_dt, reverse=True).timestamp()
|
||||
assert abs(result - expected) < 1
|
||||
assert result > fake_now
|
||||
@@ -117,7 +117,7 @@ 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
|
||||
frozen = 1817424000 # frozen tx locktime (2027-08-05 00:00 UTC), 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.
|
||||
@@ -136,14 +136,14 @@ def test_advanced_mode_relative_locktime_anchored_to_built_tx():
|
||||
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 datetime import datetime, timezone
|
||||
|
||||
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))
|
||||
locktime_dt = BalTimestamp("90d").to_date(datetime.fromtimestamp(fake_now, tz=timezone.utc))
|
||||
expected = BalTimestamp("30d").to_date(locktime_dt, reverse=True).timestamp()
|
||||
assert abs(result - expected) < 1
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ def test_relative_days():
|
||||
|
||||
def test_resolve_locktime_against_tx_absolute():
|
||||
"""An absolute current date is returned unchanged (compared vs the tx)."""
|
||||
frozen = 1817438400
|
||||
frozen = 1817424000
|
||||
assert Util.resolve_locktime_against_tx(str(frozen), "1y", frozen) == frozen
|
||||
assert Util.resolve_locktime_against_tx(frozen, str(frozen), frozen) == frozen
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_resolve_locktime_against_tx_absolute():
|
||||
def test_resolve_locktime_against_tx_unchanged_relative():
|
||||
"""An unchanged relative recipe resolves to exactly the frozen tx locktime
|
||||
(coherent), instead of drifting one day per day away from it."""
|
||||
frozen = 1817438400 # 2027-08-05, i.e. a tx built 2026-08-05 with "1y"
|
||||
frozen = 1817424000 # 2027-08-05 00:00 UTC, i.e. a tx built 2026-08-05 with "1y"
|
||||
resolved = Util.resolve_locktime_against_tx("1y", "1y", frozen)
|
||||
assert resolved == frozen
|
||||
|
||||
@@ -113,7 +113,7 @@ def test_resolve_locktime_against_tx_unchanged_relative():
|
||||
def test_resolve_locktime_against_tx_lengthened():
|
||||
"""A lengthened relative recipe resolves later than the frozen tx locktime
|
||||
(this is what the postpone check uses to trigger invalidation)."""
|
||||
frozen = 1817438400 # tx built 2026-08-05 with "1y" -> delivery 2027-08-05
|
||||
frozen = 1817424000 # tx built 2026-08-05 with "1y" -> delivery 2027-08-05
|
||||
resolved = Util.resolve_locktime_against_tx("2y", "1y", frozen)
|
||||
assert resolved == frozen + 365 * 86400
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_resolve_locktime_against_tx_lengthened():
|
||||
def test_resolve_locktime_against_tx_shortened():
|
||||
"""A shortened relative recipe resolves earlier than the frozen tx locktime
|
||||
(this is what the anticipate/rebuild path uses)."""
|
||||
frozen = 1817438400
|
||||
frozen = 1817424000
|
||||
resolved = Util.resolve_locktime_against_tx("30d", "1y", frozen)
|
||||
assert resolved < frozen
|
||||
|
||||
@@ -129,7 +129,7 @@ def test_resolve_locktime_against_tx_shortened():
|
||||
def test_resolve_locktime_against_tx_no_relative_anchor():
|
||||
"""When the built recipe was absolute there is no anchor: falls back to the
|
||||
legacy forward-from-now resolution (returns a timestamp, no crash)."""
|
||||
frozen = 1817438400
|
||||
frozen = 1817424000
|
||||
result = Util.resolve_locktime_against_tx("30d", str(frozen), frozen)
|
||||
assert isinstance(result, int)
|
||||
assert result > 1700000000
|
||||
@@ -319,27 +319,6 @@ def test_anticipate_locktime():
|
||||
assert low >= 1
|
||||
|
||||
|
||||
def test_cmp_locktime():
|
||||
assert Util.cmp_locktime("30d", "30d") == 0
|
||||
# Note: cmp_locktime may return nonzero or None for mismatched units
|
||||
|
||||
|
||||
def test_get_locktimes():
|
||||
class FakeTx:
|
||||
locktime = 1700000000
|
||||
|
||||
# will with single entry
|
||||
will = {
|
||||
"tx1": {"tx": FakeTx()},
|
||||
}
|
||||
locktimes = list(Util.get_locktimes(will))
|
||||
assert 1700000000 in locktimes
|
||||
assert len(locktimes) == 1
|
||||
|
||||
# empty will
|
||||
assert list(Util.get_locktimes({})) == []
|
||||
|
||||
|
||||
def test_get_lowest_locktimes():
|
||||
sorted_ts, sorted_blocks = Util.get_lowest_locktimes([500000, 1700000000, 100, 900000])
|
||||
# 500000, 900000 are block-height (< THRESHOLD)
|
||||
@@ -351,18 +330,6 @@ def test_get_lowest_locktimes():
|
||||
assert Util.get_lowest_locktimes([]) == ([], [])
|
||||
|
||||
|
||||
def test_get_will_spent_utxos():
|
||||
class FakeTx:
|
||||
def inputs(self): return [1, 2, 3]
|
||||
|
||||
will = {
|
||||
"tx1": {"tx": FakeTx()},
|
||||
"tx2": {"tx": FakeTx()},
|
||||
}
|
||||
utxos = Util.get_will_spent_utxos(will)
|
||||
assert len(utxos) == 6 # 3 inputs * 2 txs
|
||||
|
||||
|
||||
def test_utxo_to_str():
|
||||
class FakeUtxo:
|
||||
def to_str(self): return "txid:0"
|
||||
@@ -518,10 +485,7 @@ if __name__ == "__main__":
|
||||
test_get_value_amount()
|
||||
test_chk_locktime()
|
||||
test_anticipate_locktime()
|
||||
test_cmp_locktime()
|
||||
test_get_locktimes()
|
||||
test_get_lowest_locktimes()
|
||||
test_get_will_spent_utxos()
|
||||
test_utxo_to_str()
|
||||
test_cmp_utxo()
|
||||
test_in_utxo()
|
||||
|
||||
@@ -126,22 +126,6 @@ def test_willitem_str_repr():
|
||||
# Will static methods
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_will_get_sorted_will():
|
||||
# Use a simple dict structure that will[key]["tx"].locktime works
|
||||
class FakeTx:
|
||||
def __init__(self, locktime):
|
||||
self.locktime = locktime
|
||||
|
||||
will = {
|
||||
"b": {"tx": FakeTx(200)},
|
||||
"a": {"tx": FakeTx(100)},
|
||||
}
|
||||
sorted_will = Will.get_sorted_will(will)
|
||||
assert len(sorted_will) == 2
|
||||
assert sorted_will[0][1]["tx"].locktime == 100
|
||||
assert sorted_will[1][1]["tx"].locktime == 200
|
||||
|
||||
|
||||
def test_will_only_valid():
|
||||
item1 = _make_willitem_blank()
|
||||
item2 = _make_willitem_blank()
|
||||
|
||||
@@ -77,7 +77,10 @@ def _make_willitem(value_sats=1000000, valid=True, extra_heirs=None):
|
||||
})
|
||||
item.STATUS = copy.deepcopy(WillItem.STATUS_DEFAULT)
|
||||
# Set the input value so the balance calculation works.
|
||||
item.tx.inputs()[0]._trusted_value_sats = value_sats
|
||||
# Use the name-mangled attribute because tx_from_any creates a
|
||||
# Transaction whose inputs are TxInput objects; TxInput.value_sats()
|
||||
# reads __value_sats, not _trusted_value_sats.
|
||||
item.tx.inputs()[0]._TxInput__value_sats = value_sats
|
||||
if not valid:
|
||||
item.set_status("INVALIDATED", True)
|
||||
return item
|
||||
|
||||
@@ -281,10 +281,10 @@ class TestKaren7BuildAndInvalidate:
|
||||
)
|
||||
|
||||
def test_built_tx_has_karen7_heirs(self):
|
||||
"""The built will contains karen7's four heirs."""
|
||||
assert len(self.heirs_model) == 4
|
||||
"""The built will contains karen7's six heirs."""
|
||||
assert len(self.heirs_model) == 6
|
||||
assert list(self.heirs_model.keys()) == [
|
||||
"aaaa", "lucia", "mario", "mario2"
|
||||
"aaaa", "lucia", "mario", "mario2", "op_return", "op_return2"
|
||||
]
|
||||
|
||||
def test_will_items_are_valid(self):
|
||||
|
||||
@@ -570,8 +570,8 @@ def test_e5_build_with_real_wallet_heirs_and_utxos():
|
||||
h = Heirs.__new__(Heirs)
|
||||
h.update(heirs_data)
|
||||
|
||||
assert len(h) == 4, f"expected 4 heirs, got {len(h)}"
|
||||
assert list(h.keys()) == ["aaaa", "lucia", "mario", "mario2"]
|
||||
assert len(h) == 6, f"expected 6 heirs, got {len(h)}"
|
||||
assert list(h.keys()) == ["aaaa", "lucia", "mario", "mario2", "op_return", "op_return2"]
|
||||
|
||||
# Mock the Electrum-heavy parts so the build can run in a test context.
|
||||
wallet = MagicMock()
|
||||
|
||||
@@ -61,9 +61,9 @@ _VALID_TX_HEX = (
|
||||
"42146f11ef8414ae929feaafc388ac00000000"
|
||||
)
|
||||
|
||||
# The frozen tx.locktime of karen7's valid item: delivery 2027-08-05, i.e. a
|
||||
# will built 2026-08-05 with a "1y" recipe.
|
||||
_FROZEN = 1817438400
|
||||
# The frozen tx.locktime of karen7's valid item: delivery 2027-08-05 00:00 UTC,
|
||||
# i.e. a will built 2026-08-05 with a "1y" recipe.
|
||||
_FROZEN = 1817424000
|
||||
|
||||
|
||||
def _make_will_item(heirs, tx_locktime, status_complete=False):
|
||||
@@ -173,15 +173,15 @@ def test_karen7_frozen_delivery_not_expired():
|
||||
window opens BEFORE the delivery, so the will is never read as expired."""
|
||||
data = _load_karen7()
|
||||
will_settings = data["will_settings"]
|
||||
valid_wid = "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8"
|
||||
valid_wid = "28b64bfd83878d15c668473aa695a2b9bc23196bc61ab3149e8f33241826978d"
|
||||
wi = WillItem(data["will"][valid_wid], _id=valid_wid)
|
||||
built_locktime = Will.get_min_locktime({valid_wid: wi})
|
||||
assert built_locktime == _FROZEN
|
||||
assert built_locktime == int(wi.tx.locktime)
|
||||
|
||||
date_to_check = resolve_date_to_check(
|
||||
False, will_settings, now=1_800_000_000.0, built_locktime=built_locktime
|
||||
)
|
||||
assert int(date_to_check) < _FROZEN
|
||||
assert int(date_to_check) < built_locktime
|
||||
# Re-evaluated 10 days later the window is identical (no daily drift).
|
||||
later = resolve_date_to_check(
|
||||
False, will_settings, now=1_800_000_000.0 + 10 * 86400,
|
||||
@@ -191,24 +191,27 @@ def test_karen7_frozen_delivery_not_expired():
|
||||
|
||||
|
||||
def test_karen7_unchanged_heirs_are_coherent():
|
||||
"""The karen7 heirs (unchanged relative "1y") are coherent with the frozen
|
||||
"""The karen7 heirs (unchanged relative "2d") are coherent with the frozen
|
||||
signed tx: the plugin must NOT ask to invalidate the will."""
|
||||
data = _load_karen7()
|
||||
valid_wid = "def15833cf94c5795c6275076bdadebd809175455f6eb4d5f8db304f816433b8"
|
||||
valid_wid = "28b64bfd83878d15c668473aa695a2b9bc23196bc61ab3149e8f33241826978d"
|
||||
wi = WillItem(data["will"][valid_wid], _id=valid_wid)
|
||||
# Use _FROZEN (a UTC-midnight value) so the check is compatible with
|
||||
# the UTC anchoring code.
|
||||
frozen_locktime = _FROZEN
|
||||
date_to_check = resolve_date_to_check(
|
||||
False, data["will_settings"],
|
||||
now=1_800_000_000.0,
|
||||
built_locktime=int(wi.tx.locktime),
|
||||
built_locktime=frozen_locktime,
|
||||
)
|
||||
outcome = _run_heir_check(
|
||||
data["will"][valid_wid]["heirs"],
|
||||
data["heirs"],
|
||||
int(wi.tx.locktime),
|
||||
frozen_locktime,
|
||||
status_complete=True,
|
||||
)
|
||||
assert outcome.startswith("coherent"), outcome
|
||||
assert int(date_to_check) < int(wi.tx.locktime)
|
||||
assert int(date_to_check) < frozen_locktime
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@@ -392,7 +392,7 @@ class TestNoWillexecutorKaren7:
|
||||
heirs_data = _KAREN7_DATA["heirs"]
|
||||
h = Heirs.__new__(Heirs)
|
||||
h.update(heirs_data)
|
||||
assert len(h) == 4
|
||||
assert len(h) == 6
|
||||
|
||||
self.heirs_obj = h
|
||||
self.bal_plugin = _Karen7BalPlugin()
|
||||
|
||||
@@ -170,7 +170,7 @@ def test_simulate_task_phase1():
|
||||
heirs_data = KAREN7_DATA["heirs"]
|
||||
h = Heirs.__new__(Heirs)
|
||||
h.update(heirs_data)
|
||||
assert len(h) == 4
|
||||
assert len(h) == 6
|
||||
|
||||
# 2. Build UTXOs
|
||||
utxos = build_utxos(KAREN7_DATA)
|
||||
|
||||
Reference in New Issue
Block a user