diff --git a/app/updater.py b/app/updater.py index f06dc12..953ded5 100644 --- a/app/updater.py +++ b/app/updater.py @@ -133,6 +133,20 @@ def install_update(archive_bytes: bytes, base_dir: Path, def _api(session, url: str, token: str): headers = {"Authorization": f"token {token}"} if token else {} response = session.get(url, headers=headers, timeout=30) + + # На приватном репозитории Gitea отвечает 404, а не 403: она скрывает сам + # факт его существования. Поэтому 404 почти всегда означает не «нет релиза», + # а «этот токен сюда не пускают». + if response.status_code in (401, 403, 404): + if not token: + raise UpdateError( + "репозиторий не отдаётся без токена. Впишите его в config.toml, " + "раздел [update], поле token (Gitea: Settings - Applications - " + "Generate Token, право read:repository)") + raise UpdateError( + f"Gitea ответила {response.status_code}. Обычно это значит, что токен " + "в config.toml неверный или у него нет права read:repository") + response.raise_for_status() return response diff --git a/app/version.py b/app/version.py index b3f4756..ae73625 100644 --- a/app/version.py +++ b/app/version.py @@ -1 +1 @@ -__version__ = "0.1.2" +__version__ = "0.1.3" diff --git a/tests/test_updater.py b/tests/test_updater.py index af838bd..f304794 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -158,3 +158,34 @@ class TestPipelineImportSafety: replacements_path=tmp_path / "r.txt", base_dir=tmp_path) with pytest.raises(ModelsMissing, match="ffmpeg"): _ = p.ffmpeg + + +class TestApiDiagnostics: + """404 на приватном репозитории Gitea означает проблему с токеном, не отсутствие релиза.""" + + class FakeResponse: + def __init__(self, status): + self.status_code = status + + def raise_for_status(self): + raise AssertionError("до сюда доходить не должно") + + class FakeSession: + def __init__(self, status): + self.status = status + + def get(self, url, headers=None, timeout=None): + return TestApiDiagnostics.FakeResponse(self.status) + + @pytest.mark.parametrize("status", [401, 403, 404]) + def test_auth_errors_mention_token(self, status): + from app.updater import _api + + with pytest.raises(UpdateError, match="токен"): + _api(self.FakeSession(status), "http://x", token="какой-то") + + def test_missing_token_says_where_to_put_it(self): + from app.updater import _api + + with pytest.raises(UpdateError, match="config.toml"): + _api(self.FakeSession(404), "http://x", token="")