feat(ota): support forced firmware dispatch
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from banban.service.device import DeviceService
|
||||
from banban.service.ota_firmware import OtaFirmwareSelection
|
||||
from handlers.mqtt_handler import TalkingQMQTTService
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_simulated_device_force_ota_dispatches_once_and_completes(monkeypatch):
|
||||
device_id = "TalkingQ_XQSN00001005"
|
||||
service = DeviceService()
|
||||
mqtt_service = TalkingQMQTTService({"broker": "127.0.0.1"})
|
||||
record = None
|
||||
ota_commands = []
|
||||
published = []
|
||||
|
||||
async def fake_select_latest_for_device(status_row):
|
||||
assert status_row["device_type"] == 2
|
||||
return OtaFirmwareSelection(
|
||||
channel_key="gps",
|
||||
version="1.8.5",
|
||||
firmware_url="https://example.com/gps-1.8.5.bin",
|
||||
firmware_id=15,
|
||||
force_update=True,
|
||||
)
|
||||
|
||||
async def fake_get_firmware_update_dict(requested_device_id):
|
||||
assert requested_device_id == device_id
|
||||
return dict(record) if record else None
|
||||
|
||||
async def fake_update_firmware_update(*args, **kwargs):
|
||||
nonlocal record
|
||||
if args:
|
||||
kwargs = {"device_id": args[0], **kwargs}
|
||||
record = dict(record or {})
|
||||
record.update(kwargs)
|
||||
record.setdefault("device_id", device_id)
|
||||
record.setdefault("updated_at", datetime.now())
|
||||
return True
|
||||
|
||||
async def fake_claim_forced_update(**kwargs):
|
||||
nonlocal record
|
||||
if record and record.get("update_status") in {"sent", "accepted", "updating"}:
|
||||
return False
|
||||
record = dict(record or {})
|
||||
record.update(
|
||||
{
|
||||
"device_id": device_id,
|
||||
"firmware_version": kwargs["target_version"],
|
||||
"target_version": kwargs["target_version"],
|
||||
"firmware_url": kwargs["firmware_url"],
|
||||
"firmware_id": kwargs["firmware_id"],
|
||||
"ota_channel": kwargs["ota_channel"],
|
||||
"update_status": "sent",
|
||||
"progress": 0.0,
|
||||
"source": "forced",
|
||||
"updated_at": datetime.now(),
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
class FakeMqttService:
|
||||
async def send_ota_command(self, requested_device_id, url, version):
|
||||
ota_commands.append({"device_id": requested_device_id, "url": url, "version": version})
|
||||
return "003"
|
||||
|
||||
async def fake_get_instance():
|
||||
return FakeMqttService()
|
||||
|
||||
async def fake_publish(topic, payload):
|
||||
published.append((topic, payload))
|
||||
return True
|
||||
|
||||
async def fake_schedule_persistence(requested_device_id, label, coro):
|
||||
assert requested_device_id == device_id
|
||||
await coro
|
||||
|
||||
async def fake_insert_or_update(**kwargs):
|
||||
assert kwargs["device_id"] == device_id
|
||||
|
||||
async def fake_get_setting_by_device_id(requested_device_id):
|
||||
return {"device_id": requested_device_id, "device_type": 2, "ota_channel": None}
|
||||
|
||||
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,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"banban.service.device.device_firmware_update_manager.update_firmware_update",
|
||||
fake_update_firmware_update,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"banban.service.device.device_firmware_update_manager.claim_forced_update",
|
||||
fake_claim_forced_update,
|
||||
)
|
||||
monkeypatch.setattr("handlers.mqtt_handler.TalkingQMQTTService.get_instance", fake_get_instance)
|
||||
monkeypatch.setattr(mqtt_service, "_publish", fake_publish)
|
||||
monkeypatch.setattr(mqtt_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.device_setting_service.get_setting_by_device_id", fake_get_setting_by_device_id)
|
||||
monkeypatch.setattr(
|
||||
"handlers.mqtt_handler.wechat_mp_notification_service.schedule_low_battery_notification",
|
||||
lambda **kwargs: None,
|
||||
)
|
||||
|
||||
simulated_device_info = {
|
||||
"msg_id": "000",
|
||||
"data": {
|
||||
"id": device_id,
|
||||
"version": "1.8.2",
|
||||
"device_type": "2",
|
||||
"power": 80,
|
||||
"signal": 4,
|
||||
},
|
||||
}
|
||||
await mqtt_service._handle_device_info(device_id, simulated_device_info)
|
||||
await mqtt_service._handle_device_info(device_id, simulated_device_info)
|
||||
|
||||
assert ota_commands == [{
|
||||
"device_id": device_id,
|
||||
"url": "https://example.com/gps-1.8.5.bin",
|
||||
"version": "1.8.5",
|
||||
}]
|
||||
assert record["update_status"] == "sent"
|
||||
assert record["target_version"] == "1.8.5"
|
||||
assert record["source"] == "forced"
|
||||
assert published == [
|
||||
(f"device/{device_id}/event_resp", {"msg_id": "000", "status": "success"}),
|
||||
(f"device/{device_id}/event_resp", {"msg_id": "000", "status": "success"}),
|
||||
]
|
||||
|
||||
await mqtt_service._handle_ota_response(
|
||||
device_id,
|
||||
{
|
||||
"status": "accepted",
|
||||
"data": {"current_version": "1.8.2", "target_version": "1.8.5"},
|
||||
},
|
||||
)
|
||||
assert record["update_status"] == "accepted"
|
||||
|
||||
await mqtt_service._handle_ota_response(
|
||||
device_id,
|
||||
{"status": "updating", "data": {"version": "1.8.5", "progress": 65}},
|
||||
)
|
||||
assert record["update_status"] == "updating"
|
||||
assert record["progress"] == 65.0
|
||||
|
||||
await mqtt_service._handle_ota_response(
|
||||
device_id,
|
||||
{"status": "success", "data": {"new_version": "1.8.5"}},
|
||||
)
|
||||
assert record["update_status"] == "success"
|
||||
assert record["progress"] == 100.0
|
||||
@@ -24,10 +24,22 @@ async def test_device_info_report_persists_metadata_and_periodic_location(monkey
|
||||
calls.append(("location", kwargs))
|
||||
return {"device_id": kwargs["device_id"], "lat": kwargs["latitude"], "lng": kwargs["longitude"]}
|
||||
|
||||
async def fake_maybe_dispatch_forced_update(**kwargs):
|
||||
del kwargs
|
||||
return None
|
||||
|
||||
async def fake_get_setting_by_device_id(device_id):
|
||||
return {"device_id": device_id, "device_type": 2, "ota_channel": None}
|
||||
|
||||
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.device_setting_service.get_setting_by_device_id", fake_get_setting_by_device_id)
|
||||
monkeypatch.setattr("handlers.mqtt_handler.location_service.report_mqtt_device_location", fake_report_mqtt_device_location)
|
||||
monkeypatch.setattr(
|
||||
"banban.service.device.device_service.maybe_dispatch_forced_update",
|
||||
fake_maybe_dispatch_forced_update,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"handlers.mqtt_handler.wechat_mp_notification_service.schedule_low_battery_notification",
|
||||
lambda **kwargs: None,
|
||||
@@ -103,9 +115,21 @@ async def test_device_info_report_without_location_does_not_write_location(monke
|
||||
async def fake_insert_or_update(**kwargs):
|
||||
calls.append(("settings", kwargs["device_type"]))
|
||||
|
||||
async def fake_maybe_dispatch_forced_update(**kwargs):
|
||||
del kwargs
|
||||
return None
|
||||
|
||||
async def fake_get_setting_by_device_id(device_id):
|
||||
return {"device_id": device_id, "device_type": 1, "ota_channel": None}
|
||||
|
||||
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.device_setting_service.get_setting_by_device_id", fake_get_setting_by_device_id)
|
||||
monkeypatch.setattr(
|
||||
"banban.service.device.device_service.maybe_dispatch_forced_update",
|
||||
fake_maybe_dispatch_forced_update,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"handlers.mqtt_handler.wechat_mp_notification_service.schedule_low_battery_notification",
|
||||
lambda **kwargs: None,
|
||||
|
||||
@@ -28,6 +28,7 @@ async def test_firmware_status_selects_gps_channel(monkeypatch):
|
||||
version="1.1.0",
|
||||
firmware_url="https://example.com/gps-1.1.0.bin",
|
||||
firmware_id=10,
|
||||
force_update=True,
|
||||
)
|
||||
|
||||
async def fake_get_firmware_update_dict(device_id):
|
||||
@@ -44,6 +45,7 @@ async def test_firmware_status_selects_gps_channel(monkeypatch):
|
||||
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["force_update"] is True
|
||||
assert result["update_available"] is True
|
||||
assert result["can_update"] is True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user