Files
PingWatch/backend/tests/test_models_and_migration.py
T

99 lines
3.3 KiB
Python

"""Persistence contracts for reliability monitoring data."""
import importlib
from pathlib import Path
import pytest
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
import app.models as models
from app.config import Settings
from app.models.device import Base
@pytest.fixture
async def db_session() -> AsyncSession:
"""Provide an isolated database containing the current model metadata."""
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
try:
async with engine.begin() as connection:
await connection.run_sync(Base.metadata.create_all)
async with AsyncSession(engine, expire_on_commit=False) as session:
yield session
finally:
await engine.dispose()
async def test_probe_record_stores_packet_summary(db_session: AsyncSession):
"""A probe aggregate retains loss and latency needed by the state machine."""
record = models.PingRecord(
device_id=1,
is_alive=True,
response_time_ms=12.5,
round_num=1,
sent_count=3,
received_count=2,
packet_loss_percent=33.33,
average_rtt_ms=12.5,
is_valid=True,
)
db_session.add(record)
await db_session.commit()
assert record.packet_loss_percent == 33.33
assert record.received_count == 2
async def test_outbox_defaults_to_pending(db_session: AsyncSession):
"""New notifications stay eligible for delivery until the dispatcher claims them."""
assert hasattr(models, "NotificationOutbox")
assert hasattr(models, "NotificationStatus")
item = models.NotificationOutbox(
alert_event_id=1,
message_content="masked summary",
)
db_session.add(item)
await db_session.commit()
assert item.status == models.NotificationStatus.pending
def test_device_policy_prefers_explicit_device_overrides():
"""A device-specific threshold supersedes only the configured global default."""
device = models.Device(
probe_packets_per_round=5,
degraded_loss_percent=None,
)
policy = models.DeviceMonitoringPolicy.from_device(device, Settings())
assert policy.probe_packets_per_round == 5
assert policy.degraded_loss_percent == 20.0
async def test_migration_is_idempotent_and_records_revision(tmp_path: Path):
"""Rerunning the migration preserves the schema and writes one revision marker."""
migration = importlib.import_module(
"migrations.versions.20260803_reliability_monitoring"
)
assert hasattr(migration, "run_reliability_migration")
database_url = f"sqlite+aiosqlite:///{tmp_path / 'monitoring.db'}"
engine = create_async_engine(database_url)
try:
async with engine.begin() as connection:
await connection.run_sync(migration.run_reliability_migration)
await connection.run_sync(Base.metadata.create_all)
async with engine.begin() as connection:
await connection.run_sync(migration.run_reliability_migration)
revision_count = await connection.scalar(
text(
"SELECT count(*) FROM schema_migrations "
"WHERE revision = '20260803_reliability_monitoring'"
)
)
assert revision_count == 1
finally:
await engine.dispose()