Coverage for tests/ui/test_dl_execution_page.py: 100%

733 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 15:51 +0000

1import os 

2import pytest 

3from unittest.mock import Mock, patch, MagicMock, call 

4from PyQt6.QtWidgets import QMessageBox, QListWidgetItem 

5from PyQt6.QtCore import Qt, QCoreApplication 

6 

7from main.ui.dl_execution_page import DlExecutionPage 

8 

9 

10@pytest.fixture 

11def mock_dl_worker(): 

12 """Mock for DlWorker.""" 

13 with patch("main.ui.dl_execution_page.DlWorker") as mock: 

14 worker_instance = Mock() 

15 worker_instance.progressbar_update = Mock() 

16 worker_instance.progressbar_update.connect = Mock() 

17 worker_instance.file_update = Mock() 

18 worker_instance.file_update.connect = Mock() 

19 worker_instance.log_update = Mock() 

20 worker_instance.log_update.connect = Mock() 

21 worker_instance.finished = Mock() 

22 worker_instance.finished.connect = Mock() 

23 worker_instance.cancel_requested = Mock() 

24 worker_instance.cancel_requested.emit = Mock() 

25 worker_instance.start = Mock() 

26 

27 mock.return_value = worker_instance 

28 yield mock 

29 

30 

31@pytest.fixture 

32def mock_context_dl(temp_workspace): 

33 """Context for DlExecutionPage.""" 

34 context = { 

35 "workspace_path": temp_workspace, 

36 "selected_segmentation_files": [ 

37 os.path.join(temp_workspace, "sub-01", "anat", "sub-01_T1w.nii"), 

38 os.path.join(temp_workspace, "sub-02", "anat", "sub-02_T1w.nii") 

39 ], 

40 "update_main_buttons": Mock(), 

41 "language_changed": Mock(), 

42 "history": [] 

43 } 

44 return context 

45 

46 

47class TestDlExecutionPageInitialization: 

48 """Tests for page initialization.""" 

49 

50 def test_initialization_basic(self, qtbot, mock_context_dl): 

51 """Test basic initialization.""" 

52 page = DlExecutionPage(mock_context_dl) 

53 qtbot.addWidget(page) 

54 

55 assert page.context == mock_context_dl 

56 assert page.previous_page is None 

57 assert page.next_page is None 

58 assert page.worker is None 

59 assert page.current_file is None 

60 assert page.processing is False 

61 assert page.processing_completed is False 

62 

63 def test_initialization_with_previous_page(self, qtbot, mock_context_dl): 

64 """Test initialization with previous page.""" 

65 previous = Mock() 

66 page = DlExecutionPage(mock_context_dl, previous_page=previous) 

67 qtbot.addWidget(page) 

68 

69 assert page.previous_page == previous 

70 

71 def test_ui_elements_created(self, qtbot, mock_context_dl): 

72 """Test that all UI elements are created.""" 

73 page = DlExecutionPage(mock_context_dl) 

74 qtbot.addWidget(page) 

75 

76 assert page.header is not None 

77 assert page.current_operation is not None 

78 assert page.progress_bar is not None 

79 assert page.files_group is not None 

80 assert page.files_list is not None 

81 assert page.log_label is not None 

82 assert page.log_text is not None 

83 assert page.start_button is not None 

84 assert page.cancel_button is not None 

85 

86 def test_initial_button_visibility(self, qtbot, mock_context_dl): 

87 """Test initial button visibility.""" 

88 page = DlExecutionPage(mock_context_dl) 

89 qtbot.addWidget(page) 

90 

91 assert page.start_button.isVisibleTo(page) 

92 # cancel_button visibility depends on implementation 

93 

94 

95class TestOnEnter: 

96 """Tests for the on_enter method.""" 

97 

98 def test_on_enter_resets_state(self, qtbot, mock_context_dl): 

99 """Test that on_enter resets the state.""" 

100 page = DlExecutionPage(mock_context_dl) 

101 qtbot.addWidget(page) 

102 

103 page.processing = True 

104 page.processing_completed = True 

105 

106 page.on_enter() 

107 

108 assert page.processing is False 

109 assert page.processing_completed is False 

110 

111 def test_on_enter_populates_file_list(self, qtbot, mock_context_dl): 

112 """Test that on_enter populates the file list.""" 

113 page = DlExecutionPage(mock_context_dl) 

114 qtbot.addWidget(page) 

115 

116 page.on_enter() 

117 

118 assert page.files_list.count() == 2 

119 

120 item0 = page.files_list.item(0).text() 

121 item1 = page.files_list.item(1).text() 

122 

123 assert "sub-01_T1w.nii" in item0 

124 assert "sub-02_T1w.nii" in item1 

125 assert "Waiting" in item0 or "In attesa" in item0 

126 assert "Waiting" in item1 or "In attesa" in item1 

127 

128 def test_on_enter_clears_previous_list(self, qtbot, mock_context_dl): 

129 """Test that on_enter clears the previous list.""" 

130 page = DlExecutionPage(mock_context_dl) 

131 qtbot.addWidget(page) 

132 

133 page.files_list.addItem("Old item 1") 

134 page.files_list.addItem("Old item 2") 

135 

136 assert page.files_list.count() > 2 

137 

138 page.on_enter() 

139 

140 assert page.files_list.count() == 2 

141 

142 def test_on_enter_without_files_in_context(self, qtbot): 

143 """Test on_enter without files in context.""" 

144 context = {"workspace_path": "/fake/path"} 

145 page = DlExecutionPage(context) 

146 qtbot.addWidget(page) 

147 

148 page.on_enter() 

149 

150 assert page.files_list.count() == 0 

151 

152 def test_on_enter_with_empty_file_list(self, qtbot, temp_workspace): 

153 """Test on_enter with an empty file list.""" 

154 context = { 

155 "workspace_path": temp_workspace, 

156 "selected_segmentation_files": [] 

157 } 

