sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user