diff --git a/bridge_endpoints.py b/bridge_endpoints.py index 0fe9fee..4b590e7 100644 --- a/bridge_endpoints.py +++ b/bridge_endpoints.py @@ -2357,6 +2357,21 @@ class EndpointsMixin: async def handle_api_update_check(self, request): current = self._read_version() + # Testing channel (testing-) 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: @@ -2441,9 +2456,11 @@ class EndpointsMixin: 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( diff --git a/tests/test_update_check.py b/tests/test_update_check.py index 4f11827..65bd03c 100644 --- a/tests/test_update_check.py +++ b/tests/test_update_check.py @@ -21,6 +21,43 @@ 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- 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