158 page = DlExecutionPage(context) 

159 qtbot.addWidget(page) 

160 

161 page.on_enter() 

162 

163 assert page.files_list.count() == 0 

164 

165 

166class TestStartProcessing: 

167 """Tests for start_processing.""" 

168 

169 def test_start_processing_basic(self, qtbot, mock_context_dl, mock_dl_worker): 

170 """Test basic processing start.""" 

171 page = DlExecutionPage(mock_context_dl) 

172 qtbot.addWidget(page) 

173 

174 page.start_processing() 

175 

176 mock_dl_worker.assert_called_once() 

177 assert page.worker is not None 

178 

179 page.worker.start.assert_called_once() 

180 

181 assert page.processing is True 

182 assert not page.start_button.isVisibleTo(page) 

183 assert page.cancel_button.isVisibleTo(page) 

184 assert page.progress_bar.isVisibleTo(page) 

185 

186 def test_start_processing_no_context(self, qtbot): 

187 """Test start without context.""" 

188 page = DlExecutionPage(context=None) 

189 qtbot.addWidget(page) 

190 

191 with patch.object(QMessageBox, 'warning') as mock_warning: 

192 page.start_processing() 

193 

194 mock_warning.assert_called_once() 

195 assert page.worker is None 

196 

197 def test_start_processing_no_files_in_context(self, qtbot, temp_workspace): 

198 """Test start without files in context.""" 

199 context = {"workspace_path": temp_workspace} 

200 page = DlExecutionPage(context) 

201 qtbot.addWidget(page) 

202 

203 with patch.object(QMessageBox, 'warning') as mock_warning: 

204 page.start_processing() 

205 

206 mock_warning.assert_called_once() 

207 assert page.worker is None 

208 

209 def test_start_processing_empty_file_list(self, qtbot, temp_workspace): 

210 """Test start with an empty file list.""" 

211 context = { 

212 "workspace_path": temp_workspace, 

213 "selected_segmentation_files": [] 

214 } 

215 page = DlExecutionPage(context) 

216 qtbot.addWidget(page) 

217 

218 with patch.object(QMessageBox, 'warning') as mock_warning: 

219 page.start_processing() 

220 

221 mock_warning.assert_called_once() 

222 assert page.worker is None 

223 

224 def test_start_processing_connects_signals(self, qtbot, mock_context_dl, mock_dl_worker): 

225 """Test that signals are connected correctly.""" 

226 page = DlExecutionPage(mock_context_dl) 

227 qtbot.addWidget(page) 

228 

229 page.start_processing() 

230 

231 page.worker.progressbar_update.connect.assert_called_once() 

232 page.worker.file_update.connect.assert_called_once() 

233 page.worker.log_update.connect.assert_called_once() 

234 page.worker.finished.connect.assert_called_once() 

235 

236 def test_start_processing_worker_parameters(self, qtbot, mock_context_dl, mock_dl_worker): 

237 """Test that the worker receives correct parameters.""" 

238 page = DlExecutionPage(mock_context_dl) 

239 qtbot.addWidget(page) 

240 

241 page.start_processing() 

242 

243 call_kwargs = mock_dl_worker.call_args[1] 

244 assert call_kwargs['input_files'] == mock_context_dl["selected_segmentation_files"] 

245 assert call_kwargs['workspace_path'] == mock_context_dl["workspace_path"] 

246 

247 def test_start_processing_clears_log(self, qtbot, mock_context_dl, mock_dl_worker): 

248 """Test that the log is cleared on start.""" 

249 page = DlExecutionPage(mock_context_dl) 

250 qtbot.addWidget(page) 

251 

252 page.log_text.setText("Old log content") 

253 

254 page.start_processing() 

255 

256 log_content = page.log_text.toPlainText() 

257 assert "Old log content" not in log_content 

258 

259 

260class TestUpdateProgress: 

261 """Tests for update_progress.""" 

262 

263 def test_update_progress_basic(self, qtbot, mock_context_dl): 

264 """Test basic progress update.""" 

265 page = DlExecutionPage(mock_context_dl) 

266 qtbot.addWidget(page) 

267 

268 page.update_progress(50) 

269 

270 assert page.progress_bar.value == 50 

271 

272 def test_update_progress_multiple_values(self, qtbot, mock_context_dl): 

273 """Test multiple updates.""" 

274 page = DlExecutionPage(mock_context_dl) 

275 qtbot.addWidget(page) 

276 

277 for i in range(0, 101, 10): 

278 page.update_progress(i) 

279 assert page.progress_bar.value == i 

280 

281 def test_update_progress_zero(self, qtbot, mock_context_dl): 

282 """Test with value 0.""" 

283 page = DlExecutionPage(mock_context_dl) 

284 qtbot.addWidget(page) 

285 

286 page.update_progress(0) 

287 assert page.progress_bar.value == 0 

288 

289 def test_update_progress_hundred(self, qtbot, mock_context_dl): 

290 """Test with value 100.""" 

291 page = DlExecutionPage(mock_context_dl) 

292 qtbot.addWidget(page) 

293 

294 page.update_progress(100) 

295 assert page.progress_bar.value == 100 

296 

297 

298class TestAddLogMessage: 

299 """Tests for add_log_message.""" 

300 

301 def test_add_log_message_info(self, qtbot, mock_context_dl, mock_logger): 

302 """Test adding info message.""" 

303 page = DlExecutionPage(mock_context_dl) 

304 qtbot.addWidget(page) 

305 

306 with patch("main.ui.dl_execution_page.log", mock_logger): 

307 page.add_log_message("Test info message", 'i') 

308 

309 log_content = page.log_text.toPlainText() 

310 assert "Test info message" in log_content 

311 mock_logger.info.assert_called_once() 

312 

313 def test_add_log_message_error(self, qtbot, mock_context_dl, mock_logger): 

