Phase 3: Add path size inference and also bin some stupid migration tests while we're at it

This commit is contained in:
Jack Kingsman
2026-03-07 19:02:37 -08:00
parent e0d7c8a083
commit f97c846378
6 changed files with 35 additions and 46 deletions

View File

@@ -221,7 +221,9 @@ async def on_path_update(event: "Event") -> None:
)
return
await ContactRepository.update_path(contact.public_key, str(path), normalized_path_len)
await ContactRepository.update_path(
contact.public_key, str(path), normalized_path_len
)
async def on_new_contact(event: "Event") -> None:

View File

@@ -2,6 +2,8 @@ from typing import Literal
from pydantic import BaseModel, Field
from app.path_utils import infer_hash_size
class Contact(BaseModel):
public_key: str = Field(description="Public key (64-char hex)")
@@ -32,6 +34,7 @@ class Contact(BaseModel):
"flags": self.flags,
"out_path": self.last_path or "",
"out_path_len": self.last_path_len,
"out_path_hash_mode": infer_hash_size(self.last_path or "", self.last_path_len) - 1,
"adv_lat": self.lat if self.lat is not None else 0.0,
"adv_lon": self.lon if self.lon is not None else 0.0,
"last_advert": self.last_advert if self.last_advert is not None else 0,

View File

