Coverage for tests/ui/test_pipeline_patient_selection.py: 99%
315 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1import pytest
2import os
3import json
4import tempfile
5import shutil
6from unittest.mock import Mock, patch, MagicMock
8from main.ui.pipeline_patient_selection_page import PipelinePatientSelectionPage
10class TestPipelinePatientSelectionPageSetup:
11 """Tests for initialization"""
13 @pytest.fixture
14 def temp_workspace(self):
15 temp_dir = tempfile.mkdtemp()
16 yield temp_dir
17 shutil.rmtree(temp_dir, ignore_errors=True)
19 @pytest.fixture
20 def pipeline_page(self, qtbot, mock_context):
21 page = PipelinePatientSelectionPage(mock_context, Mock())
22 qtbot.addWidget(page)
23 return page
25 def test_page_initialization(self, pipeline_page):
26 """Verify correct initialization"""
27 assert pipeline_page.context is not None
28 assert pipeline_page.workspace_path is not None
29 assert pipeline_page.selected_patients == set()
30 assert pipeline_page.patient_status == {}
32 def test_title_created(self, pipeline_page):
33 """Verify title creation"""
34 assert pipeline_page.title is not None
35 assert pipeline_page.title.text() != ""
37 def test_buttons_created(self, pipeline_page):
38 """Verify button creation"""
39 assert pipeline_page.select_eligible_btn is not None
40 assert pipeline_page.deselect_all_btn is not None
41 assert pipeline_page.refresh_btn is not None
43 def test_summary_widget_created(self, pipeline_page):
44 """Verify summary widget creation"""
45 assert pipeline_page.summary_widget is not None
46 assert pipeline_page.total_label is not None
47 assert pipeline_page.eligible_label is not None
48 assert pipeline_page.not_eligible_label is not None
51class TestPipelinePatientRequirements:
52 """Tests for verifying patient requirements"""
54 @pytest.fixture
55 def temp_workspace(self):
56 temp_dir = tempfile.mkdtemp()
57 # Create eligible patient structure
58 patient_dir = os.path.join(temp_dir, "sub-01")
59 os.makedirs(os.path.join(patient_dir, "anat"))
61 # FLAIR
62 with open(os.path.join(patient_dir, "anat", "sub-01_flair.nii.gz"), "w") as f:
63 f.write("flair")
65 # Skull stripping
66 skull_dir = os.path.join(temp_dir, "derivatives", "skullstrips", "sub-01", "anat")
67 os.makedirs(skull_dir)
68 with open(os.path.join(skull_dir, "sub-01_brain.nii.gz"), "w") as f:
69 f.write("brain")
71 # Segmentation
72 seg_dir = os.path.join(temp_dir, "derivatives", "manual_masks", "sub-01", "anat")
73 os.makedirs(seg_dir)
74 with open(os.path.join(seg_dir, "sub-01_mask.nii.gz"), "w") as f:
75 f.write("mask")
77 yield temp_dir
78 shutil.rmtree(temp_dir, ignore_errors=True)
80 @pytest.fixture
81 def pipeline_page(self, qtbot, mock_context):
82 page = PipelinePatientSelectionPage(mock_context, Mock())
83 qtbot.addWidget(page)
84 return page
86 def test_check_patient_eligible(self, pipeline_page, temp_workspace):
87 """Verify patient with all requirements"""
88 patient_path = os.path.join(temp_workspace, "sub-01")
90 status = pipeline_page._check_patient_requirements(patient_path, "sub-01")
92 assert status['eligible'] == True
93 assert status['requirements']['flair'] == True
94 assert status['requirements']['skull_stripping'] == True
95 assert status['requirements']['segmentation'] == True
97 def test_check_patient_missing_flair(self, pipeline_page, temp_workspace):
98 """Verify patient without FLAIR"""
99 # Create patient without FLAIR
100 patient_dir = os.path.join(temp_workspace, "sub-02")
101 os.makedirs(os.path.join(patient_dir, "anat"))
103 status = pipeline_page._check_patient_requirements(patient_dir, "sub-02")
105 assert status['eligible'] == False
106 assert status['requirements']['flair'] == False
107 assert "FLAIR" in str(status['missing_files'])
109 def test_check_patient_missing_skull_stripping(self, pipeline_page, temp_workspace):
110 """Verify patient without skull stripping"""
111 patient_dir = os.path.join(temp_workspace, "sub-03")
112 os.makedirs(os.path.join(patient_dir, "anat"))
113 with open(os.path.join(patient_dir, "anat", "sub-03_flair.nii.gz"), "w") as f:
114 f.write("flair")
116 status = pipeline_page._check_patient_requirements(patient_dir, "sub-03")
118 assert status['eligible'] == False
119 assert status['requirements']['skull_stripping'] == False
121 def test_check_patient_missing_segmentation(self, pipeline_page, temp_workspace):
122 """Verify patient without segmentation"""
123 patient_dir = os.path.join(temp_workspace, "sub-04")
124 os.makedirs(os.path.join(patient_dir, "anat"))
125 with open(os.path.join(patient_dir, "anat", "sub-04_flair.nii.gz"), "w") as f:
126 f.write("flair")
128 skull_dir = os.path.join(temp_workspace, "derivatives", "skullstrips", "sub-04", "anat")
129 os.makedirs(skull_dir)
130 with open(os.path.join(skull_dir, "sub-04_brain.nii.gz"), "w") as f:
131 f.write("brain")
133 status = pipeline_page._check_patient_requirements(patient_dir, "sub-04")
135 assert status['eligible'] == False
136 assert status['requirements']['segmentation'] == False
139class TestPipelinePatientSelection:
140 """Tests for patient selection"""
142 @pytest.fixture
143 def temp_workspace(self):
144 temp_dir = tempfile.mkdtemp()
145 # Create 2 eligible patients
146 for i in range(1, 3):
147 patient_id = f"sub-{i:02d}"
148 patient_dir = os.path.join(temp_dir, patient_id)
149 os.makedirs(os.path.join(patient_dir, "anat"))
151 with open(os.path.join(patient_dir, "anat", f"{patient_id}_flair.nii.gz"), "w") as f:
152 f.write("flair")
154 skull_dir = os.path.join(temp_dir, "derivatives", "skullstrips", patient_id, "anat")
155 os.makedirs(skull_dir)
156 with open(os.path.join(skull_dir, f"{patient_id}_brain.nii.gz"), "w") as f:
157 f.write("brain")
159 seg_dir = os.path.join(temp_dir, "derivatives", "manual_masks", patient_id, "anat")
160 os.makedirs(seg_dir)
161 with open(os.path.join(seg_dir, f"{patient_id}_mask.nii.gz"), "w") as f:
162 f.write("mask")
164 yield temp_dir
165 shutil.rmtree(temp_dir, ignore_errors=True)
167 @pytest.fixture
168 def pipeline_page(self, qtbot, mock_context):
169 page = PipelinePatientSelectionPage(mock_context, Mock())
170 qtbot.addWidget(page)
171 return page
173 def test_select_all_eligible(self, pipeline_page):
174 """Verify selection of all eligible patients"""
175 pipeline_page._select_all_eligible_patients()
177 assert len(pipeline_page.selected_patients) == 2
178 assert "sub-01" in pipeline_page.selected_patients
179 assert "sub-02" in pipeline_page.selected_patients
181 def test_deselect_all(self, pipeline_page):
182 """Verify deselection of all patients"""
183 pipeline_page._select_all_eligible_patients()
184 assert len(pipeline_page.selected_patients) == 2
186 pipeline_page._deselect_all_patients()
188 assert len(pipeline_page.selected_patients) == 0
190 def test_toggle_patient_select(self, pipeline_page):
191 """Verify single patient selection"""
192 button = Mock()
193 pipeline_page._toggle_patient("sub-01", True, button)
195 assert "sub-01" in pipeline_page.selected_patients
197 def test_toggle_patient_deselect(self, pipeline_page):
198 """Verify single patient deselection"""
199 pipeline_page.selected_patients.add("sub-01")
200 button = Mock()
202 pipeline_page._toggle_patient("sub-01", False, button)
204 assert "sub-01" not in pipeline_page.selected_patients
206class TestPipelinePatientLoading:
207 """Tests for patient loading"""
209 @pytest.fixture
210 def pipeline_page(self, qtbot, mock_context):
211 page = PipelinePatientSelectionPage(mock_context, Mock())
212 qtbot.addWidget(page)
213 return page
215 def test_find_patient_dirs(self, pipeline_page):
216 """Verify patient directory search"""
217 patient_dirs = pipeline_page._find_patient_dirs()
219 assert len(patient_dirs) == 2
220 patient_ids = [os.path.basename(d) for d in patient_dirs]
221 assert "sub-01" in patient_ids
222 assert "sub-02" in patient_ids
224 def test_load_patients_creates_buttons(self, pipeline_page):
225 """Verify patient button creation"""
226 assert len(pipeline_page.patient_buttons) == 2
228 def test_load_patients_updates_status(self, pipeline_page):
229 """Verify patient status update"""
230 assert len(pipeline_page.patient_status) == 2
233class TestPipelineConfigGeneration:
234 """Tests for pipeline configuration generation"""
236 @pytest.fixture
237 def temp_workspace(self):
238 temp_dir = tempfile.mkdtemp()
239 patient_dir = os.path.join(temp_dir, "sub-01")
240 os.makedirs(os.path.join(patient_dir, "anat"))
241 os.makedirs(os.path.join(patient_dir, "ses-01", "pet"))
243 # Necessary files
244 with open(os.path.join(patient_dir, "anat", "sub-01_flair.nii.gz"), "w") as f:
245 f.write("flair")
246 with open(os.path.join(patient_dir, "ses-01", "pet", "sub-01_pet.nii.gz"), "w") as f:
247 f.write("pet")
249 skull_dir = os.path.join(temp_dir, "derivatives", "skullstrips", "sub-01", "anat")
250 os.makedirs(skull_dir)
251 with open(os.path.join(skull_dir, "sub-01_brain.nii.gz"), "w") as f:
252 f.write("brain")
254 seg_dir = os.path.join(temp_dir, "derivatives", "manual_masks", "sub-01", "anat")
255 os.makedirs(seg_dir)
256 with open(os.path.join(seg_dir, "sub-01_mask.nii.gz"), "w") as f:
257 f.write("mask")
259 yield temp_dir
260 shutil.rmtree(temp_dir, ignore_errors=True)
262 @pytest.fixture
263 def pipeline_page(self, qtbot, mock_context):
264 page = PipelinePatientSelectionPage(mock_context, Mock())
265 qtbot.addWidget(page)
266 page.selected_patients = {"sub-01"}
267 return page
269 def test_build_pipeline_config_creates_file(self, pipeline_page, temp_workspace):
270 """Verify configuration file creation"""
271 config_path = pipeline_page._build_pipeline_config()
273 assert os.path.exists(config_path)
274 assert config_path.endswith("_config.json")
276 def test_build_pipeline_config_content(self, pipeline_page):
277 """Verify configuration content"""
278 config_path = pipeline_page._build_pipeline_config()
280 with open(config_path, 'r') as f:
281 config = json.load(f)
283 assert "sub-01" in config
284 assert "mri" in config["sub-01"]
285 assert "mri_str" in config["sub-01"]
286 assert "tumor_mri" in config["sub-01"]
288 def test_get_next_config_id_first(self, pipeline_page, temp_workspace):
289 """Verify configuration ID first time"""
290 pipeline_dir = os.path.join(temp_workspace, "pipeline")
291 os.makedirs(pipeline_dir, exist_ok=True)
293 config_id = pipeline_page._get_next_config_id(pipeline_dir)
295 assert config_id == 1
297 def test_get_next_config_id_existing(self, pipeline_page, temp_workspace):
298 """Verify configuration ID with existing files"""
299 pipeline_dir = os.path.join(temp_workspace, "pipeline")
300 os.makedirs(pipeline_dir, exist_ok=True)
302 # Create existing files
303 with open(os.path.join(pipeline_dir, "01_config.json"), "w") as f:
304 f.write("{}")
305 with open(os.path.join(pipeline_dir, "02_config.json"), "w") as f:
306 f.write("{}")
308 config_id = pipeline_page._get_next_config_id(pipeline_dir)
310 assert config_id == 3
312class TestPipelinePatientRefresh:
313 """Tests for refreshing patient status"""
315 @pytest.fixture
316 def pipeline_page(self, qtbot, mock_context):
317 page = PipelinePatientSelectionPage(mock_context, Mock())
318 qtbot.addWidget(page)
319 return page
321 def test_refresh_maintains_valid_selections(self, pipeline_page):
322 """Verify that refresh maintains valid selections"""
323 # Simulate selection
324 pipeline_page.selected_patients.add("sub-01")
326 pipeline_page._refresh_patient_status()
328 # Selections should be validated
329 assert isinstance(pipeline_page.selected_patients, set)
332class TestPipelinePatientNavigation:
333 """Tests for navigation"""
335 @pytest.fixture
336 def pipeline_page(self, qtbot, mock_context):
337 previous_page = Mock()
338 page = PipelinePatientSelectionPage(mock_context, previous_page)
339 qtbot.addWidget(page)
340 return page
342 def test_is_ready_to_advance_with_selection(self, pipeline_page):
343 """Verify ready to advance with selections"""
344 pipeline_page.selected_patients.add("sub-01")
345 assert pipeline_page.is_ready_to_advance()
347 def test_is_ready_to_advance_without_selection(self, pipeline_page):
348 """Verify not ready without selections"""
349 assert not pipeline_page.is_ready_to_advance()
351 def test_is_ready_to_go_back(self, pipeline_page):
352 """Verify can go back"""
353 assert pipeline_page.is_ready_to_go_back()
355 def test_back_returns_previous_page(self, pipeline_page):
356 """Verify return to previous page"""
357 result = pipeline_page.back()
358 assert result == pipeline_page.previous_page
359 pipeline_page.previous_page.on_enter.assert_called_once()
361 @patch('main.ui.pipeline_patient_selection_page.PipelineReviewPage')
362 def test_next_creates_review_page(self, MockPage, pipeline_page):
363 """Verify review page creation"""
364 mock_page = Mock()
365 MockPage.return_value = mock_page
366 pipeline_page.selected_patients.add("sub-01")
368 result = pipeline_page.next(pipeline_page.context)
370 assert result == mock_page
371 mock_page.on_enter.assert_called_once()
373class TestPipelinePatientSummary:
374 """Tests for summary functions"""
376 @pytest.fixture
377 def pipeline_page(self, qtbot, mock_context):
378 page = PipelinePatientSelectionPage(mock_context, Mock())
379 qtbot.addWidget(page)
380 return page
382 def test_get_selected_patients(self, pipeline_page):
383 """Verify retrieval of selected patients"""
384 pipeline_page.selected_patients = {"sub-01", "sub-02"}
386 selected = pipeline_page.get_selected_patients()
388 assert isinstance(selected, list)
389 assert len(selected) == 2
391 def test_get_eligible_patients(self, pipeline_page):
392 """Verify retrieval of eligible patients"""
393 pipeline_page.patient_status = {
394 "sub-01": {"eligible": True},
395 "sub-02": {"eligible": False}
396 }
398 eligible = pipeline_page.get_eligible_patients()
400 assert len(eligible) == 1
401 assert "sub-01" in eligible
403 def test_get_patient_status_summary(self, pipeline_page):
404 """Verify status summary"""
405 pipeline_page.patient_status = {
406 "sub-01": {"eligible": True},
407 "sub-02": {"eligible": False}
408 }
409 pipeline_page.selected_patients = {"sub-01"}
411 summary = pipeline_page.get_patient_status_summary()
413 assert summary['total'] == 2
414 assert summary['eligible'] == 1
415 assert summary['selected'] == 1
416 assert summary['not_eligible'] == 1
419class TestPipelinePatientReset:
420 """Tests for page reset"""
422 @pytest.fixture
423 def pipeline_page(self, qtbot, mock_context):
424 page = PipelinePatientSelectionPage(mock_context, Mock())
425 qtbot.addWidget(page)
426 return page
428 def test_reset_clears_selections(self, pipeline_page):
429 """Verify that reset clears selections"""
430 pipeline_page.selected_patients = {"sub-01", "sub-02"}
432 pipeline_page.reset_page()
434 assert len(pipeline_page.selected_patients) == 0
436 def test_reset_clears_status(self, pipeline_page):
437 """Verify that reset clears status"""
438 pipeline_page.patient_status = {"sub-01": {"eligible": True}}
440 pipeline_page.reset_page()
442 # Status should be reloaded
443 assert isinstance(pipeline_page.patient_status, dict)
445# Integration tests
446class TestPipelinePatientIntegration:
447 """Integration tests"""
449 @pytest.fixture
450 def temp_workspace(self):
451 temp_dir = tempfile.mkdtemp()
452 # Create complete patient
453 patient_dir = os.path.join(temp_dir, "sub-01")
454 os.makedirs(os.path.join(patient_dir, "anat"))
455 os.makedirs(os.path.join(patient_dir, "ses-01", "pet"))
457 with open(os.path.join(patient_dir, "anat", "sub-01_flair.nii.gz"), "w") as f:
458 f.write("flair")
460 skull_dir = os.path.join(temp_dir, "derivatives", "skullstrips", "sub-01", "anat")
461 os.makedirs(skull_dir)
462 with open(os.path.join(skull_dir, "sub-01_brain.nii.gz"), "w") as f:
463 f.write("brain")
465 seg_dir = os.path.join(temp_dir, "derivatives", "manual_masks", "sub-01", "anat")
466 os.makedirs(seg_dir)
467 with open(os.path.join(seg_dir, "sub-01_mask.nii.gz"), "w") as f:
468 f.write("mask")
470 yield temp_dir
471 shutil.rmtree(temp_dir, ignore_errors=True)
473 @pytest.fixture
474 def pipeline_page(self, qtbot, mock_context):
475 page = PipelinePatientSelectionPage(mock_context, Mock())
476 qtbot.addWidget(page)
477 return page
479 def test_full_workflow(self, pipeline_page, temp_workspace):
480 """Test full workflow"""
481 # Verify loading
482 assert len(pipeline_page.patient_status) == 1
484 # Patient should be eligible
485 assert pipeline_page.patient_status["sub-01"]["eligible"]
487 # Select
488 pipeline_page._select_all_eligible_patients()
489 assert len(pipeline_page.selected_patients) == 1
491 # Generate config
492 config_path = pipeline_page._build_pipeline_config()
493 assert os.path.exists(config_path)
495 # Verify config
496 with open(config_path, 'r') as f:
497 config = json.load(f)
498 assert "sub-01" in config
501if __name__ == "__main__":
502 pytest.main([__file__, "-v", "--tb=short"])