314 """Test adding error message.""" 

315 page = DlExecutionPage(mock_context_dl) 

316 qtbot.addWidget(page) 

317 

318 with patch("main.ui.dl_execution_page.log", mock_logger): 

319 page.add_log_message("Test error message", 'e') 

320 

321 log_content = page.log_text.toPlainText() 

322 assert "Test error message" in log_content 

323 mock_logger.error.assert_called_once() 

324 

325 def test_add_log_message_warning(self, qtbot, mock_context_dl, mock_logger): 

326 """Test adding warning message.""" 

327 page = DlExecutionPage(mock_context_dl) 

328 qtbot.addWidget(page) 

329 

330 with patch("main.ui.dl_execution_page.log", mock_logger): 

331 page.add_log_message("Test warning message", 'w') 

332 

333 log_content = page.log_text.toPlainText() 

334 assert "Test warning message" in log_content 

335 mock_logger.warning.assert_called_once() 

336 

337 def test_add_log_message_debug(self, qtbot, mock_context_dl, mock_logger): 

338 """Test adding debug message.""" 

339 page = DlExecutionPage(mock_context_dl) 

340 qtbot.addWidget(page) 

341 

342 with patch("main.ui.dl_execution_page.log", mock_logger): 

343 page.add_log_message("Test debug message", 'd') 

344 

345 log_content = page.log_text.toPlainText() 

346 assert "Test debug message" in log_content 

347 mock_logger.debug.assert_called_once() 

348 

349 def test_add_log_message_includes_timestamp(self, qtbot, mock_context_dl): 

350 """Test that the message includes a timestamp.""" 

351 page = DlExecutionPage(mock_context_dl) 

352 qtbot.addWidget(page) 

353 

354 page.add_log_message("Test message", 'i') 

355 

356 log_content = page.log_text.toPlainText() 

357 assert "[" in log_content 

358 assert "]" in log_content 

359 assert ":" in log_content # Timestamp format 

360 

361 def test_add_log_message_autoscroll(self, qtbot, mock_context_dl): 

362 """Test auto-scroll after adding a message.""" 

363 page = DlExecutionPage(mock_context_dl) 

364 qtbot.addWidget(page) 

365 

366 for i in range(50): 

367 page.add_log_message(f"Message {i}", 'i') 

368 

369 scrollbar = page.log_text.verticalScrollBar() 

370 assert scrollbar.value() == scrollbar.maximum() 

371 

372 def test_add_log_message_multiple(self, qtbot, mock_context_dl): 

373 """Test multiple messages.""" 

374 page = DlExecutionPage(mock_context_dl) 

375 qtbot.addWidget(page) 

376 

377 messages = ["First", "Second", "Third"] 

378 for msg in messages: 

379 page.add_log_message(msg, 'i') 

380 

381 log_content = page.log_text.toPlainText() 

382 for msg in messages: 

383 assert msg in log_content 

384 

385 

386class TestUpdateFileStatus: 

387 """Tests for update_file_status.""" 

388 

389 def test_update_file_status_basic(self, qtbot, mock_context_dl): 

390 """Test file status update.""" 

391 page = DlExecutionPage(mock_context_dl) 

392 qtbot.addWidget(page) 

393 page.on_enter() 

394 

395 page.update_file_status("sub-01_T1w.nii", "Processing") 

396 

397 item = page.files_list.item(0) 

398 assert "Processing" in item.text() 

399 assert "sub-01_T1w.nii" in item.text() 

400 

401 def test_update_file_status_completed(self, qtbot, mock_context_dl): 

402 """Test completed status.""" 

403 page = DlExecutionPage(mock_context_dl) 

404 qtbot.addWidget(page) 

405 page.on_enter() 

406 

407 page.update_file_status("sub-01_T1w.nii", "Completed") 

408 

409 item = page.files_list.item(0) 

410 assert "Completed" in item.text() 

411 

412 def test_update_file_status_error(self, qtbot, mock_context_dl): 

413 """Test error status.""" 

414 page = DlExecutionPage(mock_context_dl) 

415 qtbot.addWidget(page) 

416 page.on_enter() 

417 

418 page.update_file_status("sub-01_T1w.nii", "Error") 

419 

420 item = page.files_list.item(0) 

421 assert "Error" in item.text() 

422 

423 def test_update_file_status_nonexistent_file(self, qtbot, mock_context_dl): 

424 """Test updating a file not in the list.""" 

425 page = DlExecutionPage(mock_context_dl) 

426 qtbot.addWidget(page) 

427 page.on_enter() 

428 

429 initial_count = page.files_list.count() 

430 

431 page.update_file_status("nonexistent.nii", "Processing") 

432 

433 assert page.files_list.count() == initial_count 

434 

435 def test_update_file_status_multiple_updates(self, qtbot, mock_context_dl): 

436 """Test multiple updates for the same file.""" 

437 page = DlExecutionPage(mock_context_dl) 

438 qtbot.addWidget(page) 

439 page.on_enter() 

440 

441 filename = "sub-01_T1w.nii" 

442 

443 page.update_file_status(filename, "Processing") 

444 item = page.files_list.item(0) 

445 assert "Processing" in item.text() 

446 

447 page.update_file_status(filename, "Completed") 

448 item = page.files_list.item(0) 

449 assert "Completed" in item.text() 

450 assert "Processing" not in item.text() 

451 

452 

453class TestCancelProcessing: 

454 """Tests for cancel_processing.""" 

455 

456 def test_cancel_processing_with_worker(self, qtbot, mock_context_dl, mock_dl_worker): 

457 """Test cancellation with an active worker.""" 

458 page = DlExecutionPage(mock_context_dl) 

459 qtbot.addWidget(page) 

460 

461 page.start_processing() 

462 

463 page.cancel_processing() 

464 

465 page.worker.cancel_requested.emit.assert_called_once() 

466 

