test_version_checker.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. """
  2. Tests for the Version Checker UI component (Story 7.26).
  3. """
  4. import pytest
  5. from datetime import datetime
  6. from pathlib import Path
  7. import tempfile
  8. from PyQt6.QtWidgets import QApplication
  9. from src.ui.version_checker import (
  10. UpdateSeverity,
  11. VersionInfo,
  12. CurrentVersion,
  13. VersionCheckResult,
  14. VersionCheckThread,
  15. CURRENT_VERSION,
  16. get_version_checker,
  17. )
  18. @pytest.fixture
  19. def app(qtbot):
  20. """Create QApplication for tests."""
  21. return QApplication.instance() or QApplication([])
  22. @pytest.fixture
  23. def sample_version_info():
  24. """Create sample version info."""
  25. return VersionInfo(
  26. version="1.2.0",
  27. release_date=datetime.now(),
  28. download_url="https://example.com/download/v1.2.0",
  29. release_notes="New features and bug fixes",
  30. file_size=50000000,
  31. severity=UpdateSeverity.OPTIONAL
  32. )
  33. @pytest.fixture
  34. def sample_current_version():
  35. """Create sample current version."""
  36. return CurrentVersion(
  37. version="1.0.0",
  38. build_date=datetime.now(),
  39. commit_hash="abc123",
  40. channel="stable"
  41. )
  42. class TestUpdateSeverity:
  43. """Tests for UpdateSeverity enum."""
  44. def test_severity_values(self):
  45. """Test UpdateSeverity enum values."""
  46. assert UpdateSeverity.OPTIONAL.value == "optional"
  47. assert UpdateSeverity.RECOMMENDED.value == "recommended"
  48. assert UpdateSeverity.CRITICAL.value == "critical"
  49. class TestVersionInfo:
  50. """Tests for VersionInfo."""
  51. def test_creation(self):
  52. """Test creating version info."""
  53. info = VersionInfo(
  54. version="1.0.0",
  55. release_date=datetime.now(),
  56. download_url="https://example.com",
  57. release_notes="Release notes"
  58. )
  59. assert info.version == "1.0.0"
  60. assert info.severity == UpdateSeverity.OPTIONAL
  61. def test_with_all_fields(self):
  62. """Test creating with all fields."""
  63. info = VersionInfo(
  64. version="2.0.0",
  65. release_date=datetime.now(),
  66. download_url="https://example.com",
  67. release_notes="Notes",
  68. file_size=1000000,
  69. checksum="abc123",
  70. severity=UpdateSeverity.CRITICAL,
  71. minimum_compatible_version="1.5.0",
  72. breaking_changes=True
  73. )
  74. assert info.file_size == 1000000
  75. assert info.severity == UpdateSeverity.CRITICAL
  76. assert info.breaking_changes is True
  77. class TestCurrentVersion:
  78. """Tests for CurrentVersion."""
  79. def test_creation(self):
  80. """Test creating current version."""
  81. version = CurrentVersion(
  82. version="1.0.0",
  83. build_date=datetime.now(),
  84. commit_hash="abc123",
  85. channel="stable"
  86. )
  87. assert version.version == "1.0.0"
  88. assert version.channel == "stable"
  89. def test_partial_fields(self):
  90. """Test creating with partial fields."""
  91. version = CurrentVersion(version="1.0.0")
  92. assert version.version == "1.0.0"
  93. assert version.build_date is None
  94. class TestVersionCheckResult:
  95. """Tests for VersionCheckResult."""
  96. def test_has_update_true(self, sample_current_version, sample_version_info):
  97. """Test has_update when update is available."""
  98. result = VersionCheckResult(sample_current_version, sample_version_info)
  99. assert result.has_update is True
  100. def test_has_update_false(self, sample_current_version):
  101. """Test has_update when no update available."""
  102. old_version = VersionInfo(
  103. version="0.9.0",
  104. release_date=datetime.now(),
  105. download_url="https://example.com",
  106. release_notes="Old version"
  107. )
  108. result = VersionCheckResult(sample_current_version, old_version)
  109. assert result.has_update is False
  110. def test_has_update_no_latest(self, sample_current_version):
  111. """Test has_update when latest is None."""
  112. result = VersionCheckResult(sample_current_version, None)
  113. assert result.has_update is False
  114. def test_is_critical(self, sample_current_version):
  115. """Test is_critical property."""
  116. critical_version = VersionInfo(
  117. version="2.0.0",
  118. release_date=datetime.now(),
  119. download_url="https://example.com",
  120. release_notes="Critical update",
  121. severity=UpdateSeverity.CRITICAL
  122. )
  123. result = VersionCheckResult(sample_current_version, critical_version)
  124. assert result.is_critical is True
  125. def test_version_comparison(self, sample_current_version):
  126. """Test version comparison logic."""
  127. result = VersionCheckResult(sample_current_version)
  128. # Test greater than
  129. assert result._compare_versions("1.2.0", "1.0.0") > 0
  130. assert result._compare_versions("2.0.0", "1.9.9") > 0
  131. # Test less than
  132. assert result._compare_versions("1.0.0", "1.2.0") < 0
  133. assert result._compare_versions("1.0.0", "2.0.0") < 0
  134. # Test equal
  135. assert result._compare_versions("1.0.0", "1.0.0") == 0
  136. # Test with different lengths
  137. assert result._compare_versions("1.0", "1.0.0") == 0
  138. assert result._compare_versions("1.0.0", "1.0") == 0
  139. class TestVersionCheckThread:
  140. """Tests for VersionCheckThread."""
  141. def test_initialization(self, sample_current_version):
  142. """Test thread initialization."""
  143. thread = VersionCheckThread(
  144. sample_current_version,
  145. "https://api.example.com/version"
  146. )
  147. assert thread._current == sample_current_version
  148. assert thread._check_url == "https://api.example.com"
  149. def test_with_custom_timeout(self, sample_current_version):
  150. """Test with custom timeout."""
  151. thread = VersionCheckThread(
  152. sample_current_version,
  153. "https://api.example.com/version",
  154. timeout=30.0
  155. )
  156. assert thread._timeout == 30.0
  157. class TestVersionChecker:
  158. """Tests for VersionChecker."""
  159. def test_initialization(self, sample_current_version):
  160. """Test checker initialization."""
  161. from src.ui.version_checker import VersionChecker
  162. checker = VersionChecker(sample_current_version)
  163. assert checker._current == sample_current_version
  164. assert checker._auto_check is True
  165. def test_set_check_url(self, sample_current_version):
  166. """Test setting check URL."""
  167. from src.ui.version_checker import VersionChecker
  168. checker = VersionChecker(sample_current_version)
  169. checker.set_check_url("https://custom.api.com/version")
  170. assert checker._check_url == "https://custom.api.com/version"
  171. def test_set_auto_check(self, sample_current_version):
  172. """Test setting auto check."""
  173. from src.ui.version_checker import VersionChecker
  174. checker = VersionChecker(sample_current_version)
  175. checker.set_auto_check(False)
  176. assert checker._auto_check is False
  177. def test_skip_version(self, sample_current_version):
  178. """Test skipping a version."""
  179. from src.ui.version_checker import VersionChecker
  180. checker = VersionChecker(sample_current_version)
  181. checker.skip_version("1.2.0")
  182. # Version should be in settings
  183. from PyQt6.QtCore import QSettings
  184. settings = QSettings("BMAD", "NovelTranslator")
  185. skipped = settings.value("update/skip_version", "")
  186. assert skipped == "1.2.0"
  187. def test_clear_skipped_version(self, sample_current_version):
  188. """Test clearing skipped version."""
  189. from src.ui.version_checker import VersionChecker
  190. from PyQt6.QtCore import QSettings
  191. checker = VersionChecker(sample_current_version)
  192. # First skip a version
  193. checker.skip_version("1.2.0")
  194. # Then clear it
  195. checker.clear_skipped_version()
  196. settings = QSettings("BMAD", "NovelTranslator")
  197. skipped = settings.value("update/skip_version", "")
  198. assert skipped == ""
  199. class TestCurrentVersionModule:
  200. """Tests for the module-level CURRENT_VERSION."""
  201. def test_current_version_exists(self):
  202. """Test that CURRENT_VERSION is defined."""
  203. assert CURRENT_VERSION is not None
  204. assert isinstance(CURRENT_VERSION, CurrentVersion)
  205. def test_current_version_format(self):
  206. """Test that version string is valid."""
  207. # Should be valid semver
  208. parts = CURRENT_VERSION.version.split(".")
  209. assert len(parts) >= 2
  210. class TestGetVersionChecker:
  211. """Tests for get_version_checker function."""
  212. def test_returns_instance(self):
  213. """Test that get_version_checker returns an instance."""
  214. from src.ui.version_checker import VersionChecker
  215. checker = get_version_checker()
  216. assert isinstance(checker, VersionChecker)
  217. def test_singleton_behavior(self):
  218. """Test that repeated calls return new instances (default)."""
  219. checker1 = get_version_checker()
  220. checker2 = get_version_checker()
  221. # Each call creates a new instance with CURRENT_VERSION
  222. assert isinstance(checker1, VersionChecker)
  223. assert isinstance(checker2, VersionChecker)