@@ -25,8 +25,8 @@ class ContactRepository:
await db.conn.execute(
"""
INSERT INTO contacts (public_key, name, type, flags, last_path, last_path_len,
last_advert, lat, lon, last_seen, on_radio, last_contacted,
first_seen)
last_advert, lat, lon, last_seen,
on_radio, last_contacted, first_seen)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(public_key) DO UPDATE SET
name = COALESCE(excluded.name, contacts.name),
@@ -200,9 +200,12 @@ class ContactRepository:
return [ContactRepository._row_to_contact(row) for row in rows]
@staticmethod
async def update_path(public_key: str, path: str, path_len: int) -> None:
async def update_path(
public_key: str, path: str, path_len: int
) -> None:
await db.conn.execute(
"UPDATE contacts SET last_path = ?, last_path_len = ?, last_seen = ? WHERE public_key = ?",
"""UPDATE contacts SET last_path = ?, last_path_len = ?,
last_seen = ? WHERE public_key = ?""",
(path, path_len, int(time.time()), public_key.lower()),
)
await db.conn.commit()

View File

@@ -98,10 +98,7 @@ class TestMigration001:
await conn.commit()
# Run migrations
applied = await run_migrations(conn)
assert applied == 38 # All migrations run
assert await get_version(conn) == 38
await run_migrations(conn)
# Verify columns exist by inserting and selecting
await conn.execute(
@@ -183,9 +180,8 @@ class TestMigration001:
applied1 = await run_migrations(conn)
applied2 = await run_migrations(conn)
assert applied1 == 38 # All migrations run
assert applied1 > 0 # Migrations were applied
assert applied2 == 0 # No migrations on second run
assert await get_version(conn) == 38
finally:
await conn.close()
@@ -246,8 +242,7 @@ class TestMigration001:
applied = await run_migrations(conn)
# All migrations applied (version incremented) but no error
assert applied == 38
assert await get_version(conn) == 38
assert applied > 0
finally:
await conn.close()
@@ -374,10 +369,8 @@ class TestMigration013:
)
await conn.commit()
# Run migration 13 (plus 14-38 which also run)
applied = await run_migrations(conn)
assert applied == 26
assert await get_version(conn) == 38
# Run migration 13 (plus remaining which also run)
await run_migrations(conn)
# Bots were migrated from app_settings to fanout_configs (migration 37)
# and the bots column was dropped (migration 38)
@@ -495,7 +488,6 @@ class TestMigration018:
assert await cursor.fetchone() is not None
await run_migrations(conn)
assert await get_version(conn) == 38
# Verify autoindex is gone
cursor = await conn.execute(
@@ -572,9 +564,7 @@ class TestMigration018:
)
await conn.commit()
applied = await run_migrations(conn)
assert applied == 21 # Migrations 18-38 run (18+19 skip internally)
assert await get_version(conn) == 38
await run_migrations(conn)
finally:
await conn.close()
@@ -646,7 +636,6 @@ class TestMigration019:
assert await cursor.fetchone() is not None
await run_migrations(conn)
assert await get_version(conn) == 38
# Verify autoindex is gone
cursor = await conn.execute(
@@ -711,9 +700,7 @@ class TestMigration020:
cursor = await conn.execute("PRAGMA journal_mode")
assert (await cursor.fetchone())[0] == "delete"
applied = await run_migrations(conn)
assert applied == 19 # Migrations 20-38
assert await get_version(conn) == 38
await run_migrations(conn)
# Verify WAL mode
cursor = await conn.execute("PRAGMA journal_mode")
@@ -742,8 +729,7 @@ class TestMigration020:
await conn.commit()
await set_version(conn, 20)
applied = await run_migrations(conn)
assert applied == 18 # Migrations 21-38 still run
await run_migrations(conn)
# Still WAL + INCREMENTAL
cursor = await conn.execute("PRAGMA journal_mode")
@@ -800,9 +786,7 @@ class TestMigration028:
)
await conn.commit()
applied = await run_migrations(conn)
assert applied == 11
assert await get_version(conn) == 38
await run_migrations(conn)
# Verify payload_hash column is now BLOB
cursor = await conn.execute("PRAGMA table_info(raw_packets)")
@@ -870,9 +854,7 @@ class TestMigration028:
)
await conn.commit()
applied = await run_migrations(conn)
assert applied == 11 # Version still bumped
assert await get_version(conn) == 38
await run_migrations(conn)
# Verify data unchanged
cursor = await conn.execute("SELECT payload_hash FROM raw_packets")
@@ -920,9 +902,7 @@ class TestMigration032:
await conn.execute("INSERT INTO app_settings (id) VALUES (1)")
await conn.commit()
applied = await run_migrations(conn)
assert applied == 7
assert await get_version(conn) == 38
await run_migrations(conn)
# Community MQTT columns were added by migration 32 and dropped by migration 38.
# Verify community settings were NOT migrated (no community config existed).
@@ -987,9 +967,7 @@ class TestMigration034:
""")
await conn.commit()
applied = await run_migrations(conn)
assert applied == 5
assert await get_version(conn) == 38
await run_migrations(conn)
# Verify column exists with correct default
cursor = await conn.execute("SELECT flood_scope FROM app_settings WHERE id = 1")
@@ -1030,9 +1008,7 @@ class TestMigration033:
""")
await conn.commit()
applied = await run_migrations(conn)
assert applied == 6
assert await get_version(conn) == 38
await run_migrations(conn)
cursor = await conn.execute(
"SELECT key, name, is_hashtag, on_radio FROM channels WHERE key = ?",

View File

@@ -309,6 +309,7 @@ class TestAdvertisementPipeline:
short_packet_info = MagicMock()
short_packet_info.path_length = 1
short_packet_info.path = bytes.fromhex("aa")
short_packet_info.path_hash_size = 1
short_packet_info.payload = b"" # Will be parsed by parse_advertisement
# Mock parse_advertisement to return our test contact
@@ -333,6 +334,7 @@ class TestAdvertisementPipeline:
long_packet_info = MagicMock()
long_packet_info.path_length = 5
long_packet_info.path = bytes.fromhex("aabbccddee")
long_packet_info.path_hash_size = 1
with patch("app.packet_processor.broadcast_event", mock_broadcast):
with patch("app.packet_processor.parse_advertisement") as mock_parse:
@@ -381,6 +383,7 @@ class TestAdvertisementPipeline:
packet_info = MagicMock()
packet_info.path_length = 3
packet_info.path = bytes.fromhex("aabbcc")
packet_info.path_hash_size = 1
with patch("app.packet_processor.broadcast_event", mock_broadcast):
with patch("app.packet_processor.parse_advertisement") as mock_parse:
@@ -433,6 +436,7 @@ class TestAdvertisementPipeline:
long_packet_info = MagicMock()
long_packet_info.path_length = 4
long_packet_info.path = bytes.fromhex("aabbccdd")
long_packet_info.path_hash_size = 1
long_packet_info.payload = b""
with patch("app.packet_processor.broadcast_event", mock_broadcast):

9
uv.lock generated
View File

@@ -497,16 +497,17 @@ wheels = [
[[package]]
name = "meshcore"
version = "2.2.5"
version = "2.2.28"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "bleak" },
{ name = "pycayennelpp" },
{ name = "pycryptodome" },
{ name = "pyserial-asyncio-fast" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/c2/0927a793d9742832613a42bb5c138adda75e201fc0dafe83c0bd444ab9f7/meshcore-2.2.5.tar.gz", hash = "sha256:15818150a6a8380883c2b272356fabad0bab107f3b9438cfd665bc9ca03d1d17", size = 56968 }
sdist = { url = "https://files.pythonhosted.org/packages/7d/29/4dc795be22670ebabf18ce8b12625f39551f1e9073dfd3494c8bd1b2291d/meshcore-2.2.28.tar.gz", hash = "sha256:87ece4b3d9e32c6baccab73da5eabcf5a84d520df8be715c62879985b5baec83", size = 68652 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/e6/557d0fade6ba87e955750b980ca4c7468f529c911e5a9b2fe55d49b4cfd8/meshcore-2.2.5-py3-none-any.whl", hash = "sha256:b422816f6e4c6e621be99730b92973f78ab983d4e01111ad4c8fd50c2b2b3a9c", size = 45053 },
{ url = "https://files.pythonhosted.org/packages/3a/10/0cf1ca1c948049344a303478d86425b86c6699e8cdeb7f0e611125f937c1/meshcore-2.2.28-py3-none-any.whl", hash = "sha256:e015dbf756f844d2c4191d5a9cd1c1c70ec16a235d222cdcfc88a6a760de2847", size = 52213 },
]
[[package]]
@@ -1090,7 +1091,7 @@ requires-dist = [
{ name = "fastapi", specifier = ">=0.115.0" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "httpx", marker = "extra == 'test'", specifier = ">=0.27.0" },
{ name = "meshcore" },
{ name = "meshcore", specifier = "==2.2.28" },
{ name = "pycryptodome", specifier = ">=3.20.0" },
{ name = "pydantic-settings", specifier = ">=2.0.0" },
{ name = "pynacl", specifier = ">=1.5.0" },