ForcePilot/backend/test/unit/channels/test_nostr_pair.py

169 lines
6.7 KiB
Python
Raw Normal View History

from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from yuxi.channels.adapters.nostr.pair import NostrPairingManager
class TestNostrPairingManager:
@pytest.fixture
def crypto(self):
crypto = MagicMock()
crypto.build_and_sign_event.return_value = {
"id": "pairing_event_id",
"kind": 4,
"content": "pairing challenge message",
"tags": [],
}
crypto.npub = "npub1test"
crypto.nsec.return_value = "nsec1test"
return crypto
@pytest.fixture
def relay_manager(self):
rm = MagicMock()
rm.broadcast = AsyncMock(return_value=2)
return rm
@pytest.fixture
def manager(self, crypto, relay_manager):
return NostrPairingManager(crypto, relay_manager)
def test_issue_challenge(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
assert token.startswith(manager.PAIRING_CHALLENGE_PREFIX)
assert "::" in token
def test_issue_challenge_adds_to_pending(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
body = token[len(manager.PAIRING_CHALLENGE_PREFIX):]
challenge_id = body.split("::")[0]
assert manager.get_pending(challenge_id) is not None
assert manager.get_pending(challenge_id)["status"] == "pending"
def test_verify_response_valid(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
success, challenge_id = manager.verify_response(token)
assert success is True
def test_verify_response_invalid_prefix(self, manager):
success, reason = manager.verify_response("not_a_pairing_response")
assert success is False
assert "not a pairing" in reason
def test_verify_response_wrong_format(self, manager):
success, reason = manager.verify_response(manager.PAIRING_CHALLENGE_PREFIX + "badformat")
assert success is False
assert "unknown challenge" in reason or "invalid pairing" in reason
def test_verify_response_unknown_challenge(self, manager):
success, reason = manager.verify_response(
manager.PAIRING_CHALLENGE_PREFIX + "unknown_id::some_token"
)
assert success is False
assert "unknown challenge" in reason
def test_verify_response_token_mismatch(self, manager):
manager.issue_challenge("target_pubkey_hex")
challenge_id = list(manager._pending.keys())[0]
bad_token = manager.PAIRING_CHALLENGE_PREFIX + f"{challenge_id}::wrong_token"
success, reason = manager.verify_response(bad_token)
assert success is False
assert "token mismatch" in reason
def test_verify_response_already_approved(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
manager.verify_response(token)
success, reason = manager.verify_response(token)
assert success is False
assert "already approved" in reason
async def test_approve_challenge(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
body = token[len(manager.PAIRING_CHALLENGE_PREFIX):]
challenge_id = body.split("::")[0]
success, result = await manager.approve_challenge(challenge_id)
assert success is True
assert result == challenge_id
async def test_approve_unknown_challenge(self, manager):
success, reason = await manager.approve_challenge("unknown_id")
assert success is False
def test_deny_challenge(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
body = token[len(manager.PAIRING_CHALLENGE_PREFIX):]
challenge_id = body.split("::")[0]
assert manager.deny_challenge(challenge_id) is True
assert manager.get_pending(challenge_id)["status"] == "denied"
def test_deny_unknown_challenge(self, manager):
assert manager.deny_challenge("unknown_id") is False
async def test_deny_challenge_async(self, manager):
token = manager.issue_challenge("target_pubkey_hex")
body = token[len(manager.PAIRING_CHALLENGE_PREFIX):]
challenge_id = body.split("::")[0]
assert await manager.deny_challenge_async(challenge_id) is True
assert manager.get_pending(challenge_id)["status"] == "denied"
def test_list_pending_only_shows_pending(self, manager):
manager.issue_challenge("pubkey_a")
challenge_id_b = manager.issue_challenge("pubkey_b")
body = challenge_id_b[len(manager.PAIRING_CHALLENGE_PREFIX):]
cid_b = body.split("::")[0]
manager.deny_challenge(cid_b)
pending = manager.list_pending()
assert len(pending) == 1
def test_list_all_shows_all(self, manager):
manager.issue_challenge("pubkey_a")
manager.issue_challenge("pubkey_b")
all_entries = manager.list_all()
assert len(all_entries) == 2
def test_get_pending_unknown(self, manager):
assert manager.get_pending("unknown") is None
def test_cleanup_expired_removes_non_pending(self, manager):
token = manager.issue_challenge("pubkey_a")
body = token[len(manager.PAIRING_CHALLENGE_PREFIX):]
challenge_id = body.split("::")[0]
manager.deny_challenge(challenge_id)
removed = manager.cleanup_expired(ttl_sec=0)
assert removed >= 1
assert manager.get_pending(challenge_id) is None
def test_cleanup_expired_removes_expired_pending(self, manager):
manager.issue_challenge("pubkey_a")
challenge_ids = list(manager._pending.keys())
manager._pending[challenge_ids[0]]["created_at"] = 0
removed = manager.cleanup_expired(ttl_sec=0)
assert removed >= 1
async def test_send_pairing_challenge(self, manager):
token = await manager.send_pairing_challenge("target_pubkey_hex")
assert token is not None
assert manager.PAIRING_CHALLENGE_PREFIX in token
async def test_send_pairing_challenge_no_relays(self, manager):
manager._relay_manager.broadcast = AsyncMock(return_value=0)
token = await manager.send_pairing_challenge("target_pubkey_hex")
assert token is None
def test_on_pairing_request_callback(self, manager):
callback_mock = AsyncMock()
manager.on_pairing_request(callback_mock)
assert len(manager._on_pairing_request) == 1
def test_on_pairing_approved_callback(self, manager):
callback_mock = AsyncMock()
manager.on_pairing_approved(callback_mock)
assert len(manager._on_pairing_approved) == 1
def test_on_pairing_denied_callback(self, manager):
callback_mock = AsyncMock()
manager.on_pairing_denied(callback_mock)
assert len(manager._on_pairing_denied) == 1