Distinguish "staged, not yet applied" from "up to date" in firmware check
update_available only compared the latest Gitea release against what's staged, not what the frame is actually running -- so once a release was staged (manually or via auto-update) but the frame hadn't woken up and applied it yet, "Check now" reported "Up to date" even though the device was still on the old version. Report the frame's actual running version and use it to show a distinct "staged, applies on next wake" message instead.
This commit is contained in:
@@ -384,6 +384,7 @@ def api_firmware_check(
|
||||
"board": frame.device_board_variant or None,
|
||||
"latest_version": frame.firmware_gitea_latest_version or None,
|
||||
"staged_version": frame.firmware_available_version or None,
|
||||
"running_version": frame.device_firmware_version or None,
|
||||
"update_available": update_available,
|
||||
}
|
||||
|
||||
|
||||
@@ -339,8 +339,15 @@ async function loadFirmwareCheck(force) {
|
||||
statusEl.textContent = 'Waiting for the frame to check in before it can look up the right build.';
|
||||
btn.style.display = 'none';
|
||||
} else if (data.update_available) {
|
||||
statusEl.textContent = `Update available: v${data.latest_version}.`;
|
||||
statusEl.textContent = `Update available: v${data.latest_version}` +
|
||||
(data.running_version ? ` (currently running v${data.running_version}).` : '.');
|
||||
btn.style.display = 'inline-block';
|
||||
} else if (data.latest_version && data.running_version && data.running_version !== data.latest_version) {
|
||||
// Already staged (or auto-applied) but the frame hasn't woken up
|
||||
// and picked it up yet -- not "up to date" until it actually has.
|
||||
statusEl.textContent = `v${data.latest_version} staged -- applies next time the frame wakes ` +
|
||||
`(currently running v${data.running_version}).`;
|
||||
btn.style.display = 'none';
|
||||
} else if (data.latest_version) {
|
||||
statusEl.textContent = `Up to date (v${data.latest_version}).`;
|
||||
btn.style.display = 'none';
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""POST /api/frames/{id}/firmware/check -- the "Check now" button's
|
||||
endpoint. update_available compares the latest Gitea release against
|
||||
what's *staged* (firmware_available_version), not what the device is
|
||||
actually running (device_firmware_version) -- those can differ once a
|
||||
release has been staged/auto-applied but the frame hasn't woken up and
|
||||
picked it up yet. running_version lets the UI tell "up to date" apart
|
||||
from "staged, waiting for the frame to apply it" instead of collapsing
|
||||
both into the same message."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app import gitea_releases
|
||||
from app.models import Frame
|
||||
|
||||
from .conftest import csrf_headers
|
||||
|
||||
|
||||
def _setup_frame(db_session, monkeypatch, latest_version, **overrides):
|
||||
"""firmware_update_checked_at starts at 0, so the endpoint always
|
||||
tries a real Gitea fetch on a fresh frame regardless of force= --
|
||||
stub it out rather than hitting the network."""
|
||||
monkeypatch.setattr(
|
||||
gitea_releases, "fetch_latest_release", lambda *a, **k: {"version": latest_version, "assets": {}}
|
||||
)
|
||||
frame = db_session.get(Frame, 1)
|
||||
frame.firmware_update_repo_url = "https://git.example.com/owner/repo"
|
||||
frame.device_board_variant = "devkit"
|
||||
for key, value in overrides.items():
|
||||
setattr(frame, key, value)
|
||||
db_session.commit()
|
||||
return frame
|
||||
|
||||
|
||||
def test_up_to_date_when_running_matches_latest(client, db_session, monkeypatch):
|
||||
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
||||
_setup_frame(
|
||||
db_session,
|
||||
monkeypatch,
|
||||
"1.4.1",
|
||||
firmware_available_version="1.4.1",
|
||||
device_firmware_version="1.4.1",
|
||||
)
|
||||
|
||||
resp = client.post("/api/frames/1/firmware/check", headers=csrf_headers(client))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["update_available"] is False
|
||||
assert data["latest_version"] == "1.4.1"
|
||||
assert data["running_version"] == "1.4.1"
|
||||
|
||||
|
||||
def test_staged_but_not_yet_running_is_not_update_available(client, db_session, monkeypatch):
|
||||
"""A release already staged (e.g. by a previous auto-update) but not
|
||||
yet applied by the device isn't "an update is available" -- there's
|
||||
nothing left to fetch/stage -- but it also isn't silently "up to
|
||||
date" from the UI's perspective, since running_version still lags."""
|
||||
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
||||
_setup_frame(
|
||||
db_session,
|
||||
monkeypatch,
|
||||
"1.4.1",
|
||||
firmware_available_version="1.4.1",
|
||||
device_firmware_version="1.3.0",
|
||||
)
|
||||
|
||||
resp = client.post("/api/frames/1/firmware/check", headers=csrf_headers(client))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["update_available"] is False
|
||||
assert data["latest_version"] == "1.4.1"
|
||||
assert data["staged_version"] == "1.4.1"
|
||||
assert data["running_version"] == "1.3.0"
|
||||
|
||||
|
||||
def test_update_available_reports_running_version(client, db_session, monkeypatch):
|
||||
client.post("/setup", data={"username": "alice", "password": "hunter22"})
|
||||
_setup_frame(
|
||||
db_session,
|
||||
monkeypatch,
|
||||
"1.4.1",
|
||||
firmware_available_version="1.3.0",
|
||||
device_firmware_version="1.3.0",
|
||||
)
|
||||
|
||||
resp = client.post("/api/frames/1/firmware/check", headers=csrf_headers(client))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["update_available"] is True
|
||||
assert data["running_version"] == "1.3.0"
|
||||
Reference in New Issue
Block a user