test_models.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. """
  2. Tests for UI models - no GUI required.
  3. """
  4. import pytest
  5. from pathlib import Path
  6. from datetime import datetime, timedelta
  7. from src.ui.models import (
  8. FileItem,
  9. FileStatus,
  10. TranslationTask,
  11. TaskStatus,
  12. ProgressUpdate,
  13. Statistics,
  14. )
  15. class TestFileItem:
  16. """Test FileItem data model."""
  17. def test_file_item_creation_defaults(self):
  18. """Test creating a file item with default values."""
  19. item = FileItem(
  20. path=Path("/test/file.txt"),
  21. name="file.txt",
  22. size=2048,
  23. )
  24. assert item.name == "file.txt"
  25. assert item.path == Path("/test/file.txt")
  26. assert item.size == 2048
  27. assert item.status == FileStatus.PENDING
  28. assert item.chapters == 0
  29. assert item.total_words == 0
  30. assert item.translated_words == 0
  31. assert item.error_message is None
  32. assert isinstance(item.added_time, datetime)
  33. def test_file_item_creation_with_values(self):
  34. """Test creating a file item with specific values."""
  35. now = datetime.now()
  36. item = FileItem(
  37. path=Path("/test/novel.txt"),
  38. name="novel.txt",
  39. size=1024 * 100,
  40. status=FileStatus.TRANSLATING,
  41. chapters=50,
  42. total_words=100000,
  43. translated_words=25000,
  44. error_message=None,
  45. added_time=now,
  46. )
  47. assert item.name == "novel.txt"
  48. assert item.status == FileStatus.TRANSLATING
  49. assert item.chapters == 50
  50. assert item.total_words == 100000
  51. assert item.translated_words == 25000
  52. def test_progress_calculation(self):
  53. """Test progress percentage calculation."""
  54. item = FileItem(
  55. path=Path("/test/file.txt"),
  56. name="file.txt",
  57. size=1024,
  58. total_words=1000,
  59. translated_words=500,
  60. )
  61. assert item.progress == 50.0
  62. def test_progress_zero_when_no_total(self):
  63. """Test progress is zero when total_words is 0."""
  64. item = FileItem(
  65. path=Path("/test/file.txt"),
  66. name="file.txt",
  67. size=1024,
  68. total_words=0,
  69. translated_words=0,
  70. )
  71. assert item.progress == 0.0
  72. def test_progress_complete(self):
  73. """Test progress when translation is complete."""
  74. item = FileItem(
  75. path=Path("/test/file.txt"),
  76. name="file.txt",
  77. size=1024,
  78. total_words=1000,
  79. translated_words=1000,
  80. )
  81. assert item.progress == 100.0
  82. def test_size_formatting_bytes(self):
  83. """Test size formatting for bytes."""
  84. item = FileItem(
  85. path=Path("/test/small.txt"),
  86. name="small.txt",
  87. size=512,
  88. )
  89. assert "512" in item.size_formatted
  90. assert "B" in item.size_formatted
  91. def test_size_formatting_kilobytes(self):
  92. """Test size formatting for kilobytes."""
  93. item = FileItem(
  94. path=Path("/test/medium.txt"),
  95. name="medium.txt",
  96. size=2048,
  97. )
  98. assert "KB" in item.size_formatted
  99. def test_size_formatting_megabytes(self):
  100. """Test size formatting for megabytes."""
  101. item = FileItem(
  102. path=Path("/test/large.txt"),
  103. name="large.txt",
  104. size=1024 * 1024 * 5,
  105. )
  106. assert "MB" in item.size_formatted
  107. class TestTranslationTask:
  108. """Test TranslationTask data model."""
  109. def test_task_creation_defaults(self):
  110. """Test creating a task with default values."""
  111. task = TranslationTask(id="task-123")
  112. assert task.id == "task-123"
  113. assert task.status == TaskStatus.IDLE
  114. assert task.current_stage == ""
  115. assert task.current_chapter == 0
  116. assert task.total_chapters == 0
  117. assert task.start_time is None
  118. assert task.end_time is None
  119. assert task.error_message is None
  120. assert task.file_items == []
  121. def test_task_creation_with_file_items(self):
  122. """Test creating a task with file items."""
  123. file1 = FileItem(Path("/test/file1.txt"), "file1.txt", 1024)
  124. file2 = FileItem(Path("/test/file2.txt"), "file2.txt", 2048)
  125. task = TranslationTask(
  126. id="task-456",
  127. file_items=[file1, file2],
  128. status=TaskStatus.RUNNING,
  129. current_stage="translating",
  130. current_chapter=5,
  131. total_chapters=10,
  132. )
  133. assert len(task.file_items) == 2
  134. assert task.status == TaskStatus.RUNNING
  135. assert task.current_stage == "translating"
  136. assert task.current_chapter == 5
  137. assert task.total_chapters == 10
  138. def test_progress_calculation(self):
  139. """Test task progress calculation."""
  140. task = TranslationTask(
  141. id="task-1",
  142. current_chapter=25,
  143. total_chapters=100,
  144. )
  145. assert task.progress == 25.0
  146. def test_progress_zero_when_no_total(self):
  147. """Test progress is zero when total_chapters is 0."""
  148. task = TranslationTask(
  149. id="task-1",
  150. current_chapter=0,
  151. total_chapters=0,
  152. )
  153. assert task.progress == 0.0
  154. def test_elapsed_time_without_start(self):
  155. """Test elapsed time when task hasn't started."""
  156. task = TranslationTask(id="task-1")
  157. assert task.elapsed_time is None
  158. def test_elapsed_time_with_start(self):
  159. """Test elapsed time calculation."""
  160. start = datetime.now() - timedelta(seconds=60)
  161. task = TranslationTask(
  162. id="task-1",
  163. start_time=start,
  164. )
  165. # Should be approximately 60 seconds
  166. assert task.elapsed_time is not None
  167. assert 59 <= task.elapsed_time <= 61
  168. def test_elapsed_time_with_end(self):
  169. """Test elapsed time when task is complete."""
  170. start = datetime.now() - timedelta(seconds=120)
  171. end = start + timedelta(seconds=60)
  172. task = TranslationTask(
  173. id="task-1",
  174. start_time=start,
  175. end_time=end,
  176. )
  177. assert task.elapsed_time == 60.0
  178. def test_is_running(self):
  179. """Test is_running property."""
  180. task_running = TranslationTask(id="task-1", status=TaskStatus.RUNNING)
  181. task_idle = TranslationTask(id="task-2", status=TaskStatus.IDLE)
  182. assert task_running.is_running is True
  183. assert task_idle.is_running is False
  184. def test_is_paused(self):
  185. """Test is_paused property."""
  186. task_paused = TranslationTask(id="task-1", status=TaskStatus.PAUSED)
  187. task_running = TranslationTask(id="task-2", status=TaskStatus.RUNNING)
  188. assert task_paused.is_paused is True
  189. assert task_running.is_paused is False
  190. def test_can_start(self):
  191. """Test can_start property."""
  192. statuses_can_start = [
  193. TaskStatus.IDLE,
  194. TaskStatus.PAUSED,
  195. TaskStatus.FAILED,
  196. ]
  197. for status in statuses_can_start:
  198. task = TranslationTask(id=f"task-{status.value}", status=status)
  199. assert task.can_start is True, f"Status {status} should be startable"
  200. # These cannot be started
  201. task_running = TranslationTask(id="task-running", status=TaskStatus.RUNNING)
  202. task_completed = TranslationTask(id="task-completed", status=TaskStatus.COMPLETED)
  203. task_cancelled = TranslationTask(id="task-cancelled", status=TaskStatus.CANCELLED)
  204. assert task_running.can_start is False
  205. assert task_completed.can_start is False
  206. assert task_cancelled.can_start is False
  207. def test_can_pause(self):
  208. """Test can_pause property."""
  209. task_running = TranslationTask(id="task-1", status=TaskStatus.RUNNING)
  210. task_idle = TranslationTask(id="task-2", status=TaskStatus.IDLE)
  211. assert task_running.can_pause is True
  212. assert task_idle.can_pause is False
  213. def test_can_cancel(self):
  214. """Test can_cancel property."""
  215. task_running = TranslationTask(id="task-1", status=TaskStatus.RUNNING)
  216. task_paused = TranslationTask(id="task-2", status=TaskStatus.PAUSED)
  217. task_idle = TranslationTask(id="task-3", status=TaskStatus.IDLE)
  218. assert task_running.can_cancel is True
  219. assert task_paused.can_cancel is True
  220. assert task_idle.can_cancel is False
  221. class TestProgressUpdate:
  222. """Test ProgressUpdate data model."""
  223. def test_progress_update_creation(self):
  224. """Test creating a progress update."""
  225. update = ProgressUpdate(
  226. task_id="task-123",
  227. stage="translating",
  228. current=5,
  229. total=10,
  230. message="Processing chapter 5",
  231. elapsed_seconds=60,
  232. eta_seconds=120,
  233. )
  234. assert update.task_id == "task-123"
  235. assert update.stage == "translating"
  236. assert update.current == 5
  237. assert update.total == 10
  238. assert update.message == "Processing chapter 5"
  239. assert update.elapsed_seconds == 60
  240. assert update.eta_seconds == 120
  241. def test_progress_calculation(self):
  242. """Test progress percentage calculation."""
  243. update = ProgressUpdate(
  244. task_id="task-1",
  245. stage="translating",
  246. current=3,
  247. total=10,
  248. )
  249. assert update.progress == 30.0
  250. def test_progress_zero_when_no_total(self):
  251. """Test progress is zero when total is 0."""
  252. update = ProgressUpdate(
  253. task_id="task-1",
  254. stage="translating",
  255. current=0,
  256. total=0,
  257. )
  258. assert update.progress == 0.0
  259. def test_progress_complete(self):
  260. """Test progress when complete."""
  261. update = ProgressUpdate(
  262. task_id="task-1",
  263. stage="translating",
  264. current=10,
  265. total=10,
  266. )
  267. assert update.progress == 100.0
  268. class TestStatistics:
  269. """Test Statistics data model."""
  270. def test_statistics_defaults(self):
  271. """Test statistics with default values."""
  272. stats = Statistics()
  273. assert stats.total_translated_words == 0
  274. assert stats.total_chapters == 0
  275. assert stats.total_time_seconds == 0
  276. assert stats.successful_tasks == 0
  277. assert stats.failed_tasks == 0
  278. assert stats.terminology_usage_rate == 0.0
  279. def test_statistics_with_values(self):
  280. """Test statistics with specific values."""
  281. stats = Statistics(
  282. total_translated_words=50000,
  283. total_chapters=100,
  284. total_time_seconds=3000,
  285. successful_tasks=5,
  286. failed_tasks=1,
  287. terminology_usage_rate=85.5,
  288. )
  289. assert stats.total_translated_words == 50000
  290. assert stats.total_chapters == 100
  291. assert stats.total_time_seconds == 3000
  292. assert stats.successful_tasks == 5
  293. assert stats.failed_tasks == 1
  294. assert stats.terminology_usage_rate == 85.5
  295. def test_average_speed_calculation(self):
  296. """Test average translation speed calculation."""
  297. stats = Statistics(
  298. total_translated_words=6000, # 6000 words
  299. total_time_seconds=120, # 2 minutes
  300. )
  301. # 6000 words / 120 seconds * 60 = 3000 words/minute
  302. assert stats.average_speed == 3000.0
  303. def test_average_speed_zero_time(self):
  304. """Test average speed when time is zero."""
  305. stats = Statistics(
  306. total_translated_words=1000,
  307. total_time_seconds=0,
  308. )
  309. assert stats.average_speed == 0.0
  310. def test_completion_rate_calculation(self):
  311. """Test task completion rate calculation."""
  312. stats = Statistics(
  313. successful_tasks=9,
  314. failed_tasks=1,
  315. )
  316. # 9 / (9 + 1) * 100 = 90%
  317. assert stats.completion_rate == 90.0
  318. def test_completion_rate_no_tasks(self):
  319. """Test completion rate when no tasks."""
  320. stats = Statistics()
  321. assert stats.completion_rate == 0.0
  322. def test_total_tasks(self):
  323. """Test total tasks property."""
  324. stats = Statistics(
  325. successful_tasks=5,
  326. failed_tasks=2,
  327. )
  328. assert stats.total_tasks == 7
  329. class TestFileStatus:
  330. """Test FileStatus enum."""
  331. def test_all_status_values(self):
  332. """Test all status values exist."""
  333. assert FileStatus.PENDING.value == "pending"
  334. assert FileStatus.IMPORTING.value == "importing"
  335. assert FileStatus.READY.value == "ready"
  336. assert FileStatus.TRANSLATING.value == "translating"
  337. assert FileStatus.PAUSED.value == "paused"
  338. assert FileStatus.COMPLETED.value == "completed"
  339. assert FileStatus.FAILED.value == "failed"
  340. class TestTaskStatus:
  341. """Test TaskStatus enum."""
  342. def test_all_status_values(self):
  343. """Test all status values exist."""
  344. assert TaskStatus.IDLE.value == "idle"
  345. assert TaskStatus.RUNNING.value == "running"
  346. assert TaskStatus.PAUSED.value == "paused"
  347. assert TaskStatus.COMPLETED.value == "completed"
  348. assert TaskStatus.FAILED.value == "failed"
  349. assert TaskStatus.CANCELLED.value == "cancelled"