467 def test_cancel_processing_without_worker(self, qtbot, mock_context_dl): 

468 """Test cancellation without a worker.""" 

469 page = DlExecutionPage(mock_context_dl) 

470 qtbot.addWidget(page) 

471 

472 page.cancel_processing() 

473 

474 assert page.worker is None 

475 

476 def test_cancel_processing_adds_log(self, qtbot, mock_context_dl, mock_dl_worker): 

477 """Test that cancellation adds a log entry.""" 

478 page = DlExecutionPage(mock_context_dl) 

479 qtbot.addWidget(page) 

480 

481 page.start_processing() 

482 page.cancel_processing() 

483 

484 log_content = page.log_text.toPlainText() 

485 assert "Cancellation requested" in log_content or "Annullamento richiesto" in log_content 

486 

487 

488class TestProcessingFinished: 

489 """Tests for processing_finished.""" 

490 

491 def test_processing_finished_success(self, qtbot, mock_context_dl): 

492 """Test successful completion.""" 

493 page = DlExecutionPage(mock_context_dl) 

494 qtbot.addWidget(page) 

495 

496 with patch.object(QMessageBox, 'information') as mock_info: 

497 page.processing_finished(True, "All files processed successfully") 

498 

499 assert page.processing is False 

500 assert page.processing_completed is True 

501 assert page.start_button.isVisibleTo(page) 

502 assert not page.cancel_button.isVisibleTo(page) 

503 assert not page.progress_bar.isVisibleTo(page) 

504 

505 mock_info.assert_called_once() 

506 mock_context_dl["update_main_buttons"].assert_called_once() 

507 

508 def test_processing_finished_failure(self, qtbot, mock_context_dl): 

509 """Test completion with failure.""" 

510 page = DlExecutionPage(mock_context_dl) 

511 qtbot.addWidget(page) 

512 

513 with patch.object(QMessageBox, 'critical') as mock_critical: 

514 page.processing_finished(False, "Processing failed") 

515 

516 assert page.processing is False 

517 assert page.processing_completed is True 

518 

519 mock_critical.assert_called_once() 

520 

521 def test_processing_finished_updates_context(self, qtbot, mock_context_dl): 

522 """Test that the context is updated.""" 

523 page = DlExecutionPage(mock_context_dl) 

524 qtbot.addWidget(page) 

525 

526 with patch.object(QMessageBox, 'information'): 

527 page.processing_finished(True, "Success") 

528 

529 assert "processing_output_dir" in mock_context_dl 

530 

531 def test_processing_finished_current_operation_text(self, qtbot, mock_context_dl): 

532 """Test current operation text update.""" 

533 page = DlExecutionPage(mock_context_dl) 

534 qtbot.addWidget(page) 

535 

536 with patch.object(QMessageBox, 'information'): 

537 page.processing_finished(True, "Success") 

538 

539 assert "completed" in page.current_operation.text().lower() or "✓" in page.current_operation.text() 

540 

541 page2 = DlExecutionPage(mock_context_dl) 

542 qtbot.addWidget(page2) 

543 

544 with patch.object(QMessageBox, 'critical'): 

545 page2.processing_finished(False, "Error") 

546 

547 assert "failed" in page2.current_operation.text().lower() or "✗" in page2.current_operation.text() 

548 

549 def test_processing_finished_reprocess_button(self, qtbot, mock_context_dl): 

550 """Test that the button becomes 'Reprocess' after success.""" 

551 page = DlExecutionPage(mock_context_dl) 

552 qtbot.addWidget(page) 

553 

554 with patch.object(QMessageBox, 'information'): 

555 page.processing_finished(True, "Success") 

556 

557 assert "Reprocess" in page.start_button.text() 

558 

559 

560class TestResetProcessingState: 

561 """Tests for reset_processing_state.""" 

562 

563 def test_reset_processing_state_basic(self, qtbot, mock_context_dl): 

564 """Test basic state reset.""" 

565 page = DlExecutionPage(mock_context_dl) 

566 qtbot.addWidget(page) 

567 

568 page.processing = True 

569 page.processing_completed = True 

570 page.worker = Mock() 

571 

572 page.reset_processing_state() 

573 

574 assert page.processing is False 

575 assert page.processing_completed is False 

576 assert page.worker is None 

577 assert page.start_button.isVisibleTo(page) 

578 assert not page.cancel_button.isVisibleTo(page) 

579 assert not page.progress_bar.isVisibleTo(page) 

580 

581 def test_reset_processing_state_button_text(self, qtbot, mock_context_dl): 

582 """Test button text reset.""" 

583 page = DlExecutionPage(mock_context_dl) 

584 qtbot.addWidget(page) 

585 

586 page.start_button.setText("Reprocess") 

587 

588 page.reset_processing_state() 

589 

590 assert "Start" in page.start_button.text() or "Avvio deep learning" in page.start_button.text() 

591 

592 

593class TestBackNavigation: 

594 """Tests for back navigation.""" 

595 

596 def test_back_not_processing(self, qtbot, mock_context_dl): 

597 """Test back when not processing.""" 

598 previous = Mock() 

599 page = DlExecutionPage(mock_context_dl, previous_page=previous) 

600 qtbot.addWidget(page) 

601 

602 result = page.back() 

603 

604 assert result == previous 

605 previous.on_enter.assert_called_once() 

606 

607 def test_back_while_processing_cancelled(self, qtbot, mock_context_dl, mock_dl_worker): 

608 """Test back during processing with cancellation.""" 

609 previous = Mock() 

610 page = DlExecutionPage(mock_context_dl, previous_page=previous) 

611 qtbot.addWidget(page) 

612 

613 page.start_processing() 

614 

615 with patch.object(QMessageBox, 'question', return_value=QMessageBox.StandardButton.Yes): 

616 result = page.back() 

617 

618 page.worker.cancel_requested.emit.assert_called() 

619 assert result == previous 

