Reorganize for great victory and move to blob for payload hasg

This commit is contained in:
Jack Kingsman
2026-02-27 21:03:34 -08:00
parent fc27361e37
commit ce99d63701
27 changed files with 2028 additions and 2303 deletions
+52
View File
@@ -5,8 +5,11 @@ import shutil
import tempfile
from pathlib import Path
import httpx
import pytest
from app.database import Database
# Use an isolated file-backed SQLite DB for tests that import app.main/TestClient.
# This must be set before app.config/app.database are imported, otherwise the global
# Database instance will bind to the default runtime DB (data/meshcore.db).
@@ -20,3 +23,52 @@ def cleanup_test_db_dir():
"""Clean up temporary pytest DB directory after the test session."""
yield
shutil.rmtree(_TEST_DB_DIR, ignore_errors=True)
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
from app.repository import channels, contacts, messages, raw_packets, settings
db = Database(":memory:")
await db.connect()
submodules = [contacts, channels, messages, raw_packets, settings]
originals = [(mod, mod.db) for mod in submodules]
for mod in submodules:
mod.db = db
# Also patch the db reference used by the packets router for VACUUM
import app.routers.packets as packets_module
original_packets_db = packets_module.db
packets_module.db = db
try:
yield db
finally:
for mod, original in originals:
mod.db = original
packets_module.db = original_packets_db
await db.disconnect()
@pytest.fixture
def client():
"""Create an httpx AsyncClient for testing the app."""
from app.main import app
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")
@pytest.fixture
def captured_broadcasts():
"""Capture WebSocket broadcasts for verification."""
broadcasts = []
def mock_broadcast(event_type: str, data: dict):
broadcasts.append({"type": event_type, "data": data})
return broadcasts, mock_broadcast
-29
View File
@@ -8,10 +8,8 @@ import hashlib
import time
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from app.database import Database
from app.radio import radio_manager
from app.repository import (
ChannelRepository,
@@ -31,33 +29,6 @@ def _reset_radio_state():
radio_manager._operation_lock = prev_lock
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.fixture
def client():
"""Create an httpx AsyncClient for testing the app."""
from app.main import app
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")
async def _insert_contact(public_key, name="Alice", **overrides):
"""Insert a contact into the test database."""
data = {
-29
View File
@@ -7,33 +7,13 @@ from the radio and upserts them into the database.
from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from meshcore import EventType
from app.database import Database
from app.radio import radio_manager
from app.repository import ChannelRepository
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.fixture(autouse=True)
def _reset_radio_state():
"""Save/restore radio_manager state so tests don't leak."""
@@ -44,15 +24,6 @@ def _reset_radio_state():
radio_manager._operation_lock = prev_lock
@pytest.fixture
def client():
"""Create an httpx AsyncClient for testing the app."""
from app.main import app
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")
def _make_channel_info(name: str, secret: bytes):
"""Create a mock channel info response."""
result = MagicMock()
-29
View File
@@ -9,11 +9,9 @@ Uses httpx.AsyncClient with real in-memory SQLite database.
from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from meshcore import EventType
from app.database import Database
from app.radio import radio_manager
from app.repository import ContactAdvertPathRepository, ContactRepository, MessageRepository
@@ -43,24 +41,6 @@ def _reset_radio_state():
radio_manager._operation_lock = prev_lock
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
async def _insert_contact(public_key=KEY_A, name="Alice", on_radio=False, **overrides):
"""Insert a contact into the test database."""
data = {
@@ -82,15 +62,6 @@ async def _insert_contact(public_key=KEY_A, name="Alice", on_radio=False, **over
await ContactRepository.upsert(data)
@pytest.fixture
def client():
"""Create an httpx AsyncClient for testing the app."""
from app.main import app
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")
class TestListContacts:
"""Test GET /api/contacts."""
-31
View File
@@ -11,7 +11,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.database import Database
from app.decoder import DecryptedDirectMessage
from app.repository import (
ContactRepository,
@@ -19,36 +18,6 @@ from app.repository import (
RawPacketRepository,
)
@pytest.fixture
async def test_db():
"""Create an in-memory test database."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.fixture
def captured_broadcasts():
"""Capture WebSocket broadcasts for verification."""
broadcasts = []
def mock_broadcast(event_type: str, data: dict):
broadcasts.append({"type": event_type, "data": data})
return broadcasts, mock_broadcast
# Shared test constants
CHANNEL_KEY = "ABC123DEF456ABC123DEF456ABC12345"
CONTACT_PUB = "a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7"
-19
View File
@@ -9,7 +9,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.database import Database
from app.event_handlers import (
_active_subscriptions,
_pending_acks,
@@ -23,24 +22,6 @@ from app.repository import (
)
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.fixture(autouse=True)
def clear_test_state():
"""Clear pending ACKs and subscriptions before each test."""
-19
View File
@@ -2,28 +2,9 @@
import pytest
from app.database import Database
from app.repository import AmbiguousPublicKeyPrefixError, ContactRepository, MessageRepository
@pytest.fixture
async def test_db():
"""Create an in-memory test database."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.mark.asyncio
async def test_upsert_stores_lowercase_key(test_db):
await ContactRepository.upsert(
-20
View File
@@ -2,28 +2,8 @@
import pytest
from app.database import Database
from app.repository import MessageRepository
@pytest.fixture
async def test_db():
"""Create an in-memory test database."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
CHAN_KEY = "ABC123DEF456ABC123DEF456ABC12345"
DM_KEY = "aa" * 32
-19
View File
@@ -2,28 +2,9 @@
import pytest
from app.database import Database
from app.repository import ContactRepository, MessageRepository
@pytest.fixture
async def test_db():
"""Create an in-memory test database."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.mark.asyncio
async def test_claim_prefix_promotes_dm_to_full_key(test_db):
full_key = "a1b2c3d3ba9f5fa8705b9845fe11cc6f01d1d49caaf4d122ac7121663c5beec7"
-36
View File
@@ -13,7 +13,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.database import Database
from app.decoder import DecryptedDirectMessage, PacketInfo, ParsedAdvertisement, PayloadType
from app.repository import (
ChannelRepository,
@@ -28,41 +27,6 @@ with open(FIXTURES_PATH) as f:
FIXTURES = json.load(f)
@pytest.fixture
async def test_db():
"""Create an in-memory test database.
We need to patch the db module-level variable before any repository
methods are called, so they use our test database.
"""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
# Store original and patch the module attribute directly
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.fixture
def captured_broadcasts():
"""Capture WebSocket broadcasts for verification."""
broadcasts = []
def mock_broadcast(event_type: str, data: dict):
"""Synchronous mock that captures broadcasts."""
broadcasts.append({"type": event_type, "data": data})
return broadcasts, mock_broadcast
class TestChannelMessagePipeline:
"""Test channel message flow: packet → decrypt → store → broadcast."""
-36
View File
@@ -7,47 +7,11 @@ undecrypted count endpoint, and the maintenance endpoint.
import time
from unittest.mock import patch
import httpx
import pytest
from app.database import Database
from app.repository import ChannelRepository, MessageRepository, RawPacketRepository
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
# Also patch the db reference used by the packets router for VACUUM
import app.routers.packets as packets_module
original_packets_db = packets_module.db
packets_module.db = db
try:
yield db
finally:
repo_module.db = original_db
packets_module.db = original_packets_db
await db.disconnect()
@pytest.fixture
def client():
"""Create an httpx AsyncClient for testing the app."""
from app.main import app
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")
async def _insert_raw_packets(count: int, decrypted: bool = False, age_days: int = 0) -> list[int]:
"""Insert raw packets and return their IDs."""
ids = []
-19
View File
@@ -10,7 +10,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from meshcore import EventType
from app.database import Database
from app.models import Favorite
from app.radio import RadioManager, radio_manager
from app.radio_sync import (
@@ -30,24 +29,6 @@ from app.repository import (
)
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
@pytest.fixture(autouse=True)
def reset_sync_state():
"""Reset polling pause state, sync timestamp, and radio_manager before/after each test."""
+51 -70
View File
@@ -6,11 +6,11 @@ import pytest
from fastapi import HTTPException
from meshcore import EventType
from app.database import Database
from app.models import CommandRequest, Contact, RepeaterLoginRequest
from app.radio import radio_manager
from app.repository import ContactRepository
from app.routers.contacts import (
from app.routers.contacts import request_trace
from app.routers.repeaters import (
_batch_cli_fetch,
_fetch_repeater_response,
repeater_acl,
@@ -21,7 +21,6 @@ from app.routers.contacts import (
repeater_owner_info,
repeater_radio_settings,
repeater_status,
request_trace,
send_repeater_command,
)
@@ -29,7 +28,7 @@ KEY_A = "aa" * 32
# Patch target for the wall-clock wrapper used by _fetch_repeater_response.
# We patch _monotonic (not time.monotonic) to avoid breaking the asyncio event loop.
_MONOTONIC = "app.routers.contacts._monotonic"
_MONOTONIC = "app.routers.repeaters._monotonic"
@pytest.fixture(autouse=True)
@@ -42,24 +41,6 @@ def _reset_radio_state():
radio_manager._operation_lock = prev_lock
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
def _radio_result(event_type=EventType.OK, payload=None):
result = MagicMock()
result.type = event_type
@@ -210,7 +191,7 @@ class TestFetchRepeaterResponse:
with (
patch(_MONOTONIC, side_effect=_advancing_clock()),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
result = await _fetch_repeater_response(mc, "aaaaaaaaaaaa", timeout=5.0)
@@ -229,7 +210,7 @@ class TestFetchRepeaterResponse:
with (
patch(_MONOTONIC, side_effect=times),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
result = await _fetch_repeater_response(mc, "aaaaaaaaaaaa", timeout=2.0)
@@ -247,7 +228,7 @@ class TestFetchRepeaterResponse:
with (
patch(_MONOTONIC, side_effect=_advancing_clock()),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
result = await _fetch_repeater_response(mc, "aaaaaaaaaaaa", timeout=5.0)
@@ -290,7 +271,7 @@ class TestRepeaterCommandRoute:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -308,10 +289,10 @@ class TestRepeaterCommandRoute:
# Expire the deadline after a couple of ticks
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=[0.0, 5.0, 25.0]),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
response = await send_repeater_command(KEY_A, CommandRequest(command="ver"))
@@ -337,7 +318,7 @@ class TestRepeaterCommandRoute:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -365,7 +346,7 @@ class TestRepeaterCommandRoute:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -391,7 +372,7 @@ class TestRepeaterCommandRoute:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -419,7 +400,7 @@ class TestRepeaterCommandRoute:
mc.commands.get_msg = AsyncMock(side_effect=[unrelated, expected])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -445,7 +426,7 @@ class TestRepeaterCommandRoute:
mc.commands.get_msg = AsyncMock(side_effect=[channel_msg, expected])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -468,10 +449,10 @@ class TestRepeaterCommandRoute:
mc.commands.get_msg = AsyncMock(side_effect=[no_msgs, expected])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
response = await send_repeater_command(KEY_A, CommandRequest(command="ver"))
@@ -548,10 +529,10 @@ class TestRepeaterLogin:
await _insert_contact(KEY_A, name="Repeater", contact_type=2)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(
"app.routers.contacts.prepare_repeater_connection",
"app.routers.repeaters.prepare_repeater_connection",
new_callable=AsyncMock,
) as mock_prepare,
):
@@ -564,7 +545,7 @@ class TestRepeaterLogin:
async def test_404_missing_contact(self, test_db):
mc = _mock_mc()
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -576,7 +557,7 @@ class TestRepeaterLogin:
mc = _mock_mc()
await _insert_contact(KEY_A, name="Client", contact_type=1)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -593,9 +574,9 @@ class TestRepeaterLogin:
raise HTTPException(status_code=401, detail="Login failed")
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch("app.routers.contacts.prepare_repeater_connection", side_effect=_prepare_fail),
patch("app.routers.repeaters.prepare_repeater_connection", side_effect=_prepare_fail),
):
with pytest.raises(HTTPException) as exc:
await repeater_login(KEY_A, RepeaterLoginRequest(password="bad"))
@@ -630,7 +611,7 @@ class TestRepeaterStatus:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_status(KEY_A)
@@ -653,7 +634,7 @@ class TestRepeaterStatus:
mc.commands.req_status_sync = AsyncMock(return_value=None)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -665,7 +646,7 @@ class TestRepeaterStatus:
mc = _mock_mc()
await _insert_contact(KEY_A, name="Client", contact_type=1)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -691,7 +672,7 @@ class TestRepeaterLppTelemetry:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_lpp_telemetry(KEY_A)
@@ -713,7 +694,7 @@ class TestRepeaterLppTelemetry:
mc.commands.req_telemetry_sync = AsyncMock(return_value=[])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_lpp_telemetry(KEY_A)
@@ -727,7 +708,7 @@ class TestRepeaterLppTelemetry:
mc.commands.req_telemetry_sync = AsyncMock(return_value=None)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -739,7 +720,7 @@ class TestRepeaterLppTelemetry:
mc = _mock_mc()
await _insert_contact(KEY_A, name="Client", contact_type=1)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -765,7 +746,7 @@ class TestRepeaterNeighbors:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_neighbors(KEY_A)
@@ -783,7 +764,7 @@ class TestRepeaterNeighbors:
mc.commands.fetch_all_neighbours = AsyncMock(return_value={"neighbours": []})
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_neighbors(KEY_A)
@@ -797,7 +778,7 @@ class TestRepeaterNeighbors:
mc.commands.fetch_all_neighbours = AsyncMock(return_value=None)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_neighbors(KEY_A)
@@ -821,7 +802,7 @@ class TestRepeaterAcl:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_acl(KEY_A)
@@ -839,7 +820,7 @@ class TestRepeaterAcl:
mc.commands.req_acl_sync = AsyncMock(return_value=[])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_acl(KEY_A)
@@ -853,7 +834,7 @@ class TestRepeaterAcl:
mc.commands.req_acl_sync = AsyncMock(return_value=None)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
response = await repeater_acl(KEY_A)
@@ -890,7 +871,7 @@ class TestRepeaterRadioSettings:
mc.commands.get_msg = AsyncMock(side_effect=get_msg_results)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -927,10 +908,10 @@ class TestRepeaterRadioSettings:
clock_ticks.extend([base, base + 5.0, base + 11.0])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=clock_ticks),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
response = await repeater_radio_settings(KEY_A)
@@ -943,7 +924,7 @@ class TestRepeaterRadioSettings:
mc = _mock_mc()
await _insert_contact(KEY_A, name="Client", contact_type=1)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -970,7 +951,7 @@ class TestRepeaterAdvertIntervals:
mc.commands.get_msg = AsyncMock(side_effect=responses)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -991,10 +972,10 @@ class TestRepeaterAdvertIntervals:
clock_ticks.extend([base, base + 5.0, base + 11.0])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=clock_ticks),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
response = await repeater_advert_intervals(KEY_A)
@@ -1025,7 +1006,7 @@ class TestRepeaterOwnerInfo:
mc.commands.get_msg = AsyncMock(side_effect=responses)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
):
@@ -1046,10 +1027,10 @@ class TestRepeaterOwnerInfo:
clock_ticks.extend([base, base + 5.0, base + 11.0])
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=clock_ticks),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
response = await repeater_owner_info(KEY_A)
@@ -1107,7 +1088,7 @@ class TestBatchCliFetch:
with (
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=_advancing_clock()),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
results = await _batch_cli_fetch(
contact, "test_op", [("bad_cmd", "field_a"), ("good_cmd", "field_b")]
@@ -1128,7 +1109,7 @@ class TestBatchCliFetch:
with (
patch.object(radio_manager, "_meshcore", mc),
patch(_MONOTONIC, side_effect=[0.0, 5.0, 11.0]),
patch("app.routers.contacts.asyncio.sleep", new_callable=AsyncMock),
patch("app.routers.repeaters.asyncio.sleep", new_callable=AsyncMock),
):
results = await _batch_cli_fetch(contact, "test_op", [("clock", "clock_output")])
@@ -1147,7 +1128,7 @@ class TestRepeaterAddContactError:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -1165,7 +1146,7 @@ class TestRepeaterAddContactError:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -1183,7 +1164,7 @@ class TestRepeaterAddContactError:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
@@ -1201,7 +1182,7 @@ class TestRepeaterAddContactError:
)
with (
patch("app.routers.contacts.require_connected", return_value=mc),
patch("app.routers.repeaters.require_connected", return_value=mc),
patch.object(radio_manager, "_meshcore", mc),
):
with pytest.raises(HTTPException) as exc:
+2 -21
View File
@@ -4,7 +4,6 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.database import Database
from app.repository import (
ContactAdvertPathRepository,
ContactNameHistoryRepository,
@@ -13,24 +12,6 @@ from app.repository import (
)
@pytest.fixture
async def test_db():
"""Create an in-memory test database with the module-level db swapped in."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
async def _create_message(test_db, **overrides) -> int:
"""Helper to insert a message and return its id."""
defaults = {
@@ -90,7 +71,7 @@ class TestMessageRepositoryAddPath:
"""Adding a path without received_at uses current timestamp."""
msg_id = await _create_message(test_db)
with patch("app.repository.time") as mock_time:
with patch("app.repository.messages.time") as mock_time:
mock_time.time.return_value = 1700000500.5
result = await MessageRepository.add_path(message_id=msg_id, path="1A2B")
@@ -518,7 +499,7 @@ class TestAppSettingsRepository:
mock_db = MagicMock()
mock_db.conn = mock_conn
with patch("app.repository.db", mock_db):
with patch("app.repository.settings.db", mock_db):
from app.repository import AppSettingsRepository
settings = await AppSettingsRepository.get()
-19
View File
@@ -8,7 +8,6 @@ import pytest
from fastapi import HTTPException
from meshcore import EventType
from app.database import Database
from app.models import (
SendChannelMessageRequest,
SendDirectMessageRequest,
@@ -36,24 +35,6 @@ def _reset_radio_state():
radio_manager._operation_lock = prev_lock
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
def _make_radio_result(payload=None):
"""Create a mock radio command result."""
result = MagicMock()
-19
View File
@@ -3,7 +3,6 @@
import pytest
from fastapi import HTTPException
from app.database import Database
from app.models import AppSettings, BotConfig
from app.repository import AppSettingsRepository
from app.routers.settings import (
@@ -16,24 +15,6 @@ from app.routers.settings import (
)
@pytest.fixture
async def test_db():
"""Create an in-memory test database with schema + migrations."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
class TestUpdateSettings:
@pytest.mark.asyncio
async def test_forwards_only_provided_fields(self, test_db):
-19
View File
@@ -4,28 +4,9 @@ import time
import pytest
from app.database import Database
from app.repository import StatisticsRepository
@pytest.fixture
async def test_db():
"""Create an in-memory test database with the module-level db swapped in."""
import app.repository as repo_module
db = Database(":memory:")
await db.connect()
original_db = repo_module.db
repo_module.db = db
try:
yield db
finally:
repo_module.db = original_db
await db.disconnect()
class TestStatisticsEmpty:
@pytest.mark.asyncio
async def test_empty_database(self, test_db):