VibecoderMcSwaggins's picture
fix(docker): use subprocess bridge for Python version isolation (#18)
4b42170 unverified
"""Tests for direct DeepISLES invocation module."""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from pathlib import Path
from stroke_deepisles_demo.core.exceptions import DeepISLESError, MissingInputError
from stroke_deepisles_demo.inference.deepisles import find_prediction_mask
from stroke_deepisles_demo.inference.direct import validate_input_files
class TestValidateInputFiles:
"""Tests for validate_input_files."""
def test_valid_files(self, tmp_path: Path) -> None:
"""Passes validation when required files exist."""
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
dwi.touch()
adc.touch()
# Should not raise
validate_input_files(dwi, adc)
def test_valid_files_with_flair(self, tmp_path: Path) -> None:
"""Passes validation when all files including FLAIR exist."""
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
flair = tmp_path / "flair.nii.gz"
dwi.touch()
adc.touch()
flair.touch()
# Should not raise
validate_input_files(dwi, adc, flair)
def test_missing_dwi(self, tmp_path: Path) -> None:
"""Raises MissingInputError when DWI file missing."""
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
adc.touch()
with pytest.raises(MissingInputError, match="DWI file not found"):
validate_input_files(dwi, adc)
def test_missing_adc(self, tmp_path: Path) -> None:
"""Raises MissingInputError when ADC file missing."""
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
dwi.touch()
with pytest.raises(MissingInputError, match="ADC file not found"):
validate_input_files(dwi, adc)
def test_missing_flair_when_specified(self, tmp_path: Path) -> None:
"""Raises MissingInputError when FLAIR specified but missing."""
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
flair = tmp_path / "flair.nii.gz"
dwi.touch()
adc.touch()
with pytest.raises(MissingInputError, match="FLAIR file not found"):
validate_input_files(dwi, adc, flair)
class TestFindPredictionMask:
"""Tests for find_prediction_mask (shared function in deepisles module)."""
def test_finds_prediction_in_results_dir(self, tmp_path: Path) -> None:
"""Finds prediction.nii.gz in results subdirectory."""
results = tmp_path / "results"
results.mkdir()
pred = results / "prediction.nii.gz"
pred.touch()
found = find_prediction_mask(tmp_path)
assert found == pred
def test_finds_alternative_names(self, tmp_path: Path) -> None:
"""Finds prediction with alternative naming patterns."""
results = tmp_path / "results"
results.mkdir()
pred = results / "lesion_mask.nii.gz"
pred.touch()
found = find_prediction_mask(tmp_path)
assert found == pred
def test_finds_in_output_dir_directly(self, tmp_path: Path) -> None:
"""Finds prediction directly in output directory."""
pred = tmp_path / "prediction.nii.gz"
pred.touch()
found = find_prediction_mask(tmp_path)
assert found == pred
def test_finds_any_nifti(self, tmp_path: Path) -> None:
"""Falls back to any NIfTI file if standard names not found."""
results = tmp_path / "results"
results.mkdir()
pred = results / "custom_output.nii.gz"
pred.touch()
found = find_prediction_mask(tmp_path)
assert found == pred
def test_excludes_input_files(self, tmp_path: Path) -> None:
"""Excludes DWI/ADC/FLAIR from fallback search."""
# Only input files, no prediction
(tmp_path / "dwi.nii.gz").touch()
(tmp_path / "adc.nii.gz").touch()
with pytest.raises(DeepISLESError, match="No prediction mask found"):
find_prediction_mask(tmp_path)
def test_no_mask_found(self, tmp_path: Path) -> None:
"""Raises DeepISLESError when no prediction mask found."""
with pytest.raises(DeepISLESError, match="No prediction mask found"):
find_prediction_mask(tmp_path)
class TestRunDeepISLESDirect:
"""Tests for run_deepisles_direct function.
Note: These tests don't actually run DeepISLES (which requires the
DeepISLES Docker image). They test the wrapper logic only.
"""
def test_missing_input_raises(self, tmp_path: Path) -> None:
"""Raises MissingInputError for missing input files."""
from stroke_deepisles_demo.inference.direct import run_deepisles_direct
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
output = tmp_path / "output"
with pytest.raises(MissingInputError):
run_deepisles_direct(dwi, adc, output)
def test_deepisles_not_available_raises(self, tmp_path: Path) -> None:
"""Raises DeepISLESError when DeepISLES not available."""
from stroke_deepisles_demo.inference.direct import run_deepisles_direct
# Create input files
dwi = tmp_path / "dwi.nii.gz"
adc = tmp_path / "adc.nii.gz"
output = tmp_path / "output"
dwi.touch()
adc.touch()
# Subprocess will fail because conda/adapter not available locally
# The error message depends on what's missing (conda, /app dir, etc.)
with pytest.raises(DeepISLESError, match=r"(subprocess|conda|No such file)"):
run_deepisles_direct(dwi, adc, output)