diff --git a/config.json b/config.json index 9999c93..187cb5d 100644 --- a/config.json +++ b/config.json @@ -5,7 +5,7 @@ "short_description": "A NWC service provider for LNbits", "description": "", "tile": "/nwcprovider/static/image/nwcprovider.png", - "version": "1.1.3", + "version": "1.1.3-aio.1", "min_lnbits_version": "1.4.0", "contributors": [ { diff --git a/tasks.py b/tasks.py index 5540a31..76aab58 100644 --- a/tasks.py +++ b/tasks.py @@ -264,9 +264,11 @@ async def _on_make_invoice( # Ensures amount is provided if not amount_msats: raise Exception("Missing amount") - description = params.get("description", "") - description_hash = params.get("description_hash", None) - expiry = params.get("expiry", None) + # Optional params may arrive as explicit JSON null, which dict.get does + # not default, so coerce here rather than trusting the fallback. + description = params.get("description") or "" + description_hash = params.get("description_hash") or None + expiry = params.get("expiry") or None # hardening # assert_valid_msats(amount_msats) @@ -284,7 +286,7 @@ async def _on_make_invoice( currency="sat", memo=description, description_hash=bytes.fromhex(description_hash) if description_hash else None, - unhashed_description=description.encode("utf-8"), + unhashed_description=description.encode("utf-8") if description else None, expiry=expiry, ) payment_hash = payment.payment_hash @@ -394,12 +396,12 @@ async def _on_list_transactions( if not nwc: raise Exception("Pubkey has no associated wallet") 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", "") + tfrom = params.get("from") or 0 + tuntil = params.get("until") or int(time.time()) + limit = params.get("limit") or 10 + offset = params.get("offset") or 0 + unpaid = params.get("unpaid") or False + tx_type = params.get("type") or "" # hardening # assert_valid_positive_int(tfrom) diff --git a/tests/unit/test_tasks.py b/tests/unit/test_tasks.py index 370daf4..6dfeb55 100644 --- a/tests/unit/test_tasks.py +++ b/tests/unit/test_tasks.py @@ -78,3 +78,77 @@ async def test_process_invoice_backs_off_pending_payment_polling_to_configured_m assert result["preimage"] == "b" * 64 assert result["fee_msats"] == 10 assert result["paid"] is True + + +def _stub_make_invoice(monkeypatch, captured: dict): + async def fake_get_nwc(*args, **kwargs): + return SimpleNamespace(wallet="wallet123") + + async def fake_check(nwc, method): + return None + + async def fake_create_invoice(**kwargs): + captured.update(kwargs) + return SimpleNamespace(payment_hash="b" * 64, bolt11="lnbc1example") + + async def fake_check_transaction_status(wallet_id: str, payment_hash: str): + return SimpleNamespace(preimage=None) + + monkeypatch.setattr(tasks, "get_nwc", fake_get_nwc) + monkeypatch.setattr(tasks, "_check", fake_check) + monkeypatch.setattr(tasks, "create_invoice", fake_create_invoice) + monkeypatch.setattr( + tasks, "check_transaction_status", fake_check_transaction_status + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "params", + [ + # rust-nostr, Alby JS SDK: unused optional fields are omitted + {"amount": 21000}, + # Amethyst: unused optional fields are sent as explicit null + { + "amount": 21000, + "description": None, + "description_hash": None, + "expiry": None, + }, + ], +) +async def test_make_invoice_accepts_absent_or_null_optional_params(monkeypatch, params): + captured: dict = {} + _stub_make_invoice(monkeypatch, captured) + + [(result, error, _)] = await tasks._on_make_invoice( + SimpleNamespace(), "a" * 64, {"params": params} + ) + + assert error is None + assert result["invoice"] == "lnbc1example" + assert result["description"] == "" + assert "expires_at" not in result + assert captured["memo"] == "" + assert captured["description_hash"] is None + assert captured["unhashed_description"] is None + assert captured["expiry"] is None + + +@pytest.mark.asyncio +async def test_make_invoice_passes_description_and_expiry_through(monkeypatch): + captured: dict = {} + _stub_make_invoice(monkeypatch, captured) + + [(result, error, _)] = await tasks._on_make_invoice( + SimpleNamespace(), + "a" * 64, + {"params": {"amount": 21000, "description": "coffee", "expiry": 600}}, + ) + + assert error is None + assert captured["memo"] == "coffee" + assert captured["unhashed_description"] == b"coffee" + assert captured["expiry"] == 600 + assert result["description"] == "coffee" + assert "expires_at" in result