118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
import pytest
|
|
from pytest_mock.plugin import MockerFixture
|
|
|
|
from lnbits.core.services.nostr import fetch_nip5_details, send_nostr_dm
|
|
|
|
|
|
class FakeWebSocket:
|
|
def __init__(self):
|
|
self.sent: list[str] = []
|
|
self.closed = False
|
|
|
|
def send(self, message: str):
|
|
self.sent.append(message)
|
|
|
|
def close(self):
|
|
self.closed = True
|
|
|
|
|
|
class MockHTTPClient:
|
|
def __init__(self, response):
|
|
self.response = response
|
|
self.calls: list[str] = []
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc, tb):
|
|
return False
|
|
|
|
async def get(self, url: str):
|
|
self.calls.append(url)
|
|
return self.response
|
|
|
|
|
|
class MockHTTPResponse:
|
|
def __init__(self, json_data: dict, error: Exception | None = None):
|
|
self._json_data = json_data
|
|
self._error = error
|
|
|
|
def raise_for_status(self):
|
|
if self._error:
|
|
raise self._error
|
|
|
|
def json(self):
|
|
return self._json_data
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_send_nostr_dm_sends_to_available_relays_and_closes_connections(
|
|
mocker: MockerFixture,
|
|
):
|
|
event = mocker.Mock()
|
|
event.to_message.return_value = "nostr-message"
|
|
event.to_dict.return_value = {"id": "event-id"}
|
|
dm = mocker.Mock()
|
|
dm.to_event.return_value = event
|
|
mocker.patch("lnbits.core.services.nostr.EncryptedDirectMessage", return_value=dm)
|
|
|
|
ws_one = FakeWebSocket()
|
|
ws_two = FakeWebSocket()
|
|
mocker.patch(
|
|
"lnbits.core.services.nostr.create_connection",
|
|
side_effect=[ws_one, RuntimeError("boom"), ws_two],
|
|
)
|
|
mocker.patch("lnbits.core.services.nostr.asyncio.sleep", mocker.AsyncMock())
|
|
|
|
result = await send_nostr_dm(
|
|
"privkey",
|
|
"pubkey",
|
|
"hello",
|
|
["wss://relay-1", "wss://broken", "wss://relay-2"],
|
|
)
|
|
|
|
assert ws_one.sent == ["nostr-message"]
|
|
assert ws_two.sent == ["nostr-message"]
|
|
assert ws_one.closed is True
|
|
assert ws_two.closed is True
|
|
assert result == {"id": "event-id"}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_fetch_nip5_details_returns_pubkey_and_relays(mocker: MockerFixture):
|
|
response = MockHTTPResponse(
|
|
{
|
|
"names": {"alice": "f" * 64},
|
|
"relays": {"f" * 64: ["wss://relay.example.com"]},
|
|
}
|
|
)
|
|
client = MockHTTPClient(response)
|
|
mocker.patch("lnbits.core.services.nostr.is_valid_url", return_value=True)
|
|
validate_identifier = mocker.patch("lnbits.core.services.nostr.validate_identifier")
|
|
validate_pub_key = mocker.patch("lnbits.core.services.nostr.validate_pub_key")
|
|
mocker.patch("lnbits.core.services.nostr.httpx.AsyncClient", return_value=client)
|
|
|
|
pubkey, relays = await fetch_nip5_details("alice@example.com")
|
|
|
|
validate_identifier.assert_called_once_with("alice")
|
|
validate_pub_key.assert_called_once_with("f" * 64)
|
|
assert client.calls == ["https://example.com/.well-known/nostr.json?name=alice"]
|
|
assert pubkey == "f" * 64
|
|
assert relays == ["wss://relay.example.com"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_fetch_nip5_details_rejects_invalid_values(mocker: MockerFixture):
|
|
with pytest.raises(ValueError, match="not enough values to unpack"):
|
|
await fetch_nip5_details("invalid")
|
|
|
|
mocker.patch("lnbits.core.services.nostr.is_valid_url", return_value=False)
|
|
with pytest.raises(ValueError, match="Invalid NIP5 domain"):
|
|
await fetch_nip5_details("alice@example.com")
|
|
|
|
mocker.patch("lnbits.core.services.nostr.is_valid_url", return_value=True)
|
|
client = MockHTTPClient(MockHTTPResponse({"names": {}}))
|
|
mocker.patch("lnbits.core.services.nostr.httpx.AsyncClient", return_value=client)
|
|
with pytest.raises(ValueError, match="NIP5 not name found"):
|
|
await fetch_nip5_details("alice@example.com")
|