"""Update-check regression for Issue #104. STABLE_RELEASE_API used limit=1, so it only ever saw the single newest release on Gitea regardless of type. Since nightly/dev prereleases publish far more often than stable releases, that newest release is almost always a prerelease - the stable_releases filter (not prerelease) then found nothing and /api/update/check returned "no stable releases found" even though older stable releases exist. """ from unittest.mock import AsyncMock, MagicMock, patch import pytest @pytest.mark.asyncio async def test_update_apply_invalid_json_returns_400(client): """A malformed/non-JSON body must be a clean 400, not an unhandled 500 with a raw JSONDecodeError traceback (code review finding).""" c, _ = client resp = await c.post("/api/update/apply", data=b"not json", headers={"Content-Type": "application/json"}) assert resp.status == 400 @pytest.mark.asyncio async def test_update_check_testing_channel_is_docker_only(client): """A testing- build has no Gitea releases at all - the check must report a docker-only channel with nothing to update, NOT fall through to the stable path and wrongly offer a stable "update". Must not even call the Gitea API.""" c, bridge = client bridge._read_version = lambda: "testing-2e4dbf0" with patch("aiohttp.ClientSession.get") as mock_get: resp = await c.get("/api/update/check") data = await resp.json() assert resp.status == 200 assert data["update_available"] is False assert data["docker_only"] is True assert data["current"] == "testing-2e4dbf0" mock_get.assert_not_called() # no Gitea round-trip for the testing channel @pytest.mark.asyncio async def test_update_apply_testing_channel_blocked(client): """Self-update must be refused on the testing channel, same as nightly - testing images are delivered via Docker only.""" c, bridge = client bridge._read_version = lambda: "testing-2e4dbf0" resp = await c.post("/api/update/apply", json={"tag": "whatever"}) data = await resp.json() assert resp.status == 400 assert "testing" in data["error"] assert "docker" in data["error"].lower() def _fake_releases_response(payload): resp = MagicMock() resp.status = 200 resp.json = AsyncMock(return_value=payload) ctx = MagicMock() ctx.__aenter__ = AsyncMock(return_value=resp) ctx.__aexit__ = AsyncMock(return_value=False) return ctx @pytest.mark.asyncio async def test_stable_update_check_finds_release_behind_newer_prereleases(client): c, bridge = client bridge._read_version = lambda: "0.9.27" releases = ( [{"tag_name": f"nightly-0.9.30-nightly{i}", "prerelease": True} for i in range(1, 7)] + [{"tag_name": "v0.9.29", "prerelease": False, "body": "changelog"}] ) with patch("aiohttp.ClientSession.get", return_value=_fake_releases_response(releases)): resp = await c.get("/api/update/check") data = await resp.json() assert resp.status == 200 assert data["latest"] == "0.9.29" assert data["update_available"] is True @pytest.mark.asyncio async def test_stable_update_check_requests_enough_releases_to_skip_prereleases(client): """The API URL itself must ask for more than the single newest release - a limit=1 request can never find a stable release behind a run of prereleases no matter how the response is parsed.""" c, bridge = client bridge._read_version = lambda: "0.9.27" import re assert not re.search(r"limit=1(?!\d)", bridge.STABLE_RELEASE_API), ( "STABLE_RELEASE_API must request more than 1 release, otherwise a " "recent nightly/dev prerelease being the newest release hides all " "stable releases behind it (Issue #104)" )