支持设备定时定位上报与OTA通道选择

This commit is contained in:
stu2not
2026-06-15 08:52:30 +08:00
parent 9d1537bd5d
commit 2b04a2fca8
15 changed files with 893 additions and 24 deletions

View File

@@ -0,0 +1,111 @@
import pytest
from handlers.mqtt_handler import TalkingQMQTTService
@pytest.mark.asyncio
async def test_device_info_report_persists_metadata_and_periodic_location(monkeypatch):
service = TalkingQMQTTService({"broker": "127.0.0.1"})
calls = []
published = []
async def fake_publish(topic, payload):
published.append((topic, payload))
return True
async def fake_schedule_persistence(device_id, label, coro):
calls.append(("schedule", device_id, label))
await coro
async def fake_insert_or_update(**kwargs):
calls.append(("settings", kwargs))
async def fake_report_mqtt_device_location(**kwargs):
calls.append(("location", kwargs))
return {"device_id": kwargs["device_id"], "lat": kwargs["latitude"], "lng": kwargs["longitude"]}
monkeypatch.setattr(service, "_publish", fake_publish)
monkeypatch.setattr(service, "_schedule_persistence", fake_schedule_persistence)
monkeypatch.setattr("handlers.mqtt_handler.device_setting_service.insert_or_update", fake_insert_or_update)
monkeypatch.setattr("handlers.mqtt_handler.location_service.report_mqtt_device_location", fake_report_mqtt_device_location)
await service._handle_device_info(
"TalkingQ_XQSN00001005",
{
"msg_id": "000",
"data": {
"id": "TalkingQ_XQSN00001005",
"power": 80,
"signal": 4,
"voice": 60,
"imei": "861921071409242",
"version": "1.0.1",
"latitude": 23.1173393,
"longitude": 113.2855975,
"device_type": "2",
},
},
)
assert published == [
("device/TalkingQ_XQSN00001005/event_resp", {"msg_id": "000", "status": "success"}),
]
assert calls == [
("schedule", "TalkingQ_XQSN00001005", "device_info"),
(
"settings",
{
"device_id": "TalkingQ_XQSN00001005",
"power": 80,
"signal_strength": 4,
"version_str": "1.0.1",
"volume": 60,
"imei": "861921071409242",
"device_type": "2",
},
),
("schedule", "TalkingQ_XQSN00001005", "device_info_location"),
(
"location",
{
"device_id": "TalkingQ_XQSN00001005",
"latitude": 23.1173393,
"longitude": 113.2855975,
"coord_type": None,
"accuracy_m": None,
"altitude_m": None,
"speed_mps": None,
"heading_deg": None,
"source": 0,
"battery_pct": 80,
},
),
]
@pytest.mark.asyncio
async def test_device_info_report_without_location_does_not_write_location(monkeypatch):
service = TalkingQMQTTService({"broker": "127.0.0.1"})
calls = []
async def fake_publish(topic, payload):
del topic, payload
return True
async def fake_schedule_persistence(device_id, label, coro):
calls.append(label)
await coro
async def fake_insert_or_update(**kwargs):
calls.append(("settings", kwargs["device_type"]))
monkeypatch.setattr(service, "_publish", fake_publish)
monkeypatch.setattr(service, "_schedule_persistence", fake_schedule_persistence)
monkeypatch.setattr("handlers.mqtt_handler.device_setting_service.insert_or_update", fake_insert_or_update)
await service._handle_device_info(
"TalkingQ_XQSN00001005",
{"msg_id": "000", "data": {"power": 20, "signal": 2, "voice": 40, "version": "1.0.0", "device_type": 1}},
)
assert calls == ["device_info", ("settings", 1)]

View File

