mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-01 06:22:54 +02:00
Improve DB streaming perf for cracking and statistics
This commit is contained in:
@@ -190,6 +190,29 @@ class TestDebugEndpoint:
|
||||
assert payload["database"]["total_channel_messages"] == 1
|
||||
assert payload["database"]["total_outgoing"] == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_support_snapshot_uses_lightweight_message_totals(self, test_db, client):
|
||||
"""Debug snapshot should not call the full statistics aggregation."""
|
||||
with (
|
||||
patch(
|
||||
"app.routers.debug.StatisticsRepository.get_all",
|
||||
new=AsyncMock(side_effect=AssertionError("get_all should not be called")),
|
||||
),
|
||||
patch(
|
||||
"app.routers.debug.StatisticsRepository.get_database_message_totals",
|
||||
new=AsyncMock(
|
||||
return_value={
|
||||
"total_dms": 0,
|
||||
"total_channel_messages": 0,
|
||||
"total_outgoing": 0,
|
||||
}
|
||||
),
|
||||
),
|
||||
):
|
||||
response = await client.get("/api/debug")
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
class TestRadioDisconnectedHandler:
|
||||
"""Test that RadioDisconnectedError maps to 503."""
|
||||
|
||||
@@ -5,7 +5,7 @@ undecrypted count endpoint, and the maintenance endpoint.
|
||||
"""
|
||||
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -305,6 +305,43 @@ class TestDecryptHistoricalPackets:
|
||||
assert "key_type" in data["detail"].lower()
|
||||
|
||||
|
||||
class TestUndecryptedTextPacketStreaming:
|
||||
@pytest.mark.asyncio
|
||||
async def test_count_undecrypted_text_messages_uses_batched_streaming(self, test_db):
|
||||
"""Counting undecrypted DM packets should stream batches and filter by payload type."""
|
||||
|
||||
class FakeCursor:
|
||||
def __init__(self):
|
||||
self._batches = [
|
||||
[
|
||||
{"id": 1, "data": b"\x09\x00dm", "timestamp": 1000},
|
||||
{"id": 2, "data": b"\x15\x00chan", "timestamp": 1001},
|
||||
],
|
||||
[{"id": 3, "data": b"\x09\x00dm2", "timestamp": 1002}],
|
||||
[],
|
||||
]
|
||||
self.fetchall_called = False
|
||||
|
||||
async def fetchmany(self, size):
|
||||
assert size > 0
|
||||
return self._batches.pop(0)
|
||||
|
||||
async def close(self):
|
||||
return None
|
||||
|
||||
async def fetchall(self):
|
||||
self.fetchall_called = True
|
||||
raise AssertionError("fetchall() should not be used")
|
||||
|
||||
fake_cursor = FakeCursor()
|
||||
|
||||
with patch.object(test_db.conn, "execute", new=AsyncMock(return_value=fake_cursor)):
|
||||
count = await RawPacketRepository.count_undecrypted_text_messages(batch_size=2)
|
||||
|
||||
assert fake_cursor.fetchall_called is False
|
||||
assert count == 2
|
||||
|
||||
|
||||
class TestRunHistoricalChannelDecryption:
|
||||
"""Test the _run_historical_channel_decryption background task."""
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for the statistics repository and endpoint."""
|
||||
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -349,6 +350,52 @@ class TestPathHashWidthStats:
|
||||
assert breakdown["double_byte_pct"] == pytest.approx(100 / 3, rel=1e-3)
|
||||
assert breakdown["triple_byte_pct"] == pytest.approx(100 / 3, rel=1e-3)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_path_hash_width_scan_uses_batched_fetchmany(self, test_db):
|
||||
"""Hash-width stats should stream batches instead of calling fetchall()."""
|
||||
|
||||
class FakeCursor:
|
||||
def __init__(self):
|
||||
self._batches = [
|
||||
[{"data": b"a"}, {"data": b"b"}],
|
||||
[{"data": b"c"}],
|
||||
[],
|
||||
]
|
||||
self.fetchall_called = False
|
||||
|
||||
async def fetchmany(self, size):
|
||||
assert size > 0
|
||||
return self._batches.pop(0)
|
||||
|
||||
async def fetchall(self):
|
||||
self.fetchall_called = True
|
||||
raise AssertionError("fetchall() should not be used")
|
||||
|
||||
fake_cursor = FakeCursor()
|
||||
|
||||
def fake_parse(raw_packet: bytes):
|
||||
hash_sizes = {
|
||||
b"a": 1,
|
||||
b"b": 2,
|
||||
b"c": 3,
|
||||
}
|
||||
hash_size = hash_sizes.get(raw_packet)
|
||||
if hash_size is None:
|
||||
return None
|
||||
return SimpleNamespace(hash_size=hash_size)
|
||||
|
||||
with (
|
||||
patch.object(test_db.conn, "execute", new=AsyncMock(return_value=fake_cursor)),
|
||||
patch("app.repository.settings.parse_packet_envelope", side_effect=fake_parse),
|
||||
):
|
||||
breakdown = await StatisticsRepository._path_hash_width_24h()
|
||||
|
||||
assert fake_cursor.fetchall_called is False
|
||||
assert breakdown["total_packets"] == 3
|
||||
assert breakdown["single_byte"] == 1
|
||||
assert breakdown["double_byte"] == 1
|
||||
assert breakdown["triple_byte"] == 1
|
||||
|
||||
|
||||
class TestStatisticsEndpoint:
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user