All checks were successful
Testing Build / build (push) Successful in 8m47s
A testing-<sha> build has no Gitea releases (the testing workflow builds only a Docker image), so the in-app update check must not fall through to the stable path - which would wrongly offer a stable "update". handle_api_update_check now short-circuits for a "testing" version with docker_only:true and nothing to update (no Gitea round-trip at all), and handle_api_update_apply refuses self-update on the testing channel the same way it already does for nightly. Two new tests cover both.
104 lines
3.9 KiB
Python
104 lines
3.9 KiB
Python
"""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-<sha> 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"
|
|
|
|
# Patch the API so that if the handler wrongly tried to fetch releases,
|
|
# the test would notice (mock returns something, but the handler must not
|
|
# reach it).
|
|
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)"
|
|
)
|