620 

621 def test_back_while_processing_not_cancelled(self, qtbot, mock_context_dl, mock_dl_worker): 

622 """Test back during processing without cancellation.""" 

623 previous = Mock() 

624 page = DlExecutionPage(mock_context_dl, previous_page=previous) 

625 qtbot.addWidget(page) 

626 

627 page.start_processing() 

628 

629 with patch.object(QMessageBox, 'question', return_value=QMessageBox.StandardButton.No): 

630 result = page.back() 

631 

632 assert result is None 

633 previous.on_enter.assert_not_called() 

634 

635 def test_back_no_previous_page(self, qtbot, mock_context_dl): 

636 """Test back without previous_page.""" 

637 page = DlExecutionPage(mock_context_dl) 

638 qtbot.addWidget(page) 

639 

640 result = page.back() 

641 

642 assert result is None 

643 

644 

645class TestNextNavigation: 

646 """Tests for next navigation.""" 

647 

648 def test_next_returns_none(self, qtbot, mock_context_dl): 

649 """Test that next always returns None.""" 

650 page = DlExecutionPage(mock_context_dl) 

651 qtbot.addWidget(page) 

652 

653 result = page.next(mock_context_dl) 

654 

655 assert result is None 

656 

657 

658class TestReadyToAdvance: 

659 """Tests for is_ready_to_advance.""" 

660 

661 def test_is_ready_to_advance_always_false(self, qtbot, mock_context_dl): 

662 """Test that is_ready_to_advance always returns False.""" 

663 page = DlExecutionPage(mock_context_dl) 

664 qtbot.addWidget(page) 

665 

666 assert page.is_ready_to_advance() is False 

667 

668 # Even after completion 

669 page.processing_completed = True 

670 assert page.is_ready_to_advance() is False 

671 

672 

673class TestReadyToGoBack: 

674 """Tests for is_ready_to_go_back.""" 

675 

676 def test_is_ready_to_go_back_always_true(self, qtbot, mock_context_dl): 

677 """Test that is_ready_to_go_back always returns True.""" 

678 page = DlExecutionPage(mock_context_dl) 

679 qtbot.addWidget(page) 

680 

681 assert page.is_ready_to_go_back() is True 

682 

683 # Even during processing 

684 page.processing = True 

685 assert page.is_ready_to_go_back() is True 

686 

687 

688class TestTranslation: 

689 """Tests for translations.""" 

690 

691 def test_translate_ui_called_on_init(self, qtbot, mock_context_dl): 

692 """Test that _translate_ui is called during init.""" 

693 with patch.object(DlExecutionPage, '_translate_ui') as mock_translate: 

694 page = DlExecutionPage(mock_context_dl) 

695 qtbot.addWidget(page) 

696 

697 mock_translate.assert_called() 

698 

699 def test_translate_ui_updates_labels(self, qtbot, mock_context_dl): 

700 """Test that _translate_ui updates labels.""" 

701 page = DlExecutionPage(mock_context_dl) 

702 qtbot.addWidget(page) 

703 

704 page._translate_ui() 

705 

706 assert page.header.text() is not None 

707 assert page.current_operation.text() is not None 

708 assert page.files_group.title() is not None 

709 assert page.log_label.text() is not None 

710 assert page.start_button.text() is not None 

711 assert page.cancel_button.text() is not None 

712 

713 def test_translate_ui_repopulates_file_list(self, qtbot, mock_context_dl): 

714 """Test that _translate_ui repopulates the file list.""" 

715 page = DlExecutionPage(mock_context_dl) 

716 qtbot.addWidget(page) 

717 

718 page.on_enter() 

719 initial_count = page.files_list.count() 

720 

721 page._translate_ui() 

722 

723 assert page.files_list.count() == initial_count 

724 

725 

726class TestEdgeCases: 

727 """Tests for edge cases.""" 

728 

729 def test_rapid_start_cancel(self, qtbot, mock_context_dl, mock_dl_worker): 

730 """Test rapid start and cancel.""" 

731 page = DlExecutionPage(mock_context_dl) 

732 qtbot.addWidget(page) 

733 

734 for _ in range(5): 

735 page.start_processing() 

736 page.cancel_processing() 

737 

738 assert True 

739 

740 def test_multiple_processing_finished_calls(self, qtbot, mock_context_dl): 

741 """Test multiple calls to processing_finished.""" 

742 page = DlExecutionPage(mock_context_dl) 

743 qtbot.addWidget(page) 

744 

745 with patch.object(QMessageBox, 'information'): 

746 page.processing_finished(True, "First") 

747 page.processing_finished(True, "Second") 

748 

749 assert page.processing_completed is True 

750 

751 def test_update_file_status_empty_list(self, qtbot, mock_context_dl): 

752 """Test update_file_status with an empty list.""" 

753 page = DlExecutionPage(mock_context_dl) 

754 qtbot.addWidget(page) 

755 

756 page.update_file_status("file.nii", "Processing") 

757 

758 assert page.files_list.count() == 2 

759 

760 def test_very_long_log_messages(self, qtbot, mock_context_dl): 

761 """Test with very long log messages.""" 

762 page = DlExecutionPage(mock_context_dl) 

763 qtbot.addWidget(page) 

764 

765 long_message = "A" * 10000 

766 page.add_log_message(long_message, 'i') 

767 

768 log_content = page.log_text.toPlainText() 

769 assert long_message in log_content 

770 

771 def test_unicode_in_filenames(self, qtbot, temp_workspace): 

772 """Test with unicode characters in filenames.""" 

773 context = { 

774 "workspace_path": temp_workspace, 

775 "selected_segmentation_files": [ 

776 os.path.join(temp_workspace, "sub-àèéìòù", "anat", "file.nii"), 

777 os.path.join(temp_workspace, "sub-中文", "anat", "file.nii") 

778 ], 

779 "update_main_buttons": Mock() 

780 } 

