from __future__ import annotations import os import tempfile import unittest from pathlib import Path from unittest.mock import patch os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") from PySide6.QtWidgets import QApplication from app.services.update_service import UpdateError, UpdateInfo from app.widgets.start_tab import StartTab, _UpdateCheckThread, _UpdateDownloadThread class StartTabUpdateTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls._app = QApplication.instance() or QApplication([]) def test_update_button_is_below_settings_and_hidden_initially(self) -> None: tab = StartTab(lambda: None) self.assertTrue(tab._update_btn.isHidden()) self.assertLess( tab._settings_column_layout.indexOf(tab._settings_btn), tab._settings_column_layout.indexOf(tab._update_btn), ) tab.shutdown() def test_available_update_shows_download_button(self) -> None: tab = StartTab(lambda: None) info = UpdateInfo( tag="v2.1.0", version="2.1.0", asset_name="plate_config_app-windows-x86_64.zip", download_url="https://git.robotail.pro/download/test", ) tab._on_update_check_done(info) self.assertFalse(tab._update_btn.isHidden()) self.assertEqual("Скачать обновление", tab._update_btn.text()) self.assertIn("v2.1.0", tab._update_btn.toolTip()) tab.shutdown() def test_no_update_hides_previously_visible_button(self) -> None: tab = StartTab(lambda: None) tab._on_update_check_done(self._update_info()) tab._on_update_check_done(None) self.assertTrue(tab._update_btn.isHidden()) self.assertIsNone(tab._available_update) tab.shutdown() def test_progress_updates_button_text(self) -> None: tab = StartTab(lambda: None) tab._on_update_progress(57) self.assertEqual("Скачивание 57%", tab._update_btn.text()) tab.shutdown() def test_download_failure_resets_button_and_warns(self) -> None: tab = StartTab(lambda: None) tab._update_btn.setEnabled(False) tab._update_btn.setText("Скачивание 42%") with patch("app.widgets.start_tab.QMessageBox.warning") as warning: tab._on_update_failed("ошибка сети") self.assertTrue(tab._update_btn.isEnabled()) self.assertEqual("Скачать обновление", tab._update_btn.text()) warning.assert_called_once() self.assertIn("ошибка сети", warning.call_args.args[2]) tab.shutdown() def test_launch_failure_removes_download_and_warns(self) -> None: tab = StartTab(lambda: None) with tempfile.TemporaryDirectory() as directory: downloaded = Path(directory) / ".downloaded-update" downloaded.write_bytes(b"MZupdate") with ( patch( "app.widgets.start_tab.launch_update", side_effect=UpdateError("не удалось запустить helper"), ), patch("app.widgets.start_tab.QMessageBox.warning") as warning, ): tab._on_update_downloaded(downloaded) self.assertFalse(downloaded.exists()) self.assertEqual("Скачать обновление", tab._update_btn.text()) self.assertTrue(tab._update_btn.isEnabled()) warning.assert_called_once() tab.shutdown() def test_successful_download_launches_update_and_closes_window(self) -> None: tab = StartTab(lambda: None) downloaded = Path("downloaded-update") with ( patch("app.widgets.start_tab.launch_update") as launcher, patch.object(tab, "close") as close_window, ): tab._on_update_downloaded(downloaded) launcher.assert_called_once_with(downloaded) close_window.assert_called_once_with() self.assertEqual("Перезапуск…", tab._update_btn.text()) tab.shutdown() def test_update_check_thread_hides_network_errors(self) -> None: thread = _UpdateCheckThread() results: list[UpdateInfo | None] = [] thread.result_ready.connect(results.append) with patch( "app.widgets.start_tab.check_for_update", side_effect=UpdateError("offline"), ): thread.run() self.assertEqual([None], results) def test_download_thread_emits_progress_and_result(self) -> None: info = self._update_info() result_path = Path("downloaded-update") thread = _UpdateDownloadThread(info, Path(".")) progress_values: list[int] = [] results: list[Path] = [] thread.progress_changed.connect(progress_values.append) thread.result_ready.connect(results.append) def fake_download(_info, _directory, *, progress, cancelled): self.assertFalse(cancelled()) progress(75) return result_path with patch("app.widgets.start_tab.download_update", side_effect=fake_download): thread.run() self.assertEqual([75], progress_values) self.assertEqual([result_path], results) @staticmethod def _update_info() -> UpdateInfo: return UpdateInfo( tag="v2.1.0", version="2.1.0", asset_name="plate_config_app-windows-x86_64.zip", download_url="https://git.robotail.pro/download/test", ) if __name__ == "__main__": unittest.main()