fix(ota): settle stale firmware update states
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import type { DeviceFirmwareStatus } from '@/services/device'
|
import type { DeviceFirmwareStatus } from '@/services/device'
|
||||||
import { isFirmwareCurrent, isFirmwareUpdateInProgress } from '../../utils/firmware-status'
|
import { isFirmwareCurrent, isFirmwareTargetReached, isFirmwareUpdateInProgress } from '../../utils/firmware-status'
|
||||||
|
|
||||||
export function firmwareProgressView(status: DeviceFirmwareStatus | null, refreshError = false) {
|
export function firmwareProgressView(status: DeviceFirmwareStatus | null, refreshError = false) {
|
||||||
const state = String(status?.update_status || '').trim().toLowerCase()
|
const state = String(status?.update_status || '').trim().toLowerCase()
|
||||||
const complete = state === 'success' || state === 'completed'
|
const complete = state === 'success' || state === 'completed'
|
||||||
|
|| (isFirmwareUpdateInProgress(state) && isFirmwareTargetReached(status))
|
||||||
const current = !complete && !isFirmwareUpdateInProgress(state) && isFirmwareCurrent(status)
|
const current = !complete && !isFirmwareUpdateInProgress(state) && isFirmwareCurrent(status)
|
||||||
const failed = state === 'failed' && !current
|
const failed = state === 'failed' && !current
|
||||||
const value = Number(status?.progress)
|
const value = Number(status?.progress)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import FirmwareProgress from '@/components/firmware-progress'
|
import FirmwareProgress from '@/components/firmware-progress'
|
||||||
import PageTitle from '@/components/page-title'
|
import PageTitle from '@/components/page-title'
|
||||||
import { getParticipantArtwork, getRoleArtwork } from '@/utils/artwork'
|
import { getParticipantArtwork, getRoleArtwork } from '@/utils/artwork'
|
||||||
import { firmwareSummaryView, isFirmwareCurrent, isFirmwareUpdateInProgress, normalizeFirmwareUpdateStatus } from '@/utils/firmware-status'
|
import { firmwareSummaryView, isFirmwareCurrent, isFirmwareTargetReached, isFirmwareUpdateInProgress, normalizeFirmwareUpdateStatus } from '@/utils/firmware-status'
|
||||||
import { getPageStyle } from '@/utils/page-style'
|
import { getPageStyle } from '@/utils/page-style'
|
||||||
import { setActiveTabRoute } from '@/utils/tab-bar'
|
import { setActiveTabRoute } from '@/utils/tab-bar'
|
||||||
import { View, Text, Image, Input, Button } from '@tarojs/components'
|
import { View, Text, Image, Input, Button } from '@tarojs/components'
|
||||||
@@ -248,7 +248,7 @@ export default function Sleep() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status)) {
|
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status) && !isFirmwareTargetReached(nextFirmwareStatus)) {
|
||||||
startFirmwareStatusPolling(normalizedDeviceId)
|
startFirmwareStatusPolling(normalizedDeviceId)
|
||||||
} else {
|
} else {
|
||||||
stopFirmwareStatusPolling()
|
stopFirmwareStatusPolling()
|
||||||
@@ -272,14 +272,14 @@ export default function Sleep() {
|
|||||||
|
|
||||||
setFirmwareStatus(nextFirmwareStatus)
|
setFirmwareStatus(nextFirmwareStatus)
|
||||||
setFirmwareRefreshError(false)
|
setFirmwareRefreshError(false)
|
||||||
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status)) {
|
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status) && !isFirmwareTargetReached(nextFirmwareStatus)) {
|
||||||
scheduleFirmwareStatusPoll(normalizedDeviceId)
|
scheduleFirmwareStatusPoll(normalizedDeviceId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stopFirmwareStatusPolling()
|
stopFirmwareStatusPolling()
|
||||||
const nextStatus = normalizeFirmwareUpdateStatus(nextFirmwareStatus?.update_status)
|
const nextStatus = normalizeFirmwareUpdateStatus(nextFirmwareStatus?.update_status)
|
||||||
if (isFirmwareUpdateSuccessful(nextStatus)) {
|
if (isFirmwareUpdateSuccessful(nextStatus) || isFirmwareTargetReached(nextFirmwareStatus)) {
|
||||||
Taro.showToast({ title: '系统更新完成', icon: 'success' })
|
Taro.showToast({ title: '系统更新完成', icon: 'success' })
|
||||||
} else if (isFirmwareCurrent(nextFirmwareStatus)) {
|
} else if (isFirmwareCurrent(nextFirmwareStatus)) {
|
||||||
Taro.showToast({ title: '当前已是最新版本', icon: 'none' })
|
Taro.showToast({ title: '当前已是最新版本', icon: 'none' })
|
||||||
@@ -319,7 +319,9 @@ export default function Sleep() {
|
|||||||
if (generation !== firmwareLoadGenerationRef.current) return
|
if (generation !== firmwareLoadGenerationRef.current) return
|
||||||
setFirmwareStatus(nextFirmwareStatus)
|
setFirmwareStatus(nextFirmwareStatus)
|
||||||
setFirmwareRefreshError(false)
|
setFirmwareRefreshError(false)
|
||||||
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status)) setShowFirmwareProgress(true)
|
if (isFirmwareUpdateInProgress(nextFirmwareStatus?.update_status) && !isFirmwareTargetReached(nextFirmwareStatus)) {
|
||||||
|
setShowFirmwareProgress(true)
|
||||||
|
}
|
||||||
syncFirmwareStatusPolling(normalizedDeviceId, nextFirmwareStatus)
|
syncFirmwareStatusPolling(normalizedDeviceId, nextFirmwareStatus)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (generation !== firmwareLoadGenerationRef.current) return
|
if (generation !== firmwareLoadGenerationRef.current) return
|
||||||
@@ -1060,6 +1062,7 @@ export default function Sleep() {
|
|||||||
isFamilyOwner
|
isFamilyOwner
|
||||||
)
|
)
|
||||||
const isFirmwareUpdatingByStatus = isFirmwareUpdateInProgress(firmwareStatus?.update_status)
|
const isFirmwareUpdatingByStatus = isFirmwareUpdateInProgress(firmwareStatus?.update_status)
|
||||||
|
&& !isFirmwareTargetReached(firmwareStatus)
|
||||||
const firmwareSummary = firmwareSummaryView(firmwareStatus)
|
const firmwareSummary = firmwareSummaryView(firmwareStatus)
|
||||||
const canUpdateFirmware = Boolean(
|
const canUpdateFirmware = Boolean(
|
||||||
binding?.device_id &&
|
binding?.device_id &&
|
||||||
|
|||||||
@@ -8,6 +8,28 @@ export function isFirmwareUpdateInProgress(status?: string | null): boolean {
|
|||||||
return ['sent', 'accepted', 'updating'].includes(normalizeFirmwareUpdateStatus(status))
|
return ['sent', 'accepted', 'updating'].includes(normalizeFirmwareUpdateStatus(status))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function firmwareVersionParts(value?: string | null): number[] | null {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase()
|
||||||
|
if (!normalized || ['unknown', 'updating', '0.0.0', '--'].includes(normalized)) return null
|
||||||
|
const parts = normalized.split('.')
|
||||||
|
if (parts.some((part) => !/^\d+$/.test(part))) return null
|
||||||
|
return parts.map((part) => Number(part))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFirmwareTargetReached(status: DeviceFirmwareStatus | null): boolean {
|
||||||
|
if (!status) return false
|
||||||
|
const current = firmwareVersionParts(status.current_version)
|
||||||
|
const target = firmwareVersionParts(status.target_version)
|
||||||
|
if (!current || !target) return false
|
||||||
|
const length = Math.max(current.length, target.length)
|
||||||
|
for (let index = 0; index < length; index += 1) {
|
||||||
|
const currentPart = current[index] || 0
|
||||||
|
const targetPart = target[index] || 0
|
||||||
|
if (currentPart !== targetPart) return currentPart > targetPart
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
export function isFirmwareCurrent(status: DeviceFirmwareStatus | null): boolean {
|
export function isFirmwareCurrent(status: DeviceFirmwareStatus | null): boolean {
|
||||||
const knownVersion = (value?: string | null) => Boolean(value?.trim() && !['unknown', 'updating', '0.0.0', '--'].includes(value.trim().toLowerCase()))
|
const knownVersion = (value?: string | null) => Boolean(value?.trim() && !['unknown', 'updating', '0.0.0', '--'].includes(value.trim().toLowerCase()))
|
||||||
return Boolean(status && !status.update_available && knownVersion(status.current_version) && knownVersion(status.latest_version))
|
return Boolean(status && !status.update_available && knownVersion(status.current_version) && knownVersion(status.latest_version))
|
||||||
@@ -15,7 +37,9 @@ export function isFirmwareCurrent(status: DeviceFirmwareStatus | null): boolean
|
|||||||
|
|
||||||
export function firmwareSummaryView(status: DeviceFirmwareStatus | null) {
|
export function firmwareSummaryView(status: DeviceFirmwareStatus | null) {
|
||||||
if (!status) return { action: '暂不可用', subtitle: '暂时无法获取版本,请稍后重试' }
|
if (!status) return { action: '暂不可用', subtitle: '暂时无法获取版本,请稍后重试' }
|
||||||
if (isFirmwareUpdateInProgress(status.update_status)) return { action: '查看进度', subtitle: '设备正在更新,请稍候' }
|
if (isFirmwareUpdateInProgress(status.update_status) && !isFirmwareTargetReached(status)) {
|
||||||
|
return { action: '查看进度', subtitle: '设备正在更新,请稍候' }
|
||||||
|
}
|
||||||
if (isFirmwareCurrent(status)) return { action: '已最新', subtitle: '当前已是最新版本' }
|
if (isFirmwareCurrent(status)) return { action: '已最新', subtitle: '当前已是最新版本' }
|
||||||
if (normalizeFirmwareUpdateStatus(status.update_status) === 'failed') {
|
if (normalizeFirmwareUpdateStatus(status.update_status) === 'failed') {
|
||||||
return { action: status.update_available ? '更新' : '版本待确认', subtitle: status.can_update ? '更新失败,可重试' : '上次更新失败,当前版本待确认' }
|
return { action: status.update_available ? '更新' : '版本待确认', subtitle: status.can_update ? '更新失败,可重试' : '上次更新失败,当前版本待确认' }
|
||||||
|
|||||||
@@ -99,6 +99,39 @@ test('a failed update with a newer version still offers a retry', () => {
|
|||||||
assert.equal(firmwareProgressView(pending).failed, true)
|
assert.equal(firmwareProgressView(pending).failed, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('an active update whose target version is already installed is settled', () => {
|
||||||
|
const settled = {
|
||||||
|
...currentFirmware,
|
||||||
|
current_version: '1.8.3',
|
||||||
|
latest_version: '1.8.4',
|
||||||
|
target_version: '1.8.3',
|
||||||
|
update_available: true,
|
||||||
|
can_update: true,
|
||||||
|
update_status: 'accepted',
|
||||||
|
progress: 0,
|
||||||
|
}
|
||||||
|
assert.deepEqual(firmwareSummaryView(settled), { action: '更新', subtitle: '发现可更新版本' })
|
||||||
|
const view = firmwareProgressView(settled)
|
||||||
|
assert.equal(view.complete, true)
|
||||||
|
assert.equal(view.progress, 100)
|
||||||
|
assert.match(view.message, /已完成更新/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('an active update below its target stays in progress even at 100 percent', () => {
|
||||||
|
const pending = {
|
||||||
|
...currentFirmware,
|
||||||
|
current_version: '1.8.2',
|
||||||
|
target_version: '1.8.3',
|
||||||
|
latest_version: '1.8.4',
|
||||||
|
update_available: true,
|
||||||
|
can_update: true,
|
||||||
|
update_status: 'updating',
|
||||||
|
progress: 100,
|
||||||
|
}
|
||||||
|
assert.deepEqual(firmwareSummaryView(pending), { action: '查看进度', subtitle: '设备正在更新,请稍候' })
|
||||||
|
assert.equal(firmwareProgressView(pending).complete, false)
|
||||||
|
})
|
||||||
|
|
||||||
test('missing or placeholder versions never claim the device is up to date', () => {
|
test('missing or placeholder versions never claim the device is up to date', () => {
|
||||||
const cases = [null, ...['', 'unknown', '0.0.0', '--', 'updating'].flatMap(value => [
|
const cases = [null, ...['', 'unknown', '0.0.0', '--', 'updating'].flatMap(value => [
|
||||||
{ ...currentFirmware, current_version: value }, { ...currentFirmware, latest_version: value },
|
{ ...currentFirmware, current_version: value }, { ...currentFirmware, latest_version: value },
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ class DeviceService(DatabaseServiceBase):
|
|||||||
latest_parts.extend([0] * (max_len - len(latest_parts)))
|
latest_parts.extend([0] * (max_len - len(latest_parts)))
|
||||||
return latest_parts > current_parts
|
return latest_parts > current_parts
|
||||||
|
|
||||||
def _firmware_sent_timed_out(self, updated_at: Any) -> bool:
|
def _firmware_update_timed_out(self, updated_at: Any) -> bool:
|
||||||
timeout_seconds = max(0, int(settings.ota_sent_timeout_seconds or 0))
|
timeout_seconds = max(0, int(settings.ota_sent_timeout_seconds or 0))
|
||||||
if timeout_seconds <= 0 or not updated_at:
|
if timeout_seconds <= 0 or not updated_at:
|
||||||
return False
|
return False
|
||||||
@@ -331,6 +331,16 @@ class DeviceService(DatabaseServiceBase):
|
|||||||
now = datetime.now(timezone.utc) if updated_at.tzinfo else datetime.now()
|
now = datetime.now(timezone.utc) if updated_at.tzinfo else datetime.now()
|
||||||
return (now - updated_at).total_seconds() >= timeout_seconds
|
return (now - updated_at).total_seconds() >= timeout_seconds
|
||||||
|
|
||||||
|
def _firmware_update_reached_target(self, current_version: str | None, target_version: str | None) -> bool:
|
||||||
|
current = (current_version or "").strip().lower()
|
||||||
|
target = (target_version or "").strip().lower()
|
||||||
|
placeholders = {"", "unknown", "updating", "0.0.0", "--"}
|
||||||
|
if current in placeholders or target in placeholders:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Treat an equal or newer reported version as completion of this OTA attempt.
|
||||||
|
return not self._compare_versions(current, target)
|
||||||
|
|
||||||
async def get_firmware_status(
|
async def get_firmware_status(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -348,9 +358,29 @@ class DeviceService(DatabaseServiceBase):
|
|||||||
update_record = await device_firmware_update_manager.get_firmware_update_dict(device_id)
|
update_record = await device_firmware_update_manager.get_firmware_update_dict(device_id)
|
||||||
update_status = update_record.get("update_status") if update_record else "idle"
|
update_status = update_record.get("update_status") if update_record else "idle"
|
||||||
progress = update_record.get("progress") if update_record else 0.0
|
progress = update_record.get("progress") if update_record else 0.0
|
||||||
target_version = update_record.get("firmware_version") if update_record else None
|
target_version = (
|
||||||
|
(update_record.get("target_version") or update_record.get("firmware_version"))
|
||||||
|
if update_record else None
|
||||||
|
)
|
||||||
updated_at = update_record.get("updated_at") if update_record else None
|
updated_at = update_record.get("updated_at") if update_record else None
|
||||||
if str(update_status or "").strip().lower() == "sent" and self._firmware_sent_timed_out(updated_at):
|
normalized_update_status = str(update_status or "").strip().lower()
|
||||||
|
active_update_statuses = {"sent", "accepted", "updating"}
|
||||||
|
if normalized_update_status in active_update_statuses and self._firmware_update_reached_target(
|
||||||
|
current_version,
|
||||||
|
target_version,
|
||||||
|
):
|
||||||
|
update_status = "success"
|
||||||
|
progress = 100.0
|
||||||
|
await device_firmware_update_manager.update_firmware_update(
|
||||||
|
device_id=device_id,
|
||||||
|
firmware_version=target_version or latest_version or current_version or "unknown",
|
||||||
|
update_status="success",
|
||||||
|
progress=100.0,
|
||||||
|
)
|
||||||
|
update_record = await device_firmware_update_manager.get_firmware_update_dict(device_id)
|
||||||
|
if update_record:
|
||||||
|
updated_at = update_record.get("updated_at") or updated_at
|
||||||
|
elif normalized_update_status in active_update_statuses and self._firmware_update_timed_out(updated_at):
|
||||||
update_status = "failed"
|
update_status = "failed"
|
||||||
progress = 0.0
|
progress = 0.0
|
||||||
await device_firmware_update_manager.update_firmware_update(
|
await device_firmware_update_manager.update_firmware_update(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
@@ -78,6 +80,143 @@ async def test_firmware_status_unknown_channel_cannot_update(monkeypatch):
|
|||||||
assert result["can_update"] is False
|
assert result["can_update"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_firmware_status_settles_active_update_when_target_is_installed(monkeypatch):
|
||||||
|
service = DeviceService()
|
||||||
|
calls = []
|
||||||
|
update_record = {
|
||||||
|
"device_id": "TalkingQ_ZHWK00003111",
|
||||||
|
"firmware_version": "1.8.3",
|
||||||
|
"target_version": "1.8.3",
|
||||||
|
"firmware_url": "https://example.com/gps-1.8.3.bin",
|
||||||
|
"firmware_id": 12,
|
||||||
|
"update_status": "accepted",
|
||||||
|
"progress": 0.0,
|
||||||
|
"updated_at": datetime.now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fake_get_device_status(**kwargs):
|
||||||
|
del kwargs
|
||||||
|
return {
|
||||||
|
"device_id": "TalkingQ_ZHWK00003111",
|
||||||
|
"version": "1.8.3",
|
||||||
|
"device_type": 2,
|
||||||
|
"ota_channel": "gps",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fake_select_latest_for_device(status_row):
|
||||||
|
del status_row
|
||||||
|
return OtaFirmwareSelection(
|
||||||
|
channel_key="gps",
|
||||||
|
version="1.8.4",
|
||||||
|
firmware_url="https://example.com/gps-1.8.4.bin",
|
||||||
|
firmware_id=13,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def fake_get_firmware_update_dict(device_id):
|
||||||
|
assert device_id == "TalkingQ_ZHWK00003111"
|
||||||
|
return update_record
|
||||||
|
|
||||||
|
async def fake_update_firmware_update(**kwargs):
|
||||||
|
calls.append(kwargs)
|
||||||
|
return True
|
||||||
|
|
||||||
|
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)
|
||||||
|
monkeypatch.setattr("banban.service.device.device_firmware_update_manager.update_firmware_update", fake_update_firmware_update)
|
||||||
|
|
||||||
|
result = await service.get_firmware_status(device_id="TalkingQ_ZHWK00003111", user_id=1)
|
||||||
|
|
||||||
|
assert result["update_status"] == "success"
|
||||||
|
assert result["progress"] == 100.0
|
||||||
|
assert result["update_available"] is True
|
||||||
|
assert calls == [{
|
||||||
|
"device_id": "TalkingQ_ZHWK00003111",
|
||||||
|
"firmware_version": "1.8.3",
|
||||||
|
"update_status": "success",
|
||||||
|
"progress": 100.0,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize("active_status", ["sent", "accepted", "updating"])
|
||||||
|
async def test_firmware_status_times_out_stale_active_update(monkeypatch, active_status):
|
||||||
|
service = DeviceService()
|
||||||
|
calls = []
|
||||||
|
update_record = {
|
||||||
|
"device_id": "TalkingQ_ZHWK00003111",
|
||||||
|
"firmware_version": "1.8.3",
|
||||||
|
"target_version": "1.8.3",
|
||||||
|
"firmware_url": "https://example.com/gps-1.8.3.bin",
|
||||||
|
"firmware_id": 12,
|
||||||
|
"update_status": active_status,
|
||||||
|
"progress": 42.0,
|
||||||
|
"updated_at": datetime.now() - timedelta(seconds=301),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fake_get_device_status(**kwargs):
|
||||||
|
del kwargs
|
||||||
|
return {
|
||||||
|
"device_id": "TalkingQ_ZHWK00003111",
|
||||||
|
"version": "1.8.2",
|
||||||
|
"device_type": 2,
|
||||||
|
"ota_channel": "gps",
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fake_select_latest_for_device(status_row):
|
||||||
|
del status_row
|
||||||
|
return OtaFirmwareSelection(
|
||||||
|
channel_key="gps",
|
||||||
|
version="1.8.4",
|
||||||
|
firmware_url="https://example.com/gps-1.8.4.bin",
|
||||||
|
firmware_id=13,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def fake_get_firmware_update_dict(device_id):
|
||||||
|
assert device_id == "TalkingQ_ZHWK00003111"
|
||||||
|
return update_record
|
||||||
|
|
||||||
|
async def fake_update_firmware_update(**kwargs):
|
||||||
|
calls.append(kwargs)
|
||||||
|
return True
|
||||||
|
|
||||||
|
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)
|
||||||
|
monkeypatch.setattr("banban.service.device.device_firmware_update_manager.update_firmware_update", fake_update_firmware_update)
|
||||||
|
|
||||||
|
result = await service.get_firmware_status(device_id="TalkingQ_ZHWK00003111", user_id=1)
|
||||||
|
|
||||||
|
assert result["update_status"] == "failed"
|
||||||
|
assert result["progress"] == 0.0
|
||||||
|
assert result["can_update"] is True
|
||||||
|
assert calls == [{
|
||||||
|
"device_id": "TalkingQ_ZHWK00003111",
|
||||||
|
"firmware_version": "1.8.3",
|
||||||
|
"update_status": "failed",
|
||||||
|
"progress": 0.0,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
def test_firmware_update_not_complete_until_target_version_is_reported():
|
||||||
|
service = DeviceService()
|
||||||
|
|
||||||
|
assert not service._firmware_update_reached_target("1.8.2", "1.8.3")
|
||||||
|
assert not service._firmware_update_reached_target("unknown", "1.8.3")
|
||||||
|
assert not service._firmware_update_reached_target("1.8.4", "unknown")
|
||||||
|
assert service._firmware_update_reached_target("1.8.3", "1.8.3")
|
||||||
|
assert service._firmware_update_reached_target("1.8.4", "1.8.3")
|
||||||
|
|
||||||
|
|
||||||
|
def test_firmware_update_timeout_does_not_fail_recent_progress(monkeypatch):
|
||||||
|
service = DeviceService()
|
||||||
|
monkeypatch.setattr("banban.service.device.settings.ota_sent_timeout_seconds", 300)
|
||||||
|
|
||||||
|
assert not service._firmware_update_timed_out(datetime.now())
|
||||||
|
assert service._firmware_update_timed_out(datetime.now() - timedelta(seconds=301))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_start_firmware_update_records_channel_and_keeps_mqtt_payload(monkeypatch):
|
async def test_start_firmware_update_records_channel_and_keeps_mqtt_payload(monkeypatch):
|
||||||
service = DeviceService()
|
service = DeviceService()
|
||||||
|
|||||||
Reference in New Issue
Block a user