import asyncio import random from http import HTTPStatus import pytest from httpx import AsyncClient, Headers from pydantic import TypeAdapter from lnbits import bolt11 from lnbits.nodes.base import ChannelPoint, ChannelState, NodeChannel from lnbits.settings import Settings from ..helpers import ( funding_source, get_random_invoice_data, ) from .helpers import ( get_unconnected_node_uri, mine_blocks, ) pytestmark = pytest.mark.skipif( funding_source.__node_cls__ is None, reason="Cant test if node implementation isnt available", ) @pytest.fixture() async def node_client(client: AsyncClient, settings: Settings, superuser_token: str): settings.lnbits_node_ui = True settings.lnbits_public_node_ui = False settings.lnbits_node_ui_transactions = True headers = client.headers client.headers = Headers({"Authorization": f"Bearer {superuser_token}"}) yield client client.headers = headers settings.lnbits_node_ui = False @pytest.fixture() async def public_node_client(node_client, settings): settings.lnbits_public_node_ui = True yield node_client settings.lnbits_public_node_ui = False @pytest.mark.anyio async def test_node_info_not_found( client: AsyncClient, settings: Settings, superuser_token: str ): settings.lnbits_node_ui = False response = await client.get( "/node/api/v1/info", headers={"Authorization": f"Bearer {superuser_token}"} ) assert response.status_code == HTTPStatus.SERVICE_UNAVAILABLE @pytest.mark.anyio async def test_public_node_info_not_found(node_client): response = await node_client.get("/node/public/api/v1/info") assert response.status_code == HTTPStatus.SERVICE_UNAVAILABLE @pytest.mark.anyio async def test_public_node_info(public_node_client): response = await public_node_client.get("/node/public/api/v1/info") assert response.status_code == 200 @pytest.mark.anyio async def test_node_info(node_client): response = await node_client.get("/node/api/v1/info") assert response.status_code == 200 @pytest.mark.anyio async def test_node_invoices(inkey_headers_from, node_client): data = await get_random_invoice_data() response = await node_client.post( "/api/v1/payments", json=data, headers=inkey_headers_from ) invoice = response.json() response = await node_client.get("/node/api/v1/invoices", params={"limit": 1}) assert response.status_code == 200 invoices = response.json()["data"] assert len(invoices) == 1 assert invoices[0]["payment_hash"] == invoice["payment_hash"] @pytest.mark.anyio async def test_node_payments(node_client, real_invoice, adminkey_headers_from): response = await node_client.post( "/api/v1/payments", json=real_invoice, headers=adminkey_headers_from ) assert response.status_code < 300 response = await node_client.get("/node/api/v1/payments", params={"limit": 1}) assert response.status_code == 200 payments = response.json()["data"] assert len(payments) == 1 assert ( payments[0]["payment_hash"] == bolt11.decode(real_invoice["bolt11"]).payment_hash ) @pytest.mark.anyio async def test_get_channel(node_client): # lndrest is slow / async with channel commands await asyncio.sleep(3) response = await node_client.get("/node/api/v1/channels") assert response.status_code == 200 channels = TypeAdapter(list[NodeChannel]).validate_python(response.json()) ch = random.choice( [channel for channel in channels if channel.state == ChannelState.ACTIVE] ) assert ch, "No active channel found" assert ch.point, "No channel point found" response = await node_client.get(f"/node/api/v1/channels/{ch.id}") assert response.status_code == 200 channel = TypeAdapter(NodeChannel).validate_python(response.json()) assert channel.id == ch.id @pytest.mark.anyio async def test_set_channel_fees(node_client): # lndrest is slow / async with channel commands await asyncio.sleep(3) response = await node_client.get("/node/api/v1/channels") assert response.status_code == 200 channels = TypeAdapter(list[NodeChannel]).validate_python(response.json()) ch = random.choice( [channel for channel in channels if channel.state == ChannelState.ACTIVE] ) assert ch, "No active channel found" assert ch.point, "No channel point found" response = await node_client.put( f"/node/api/v1/channels/{ch.id}", json={"fee_base_msat": 42, "fee_ppm": 69} ) assert response.status_code == 200 response = await node_client.get(f"/node/api/v1/channels/{ch.id}") assert response.status_code == 200 channel = TypeAdapter(NodeChannel).validate_python(response.json()) assert channel.fee_ppm == 69 assert channel.fee_base_msat == 42 @pytest.mark.anyio @pytest.mark.skipif( funding_source.__class__.__name__ == "LndRestWallet", reason="Lndrest is slow / async with channel commands", ) async def test_channel_management(node_client): async def get_channels(): # lndrest is slow / async with channel commands await asyncio.sleep(3) response = await node_client.get("/node/api/v1/channels") assert response.status_code == 200 return TypeAdapter(list[NodeChannel]).validate_python(response.json()) data = await get_channels() close = random.choice( [channel for channel in data if channel.state == ChannelState.ACTIVE] ) assert close, "No active channel found" assert close.point, "No channel point found" response = await node_client.delete( "/node/api/v1/channels", params={"short_id": close.short_id, **close.point.dict()}, ) assert response.status_code == 200 data = await get_channels() assert any( channel.point == close.point and channel.state == ChannelState.CLOSED for channel in data ) response = await node_client.post( "/node/api/v1/channels", json={ "peer_id": close.peer_id, "funding_amount": 100000, }, ) assert response.status_code == 200 created = ChannelPoint(**response.json()) data = await get_channels() assert any( channel.point == created and channel.state == ChannelState.PENDING for channel in data ) # mine some blocks so that the newly created channel eventually # gets confirmed to avoid a situation where no channels are # left for testing mine_blocks(5) await asyncio.sleep(1) @pytest.mark.anyio async def test_peer_management(node_client): connect_uri = get_unconnected_node_uri() peer_id = connect_uri.split("@")[0] response = await node_client.post("/node/api/v1/peers", json={"uri": connect_uri}) assert response.status_code == 200 response = await node_client.get("/node/api/v1/peers") assert response.status_code == 200 assert any(peer["id"] == peer_id for peer in response.json()) response = await node_client.delete(f"/node/api/v1/peers/{peer_id}") assert response.status_code == 200 # lndrest is slow to remove the peer await asyncio.sleep(0.3) response = await node_client.get("/node/api/v1/peers") assert response.status_code == 200 assert not any(peer["id"] == peer_id for peer in response.json()) response = await node_client.delete(f"/node/api/v1/peers/{peer_id}") assert response.status_code == 400 @pytest.mark.anyio async def test_connect_invalid_uri(node_client): response = await node_client.post("/node/api/v1/peers", json={"uri": "invalid"}) assert response.status_code == 400