feat(update): treat the testing channel as docker-only in the update check

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. (Mirrors the same fix already on the testing
branch, applied here to the pre-refactor monolithic module.)
This commit is contained in:
2026-08-05 09:35:26 +02:00
parent 3daf945612
commit 0c520ffa00
2 changed files with 53 additions and 2 deletions

View File

@@ -5451,6 +5451,21 @@ class KobraXBridge:
async def handle_api_update_check(self, request):
current = self._read_version()
# Testing channel (testing-<sha>) has no Gitea releases at all - it's
# a Docker-only channel. Report that directly instead of falling
# through to the stable path (which would wrongly offer a stable
# "update"). The :testing image is rolling, so there's nothing to
# compare a version against.
if "testing" in current:
return web.json_response({
"current": current,
"latest": current,
"update_available": False,
"tag": current,
"docker_only": True,
"changelog": "Testing channel - updates are delivered via Docker: "
"docker compose pull && docker compose up -d",
})
is_nightly = "nightly" in current
is_dev = "-dev+" in current
if is_nightly:
@@ -5535,9 +5550,11 @@ class KobraXBridge:
except Exception:
return web.json_response({"error": "invalid json"}, status=400)
new_tag = data.get("tag", "")
if "nightly" in self._read_version():
_cur = self._read_version()
if "nightly" in _cur or "testing" in _cur:
channel = "testing" if "testing" in _cur else "nightly"
return web.json_response(
{"error": "nightly updates are delivered via Docker: "
{"error": f"{channel} updates are delivered via Docker: "
"docker compose pull && docker compose up -d"}, status=400)
if getattr(sys, "frozen", False):
return web.json_response(

View File

@@ -21,6 +21,40 @@ async def test_update_apply_invalid_json_returns_400(client):
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"
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