781 

782 page = DlExecutionPage(context) 

783 qtbot.addWidget(page) 

784 

785 page.on_enter() 

786 

787 assert page.files_list.count() == 2 

788 

789 def test_progress_value_out_of_range(self, qtbot, mock_context_dl): 

790 """Test with progress values out of range.""" 

791 page = DlExecutionPage(mock_context_dl) 

792 qtbot.addWidget(page) 

793 

794 page.update_progress(-10) 

795 page.update_progress(150) 

796 

797 assert True 

798 

799 def test_concurrent_file_updates(self, qtbot, mock_context_dl): 

800 """Test concurrent file updates.""" 

801 page = DlExecutionPage(mock_context_dl) 

802 qtbot.addWidget(page) 

803 page.on_enter() 

804 

805 for i in range(100): 

806 page.update_file_status("sub-01_T1w.nii", f"Status {i}") 

807 page.update_file_status("sub-02_T1w.nii", f"Status {i}") 

808 

809 assert True 

810 

811 

812class TestUIInteraction: 

813 """Tests for UI interaction.""" 

814 

815 def test_button_styling(self, qtbot, mock_context_dl): 

816 """Test that buttons have the correct style.""" 

817 page = DlExecutionPage(mock_context_dl) 

818 qtbot.addWidget(page) 

819 

820 start_style = page.start_button.styleSheet() 

821 cancel_style = page.cancel_button.styleSheet() 

822 

823 assert "background-color" in start_style 

824 assert "background-color" in cancel_style 

825 

826 def test_log_text_readonly(self, qtbot, mock_context_dl): 

827 """Test that the log is read-only.""" 

828 page = DlExecutionPage(mock_context_dl) 

829 qtbot.addWidget(page) 

830 

831 assert page.log_text.isReadOnly() 

832 

833 def test_files_list_max_height(self, qtbot, mock_context_dl): 

834 """Test that the file list has a maximum height.""" 

835 page = DlExecutionPage(mock_context_dl) 

836 qtbot.addWidget(page) 

837 

838 assert page.files_list.maximumHeight() == 150 

839 

840 def test_log_text_max_height(self, qtbot, mock_context_dl): 

841 """Test that the log has a maximum height.""" 

842 page = DlExecutionPage(mock_context_dl) 

843 qtbot.addWidget(page) 

844 

845 assert page.log_text.maximumHeight() == 200 

846 

847 def test_header_alignment(self, qtbot, mock_context_dl): 

848 """Test that the header is centered.""" 

849 page = DlExecutionPage(mock_context_dl) 

850 qtbot.addWidget(page) 

851 

852 assert page.header.alignment() == Qt.AlignmentFlag.AlignCenter 

853 

854 def test_current_operation_alignment(self, qtbot, mock_context_dl): 

855 """Test that current_operation is centered.""" 

856 page = DlExecutionPage(mock_context_dl) 

857 qtbot.addWidget(page) 

858 

859 assert page.current_operation.alignment() == Qt.AlignmentFlag.AlignCenter 

860 

861 

862class TestWorkerIntegration: 

863 """Tests for worker integration.""" 

864 

865 def test_worker_signals_connected(self, qtbot, mock_context_dl, mock_dl_worker): 

866 """Test that all worker signals are connected.""" 

867 page = DlExecutionPage(mock_context_dl) 

868 qtbot.addWidget(page) 

869 

870 page.start_processing() 

871 

872 assert page.worker.progressbar_update.connect.called 

873 assert page.worker.file_update.connect.called 

874 assert page.worker.log_update.connect.called 

875 assert page.worker.finished.connect.called 

876 

877 def test_worker_created_with_correct_parameters(self, qtbot, mock_context_dl, mock_dl_worker): 

878 """Test that the worker is created with correct parameters.""" 

879 page = DlExecutionPage(mock_context_dl) 

880 qtbot.addWidget(page) 

881 

882 page.start_processing() 

883 

884 call_kwargs = mock_dl_worker.call_args[1] 

885 

886 assert 'input_files' in call_kwargs 

887 assert 'workspace_path' in call_kwargs 

888 assert call_kwargs['workspace_path'] == mock_context_dl['workspace_path'] 

889 

890 def test_worker_start_called(self, qtbot, mock_context_dl, mock_dl_worker): 

891 """Test that worker.start() is called.""" 

892 page = DlExecutionPage(mock_context_dl) 

893 qtbot.addWidget(page) 

894 

895 page.start_processing() 

896 

897 page.worker.start.assert_called_once() 

898 

899 

900class TestContextHandling: 

901 """Tests for context handling.""" 

902 

903 def test_context_none_handling(self, qtbot): 

904 """Test handling None context.""" 

905 page = DlExecutionPage(context=None) 

906 qtbot.addWidget(page) 

907 

908 page.on_enter() 

909 

910 with patch.object(QMessageBox, 'warning'): 

911 page.start_processing() 

912 

913 def test_context_without_workspace_path(self, qtbot): 

914 """Test context without workspace_path.""" 

915 context = {"selected_segmentation_files": ["file.nii"]} 

916 page = DlExecutionPage(context) 

917 qtbot.addWidget(page) 

918 

919 with patch.object(QMessageBox, 'information'): 

920 page.processing_finished(True, "Success") 

921 

922 def test_context_without_update_main_buttons(self, qtbot, temp_workspace): 

923 """Test context without update_main_buttons.""" 

924 context = { 

925 "workspace_path": temp_workspace, 

926 "selected_segmentation_files": ["file.nii"] 

927 } 

928 page = DlExecutionPage(context) 

929 qtbot.addWidget(page) 

930 

931 with patch.object(QMessageBox, 'information'): 

932 page.processing_finished(True, "Success") 

933 

934 def test_processing_output_dir_set(self, qtbot, mock_context_dl): 

935 """Test that processing_output_dir is set in the context.""" 

