Linting and code cleanup for an imitation of order

This commit is contained in:
Jack Kingsman
2026-01-14 20:08:41 -08:00
parent c6d38ce400
commit 076d466fbd
73 changed files with 2799 additions and 1192 deletions
+1
View File
@@ -13,4 +13,5 @@ def sample_channel_key():
def sample_hashtag_key():
"""A channel key derived from hashtag name '#test'."""
import hashlib
return hashlib.sha256(b"#test").digest()[:16]
+80 -43
View File
@@ -4,9 +4,10 @@ These tests verify the REST API behavior for critical operations.
Uses FastAPI's TestClient for synchronous testing.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
class TestHealthEndpoint:
"""Test the health check endpoint."""
@@ -20,6 +21,7 @@ class TestHealthEndpoint:
mock_rm.port = "/dev/ttyUSB0"
from app.main import app
client = TestClient(app)
response = client.get("/api/health")
@@ -38,6 +40,7 @@ class TestHealthEndpoint:
mock_rm.port = None
from app.main import app
client = TestClient(app)
response = client.get("/api/health")
@@ -60,11 +63,11 @@ class TestMessagesEndpoint:
mock_rm.meshcore = None
from app.main import app
client = TestClient(app)
response = client.post(
"/api/messages/direct",
json={"destination": "abc123", "text": "Hello"}
"/api/messages/direct", json={"destination": "abc123", "text": "Hello"}
)
assert response.status_code == 503
@@ -79,11 +82,12 @@ class TestMessagesEndpoint:
mock_rm.meshcore = None
from app.main import app
client = TestClient(app)
response = client.post(
"/api/messages/channel",
json={"channel_key": "0123456789ABCDEF0123456789ABCDEF", "text": "Hello"}
json={"channel_key": "0123456789ABCDEF0123456789ABCDEF", "text": "Hello"},
)
assert response.status_code == 503
@@ -95,18 +99,22 @@ class TestMessagesEndpoint:
mock_mc = MagicMock()
mock_mc.get_contact_by_key_prefix.return_value = None
with patch("app.dependencies.radio_manager") as mock_rm, \
patch("app.repository.ContactRepository.get_by_key_or_prefix", new_callable=AsyncMock) as mock_get:
with (
patch("app.dependencies.radio_manager") as mock_rm,
patch(
"app.repository.ContactRepository.get_by_key_or_prefix", new_callable=AsyncMock
) as mock_get,
):
mock_rm.is_connected = True
mock_rm.meshcore = mock_mc
mock_get.return_value = None
from app.main import app
client = TestClient(app)
response = client.post(
"/api/messages/direct",
json={"destination": "nonexistent", "text": "Hello"}
"/api/messages/direct", json={"destination": "nonexistent", "text": "Hello"}
)
assert response.status_code == 404
@@ -120,7 +128,8 @@ class TestChannelsEndpoint:
async def test_create_hashtag_channel_derives_key(self):
"""Creating hashtag channel derives key from name and stores in DB."""
import hashlib
from app.routers.channels import create_channel, CreateChannelRequest
from app.routers.channels import CreateChannelRequest, create_channel
with patch("app.routers.channels.ChannelRepository") as mock_repo:
mock_repo.upsert = AsyncMock()
@@ -145,7 +154,7 @@ class TestChannelsEndpoint:
@pytest.mark.asyncio
async def test_create_channel_with_explicit_key(self):
"""Creating channel with explicit key uses provided key."""
from app.routers.channels import create_channel, CreateChannelRequest
from app.routers.channels import CreateChannelRequest, create_channel
with patch("app.routers.channels.ChannelRepository") as mock_repo:
mock_repo.upsert = AsyncMock()
@@ -177,6 +186,7 @@ class TestPacketsEndpoint:
mock_repo.get_undecrypted_count = AsyncMock(return_value=42)
from app.main import app
client = TestClient(app)
response = client.get("/api/packets/undecrypted/count")
@@ -191,10 +201,12 @@ class TestReadStateEndpoints:
@pytest.mark.asyncio
async def test_mark_contact_read_updates_timestamp(self):
"""Marking contact as read updates last_read_at in database."""
import aiosqlite
import time
from app.repository import ContactRepository
import aiosqlite
from app.database import db
from app.repository import ContactRepository
# Use in-memory database for testing
conn = await aiosqlite.connect(":memory:")
@@ -222,7 +234,7 @@ class TestReadStateEndpoints:
# Insert a test contact
await conn.execute(
"INSERT INTO contacts (public_key, name) VALUES (?, ?)",
("abc123def456789012345678901234567890123456789012345678901234", "TestContact")
("abc123def456789012345678901234567890123456789012345678901234", "TestContact"),
)
await conn.commit()
@@ -253,10 +265,12 @@ class TestReadStateEndpoints:
@pytest.mark.asyncio
async def test_mark_channel_read_updates_timestamp(self):
"""Marking channel as read updates last_read_at in database."""
import aiosqlite
import time
from app.repository import ChannelRepository
import aiosqlite
from app.database import db
from app.repository import ChannelRepository
# Use in-memory database for testing
conn = await aiosqlite.connect(":memory:")
@@ -276,7 +290,7 @@ class TestReadStateEndpoints:
# Insert a test channel
await conn.execute(
"INSERT INTO channels (key, name) VALUES (?, ?)",
("0123456789ABCDEF0123456789ABCDEF", "#testchannel")
("0123456789ABCDEF0123456789ABCDEF", "#testchannel"),
)
await conn.commit()
@@ -294,9 +308,7 @@ class TestReadStateEndpoints:
assert updated is True
# Verify the timestamp was set
channel = await ChannelRepository.get_by_key(
"0123456789ABCDEF0123456789ABCDEF"
)
channel = await ChannelRepository.get_by_key("0123456789ABCDEF0123456789ABCDEF")
assert channel is not None
assert channel.last_read_at is not None
assert channel.last_read_at >= before_time
@@ -308,8 +320,9 @@ class TestReadStateEndpoints:
async def test_mark_nonexistent_contact_returns_false(self):
"""Marking nonexistent contact returns False."""
import aiosqlite
from app.repository import ContactRepository
from app.database import db
from app.repository import ContactRepository
# Use in-memory database for testing
conn = await aiosqlite.connect(":memory:")
@@ -348,10 +361,13 @@ class TestReadStateEndpoints:
"""Mark-read endpoint returns 404 for nonexistent contact."""
from fastapi.testclient import TestClient
with patch("app.repository.ContactRepository.get_by_key_or_prefix", new_callable=AsyncMock) as mock_get:
with patch(
"app.repository.ContactRepository.get_by_key_or_prefix", new_callable=AsyncMock
) as mock_get:
mock_get.return_value = None
from app.main import app
client = TestClient(app)
response = client.post("/api/contacts/nonexistent/mark-read")
@@ -363,10 +379,13 @@ class TestReadStateEndpoints:
"""Mark-read endpoint returns 404 for nonexistent channel."""
from fastapi.testclient import TestClient
with patch("app.repository.ChannelRepository.get_by_key", new_callable=AsyncMock) as mock_get:
with patch(
"app.repository.ChannelRepository.get_by_key", new_callable=AsyncMock
) as mock_get:
mock_get.return_value = None
from app.main import app
client = TestClient(app)
response = client.post("/api/channels/NONEXISTENT/mark-read")
@@ -377,8 +396,10 @@ class TestReadStateEndpoints:
@pytest.mark.asyncio
async def test_mark_all_read_updates_all_conversations(self):
"""Bulk mark-all-read updates all contacts and channels."""
import aiosqlite
import time
import aiosqlite
from app.database import db
conn = await aiosqlite.connect(":memory:")
@@ -401,8 +422,12 @@ class TestReadStateEndpoints:
""")
# Insert test data with NULL last_read_at
await conn.execute("INSERT INTO contacts (public_key, name) VALUES (?, ?)", ("contact1", "Alice"))
await conn.execute("INSERT INTO contacts (public_key, name) VALUES (?, ?)", ("contact2", "Bob"))
await conn.execute(
"INSERT INTO contacts (public_key, name) VALUES (?, ?)", ("contact1", "Alice")
)
await conn.execute(
"INSERT INTO contacts (public_key, name) VALUES (?, ?)", ("contact2", "Bob")
)
await conn.execute("INSERT INTO channels (key, name) VALUES (?, ?)", ("CHAN1", "#test1"))
await conn.execute("INSERT INTO channels (key, name) VALUES (?, ?)", ("CHAN2", "#test2"))
await conn.commit()
@@ -415,6 +440,7 @@ class TestReadStateEndpoints:
# Call the endpoint
from app.routers.read_state import mark_all_read
result = await mark_all_read()
assert result["status"] == "ok"
@@ -443,8 +469,9 @@ class TestRawPacketRepository:
async def test_create_returns_id_for_new_packet(self):
"""First insert of packet data returns a valid ID."""
import aiosqlite
from app.repository import RawPacketRepository
from app.database import db
from app.repository import RawPacketRepository
# Use in-memory database for testing
conn = await aiosqlite.connect(":memory:")
@@ -482,8 +509,9 @@ class TestRawPacketRepository:
async def test_different_packets_both_stored(self):
"""Different packet data both get stored with unique IDs."""
import aiosqlite
from app.repository import RawPacketRepository
from app.database import db
from app.repository import RawPacketRepository
# Use in-memory database for testing
conn = await aiosqlite.connect(":memory:")
@@ -524,10 +552,12 @@ class TestRawPacketRepository:
@pytest.mark.asyncio
async def test_prune_old_undecrypted_deletes_old_packets(self):
"""Prune deletes undecrypted packets older than specified days."""
import aiosqlite
import time
from app.repository import RawPacketRepository
import aiosqlite
from app.database import db
from app.repository import RawPacketRepository
conn = await aiosqlite.connect(":memory:")
conn.row_factory = aiosqlite.Row
@@ -551,17 +581,17 @@ class TestRawPacketRepository:
# Insert old undecrypted packet
await conn.execute(
"INSERT INTO raw_packets (timestamp, data, decrypted) VALUES (?, ?, 0)",
(old_timestamp, b"\x01\x02\x03")
(old_timestamp, b"\x01\x02\x03"),
)
# Insert recent undecrypted packet
await conn.execute(
"INSERT INTO raw_packets (timestamp, data, decrypted) VALUES (?, ?, 0)",
(recent_timestamp, b"\x04\x05\x06")
(recent_timestamp, b"\x04\x05\x06"),
)
# Insert old but decrypted packet (should NOT be deleted)
await conn.execute(
"INSERT INTO raw_packets (timestamp, data, decrypted) VALUES (?, ?, 1)",
(old_timestamp, b"\x07\x08\x09")
(old_timestamp, b"\x07\x08\x09"),
)
await conn.commit()
@@ -585,10 +615,12 @@ class TestRawPacketRepository:
@pytest.mark.asyncio
async def test_prune_old_undecrypted_returns_zero_when_nothing_to_delete(self):
"""Prune returns 0 when no packets match criteria."""
import aiosqlite
import time
from app.repository import RawPacketRepository
import aiosqlite
from app.database import db
from app.repository import RawPacketRepository
conn = await aiosqlite.connect(":memory:")
conn.row_factory = aiosqlite.Row
@@ -611,7 +643,7 @@ class TestRawPacketRepository:
# Insert only recent packet
await conn.execute(
"INSERT INTO raw_packets (timestamp, data, decrypted) VALUES (?, ?, 0)",
(recent_timestamp, b"\x01\x02\x03")
(recent_timestamp, b"\x01\x02\x03"),
)
await conn.commit()
@@ -633,11 +665,12 @@ class TestMaintenanceEndpoint:
@pytest.mark.asyncio
async def test_maintenance_prunes_and_vacuums(self):
"""Maintenance endpoint prunes old packets and runs vacuum."""
import aiosqlite
import time
from app.repository import RawPacketRepository
import aiosqlite
from app.database import db
from app.routers.packets import run_maintenance, MaintenanceRequest
from app.routers.packets import MaintenanceRequest, run_maintenance
conn = await aiosqlite.connect(":memory:")
conn.row_factory = aiosqlite.Row
@@ -660,11 +693,11 @@ class TestMaintenanceEndpoint:
# Insert old undecrypted packets
await conn.execute(
"INSERT INTO raw_packets (timestamp, data, decrypted) VALUES (?, ?, 0)",
(old_timestamp, b"\x01\x02\x03")
(old_timestamp, b"\x01\x02\x03"),
)
await conn.execute(
"INSERT INTO raw_packets (timestamp, data, decrypted) VALUES (?, ?, 0)",
(old_timestamp, b"\x04\x05\x06")
(old_timestamp, b"\x04\x05\x06"),
)
await conn.commit()
@@ -687,16 +720,20 @@ class TestHealthEndpointDatabaseSize:
def test_health_includes_database_size(self):
"""Health endpoint includes database_size_mb field."""
from fastapi.testclient import TestClient
from unittest.mock import patch
with patch("app.routers.health.radio_manager") as mock_rm, \
patch("app.routers.health.os.path.getsize") as mock_getsize:
from fastapi.testclient import TestClient
with (
patch("app.routers.health.radio_manager") as mock_rm,
patch("app.routers.health.os.path.getsize") as mock_getsize,
):
mock_rm.is_connected = True
mock_rm.port = "/dev/ttyUSB0"
mock_getsize.return_value = 10 * 1024 * 1024 # 10 MB
from app.main import app
client = TestClient(app)
response = client.get("/api/health")
+11 -11
View File
@@ -7,12 +7,9 @@ which is critical for correctly interpreting mesh network messages.
import hashlib
import hmac
import pytest
from Crypto.Cipher import AES
from app.decoder import (
DecryptedGroupText,
PacketInfo,
PayloadType,
RouteType,
calculate_channel_hash,
@@ -113,10 +110,7 @@ class TestGroupTextDecryption:
"""Helper to create a valid encrypted GROUP_TEXT payload."""
# Build plaintext: timestamp(4) + flags(1) + message + null terminator
plaintext = (
timestamp.to_bytes(4, "little")
+ bytes([flags])
+ message.encode("utf-8")
+ b"\x00"
timestamp.to_bytes(4, "little") + bytes([flags]) + message.encode("utf-8") + b"\x00"
)
# Pad to 16-byte boundary
@@ -269,7 +263,9 @@ class TestAdvertisementParsing:
result = try_parse_advertisement(packet)
assert result is not None
assert result.public_key == "8576dc7f679b493f9ab5ac316173e1a56d3388bc3ba75f583f63ab0d1ba2a8ab"
assert (
result.public_key == "8576dc7f679b493f9ab5ac316173e1a56d3388bc3ba75f583f63ab0d1ba2a8ab"
)
assert result.name == "Can O Mesh 2 🥫"
assert result.device_role == 2 # Repeater
assert result.timestamp > 0 # Has valid timestamp
@@ -295,7 +291,9 @@ class TestAdvertisementParsing:
result = try_parse_advertisement(packet)
assert result is not None
assert result.public_key == "ae92564c5c9884854f04f469bbb2bab8871a078053af6cf4aa2c014b18ce8a83"
assert (
result.public_key == "ae92564c5c9884854f04f469bbb2bab8871a078053af6cf4aa2c014b18ce8a83"
)
assert result.name == "Flightless🥝"
assert result.device_role == 1 # Chat node
assert result.timestamp > 0 # Has valid timestamp
@@ -321,7 +319,9 @@ class TestAdvertisementParsing:
result = try_parse_advertisement(packet)
assert result is not None
assert result.public_key == "2e38c81f7dc0c1cedded6b415b4367cf48f578c5a092ced3490ff0c76efdf1f5"
assert (
result.public_key == "2e38c81f7dc0c1cedded6b415b4367cf48f578c5a092ced3490ff0c76efdf1f5"
)
assert result.name == "MennisD"
assert result.device_role == 1 # Chat node
assert result.timestamp > 0 # Has valid timestamp
@@ -330,7 +330,7 @@ class TestAdvertisementParsing:
def test_parse_advertisement_extracts_public_key(self):
"""Advertisement parsing extracts the public key correctly."""
from app.decoder import parse_packet, PayloadType
from app.decoder import PayloadType, parse_packet
packet_hex = (
"1100AE92564C5C9884854F04F469BBB2BAB8871A078053AF6CF4AA2C014B18CE8A83"
+41 -19
View File
@@ -86,7 +86,9 @@ class TestRepeatTracking:
def test_track_pending_repeat_stores_correctly(self):
"""Pending repeats are stored with channel key, text hash, and timestamp."""
channel_key = "0123456789ABCDEF0123456789ABCDEF"
track_pending_repeat(channel_key=channel_key, text="Hello", timestamp=1700000000, message_id=99)
track_pending_repeat(
channel_key=channel_key, text="Hello", timestamp=1700000000, message_id=99
)
# Key is (channel_key, text_hash, timestamp)
text_hash = str(hash("Hello"))
@@ -97,8 +99,18 @@ class TestRepeatTracking:
def test_same_message_different_channels_tracked_separately(self):
"""Same message on different channels creates separate entries."""
track_pending_repeat(channel_key="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1", text="Test", timestamp=1000, message_id=1)
track_pending_repeat(channel_key="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2", text="Test", timestamp=1000, message_id=2)
track_pending_repeat(
channel_key="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1",
text="Test",
timestamp=1000,
message_id=1,
)
track_pending_repeat(
channel_key="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2",
text="Test",
timestamp=1000,
message_id=2,
)
assert len(_pending_repeats) == 2
@@ -141,8 +153,10 @@ class TestAckEventHandler:
track_pending_ack("deadbeef", message_id=123, timeout_ms=10000)
# Mock dependencies
with patch("app.event_handlers.MessageRepository") as mock_repo, \
patch("app.event_handlers.broadcast_event") as mock_broadcast:
with (
patch("app.event_handlers.MessageRepository") as mock_repo,
patch("app.event_handlers.broadcast_event") as mock_broadcast,
):
mock_repo.increment_ack_count = AsyncMock(return_value=1)
# Create mock event
@@ -155,7 +169,9 @@ class TestAckEventHandler:
mock_repo.increment_ack_count.assert_called_once_with(123)
# Verify broadcast sent with ack_count
mock_broadcast.assert_called_once_with("message_acked", {"message_id": 123, "ack_count": 1})
mock_broadcast.assert_called_once_with(
"message_acked", {"message_id": 123, "ack_count": 1}
)
# Verify pending ACK removed
assert "deadbeef" not in _pending_acks
@@ -167,8 +183,10 @@ class TestAckEventHandler:
track_pending_ack("expected", message_id=1, timeout_ms=10000)
with patch("app.event_handlers.MessageRepository") as mock_repo, \
patch("app.event_handlers.broadcast_event") as mock_broadcast:
with (
patch("app.event_handlers.MessageRepository") as mock_repo,
patch("app.event_handlers.broadcast_event") as mock_broadcast,
):
mock_repo.increment_ack_count = AsyncMock()
class MockEvent:
@@ -209,9 +227,11 @@ class TestContactMessageCLIFiltering:
"""CLI responses (txt_type=1) are not stored in database."""
from app.event_handlers import on_contact_message
with patch("app.event_handlers.MessageRepository") as mock_repo, \
patch("app.event_handlers.ContactRepository") as mock_contact_repo, \
patch("app.event_handlers.broadcast_event") as mock_broadcast:
with (
patch("app.event_handlers.MessageRepository") as mock_repo,
patch("app.event_handlers.ContactRepository") as mock_contact_repo,
patch("app.event_handlers.broadcast_event") as mock_broadcast,
):
class MockEvent:
payload = {
@@ -235,10 +255,11 @@ class TestContactMessageCLIFiltering:
"""Normal messages (txt_type=0) are still processed normally."""
from app.event_handlers import on_contact_message
with patch("app.event_handlers.MessageRepository") as mock_repo, \
patch("app.event_handlers.ContactRepository") as mock_contact_repo, \
patch("app.event_handlers.broadcast_event") as mock_broadcast:
with (
patch("app.event_handlers.MessageRepository") as mock_repo,
patch("app.event_handlers.ContactRepository") as mock_contact_repo,
patch("app.event_handlers.broadcast_event") as mock_broadcast,
):
mock_repo.create = AsyncMock(return_value=42)
mock_contact_repo.get_by_key_prefix = AsyncMock(return_value=None)
@@ -262,10 +283,11 @@ class TestContactMessageCLIFiltering:
"""Messages without txt_type field are treated as normal (not filtered)."""
from app.event_handlers import on_contact_message
with patch("app.event_handlers.MessageRepository") as mock_repo, \
patch("app.event_handlers.ContactRepository") as mock_contact_repo, \
patch("app.event_handlers.broadcast_event") as mock_broadcast:
with (
patch("app.event_handlers.MessageRepository") as mock_repo,
patch("app.event_handlers.ContactRepository") as mock_contact_repo,
patch("app.event_handlers.broadcast_event"),
):
mock_repo.create = AsyncMock(return_value=42)
mock_contact_repo.get_by_key_prefix = AsyncMock(return_value=None)
+10 -12
View File
@@ -1,9 +1,9 @@
"""Tests for database migrations."""
import pytest
import aiosqlite
import pytest
from app.migrations import get_version, set_version, run_migrations
from app.migrations import get_version, run_migrations, set_version
class TestMigrationSystem:
@@ -76,24 +76,22 @@ class TestMigration001:
# Verify columns exist by inserting and selecting
await conn.execute(
"INSERT INTO contacts (public_key, name, last_read_at) VALUES (?, ?, ?)",
("abc123", "Test", 12345)
("abc123", "Test", 12345),
)
await conn.execute(
"INSERT INTO channels (key, name, last_read_at) VALUES (?, ?, ?)",
("KEY123", "#test", 67890)
("KEY123", "#test", 67890),
)
await conn.commit()
cursor = await conn.execute(
"SELECT last_read_at FROM contacts WHERE public_key = ?",
("abc123",)
"SELECT last_read_at FROM contacts WHERE public_key = ?", ("abc123",)
)
row = await cursor.fetchone()
assert row["last_read_at"] == 12345
cursor = await conn.execute(
"SELECT last_read_at FROM channels WHERE key = ?",
("KEY123",)
"SELECT last_read_at FROM channels WHERE key = ?", ("KEY123",)
)
row = await cursor.fetchone()
assert row["last_read_at"] == 67890
@@ -186,11 +184,11 @@ class TestMigration001:
""")
await conn.execute(
"INSERT INTO contacts (public_key, name, type) VALUES (?, ?, ?)",
("existingkey", "ExistingContact", 1)
("existingkey", "ExistingContact", 1),
)
await conn.execute(
"INSERT INTO channels (key, name, is_hashtag) VALUES (?, ?, ?)",
("EXISTINGCHAN", "#existing", 1)
("EXISTINGCHAN", "#existing", 1),
)
await conn.commit()
@@ -200,7 +198,7 @@ class TestMigration001:
# Verify data is preserved
cursor = await conn.execute(
"SELECT public_key, name, type, last_read_at FROM contacts WHERE public_key = ?",
("existingkey",)
("existingkey",),
)
row = await cursor.fetchone()
assert row["public_key"] == "existingkey"
@@ -210,7 +208,7 @@ class TestMigration001:
cursor = await conn.execute(
"SELECT key, name, is_hashtag, last_read_at FROM channels WHERE key = ?",
("EXISTINGCHAN",)
("EXISTINGCHAN",),
)
row = await cursor.fetchone()
assert row["key"] == "EXISTINGCHAN"
+50 -53
View File
@@ -9,13 +9,17 @@ between backend and frontend - both sides test against the same data.
import json
from pathlib import Path
from unittest.mock import patch, MagicMock
from unittest.mock import MagicMock, patch
import pytest
from app.database import Database
from app.repository import ChannelRepository, MessageRepository, ContactRepository, RawPacketRepository
from app.repository import (
ChannelRepository,
ContactRepository,
MessageRepository,
RawPacketRepository,
)
# Load shared fixtures
FIXTURES_PATH = Path(__file__).parent / "fixtures" / "websocket_events.json"
@@ -62,7 +66,9 @@ class TestChannelMessagePipeline:
"""Test channel message flow: packet → decrypt → store → broadcast."""
@pytest.mark.asyncio
async def test_channel_message_creates_message_and_broadcasts(self, test_db, captured_broadcasts):
async def test_channel_message_creates_message_and_broadcasts(
self, test_db, captured_broadcasts
):
"""A decryptable channel packet creates a message and broadcasts it."""
from app.packet_processor import process_raw_packet
@@ -71,9 +77,7 @@ class TestChannelMessagePipeline:
# Create the channel in DB first using upsert
await ChannelRepository.upsert(
key=fixture["channel_key_hex"].upper(),
name=fixture["channel_name"],
is_hashtag=True
key=fixture["channel_key_hex"].upper(), name=fixture["channel_name"], is_hashtag=True
)
broadcasts, mock_broadcast = captured_broadcasts
@@ -87,9 +91,7 @@ class TestChannelMessagePipeline:
# Verify message was stored in database
messages = await MessageRepository.get_all(
msg_type="CHAN",
conversation_key=fixture["channel_key_hex"].upper(),
limit=10
msg_type="CHAN", conversation_key=fixture["channel_key_hex"].upper(), limit=10
)
assert len(messages) == 1
msg = messages[0]
@@ -105,7 +107,9 @@ class TestChannelMessagePipeline:
assert broadcast["data"]["type"] == expected["type"]
assert broadcast["data"]["conversation_key"] == expected["conversation_key"]
assert broadcast["data"]["outgoing"] == expected["outgoing"]
assert expected["text"][:30] in broadcast["data"]["text"] # Check text contains expected content
assert (
expected["text"][:30] in broadcast["data"]["text"]
) # Check text contains expected content
@pytest.mark.asyncio
async def test_duplicate_packet_not_broadcast_twice(self, test_db, captured_broadcasts):
@@ -118,9 +122,7 @@ class TestChannelMessagePipeline:
# Create the channel in DB first
await ChannelRepository.upsert(
key=channel_key_hex,
name=fixture["channel_name"],
is_hashtag=True
key=channel_key_hex, name=fixture["channel_name"], is_hashtag=True
)
broadcasts, mock_broadcast = captured_broadcasts
@@ -139,9 +141,7 @@ class TestChannelMessagePipeline:
# Only ONE message should exist in database
messages = await MessageRepository.get_all(
msg_type="CHAN",
conversation_key=channel_key_hex,
limit=10
msg_type="CHAN", conversation_key=channel_key_hex, limit=10
)
assert len(messages) == 1
@@ -191,7 +191,7 @@ class TestAdvertisementPipeline:
with patch("app.packet_processor.broadcast_event", mock_broadcast):
# Process the advertisement packet through the normal pipeline
result = await process_raw_packet(packet_bytes, timestamp=1700000000)
await process_raw_packet(packet_bytes, timestamp=1700000000)
# Verify contact was created in database
expected = fixture["expected_ws_event"]["data"]
@@ -229,13 +229,15 @@ class TestAdvertisementPipeline:
expected = fixture["expected_ws_event"]["data"]
# Create existing contact with different/missing data
await ContactRepository.upsert({
"public_key": expected["public_key"],
"name": "OldName",
"type": 0,
"lat": None,
"lon": None
})
await ContactRepository.upsert(
{
"public_key": expected["public_key"],
"name": "OldName",
"type": 0,
"lat": None,
"lon": None,
}
)
broadcasts, mock_broadcast = captured_broadcasts
@@ -255,26 +257,30 @@ class TestAdvertisementPipeline:
assert contact.last_path in (None, "")
@pytest.mark.asyncio
async def test_advertisement_keeps_shorter_path_within_window(self, test_db, captured_broadcasts):
async def test_advertisement_keeps_shorter_path_within_window(
self, test_db, captured_broadcasts
):
"""When receiving echoed advertisements, keep the shortest path within 60s window."""
from app.packet_processor import _process_advertisement
from app.decoder import parse_packet
# Create a contact with a longer path (path_len=3)
test_pubkey = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
await ContactRepository.upsert({
"public_key": test_pubkey,
"name": "TestNode",
"type": 1,
"last_seen": 1000,
"last_path_len": 3,
"last_path": "aabbcc", # 3 bytes = 3 hops
})
await ContactRepository.upsert(
{
"public_key": test_pubkey,
"name": "TestNode",
"type": 1,
"last_seen": 1000,
"last_path_len": 3,
"last_path": "aabbcc", # 3 bytes = 3 hops
}
)
# Simulate receiving a shorter path (path_len=1) within 60s
# We'll call _process_advertisement directly with mock packet_info
from unittest.mock import MagicMock
from app.decoder import PacketInfo, RouteType, PayloadType, ParsedAdvertisement
from app.decoder import ParsedAdvertisement
broadcasts, mock_broadcast = captured_broadcasts
@@ -341,7 +347,7 @@ class TestAckPipeline:
conversation_key="abc123def456789012345678901234567890123456789012345678901234",
sender_timestamp=1700000000,
received_at=1700000000,
outgoing=True
outgoing=True,
)
# Track pending ACK
@@ -363,7 +369,7 @@ class TestAckPipeline:
messages = await MessageRepository.get_all(
msg_type="PRIV",
conversation_key="abc123def456789012345678901234567890123456789012345678901234",
limit=10
limit=10,
)
assert len(messages) == 1
assert messages[0].acked == 1
@@ -372,7 +378,6 @@ class TestAckPipeline:
ack_broadcasts = [b for b in broadcasts if b["type"] == "message_acked"]
assert len(ack_broadcasts) == 1
expected = FIXTURES["message_acked"]["expected_ws_event"]["data"]
broadcast = ack_broadcasts[0]
assert "message_id" in broadcast["data"]
assert "ack_count" in broadcast["data"]
@@ -408,9 +413,7 @@ class TestCreateMessageFromDecrypted:
# Verify message was stored in database
messages = await MessageRepository.get_all(
msg_type="CHAN",
conversation_key="ABC123DEF456",
limit=10
msg_type="CHAN", conversation_key="ABC123DEF456", limit=10
)
assert len(messages) == 1
assert messages[0].text == "TestSender: Hello world"
@@ -454,9 +457,7 @@ class TestCreateMessageFromDecrypted:
# Verify text is stored without sender prefix
messages = await MessageRepository.get_all(
msg_type="CHAN",
conversation_key="ABC123DEF456",
limit=10
msg_type="CHAN", conversation_key="ABC123DEF456", limit=10
)
assert len(messages) == 1
assert messages[0].text == "System message" # No "None: " prefix
@@ -509,7 +510,7 @@ class TestCreateMessageFromDecrypted:
broadcasts, mock_broadcast = captured_broadcasts
with patch("app.packet_processor.broadcast_event", mock_broadcast):
msg_id = await create_message_from_decrypted(
await create_message_from_decrypted(
packet_id=packet_id,
channel_key="ABC123DEF456",
sender="Sender",
@@ -537,9 +538,7 @@ class TestMessageBroadcastStructure:
channel_key_hex = fixture["channel_key_hex"].upper()
await ChannelRepository.upsert(
key=channel_key_hex,
name=fixture["channel_name"],
is_hashtag=True
key=channel_key_hex, name=fixture["channel_name"], is_hashtag=True
)
broadcasts, mock_broadcast = captured_broadcasts
@@ -570,15 +569,13 @@ class TestRawPacketStorage:
# Create channel so packet can be decrypted
await ChannelRepository.upsert(
key=channel_key_hex,
name=fixture["channel_name"],
is_hashtag=True
key=channel_key_hex, name=fixture["channel_name"], is_hashtag=True
)
broadcasts, mock_broadcast = captured_broadcasts
with patch("app.packet_processor.broadcast_event", mock_broadcast):
result = await process_raw_packet(packet_bytes, timestamp=1700000000)
await process_raw_packet(packet_bytes, timestamp=1700000000)
# Verify raw_packet broadcast was sent
raw_broadcasts = [b for b in broadcasts if b["type"] == "raw_packet"]
+4 -2
View File
@@ -4,11 +4,11 @@ These tests verify the polling pause mechanism that prevents
message polling from interfering with repeater CLI operations.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.radio_sync import (
_polling_pause_count,
is_polling_paused,
pause_polling,
sync_radio_time,
@@ -19,6 +19,7 @@ from app.radio_sync import (
def reset_polling_state():
"""Reset polling pause state before and after each test."""
import app.radio_sync as radio_sync
radio_sync._polling_pause_count = 0
yield
radio_sync._polling_pause_count = 0
@@ -143,6 +144,7 @@ class TestSyncRadioTime:
# Verify timestamp is reasonable (within last few seconds)
call_args = mock_mc.commands.set_time.call_args[0][0]
import time
assert abs(call_args - int(time.time())) < 5
@pytest.mark.asyncio