"""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"