936 page = DlExecutionPage(mock_context_dl) 

937 qtbot.addWidget(page) 

938 

939 with patch.object(QMessageBox, 'information'): 

940 page.processing_finished(True, "Success") 

941 

942 assert "processing_output_dir" in mock_context_dl 

943 expected_dir = os.path.join(mock_context_dl["workspace_path"], "outputs") 

944 assert mock_context_dl["processing_output_dir"] == expected_dir 

945 

946 

947class TestStateTransitions: 

948 """Tests for state transitions.""" 

949 

950 def test_state_idle_to_processing(self, qtbot, mock_context_dl, mock_dl_worker): 

951 """Test transition from idle to processing.""" 

952 page = DlExecutionPage(mock_context_dl) 

953 qtbot.addWidget(page) 

954 

955 assert page.processing is False 

956 

957 page.start_processing() 

958 

959 assert page.processing is True 

960 assert not page.start_button.isVisibleTo(page) 

961 assert page.cancel_button.isVisibleTo(page) 

962 

963 def test_state_processing_to_completed(self, qtbot, mock_context_dl, mock_dl_worker): 

964 """Test transition from processing to completed.""" 

965 page = DlExecutionPage(mock_context_dl) 

966 qtbot.addWidget(page) 

967 

968 page.start_processing() 

969 assert page.processing is True 

970 

971 with patch.object(QMessageBox, 'information'): 

972 page.processing_finished(True, "Success") 

973 

974 assert page.processing is False 

975 assert page.processing_completed is True 

976 assert page.start_button.isVisibleTo(page) 

977 

978 def test_state_processing_to_cancelled(self, qtbot, mock_context_dl, mock_dl_worker): 

979 """Test transition from processing to cancelled.""" 

980 page = DlExecutionPage(mock_context_dl) 

981 qtbot.addWidget(page) 

982 

983 page.start_processing() 

984 page.cancel_processing() 

985 

986 with patch.object(QMessageBox, 'critical'): 

987 page.processing_finished(False, "Cancelled") 

988 

989 assert page.processing is False 

990 

991 def test_state_completed_to_reset(self, qtbot, mock_context_dl, mock_dl_worker): 

992 """Test transition from completed to reset.""" 

993 page = DlExecutionPage(mock_context_dl) 

994 qtbot.addWidget(page) 

995 

996 page.start_processing() 

997 

998 with patch.object(QMessageBox, 'information'): 

999 page.processing_finished(True, "Success") 

1000 

1001 assert page.processing_completed is True 

1002 

1003 page.reset_processing_state() 

1004 

1005 assert page.processing is False 

1006 assert page.processing_completed is False 

1007 

1008 

1009class TestIntegration: 

1010 """Integration tests for complete flows.""" 

1011 

1012 def test_full_processing_flow_success(self, qtbot, mock_context_dl, mock_dl_worker): 

1013 """Test full flow: start -> processing -> success.""" 

1014 page = DlExecutionPage(mock_context_dl) 

1015 qtbot.addWidget(page) 

1016 

1017 page.on_enter() 

1018 assert page.files_list.count() == 2 

1019 

1020 page.start_processing() 

1021 assert page.processing is True 

1022 assert page.worker is not None 

1023 

1024 page.update_progress(25) 

1025 page.update_file_status("sub-01_T1w.nii", "Processing") 

1026 page.add_log_message("Processing file 1", 'i') 

1027 

1028 page.update_progress(50) 

1029 page.update_file_status("sub-01_T1w.nii", "Completed") 

1030 page.update_file_status("sub-02_T1w.nii", "Processing") 

1031 

1032 page.update_progress(100) 

1033 page.update_file_status("sub-02_T1nii", "Completed") 

1034 

1035 with patch.object(QMessageBox, 'information'): 

1036 page.processing_finished(True, "All files processed") 

1037 

1038 assert page.processing_completed is True 

1039 assert not page.processing 

1040 

1041 def test_full_processing_flow_with_cancel(self, qtbot, mock_context_dl, mock_dl_worker): 

1042 """Test flow with cancellation.""" 

1043 page = DlExecutionPage(mock_context_dl) 

1044 qtbot.addWidget(page) 

1045 

1046 page.on_enter() 

1047 page.start_processing() 

1048 

1049 page.update_progress(30) 

1050 page.add_log_message("Processing...", 'i') 

1051 

1052 page.cancel_processing() 

1053 

1054 with patch.object(QMessageBox, 'critical'): 

1055 page.processing_finished(False, "Cancelled by user") 

1056 

1057 assert not page.processing 

1058 assert page.processing_completed 

1059 

1060 def test_full_processing_flow_with_error(self, qtbot, mock_context_dl, mock_dl_worker): 

1061 """Test flow with error.""" 

1062 page = DlExecutionPage(mock_context_dl) 

1063 qtbot.addWidget(page) 

1064 

1065 page.on_enter() 

1066 page.start_processing() 

1067 

1068 page.update_progress(20) 

1069 page.update_file_status("sub-01_T1w.nii", "Error") 

1070 page.add_log_message("Error processing file", 'e') 

1071 

1072 with patch.object(QMessageBox, 'critical'): 

1073 page.processing_finished(False, "Processing error") 

1074 

1075 assert not page.processing 

1076 assert page.processing_completed 

1077 

1078 def test_reprocess_flow(self, qtbot, mock_context_dl, mock_dl_worker): 

1079 """Test flow: processing -> completed -> reprocess.""" 

1080 page = DlExecutionPage(mock_context_dl) 

1081 qtbot.addWidget(page) 

1082 

1083 page.on_enter() 

1084 page.start_processing() 

1085 

1086 with patch.object(QMessageBox, 'information'): 

1087 page.processing_finished(True, "Success") 

1088 

1089 assert "Reprocess" in page.start_button.text() 

1090 

1091 page.reset_processing_state() 

