from sqlmodel import Session, SQLModel, create_engine from app.models import TerminalDeviceState, TerminalMessageRaw from app.mqtt.parser_worker import process_pending_batch from app.mqtt.repository import insert_raw_message def _session() -> Session: engine = create_engine("sqlite://") SQLModel.metadata.create_all(engine) return Session(engine) def test_insert_raw_message_sets_unparsed_defaults() -> None: with _session() as session: row = insert_raw_message( session=session, device_id="device-001", topic="terminal/device-001/raw", payload=b"$GPRMC,123520,A,3723.2475,N,12158.3416,W,0.13,309.62,120598,,,A*74\r\n", qos=1, retain=False, ) assert row.parsed_at is None assert row.parse_error is None assert row.payload_size > 0 assert len(row.payload_sha256) == 64 def test_worker_marks_success_and_updates_state() -> None: with _session() as session: raw = insert_raw_message( session=session, device_id="device-001", topic="terminal/device-001/raw", payload=b"$GPGGA,123519,3723.2475,N,12158.3416,W,1,12,0.8,10.0,M,-25.0,M,,*65\r\n", qos=1, retain=False, ) processed = process_pending_batch(session=session, batch_size=10) assert processed == 1 refreshed = session.get(TerminalMessageRaw, raw.id) assert refreshed is not None assert refreshed.parsed_at is not None assert refreshed.parse_error is None state = session.get(TerminalDeviceState, "device-001") assert state is not None assert state.lat is not None assert state.lon is not None def test_worker_marks_failed_without_retry_schedule(monkeypatch) -> None: def _raise_parse_error(_payload: bytes) -> list[dict[str, object]]: raise ValueError("boom") monkeypatch.setattr( "app.mqtt.parser_worker.parse_mixed_payload", _raise_parse_error ) with _session() as session: raw = insert_raw_message( session=session, device_id="device-err", topic="terminal/device-err/raw", payload=b"$GPGGA,broken*00\r\n", qos=1, retain=False, ) processed = process_pending_batch(session=session, batch_size=10) assert processed == 1 refreshed = session.get(TerminalMessageRaw, raw.id) assert refreshed is not None assert refreshed.parsed_at is not None assert refreshed.parse_error == "boom" processed_again = process_pending_batch(session=session, batch_size=10) assert processed_again == 0 def test_schema_excludes_terminal_observation_event_table() -> None: assert "terminal_observation_event" not in SQLModel.metadata.tables