From 72dfc61d9cad53917a141ff6abbdc7699042b45a Mon Sep 17 00:00:00 2001 From: f321x Date: Tue, 1 Jul 2025 11:45:04 +0200 Subject: [PATCH] tests: add more lnurl testing for PaymentIdentifier adds some more detailed tests to `test_payment_identifier.py` to test lnurlp and lnurlw separately and mock their resolve. --- tests/test_payment_identifier.py | 112 +++++++++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 5 deletions(-) diff --git a/tests/test_payment_identifier.py b/tests/test_payment_identifier.py index 6f4b4f506..04f59d93b 100644 --- a/tests/test_payment_identifier.py +++ b/tests/test_payment_identifier.py @@ -1,9 +1,13 @@ import os +import asyncio +from unittest.mock import patch from electrum import SimpleConfig from electrum.invoices import Invoice from electrum.payment_identifier import (maybe_extract_lightning_payment_identifier, PaymentIdentifier, - PaymentIdentifierType, invoice_from_payment_identifier) + PaymentIdentifierType, PaymentIdentifierState, + invoice_from_payment_identifier) +from electrum.lnurl import LNURL6Data, LNURL3Data, LNURLError from electrum.transaction import PartialTxOutput from . import ElectrumTestCase @@ -143,14 +147,112 @@ class TestPaymentIdentifier(ElectrumTestCase): pi = PaymentIdentifier(None, bip21) self.assertFalse(pi.is_valid()) - def test_lnurl(self): - lnurl = 'lnurl1dp68gurn8ghj7um9wfmxjcm99e5k7telwy7nxenrxvmrgdtzxsenjcm98pjnwxq96s9' - pi = PaymentIdentifier(None, lnurl) + def test_lnurl_basic(self): + """Test basic LNURL parsing without resolve""" + valid_lnurl = 'lnurl1dp68gurn8ghj7um9wfmxjcm99e5k7telwy7nxenrxvmrgdtzxsenjcm98pjnwxq96s9' + pi = PaymentIdentifier(None, valid_lnurl) self.assertTrue(pi.is_valid()) + self.assertEqual(PaymentIdentifierType.LNURL, pi.type) self.assertFalse(pi.is_available()) self.assertTrue(pi.need_resolve()) + self.assertEqual(PaymentIdentifierState.NEED_RESOLVE, pi.state) - # TODO: resolve mock + # Test with lightning: prefix + lightning_lnurl = f'lightning:{valid_lnurl}' + pi = PaymentIdentifier(None, lightning_lnurl) + self.assertTrue(pi.is_valid()) + self.assertEqual(PaymentIdentifierType.LNURL, pi.type) + self.assertTrue(pi.need_resolve()) + + @patch('electrum.payment_identifier.request_lnurl') + def test_lnurl_pay_resolve(self, mock_request_lnurl): + """Test LNURL-pay (LNURL6) with mocked resolve""" + valid_lnurl = 'LNURL1DP68GURN8GHJ7MRWVF5HGUEWD3HXZERYWFJHXUEWVDHK6TMVDE6HYMRS9ANRV46DXETQPJQCS4' + + # Mock lnurl-p response + mock_lnurl6_data = LNURL6Data( + callback_url='https://example.com/lnurl-pay', + max_sendable_sat=1_000_000, + min_sendable_sat=1_000, + metadata_plaintext='Test payment', + comment_allowed=100, + ) + mock_request_lnurl.return_value = mock_lnurl6_data + + pi = PaymentIdentifier(None, valid_lnurl) + self.assertTrue(pi.need_resolve()) + self.assertEqual(PaymentIdentifierType.LNURL, pi.type) + + async def run_resolve(): + await pi._do_resolve() + + asyncio.run(run_resolve()) + + self.assertEqual(PaymentIdentifierType.LNURLP, pi.type) + self.assertEqual(PaymentIdentifierState.LNURLP_FINALIZE, pi.state) + self.assertTrue(pi.need_finalize()) + self.assertIsNotNone(pi.lnurl_data) + self.assertTrue(isinstance(pi.lnurl_data, LNURL6Data)) + self.assertEqual(1_000, pi.lnurl_data.min_sendable_sat) + self.assertEqual(1_000_000, pi.lnurl_data.max_sendable_sat) + self.assertEqual('Test payment', pi.lnurl_data.metadata_plaintext) + self.assertEqual(100, pi.lnurl_data.comment_allowed) + + @patch('electrum.payment_identifier.request_lnurl') + def test_lnurl_withdraw_resolve(self, mock_request_lnurl): + """Test LNURL-withdraw (LNURL3) with mocked resolve""" + valid_lnurl = 'LNURL1DP68GURN8GHJ7MRWVF5HGUEWD3HXZERYWFJHXUEWVDHK6TM4WPNHYCTYV4EJ7DFCVGENSDPH8QCRZETXVGCXGCMPVFJR' \ + 'WENP8P3NJEP3XE3NQWRPXFJR2VRRVSCX2V33V5UNVC3SXP3RXCFSVFSKVWPCV3SKZWTP8YUZ7AMFW35XGUNPWUHKZURF9AMRZT' \ + 'MVDE6HYMP0FETHVUNZDAMHQ7JSF4RX73TZ2VU9Z3J3GVMSLCJ57F' + + # Mock lnurl-w response + mock_lnurl3_data = LNURL3Data( + callback_url='https://example.com/lnurl-withdraw', + k1='test-k1-value', + default_description='Test withdrawal', + min_withdrawable_sat=1_000, + max_withdrawable_sat=500_000, + ) + mock_request_lnurl.return_value = mock_lnurl3_data + + pi = PaymentIdentifier(None, valid_lnurl) + self.assertTrue(pi.need_resolve()) + self.assertEqual(PaymentIdentifierType.LNURL, pi.type) + + async def run_resolve(): + await pi._do_resolve() + + asyncio.run(run_resolve()) + + self.assertEqual(PaymentIdentifierType.LNURLW, pi.type) + self.assertEqual(PaymentIdentifierState.LNURLW_FINALIZE, pi.state) + self.assertIsNotNone(pi.lnurl_data) + self.assertEqual('test-k1-value', pi.lnurl_data.k1) + self.assertEqual('Test withdrawal', pi.lnurl_data.default_description) + self.assertEqual(1000, pi.lnurl_data.min_withdrawable_sat) + self.assertEqual(500000, pi.lnurl_data.max_withdrawable_sat) + + @patch('electrum.payment_identifier.request_lnurl') + def test_lnurl_resolve_error(self, mock_request_lnurl): + """Test LNURL resolve error handling""" + lnurl = 'LNURL1DP68GURN8GHJ7MRWVF5HGUEWD3HXZERYWFJHXUEWVDHK6TM4WPNHYCTYV4EJ7DFCVGENSDPH8QCRZETXVGCXGCMPVFJR' \ + 'WENP8P3NJEP3XE3NQWRPXFJR2VRRVSCX2V33V5UNVC3SXP3RXCFSVFSKVWPCV3SKZWTP8YUZ7AMFW35XGUNPWUHKZURF9AMRZT' \ + 'MVDE6HYMP0FETHVUNZDAMHQ7JSF4RX73TZ2VU9Z3J3GVMSLCJ57F' + + # Mock LNURL error + mock_request_lnurl.side_effect = LNURLError("Server error") + + pi = PaymentIdentifier(None, lnurl) + self.assertTrue(pi.need_resolve()) + + async def run_resolve(): + await pi._do_resolve() + + asyncio.run(run_resolve()) + + self.assertEqual(PaymentIdentifierState.ERROR, pi.state) + self.assertTrue(pi.is_error()) + self.assertIn("Server error", pi.get_error()) def test_multiline(self): pi_str = '\n'.join([