Analyze test coverage (75.3% → 85.4%) and add functional test suites for the major gaps identified: - test_agent_core.py: Full coverage for agent_core/interface.py (0→100%) and agent_core/ollama_adapter.py (0→100%) — data classes, factories, abstract enforcement, perceive/reason/act/recall workflow, effect logging - test_docker_runner.py: Full coverage for swarm/docker_runner.py (0→100%) — container spawn/stop/list lifecycle with mocked subprocess - test_timmy_tools.py: Tool usage tracking, persona toolkit mapping, catalog generation, graceful degradation without Agno - test_routes_tools.py: /tools page, API stats endpoint, and WebSocket /swarm/live connect/disconnect/send lifecycle (41→82%) - test_voice_tts_functional.py: VoiceTTS init, speak, volume clamping, voice listing, graceful degradation (41→94%) - test_watchdog_functional.py: _run_tests, watch loop state transitions, regression detection, KeyboardInterrupt (47→97%) - test_lnd_backend.py: LND init from params/env, grpc stub enforcement, method-level BackendNotAvailableError, settle returns False (25→61%) - test_swarm_routes_functional.py: Agent spawn/stop, task CRUD, auction, insights, UI partials, error paths (63→92%) https://claude.ai/code/session_01WU4h3cQQiouMwmgYmAgkMM
130 lines
4.2 KiB
Python
130 lines
4.2 KiB
Python
"""Functional tests for lightning.lnd_backend — LND gRPC backend.
|
|
|
|
gRPC is stubbed via sys.modules; tests verify initialization, error
|
|
handling, and the placeholder method behavior.
|
|
"""
|
|
|
|
import importlib
|
|
import os
|
|
import sys
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
|
|
from lightning.base import (
|
|
BackendNotAvailableError,
|
|
Invoice,
|
|
LightningError,
|
|
)
|
|
|
|
|
|
def _make_grpc_mock():
|
|
"""Create a mock grpc module with required attributes."""
|
|
mock_grpc = MagicMock()
|
|
mock_grpc.StatusCode.NOT_FOUND = "NOT_FOUND"
|
|
mock_grpc.RpcError = type("RpcError", (Exception,), {
|
|
"code": lambda self: "NOT_FOUND",
|
|
"details": lambda self: "mocked error",
|
|
})
|
|
return mock_grpc
|
|
|
|
|
|
@pytest.fixture
|
|
def lnd_module():
|
|
"""Reload lnd_backend with grpc stubbed so GRPC_AVAILABLE=True."""
|
|
grpc_mock = _make_grpc_mock()
|
|
old = sys.modules.get("grpc")
|
|
sys.modules["grpc"] = grpc_mock
|
|
try:
|
|
import lightning.lnd_backend as mod
|
|
importlib.reload(mod)
|
|
yield mod
|
|
finally:
|
|
if old is not None:
|
|
sys.modules["grpc"] = old
|
|
else:
|
|
sys.modules.pop("grpc", None)
|
|
# Reload to restore original state
|
|
import lightning.lnd_backend as mod2
|
|
importlib.reload(mod2)
|
|
|
|
|
|
class TestLndBackendInit:
|
|
def test_init_with_explicit_params(self, lnd_module):
|
|
backend = lnd_module.LndBackend(
|
|
host="localhost:10009",
|
|
tls_cert_path="/fake/tls.cert",
|
|
macaroon_path="/fake/admin.macaroon",
|
|
verify_ssl=True,
|
|
)
|
|
assert backend._host == "localhost:10009"
|
|
assert backend._tls_cert_path == "/fake/tls.cert"
|
|
assert backend._macaroon_path == "/fake/admin.macaroon"
|
|
assert backend._verify_ssl is True
|
|
|
|
def test_init_from_env_vars(self, lnd_module):
|
|
env = {
|
|
"LND_GRPC_HOST": "remote:9999",
|
|
"LND_TLS_CERT_PATH": "/env/tls.cert",
|
|
"LND_MACAROON_PATH": "/env/macaroon",
|
|
"LND_VERIFY_SSL": "false",
|
|
}
|
|
with patch.dict(os.environ, env):
|
|
backend = lnd_module.LndBackend()
|
|
assert backend._host == "remote:9999"
|
|
assert backend._verify_ssl is False
|
|
|
|
def test_init_raises_without_grpc(self):
|
|
from lightning.lnd_backend import LndBackend
|
|
with pytest.raises(LightningError, match="grpcio not installed"):
|
|
LndBackend()
|
|
|
|
def test_name_is_lnd(self, lnd_module):
|
|
assert lnd_module.LndBackend.name == "lnd"
|
|
|
|
def test_grpc_available_true_after_reload(self, lnd_module):
|
|
assert lnd_module.GRPC_AVAILABLE is True
|
|
|
|
|
|
class TestLndBackendMethods:
|
|
@pytest.fixture
|
|
def backend(self, lnd_module):
|
|
return lnd_module.LndBackend(
|
|
host="localhost:10009",
|
|
macaroon_path="/fake/path",
|
|
)
|
|
|
|
def test_check_stub_raises_not_available(self, backend):
|
|
"""_check_stub should raise BackendNotAvailableError when stub is None."""
|
|
with pytest.raises(BackendNotAvailableError, match="not fully implemented"):
|
|
backend._check_stub()
|
|
|
|
def test_create_invoice_raises_not_available(self, backend):
|
|
with pytest.raises(BackendNotAvailableError):
|
|
backend.create_invoice(1000, memo="test")
|
|
|
|
def test_check_payment_raises_not_available(self, backend):
|
|
with pytest.raises(BackendNotAvailableError):
|
|
backend.check_payment("abc123")
|
|
|
|
def test_get_invoice_raises_not_available(self, backend):
|
|
with pytest.raises(BackendNotAvailableError):
|
|
backend.get_invoice("abc123")
|
|
|
|
def test_settle_invoice_returns_false(self, backend):
|
|
"""LND auto-settles, so manual settle always returns False."""
|
|
result = backend.settle_invoice("hash", "preimage")
|
|
assert result is False
|
|
|
|
def test_list_invoices_raises_not_available(self, backend):
|
|
with pytest.raises(BackendNotAvailableError):
|
|
backend.list_invoices()
|
|
|
|
def test_get_balance_raises_not_available(self, backend):
|
|
with pytest.raises(BackendNotAvailableError):
|
|
backend.get_balance_sats()
|
|
|
|
def test_health_check_raises_not_available(self, backend):
|
|
with pytest.raises(BackendNotAvailableError):
|
|
backend.health_check()
|