144 lines
4.9 KiB
Python
144 lines
4.9 KiB
Python
from typing import cast
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from lnurl import (
|
|
LnAddress,
|
|
LnurlAuthResponse,
|
|
LnurlErrorResponse,
|
|
LnurlException,
|
|
LnurlPayActionResponse,
|
|
LnurlPayResponse,
|
|
LnurlResponseException,
|
|
)
|
|
from lnurl.models import MessageAction
|
|
from lnurl.types import CallbackUrl, LightningInvoice
|
|
from pydantic import parse_obj_as
|
|
|
|
from lnbits.core.models import Account, CreateInvoice
|
|
from lnbits.core.models.lnurl import CreateLnurlPayment, LnurlScan
|
|
from lnbits.core.models.wallets import KeyType, WalletTypeInfo
|
|
from lnbits.core.services.payments import create_wallet_invoice
|
|
from lnbits.core.services.users import create_user_account
|
|
from lnbits.core.views.lnurl_api import (
|
|
api_lnurlscan,
|
|
api_lnurlscan_post,
|
|
api_payments_pay_lnurl,
|
|
api_perform_lnurlauth,
|
|
)
|
|
from tests.helpers import make_lnurl_pay_response
|
|
|
|
TEST_BOLT11 = (
|
|
"lnbc1pnsu5z3pp57getmdaxhg5kc9yh2a2qsh7cjf4gnccgkw0qenm8vsqv50w7s"
|
|
"ygqdqj0fjhymeqv9kk7atwwscqzzsxqyz5vqsp5e2yyqcp0a3ujeesp24ya0glej"
|
|
"srh703md8mrx0g2lyvjxy5w27ss9qxpqysgqyjreasng8a086kpkczv48er5c6l5"
|
|
"73aym6ynrdl9nkzqnag49vt3sjjn8qdfq5cr6ha0vrdz5c5r3v4aghndly0hplmv"
|
|
"6hjxepwp93cq398l3s"
|
|
)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_lnurl_api_scan_routes_validate_and_forward(mocker):
|
|
pay_response = make_lnurl_pay_response()
|
|
mocker.patch(
|
|
"lnbits.core.views.lnurl_api.lnurl_handle",
|
|
mocker.AsyncMock(return_value=pay_response),
|
|
)
|
|
|
|
scanned = await api_lnurlscan("lnurl1example")
|
|
assert isinstance(scanned, LnurlPayResponse)
|
|
assert scanned.callback == pay_response.callback
|
|
|
|
scanned_post = await api_lnurlscan_post(
|
|
scan=LnurlScan(lnurl=LnAddress("alice@example.com"))
|
|
)
|
|
assert isinstance(scanned_post, LnurlPayResponse)
|
|
assert scanned_post.callback == pay_response.callback
|
|
|
|
mocker.patch(
|
|
"lnbits.core.views.lnurl_api.lnurl_handle",
|
|
mocker.AsyncMock(return_value=LnurlErrorResponse(reason="blocked callback")),
|
|
)
|
|
with pytest.raises(HTTPException, match="blocked callback"):
|
|
await api_lnurlscan("lnurl1blocked")
|
|
|
|
mocker.patch(
|
|
"lnbits.core.views.lnurl_api.lnurl_handle",
|
|
mocker.AsyncMock(side_effect=LnurlException("invalid lnurl")),
|
|
)
|
|
with pytest.raises(HTTPException, match="invalid lnurl"):
|
|
await api_lnurlscan("lnurl1invalid")
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_lnurl_api_auth_and_pay_flow(mocker):
|
|
user = await create_user_account(
|
|
Account(
|
|
id=uuid4().hex,
|
|
username=f"user_{uuid4().hex[:8]}",
|
|
email=f"user_{uuid4().hex[:8]}@lnbits.com",
|
|
)
|
|
)
|
|
wallet = user.wallets[0]
|
|
wallet_info = WalletTypeInfo(key_type=KeyType.admin, wallet=wallet)
|
|
pay_response = make_lnurl_pay_response()
|
|
payment = await create_wallet_invoice(
|
|
wallet.id, CreateInvoice(out=False, amount=21, memo="lnurl")
|
|
)
|
|
|
|
auth_response = LnurlAuthResponse(
|
|
callback=parse_obj_as(CallbackUrl, "https://example.com/auth"),
|
|
k1="k1-value",
|
|
)
|
|
mocker.patch(
|
|
"lnbits.core.views.lnurl_api.lnurlauth",
|
|
mocker.AsyncMock(return_value=auth_response),
|
|
)
|
|
authenticated = await api_perform_lnurlauth(auth_response, wallet_info)
|
|
assert isinstance(authenticated, LnurlAuthResponse)
|
|
assert authenticated.k1 == "k1-value"
|
|
|
|
mocker.patch(
|
|
"lnbits.core.views.lnurl_api.lnurlauth",
|
|
mocker.AsyncMock(side_effect=LnurlResponseException("denied")),
|
|
)
|
|
with pytest.raises(HTTPException, match="denied"):
|
|
await api_perform_lnurlauth(auth_response, wallet_info)
|
|
|
|
action_response = LnurlPayActionResponse(
|
|
pr=cast(LightningInvoice, LightningInvoice(TEST_BOLT11)),
|
|
disposable=False,
|
|
successAction=parse_obj_as(MessageAction, {"message": "paid"}),
|
|
)
|
|
fetch_mock = mocker.patch(
|
|
"lnbits.core.views.lnurl_api.fetch_lnurl_pay_request",
|
|
mocker.AsyncMock(return_value=(pay_response, action_response)),
|
|
)
|
|
pay_mock = mocker.patch(
|
|
"lnbits.core.views.lnurl_api.pay_invoice",
|
|
mocker.AsyncMock(return_value=payment),
|
|
)
|
|
|
|
paid = await api_payments_pay_lnurl(
|
|
CreateLnurlPayment(
|
|
res=pay_response, amount=2_000, unit="USD", comment="thanks"
|
|
),
|
|
wallet_info,
|
|
)
|
|
assert paid.payment_hash == payment.payment_hash
|
|
fetch_mock.assert_awaited_once()
|
|
pay_mock.assert_awaited_once()
|
|
assert pay_mock.await_args is not None
|
|
assert action_response.successAction is not None
|
|
assert pay_mock.await_args.kwargs["extra"] == {
|
|
"stored": True,
|
|
"success_action": action_response.successAction.json(),
|
|
"comment": "thanks",
|
|
"fiat_currency": "USD",
|
|
"fiat_amount": 2.0,
|
|
}
|
|
|
|
with pytest.raises(HTTPException, match="Missing LNURL or LnurlPayResponse data."):
|
|
await api_payments_pay_lnurl(CreateLnurlPayment(amount=1), wallet_info)
|