1092 page.on_enter() 

1093 

1094 mock_dl_worker.reset_mock() 

1095 page.start_processing() 

1096 

1097 assert page.processing is True 

1098 mock_dl_worker.assert_called() 

1099 

1100 

1101class TestErrorHandling: 

1102 """Tests for error handling.""" 

1103 

1104 def test_start_processing_handles_worker_creation_error(self, qtbot, mock_context_dl): 

1105 """Test worker creation error handling.""" 

1106 page = DlExecutionPage(mock_context_dl) 

1107 qtbot.addWidget(page) 

1108 

1109 with patch("main.ui.dl_execution_page.DlWorker") as mock_worker: 

1110 mock_worker.side_effect = Exception("Worker creation failed") 

1111 

1112 try: 

1113 page.start_processing() 

1114 except Exception: 

1115 pass 

1116 

1117 def test_processing_finished_without_workspace_path(self, qtbot): 

1118 """Test processing_finished without workspace_path.""" 

1119 context = {"selected_segmentation_files": ["file.nii"]} 

1120 page = DlExecutionPage(context) 

1121 qtbot.addWidget(page) 

1122 

1123 with patch.object(QMessageBox, 'information'): 

1124 page.processing_finished(True, "Success") 

1125 

1126 

1127class TestBoundaryConditions: 

1128 """Tests for boundary conditions.""" 

1129 

1130 def test_many_files_in_list(self, qtbot, temp_workspace): 

1131 """Test with many files in the list.""" 

1132 files = [os.path.join(temp_workspace, f"sub-{i:03d}", "anat", "file.nii") 

1133 for i in range(100)] 

1134 

1135 context = { 

1136 "workspace_path": temp_workspace, 

1137 "selected_segmentation_files": files, 

1138 "update_main_buttons": Mock() 

1139 } 

1140 

1141 page = DlExecutionPage(context) 

1142 qtbot.addWidget(page) 

1143 

1144 page.on_enter() 

1145 

1146 assert page.files_list.count() == 100 

1147 

1148 def test_empty_filename(self, qtbot, mock_context_dl): 

1149 """Test with empty filename.""" 

1150 page = DlExecutionPage(mock_context_dl) 

1151 qtbot.addWidget(page) 

1152 page.on_enter() 

1153 

1154 page.update_file_status("", "Processing") 

1155 

1156 def test_very_long_filename(self, qtbot, temp_workspace): 

1157 """Test with very long filename.""" 

1158 long_name = "a" * 500 + ".nii" 

1159 context = { 

1160 "workspace_path": temp_workspace, 

1161 "selected_segmentation_files": [os.path.join(temp_workspace, long_name)], 

1162 "update_main_buttons": Mock() 

1163 } 

1164 

1165 page = DlExecutionPage(context) 

1166 qtbot.addWidget(page) 

1167 

1168 page.on_enter() 

1169 

1170 assert page.files_list.count() == 1 

1171 

1172 def test_rapid_log_messages(self, qtbot, mock_context_dl): 

1173 """Test with very rapid log messages.""" 

1174 page = DlExecutionPage(mock_context_dl) 

1175 qtbot.addWidget(page) 

1176 

1177 for i in range(1000): 

1178 page.add_log_message(f"Message {i}", 'i') 

1179 

1180 assert "Message 999" in page.log_text.toPlainText() 

1181 

1182 

1183class TestMemoryAndCleanup: 

1184 """Tests for memory management and cleanup.""" 

1185 

1186 def test_worker_cleanup_after_finish(self, qtbot, mock_context_dl, mock_dl_worker): 

1187 """Test that the worker is cleaned up after finish.""" 

1188 page = DlExecutionPage(mock_context_dl) 

1189 qtbot.addWidget(page) 

1190 

1191 page.start_processing() 

1192 assert page.worker is not None 

1193 

1194 page.reset_processing_state() 

1195 assert page.worker is None 

1196 

1197 def test_multiple_processing_cycles(self, qtbot, mock_context_dl, mock_dl_worker): 

1198 """Test multiple processing cycles.""" 

1199 page = DlExecutionPage(mock_context_dl) 

1200 qtbot.addWidget(page) 

1201 

1202 for cycle in range(3): 

1203 page.on_enter() 

1204 page.start_processing() 

1205 

1206 with patch.object(QMessageBox, 'information'): 

1207 page.processing_finished(True, f"Cycle {cycle} complete") 

1208 

1209 page.reset_processing_state() 

1210 

1211 assert True 

1212 

1213 

1214class TestDocumentation: 

1215 """Tests for documentation.""" 

1216 

1217 def test_class_docstring(self): 

1218 """Test that the class has a docstring.""" 

1219 assert DlExecutionPage.__doc__ is not None 

1220 

1221 def test_on_enter_docstring(self, qtbot, mock_context_dl): 

1222 """Test that on_enter has a docstring.""" 

1223 page = DlExecutionPage(mock_context_dl) 

1224 qtbot.addWidget(page) 

1225 

1226 assert page.on_enter.__doc__ is not None 

1227 

1228 

1229class TestAccessibility: 

1230 """Tests for accessibility.""" 

1231 

1232 def test_buttons_have_text(self, qtbot, mock_context_dl): 

1233 """Test that buttons have text.""" 

1234 page = DlExecutionPage(mock_context_dl) 

1235 qtbot.addWidget(page) 

1236 

1237 assert len(page.start_button.text()) > 0 

1238 assert len(page.cancel_button.text()) > 0 

1239 

1240 def test_labels_have_text(self, qtbot, mock_context_dl): 

1241 """Test that labels have text.""" 

1242 page = DlExecutionPage(mock_context_dl) 

1243 qtbot.addWidget(page) 

1244 

1245 assert len(page.header.text()) > 0 

1246 assert len(page.current_operation.text()) > 0 

1247 assert len(page.log_label.text()) > 0