"""Unit tests for the ImageHub segmentation-linking domain service. The service was built with INJECTED infrastructure (put_blob / sniff_meta / safe_name) precisely so the domain rules can be exercised with fakes — no Postgres, no MinIO. Covers: parent validation (bad uuid / not-found / not-an-image), the mask path namespacing, organ-label fallback, and the empty-payload guard. """ from __future__ import annotations import unittest import uuid from src.imagehub_segmentation import MaskUpload, SegmentationError, SegmentationService from src.initiative_db.models import ImagehubDataset, ImagehubDatasetFile class _FakeResult: def __init__(self, value): self._value = value def scalar_one_or_none(self): return self._value class _FakeSession: """Minimal AsyncSession stand-in. The 1st execute() resolves the parent file; every later execute() resolves the 'existing mask at this path' lookup (None = create new). Records added rows.""" def __init__(self, parent=None, existing=None): self._parent = parent self._existing = existing self.added: list = [] self.exec_calls = 0 self.flushes = 0 async def execute(self, _stmt): self.exec_calls += 1 return _FakeResult(self._parent if self.exec_calls == 1 else self._existing) async def get(self, _model, _key): return None # blob absent → service will add it def add(self, obj): self.added.append(obj) async def flush(self): self.flushes += 1 def _safe_name(name): base = (name or "").strip().replace("\\", "/").rsplit("/", 1)[-1] return base or "file" async def _put_blob(data, media_type): return { "sha256": "deadbeef" + str(len(data)), "size": len(data), "bucket": "imagehub-blobs", "key": "blobs/de/ad/deadbeef", "media_type": media_type or "application/octet-stream", "deduped": False, } def _sniff(_filename, _data, _media): return {"format": "nifti", "shape": [4, 4, 4]} def _service(session): return SegmentationService(session, put_blob=_put_blob, sniff_meta=_sniff, safe_name=_safe_name) def _image_parent(dataset_id, logical_path="ct.nii.gz"): return ImagehubDatasetFile( id=uuid.uuid4(), dataset_id=dataset_id, logical_path=logical_path, file_kind="image" ) def _mask(filename="liver.nii.gz", organ="Gan", data=b"xyz"): return MaskUpload(filename=filename, data=data, media_type="application/gzip", organ_label=organ) class TestMaskLogicalPath(unittest.TestCase): def test_namespaces_under_parent_stem(self): svc = _service(_FakeSession()) self.assertEqual(svc._mask_logical_path("ct.nii.gz", "liver.nii.gz"), "ct.seg/liver.nii.gz") self.assertEqual(svc._mask_logical_path("scan.nii", "a.nii.gz"), "scan.seg/a.nii.gz") self.assertEqual(svc._mask_logical_path("study.dcm", "k.nii.gz"), "study.seg/k.nii.gz") def test_mask_path_cannot_collide_with_a_real_image_path(self): # A real file's logical_path never contains '/', a mask path always does. svc = _service(_FakeSession()) self.assertIn("/", svc._mask_logical_path("ct.nii.gz", "ct.nii.gz")) class TestLinkMasks(unittest.IsolatedAsyncioTestCase): def setUp(self): self.ds = ImagehubDataset(id=uuid.uuid4(), owner_user_id=uuid.uuid4()) self.uid = uuid.uuid4() async def test_happy_path_links_mask_to_image(self): parent = _image_parent(self.ds.id) sess = _FakeSession(parent=parent, existing=None) rows = await _service(sess).link_masks(self.ds, str(parent.id), [_mask()], self.uid) self.assertEqual(len(rows), 1) r = rows[0] self.assertEqual(r.file_kind, "segmentation") self.assertEqual(r.parent_file_id, parent.id) self.assertEqual(r.organ_label, "Gan") self.assertEqual(r.logical_path, "ct.seg/liver.nii.gz") self.assertEqual(r.dataset_id, self.ds.id) async def test_organ_label_falls_back_to_filename(self): parent = _image_parent(self.ds.id) sess = _FakeSession(parent=parent) rows = await _service(sess).link_masks(self.ds, str(parent.id), [_mask(organ=" ")], self.uid) self.assertEqual(rows[0].organ_label, "liver.nii.gz") async def test_bad_parent_uuid_is_404(self): with self.assertRaises(SegmentationError) as ctx: await _service(_FakeSession()).link_masks(self.ds, "not-a-uuid", [_mask()], self.uid) self.assertEqual(ctx.exception.status, 404) async def test_parent_not_found_is_404(self): sess = _FakeSession(parent=None) with self.assertRaises(SegmentationError) as ctx: await _service(sess).link_masks(self.ds, str(uuid.uuid4()), [_mask()], self.uid) self.assertEqual(ctx.exception.status, 404) async def test_attaching_to_a_mask_is_422(self): mask_parent = ImagehubDatasetFile( id=uuid.uuid4(), dataset_id=self.ds.id, logical_path="ct.seg/x.nii.gz", file_kind="segmentation" ) sess = _FakeSession(parent=mask_parent) with self.assertRaises(SegmentationError) as ctx: await _service(sess).link_masks(self.ds, str(mask_parent.id), [_mask()], self.uid) self.assertEqual(ctx.exception.status, 422) async def test_empty_payload_is_422(self): parent = _image_parent(self.ds.id) sess = _FakeSession(parent=parent) with self.assertRaises(SegmentationError) as ctx: await _service(sess).link_masks(self.ds, str(parent.id), [], self.uid) self.assertEqual(ctx.exception.status, 422) async def test_all_empty_byte_masks_is_422(self): parent = _image_parent(self.ds.id) sess = _FakeSession(parent=parent) with self.assertRaises(SegmentationError) as ctx: await _service(sess).link_masks(self.ds, str(parent.id), [_mask(data=b"")], self.uid) self.assertEqual(ctx.exception.status, 422) if __name__ == "__main__": unittest.main()