From ad5dcb5251a51503c0318cfdbd75316f1525f609 Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:28:36 +0100 Subject: [PATCH 1/4] feat: add test for failing GitHub API call. --- pyproject.toml | 1 + tests/qt/test_about_modal.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/qt/test_about_modal.py diff --git a/pyproject.toml b/pyproject.toml index 9d182607d..eb2eb17a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ pyinstaller = ["Pyinstaller~=6.13"] pytest = [ "pytest==8.3.5", "pytest-cov==6.1.1", + "pytest-mock==3.15.1", "pytest-qt==4.4.0", "syrupy==4.9.1", ] diff --git a/tests/qt/test_about_modal.py b/tests/qt/test_about_modal.py new file mode 100644 index 000000000..112e066b8 --- /dev/null +++ b/tests/qt/test_about_modal.py @@ -0,0 +1,14 @@ +from pytestqt.qtbot import QtBot + +from tagstudio.qt.mixed.about_modal import AboutModal + + +def test_github_api_unavailable(qtbot: QtBot, mocker) -> None: + mocker.patch( + "requests.get", + side_effect=ConnectionError( + "Failed to resolve 'api.github.com' ([Errno -3] Temporary failure in name resolution)" + ) + ) + modal = AboutModal("/tmp") + qtbot.addWidget(modal) From eaddc0a7cc398592ca73d4d36d2c41a7d3a716a4 Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:32:23 +0100 Subject: [PATCH 2/4] fix: don't raise exceptions in get_most_recent_release_version. --- src/tagstudio/core/ts_core.py | 25 +++++++++++++------ .../qt/controllers/out_of_date_message_box.py | 3 ++- src/tagstudio/qt/mixed/about_modal.py | 3 ++- src/tagstudio/qt/ts_qt.py | 3 ++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/tagstudio/core/ts_core.py b/src/tagstudio/core/ts_core.py index c91641dd9..d4ef0e099 100644 --- a/src/tagstudio/core/ts_core.py +++ b/src/tagstudio/core/ts_core.py @@ -193,18 +193,29 @@ def _build_instagram_url(cls, entry: Entry): @staticmethod @lru_cache(maxsize=1) - def get_most_recent_release_version() -> str: - """Get the version of the most recent Github release.""" - resp = requests.get("https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest") - assert resp.status_code == 200, "Could not fetch information on latest release." + def get_most_recent_release_version() -> str | None: + """Get the version of the most recent GitHub release.""" + try: + resp = requests.get("https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest") + except Exception as e: + logger.error("Error getting most recent GitHub release.", error=e) + return None + + if resp.status_code != 200: + logger.error("Error getting most recent GitHub release.", status_code=resp.status_code) + return None data = resp.json() tag: str = data["tag_name"] - assert tag.startswith("v") + if not tag.startswith("v"): + logger.error("Unexpected tag format.", tag=tag) + return None version = tag[1:] - # the assert does not allow for prerelease/build, + # the assertion does not allow for prerelease/build, # because the latest release should never have them - assert re.match(r"^\d+\.\d+\.\d+$", version) is not None, "Invalid version format." + if re.match(r"^\d+\.\d+\.\d+$", version) is not None: + logger.error("Invalid version format.", version=version) + return None return version diff --git a/src/tagstudio/qt/controllers/out_of_date_message_box.py b/src/tagstudio/qt/controllers/out_of_date_message_box.py index 5254e57e1..163cc4558 100644 --- a/src/tagstudio/qt/controllers/out_of_date_message_box.py +++ b/src/tagstudio/qt/controllers/out_of_date_message_box.py @@ -4,6 +4,7 @@ from tagstudio.core.constants import VERSION from tagstudio.core.ts_core import TagStudioCore +from tagstudio.core.utils.types import unwrap from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.translations import Translations @@ -30,7 +31,7 @@ def __init__(self): red = get_ui_color(ColorType.PRIMARY, UiColor.RED) green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN) - latest_release_version = TagStudioCore.get_most_recent_release_version() + latest_release_version = unwrap(TagStudioCore.get_most_recent_release_version()) status = Translations.format( "version_modal.status", installed_version=f"{VERSION}", diff --git a/src/tagstudio/qt/mixed/about_modal.py b/src/tagstudio/qt/mixed/about_modal.py index 12942df95..e6be1c7de 100644 --- a/src/tagstudio/qt/mixed/about_modal.py +++ b/src/tagstudio/qt/mixed/about_modal.py @@ -21,6 +21,7 @@ from tagstudio.core.constants import VERSION, VERSION_BRANCH from tagstudio.core.enums import Theme from tagstudio.core.ts_core import TagStudioCore +from tagstudio.core.utils.types import unwrap from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color from tagstudio.qt.previews.vendored import ffmpeg from tagstudio.qt.resource_manager import ResourceManager @@ -106,7 +107,7 @@ def __init__(self, config_path): # Version version_title = QLabel("Version") - most_recent_release = TagStudioCore.get_most_recent_release_version() + most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN") version_content_style = self.form_content_style if most_recent_release == VERSION: version_content = QLabel(f"{VERSION}") diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index 433047786..a7c3f5cd6 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -595,7 +595,8 @@ def create_about_modal(): if not which(FFMPEG_CMD) or not which(FFPROBE_CMD): FfmpegMissingMessageBox().show() - if is_version_outdated(VERSION, TagStudioCore.get_most_recent_release_version()): + latest_version = TagStudioCore.get_most_recent_release_version() + if latest_version and is_version_outdated(VERSION, latest_version): OutOfDateMessageBox().exec() self.app.exec() From 15ea395eea207f3e39b4274200ac91f4ae6bdcc4 Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:34:04 +0100 Subject: [PATCH 3/4] style: apply ruff. --- src/tagstudio/core/ts_core.py | 4 +++- tests/qt/test_about_modal.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tagstudio/core/ts_core.py b/src/tagstudio/core/ts_core.py index d4ef0e099..a90cdd22e 100644 --- a/src/tagstudio/core/ts_core.py +++ b/src/tagstudio/core/ts_core.py @@ -196,7 +196,9 @@ def _build_instagram_url(cls, entry: Entry): def get_most_recent_release_version() -> str | None: """Get the version of the most recent GitHub release.""" try: - resp = requests.get("https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest") + resp = requests.get( + "https://api.github.com/repos/TagStudioDev/TagStudio/releases/latest" + ) except Exception as e: logger.error("Error getting most recent GitHub release.", error=e) return None diff --git a/tests/qt/test_about_modal.py b/tests/qt/test_about_modal.py index 112e066b8..523eb6a60 100644 --- a/tests/qt/test_about_modal.py +++ b/tests/qt/test_about_modal.py @@ -8,7 +8,7 @@ def test_github_api_unavailable(qtbot: QtBot, mocker) -> None: "requests.get", side_effect=ConnectionError( "Failed to resolve 'api.github.com' ([Errno -3] Temporary failure in name resolution)" - ) + ), ) modal = AboutModal("/tmp") qtbot.addWidget(modal) From 2803eaa3ec3008b1afc83d7551f87e3131f5dbc0 Mon Sep 17 00:00:00 2001 From: Sola-ris <190788035+Sola-ris@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:45:53 +0100 Subject: [PATCH 4/4] That not should not have been there. --- src/tagstudio/core/ts_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tagstudio/core/ts_core.py b/src/tagstudio/core/ts_core.py index a90cdd22e..d0568869c 100644 --- a/src/tagstudio/core/ts_core.py +++ b/src/tagstudio/core/ts_core.py @@ -216,7 +216,7 @@ def get_most_recent_release_version() -> str | None: version = tag[1:] # the assertion does not allow for prerelease/build, # because the latest release should never have them - if re.match(r"^\d+\.\d+\.\d+$", version) is not None: + if re.match(r"^\d+\.\d+\.\d+$", version) is None: logger.error("Invalid version format.", version=version) return None