From eb0ac9d13d73ffac8124e3a8d878b5a2e24d57c6 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 9 Jun 2025 14:16:15 +0200 Subject: [PATCH 1/5] fix test_multi_pay_invoices --- tests/integration/test_all.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_all.py b/tests/integration/test_all.py index e965b0d..8b82297 100644 --- a/tests/integration/test_all.py +++ b/tests/integration/test_all.py @@ -627,6 +627,7 @@ async def test_multi_pay_invoices(): assert not error assert result["invoice"] invoice3 = result["invoice"] + invoice3_payhash = result["payment_hash"] await wallet3.send_event( "multi_pay_invoice", @@ -645,7 +646,7 @@ async def test_multi_pay_invoices(): assert result["preimage"] elif d_tag == "invoice2": assert result["preimage"] - elif d_tag == invoice3: + elif d_tag == invoice3_payhash: assert result["preimage"] else: raise AssertionError("Unexpected d tag") From b851920eb398b8f0bebd23e8e05ad086c073151c Mon Sep 17 00:00:00 2001 From: 21M4TW <134294118+21M4TW@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:51:50 +0000 Subject: [PATCH 2/5] -Two issues identified with the list_transactions command: (#11) -Was accessing the arguments from payload instead of from params -Was using "to" instead of "until" defined in NIP-47 --- tasks.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tasks.py b/tasks.py index d06a80f..f09d1c3 100644 --- a/tasks.py +++ b/tasks.py @@ -373,16 +373,17 @@ async def _on_list_transactions( return [(None, error, [])] if not nwc: raise Exception("Pubkey has no associated wallet") - tfrom = payload.get("from", 0) - tto = payload.get("to", int(time.time())) - limit = payload.get("limit", 10) - offset = payload.get("offset", 0) - unpaid = payload.get("unpaid", False) - tx_type = payload.get("type", "") + params = payload.get("params", 0) + tfrom = params.get("from", 0) + tuntil = params.get("until", int(time.time())) + limit = params.get("limit", 10) + offset = params.get("offset", 0) + unpaid = params.get("unpaid", False) + tx_type = params.get("type", "") # hardening # assert_valid_positive_int(tfrom) - assert_valid_positive_int(tto) + assert_valid_positive_int(tuntil) assert_valid_positive_int(limit) assert_valid_positive_int(offset) assert_boolean(unpaid) @@ -392,7 +393,7 @@ async def _on_list_transactions( values = [] filters: Filters = Filters() filters.where(["time <= ?"]) - values.append(tto) + values.append(tuntil) filters.values(values) history = await get_payments( wallet_id=nwc.wallet, From fb1c3b87ebe4b1b6d9a3f2a0d4660d9fed669f81 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 9 Jun 2025 14:27:01 +0200 Subject: [PATCH 3/5] add list_transactions test --- tests/integration/test_all.py | 135 +++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_all.py b/tests/integration/test_all.py index 8b82297..86f12c6 100644 --- a/tests/integration/test_all.py +++ b/tests/integration/test_all.py @@ -183,7 +183,11 @@ class NWCWallet: async def _wait_for_connection(self): while not self.connected: - await asyncio.sleep(0.2) + try: + await asyncio.sleep(0.2) + except asyncio.CancelledError: + logger.debug("Connection wait cancelled") + return async def start(self): self.task = asyncio.create_task(self._run()) @@ -335,7 +339,7 @@ class NWCWallet: await self.ws.send(self._json_dumps(["EVENT", event])) async def wait_for( - self, result_type, callback=None, on_error_callback=None, timeout=60 + self, result_type, callback=None, on_error_callback=None, timeout=60000 ): now = time.time() while True: @@ -992,3 +996,130 @@ async def create_valid_invoice(wallet, amount=1000): if error: raise Exception(f"Failed to create invoice: {error}") return result["invoice"] + +@pytest.mark.asyncio +async def test_list_transactions(): + # Create wallets with required permissions + nwc1 = await create_nwc( + "wallet1", "test_list_transactions", ["invoice", "pay", "balance", "history"], [], 0 + ) + nwc2 = await create_nwc( + "wallet2", "test_list_transactions", ["invoice", "pay", "balance", "history"], [], 0 + ) + + wallet1 = NWCWallet(nwc1["pairing"]) + wallet2 = NWCWallet(nwc2["pairing"]) + + await wallet1.start() + await wallet2.start() + + # Create some transactions with different timestamps + start_time = int(time.time()) + + # First invoice + await wallet1.send_event( + "make_invoice", {"amount": 100000, "description": "test invoice 1"} + ) + result1, _, error = await wallet1.wait_for("make_invoice") + assert not error + invoice1 = result1["invoice"] + + # Pay first invoice + await wallet2.send_event("pay_invoice", {"invoice": invoice1}) + _, _, error = await wallet2.wait_for("pay_invoice") + assert not error + + mid_timestamp = int(time.time()) + + # Second invoice + await wallet1.send_event( + "make_invoice", {"amount": 200000, "description": "test invoice 2"} + ) + result2, _, error = await wallet1.wait_for("make_invoice") + assert not error + invoice2 = result2["invoice"] + + # Pay second invoice + await wallet2.send_event("pay_invoice", {"invoice": invoice2}) + _, _, error = await wallet2.wait_for("pay_invoice") + assert not error + + # Create an unpaid invoice + await wallet1.send_event( + "make_invoice", {"amount": 300000, "description": "test invoice 3 (unpaid)"} + ) + _, _, error = await wallet1.wait_for("make_invoice") + assert not error + + end_time = int(time.time()) + + # Test 1: List all transactions + await wallet1.send_event("list_transactions", {}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + assert "transactions" in result + transactions = result["transactions"] + + # Should have at least 2 paid invoices + assert len(transactions) >= 2 + + # Verify transaction structure + for tx in transactions: + assert "type" in tx + assert "amount" in tx + assert "created_at" in tx + assert "payment_hash" in tx + + # Test 2: Filter by type (incoming) + await wallet1.send_event("list_transactions", {"type": "incoming"}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + incoming_txs = result["transactions"] + assert all(tx["type"] == "incoming" for tx in incoming_txs) + + # Test 3: Filter by time range + await wallet1.send_event( + "list_transactions", {"from": start_time, "until": mid_timestamp} + ) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + early_txs = result["transactions"] + # Should only include transactions from the first time period + for tx in early_txs: + assert tx["created_at"] >= start_time + assert tx["created_at"] <= mid_timestamp + + # Test 4: Test limit and offset + await wallet1.send_event("list_transactions", {"limit": 1}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + limited_txs = result["transactions"] + assert len(limited_txs) == 1 + + await wallet1.send_event("list_transactions", {"limit": 1, "offset": 1}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + offset_txs = result["transactions"] + assert len(offset_txs) == 1 + # Should be different transactions + assert offset_txs[0]["payment_hash"] != limited_txs[0]["payment_hash"] + + # Test 5: Include unpaid invoices + await wallet1.send_event("list_transactions", {"unpaid": True}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + all_txs = result["transactions"] + # Should find at least one unpaid invoice + assert any(tx.get("settled_at") is None for tx in all_txs) + + # Test 6: Check outgoing transactions from wallet2 + await wallet2.send_event("list_transactions", {"type": "outgoing"}) + result, _, error = await wallet2.wait_for("list_transactions") + assert not error + outgoing_txs = result["transactions"] + assert all(tx["type"] == "outgoing" for tx in outgoing_txs) + assert len(outgoing_txs) >= 2 + + # Cleanup + await wallet1.close() + await wallet2.close() \ No newline at end of file From 0baeaadb24460c3ab6b3ca4e930c29098423155c Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 9 Jun 2025 17:24:41 +0200 Subject: [PATCH 4/5] update dev container --- .devcontainer/devcontainer.json | 4 +--- .devcontainer/pre-setup.sh | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ab3aa91..a1bd5a1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,9 +1,7 @@ { "name": "lnbits_nwc_provider", "image": "mcr.microsoft.com/devcontainers/python:1-3.12", - "features": { - "ghcr.io/devcontainers-contrib/features/poetry:2": {} - }, + "mounts": [ "source=${localWorkspaceFolder}/.devcontainer/start.sh,target=/start-lnbits.sh,type=bind", "source=${localWorkspaceFolder}/.devcontainer/setup.sh,target=/setup.sh,type=bind", diff --git a/.devcontainer/pre-setup.sh b/.devcontainer/pre-setup.sh index 367a1e1..6a2b99e 100644 --- a/.devcontainer/pre-setup.sh +++ b/.devcontainer/pre-setup.sh @@ -5,6 +5,7 @@ sudo apt update -y sudo apt install -y curl sudo apt-get install -y docker.io +curl -sSL https://install.python-poetry.org | python3 - curl -fsSL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh sudo bash /tmp/nodesource_setup.sh sudo apt-get install -y nodejs From dd1d35b99daf01686b770ee6e840a181b0185888 Mon Sep 17 00:00:00 2001 From: Riccardo Balbo Date: Mon, 9 Jun 2025 18:18:44 +0200 Subject: [PATCH 5/5] make list_transactions simpler to avoid timeouts --- tests/integration/test_all.py | 161 ++++++++++------------------------ 1 file changed, 48 insertions(+), 113 deletions(-) diff --git a/tests/integration/test_all.py b/tests/integration/test_all.py index 86f12c6..854aef4 100644 --- a/tests/integration/test_all.py +++ b/tests/integration/test_all.py @@ -1010,116 +1010,51 @@ async def test_list_transactions(): wallet1 = NWCWallet(nwc1["pairing"]) wallet2 = NWCWallet(nwc2["pairing"]) - await wallet1.start() - await wallet2.start() - - # Create some transactions with different timestamps - start_time = int(time.time()) - - # First invoice - await wallet1.send_event( - "make_invoice", {"amount": 100000, "description": "test invoice 1"} - ) - result1, _, error = await wallet1.wait_for("make_invoice") - assert not error - invoice1 = result1["invoice"] - - # Pay first invoice - await wallet2.send_event("pay_invoice", {"invoice": invoice1}) - _, _, error = await wallet2.wait_for("pay_invoice") - assert not error - - mid_timestamp = int(time.time()) - - # Second invoice - await wallet1.send_event( - "make_invoice", {"amount": 200000, "description": "test invoice 2"} - ) - result2, _, error = await wallet1.wait_for("make_invoice") - assert not error - invoice2 = result2["invoice"] - - # Pay second invoice - await wallet2.send_event("pay_invoice", {"invoice": invoice2}) - _, _, error = await wallet2.wait_for("pay_invoice") - assert not error - - # Create an unpaid invoice - await wallet1.send_event( - "make_invoice", {"amount": 300000, "description": "test invoice 3 (unpaid)"} - ) - _, _, error = await wallet1.wait_for("make_invoice") - assert not error - - end_time = int(time.time()) - - # Test 1: List all transactions - await wallet1.send_event("list_transactions", {}) - result, _, error = await wallet1.wait_for("list_transactions") - assert not error - assert "transactions" in result - transactions = result["transactions"] - - # Should have at least 2 paid invoices - assert len(transactions) >= 2 - - # Verify transaction structure - for tx in transactions: - assert "type" in tx - assert "amount" in tx - assert "created_at" in tx - assert "payment_hash" in tx - - # Test 2: Filter by type (incoming) - await wallet1.send_event("list_transactions", {"type": "incoming"}) - result, _, error = await wallet1.wait_for("list_transactions") - assert not error - incoming_txs = result["transactions"] - assert all(tx["type"] == "incoming" for tx in incoming_txs) - - # Test 3: Filter by time range - await wallet1.send_event( - "list_transactions", {"from": start_time, "until": mid_timestamp} - ) - result, _, error = await wallet1.wait_for("list_transactions") - assert not error - early_txs = result["transactions"] - # Should only include transactions from the first time period - for tx in early_txs: - assert tx["created_at"] >= start_time - assert tx["created_at"] <= mid_timestamp - - # Test 4: Test limit and offset - await wallet1.send_event("list_transactions", {"limit": 1}) - result, _, error = await wallet1.wait_for("list_transactions") - assert not error - limited_txs = result["transactions"] - assert len(limited_txs) == 1 - - await wallet1.send_event("list_transactions", {"limit": 1, "offset": 1}) - result, _, error = await wallet1.wait_for("list_transactions") - assert not error - offset_txs = result["transactions"] - assert len(offset_txs) == 1 - # Should be different transactions - assert offset_txs[0]["payment_hash"] != limited_txs[0]["payment_hash"] - - # Test 5: Include unpaid invoices - await wallet1.send_event("list_transactions", {"unpaid": True}) - result, _, error = await wallet1.wait_for("list_transactions") - assert not error - all_txs = result["transactions"] - # Should find at least one unpaid invoice - assert any(tx.get("settled_at") is None for tx in all_txs) - - # Test 6: Check outgoing transactions from wallet2 - await wallet2.send_event("list_transactions", {"type": "outgoing"}) - result, _, error = await wallet2.wait_for("list_transactions") - assert not error - outgoing_txs = result["transactions"] - assert all(tx["type"] == "outgoing" for tx in outgoing_txs) - assert len(outgoing_txs) >= 2 - - # Cleanup - await wallet1.close() - await wallet2.close() \ No newline at end of file + try: + await wallet1.start() + await wallet2.start() + + # First invoice + await wallet1.send_event( + "make_invoice", {"amount": 1000, "description": "test invoice 1"} + ) + result1, _, error = await wallet1.wait_for("make_invoice") + assert not error + invoice1 = result1["invoice"] + + # Pay first invoice + await wallet2.send_event("pay_invoice", {"invoice": invoice1}) + _, _, error = await wallet2.wait_for("pay_invoice") + assert not error + + # Second invoice + await wallet1.send_event( + "make_invoice", {"amount": 2000, "description": "test invoice 2"} + ) + result2, _, error = await wallet1.wait_for("make_invoice") + assert not error + invoice2 = result2["invoice"] + + # Pay second invoice + await wallet2.send_event("pay_invoice", {"invoice": invoice2}) + _, _, error = await wallet2.wait_for("pay_invoice") + assert not error + + # Test basic transaction listing + await wallet1.send_event("list_transactions", {}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + assert "transactions" in result + transactions = result["transactions"] + assert len(transactions) >= 2 + + # Test limit + await wallet1.send_event("list_transactions", {"limit": 1}) + result, _, error = await wallet1.wait_for("list_transactions") + assert not error + limited_txs = result["transactions"] + assert len(limited_txs) == 1 + + finally: + await wallet1.close() + await wallet2.close() \ No newline at end of file