@@ -0,0 +1,201 @@
import pytest
from fastapi import HTTPException
from banban.service.device import DeviceService
from banban.service.ota_firmware import OtaFirmwareSelection
from banban.service.device_setting import DeviceSettingService
@pytest.mark.asyncio
async def test_firmware_status_selects_gps_channel(monkeypatch):
service = DeviceService()
async def fake_get_device_status(**kwargs):
del kwargs
return {
"device_id": "TalkingQ_XQSN00001005",
"version": "1.0.0",
"device_type": 2,
"ota_channel": None,
}
async def fake_select_latest_for_device(status_row):
assert status_row["device_type"] == 2
return OtaFirmwareSelection(
channel_key="gps",
version="1.1.0",
firmware_url="https://example.com/gps-1.1.0.bin",
firmware_id=10,
)
async def fake_get_firmware_update_dict(device_id):
del device_id
return None
monkeypatch.setattr(service, "get_device_status", fake_get_device_status)
monkeypatch.setattr("banban.service.device.ota_firmware_service.select_latest_for_device", fake_select_latest_for_device)
monkeypatch.setattr("banban.service.device.device_firmware_update_manager.get_firmware_update_dict", fake_get_firmware_update_dict)
result = await service.get_firmware_status(device_id="TalkingQ_XQSN00001005", user_id=1)
assert result["ota_channel"] == "gps"
assert result["firmware_id"] == 10
assert result["latest_version"] == "1.1.0"
assert result["firmware_url"] == "https://example.com/gps-1.1.0.bin"
assert result["update_available"] is True
assert result["can_update"] is True
@pytest.mark.asyncio
async def test_firmware_status_unknown_channel_cannot_update(monkeypatch):
service = DeviceService()
async def fake_get_device_status(**kwargs):
del kwargs
return {
"device_id": "TalkingQ_XQSN00001005",
"version": "1.0.0",
"device_type": None,
"ota_channel": None,
}
async def fake_select_latest_for_device(status_row):
assert status_row["device_type"] is None
return None
async def fake_get_firmware_update_dict(device_id):
del device_id
return None
monkeypatch.setattr(service, "get_device_status", fake_get_device_status)
monkeypatch.setattr("banban.service.device.ota_firmware_service.select_latest_for_device", fake_select_latest_for_device)
monkeypatch.setattr("banban.service.device.device_firmware_update_manager.get_firmware_update_dict", fake_get_firmware_update_dict)
result = await service.get_firmware_status(device_id="TalkingQ_XQSN00001005", user_id=1)
assert result["ota_channel"] is None
assert result["latest_version"] is None
assert result["can_update"] is False
@pytest.mark.asyncio
async def test_start_firmware_update_records_channel_and_keeps_mqtt_payload(monkeypatch):
service = DeviceService()
calls = []
async def fake_get_firmware_status(**kwargs):
del kwargs
return {
"device_id": "TalkingQ_XQSN00001005",
"current_version": "1.0.0",
"latest_version": "1.1.0",
"ota_channel": "no_gps",
"firmware_id": 22,
"firmware_url": "https://example.com/no-gps-1.1.0.bin",
"update_available": True,
"can_update": True,
"update_status": "idle",
"progress": 0.0,
"target_version": None,
"updated_at": None,
}
async def fake_update_firmware_update(**kwargs):
calls.append(("record", kwargs))
return True
class FakeMqttService:
async def send_ota_command(self, device_id, url, version):
calls.append(("mqtt", {"device_id": device_id, "url": url, "version": version}))
return "003"
async def fake_get_instance():
return FakeMqttService()
monkeypatch.setattr(service, "get_firmware_status", fake_get_firmware_status)
monkeypatch.setattr("banban.service.device.device_firmware_update_manager.update_firmware_update", fake_update_firmware_update)
monkeypatch.setattr("handlers.mqtt_handler.TalkingQMQTTService.get_instance", fake_get_instance)
result = await service.start_firmware_update(device_id="TalkingQ_XQSN00001005", user_id=1)
assert calls == [
(
"record",
{
"device_id": "TalkingQ_XQSN00001005",
"firmware_version": "1.1.0",
"update_status": "sent",
"progress": 0.0,
"ota_channel": "no_gps",
"target_version": "1.1.0",
"firmware_url": "https://example.com/no-gps-1.1.0.bin",
"firmware_id": 22,
"source": "manual",
},
),
(
"mqtt",
{
"device_id": "TalkingQ_XQSN00001005",
"url": "https://example.com/no-gps-1.1.0.bin",
"version": "1.1.0",
},
),
]
assert result["msg_id"] == "003"
assert result["ota_channel"] == "no_gps"
assert result["target_version"] == "1.1.0"
@pytest.mark.asyncio
async def test_start_firmware_update_without_known_channel_is_blocked(monkeypatch):
service = DeviceService()
async def fake_get_firmware_status(**kwargs):
del kwargs
return {
"device_id": "TalkingQ_XQSN00001005",
"current_version": "1.0.0",
"latest_version": None,
"update_available": False,
"can_update": False,
"update_status": "idle",
"progress": 0.0,
"target_version": None,
"updated_at": None,
}
monkeypatch.setattr(service, "get_firmware_status", fake_get_firmware_status)
with pytest.raises(HTTPException) as exc_info:
await service.start_firmware_update(device_id="TalkingQ_XQSN00001005", user_id=1)
assert exc_info.value.status_code == 409
@pytest.mark.asyncio
async def test_device_type_update_preserves_existing_manual_ota_channel(monkeypatch):
service = DeviceSettingService()
calls = []
async def fake_get_setting_by_device_id(device_id):
assert device_id == "TalkingQ_XQSN00001005"
return {"device_id": device_id, "ota_channel": "factory_gps"}
async def fake_update_setting(**kwargs):
calls.append(kwargs)
monkeypatch.setattr(service, "get_setting_by_device_id", fake_get_setting_by_device_id)
monkeypatch.setattr(service, "update_setting", fake_update_setting)
await service.insert_or_update(
device_id="TalkingQ_XQSN00001005",
power=80,
volume=60,
signal_strength=4,
version_str="1.0.0",
device_type=1,
)
assert calls[0]["device_type"] == 1
assert calls[0]["ota_channel"] is None