diff --git a/tests/test_destination_writer.py b/tests/test_destination_writer.py index 40cf5b7..35f2bb0 100644 --- a/tests/test_destination_writer.py +++ b/tests/test_destination_writer.py @@ -2,7 +2,7 @@ import pandas as pd import pytest -from destination_writer.destination_writer import destination_writer, write_file, write_metadata +from destination_writer.destination_writer import destination_writer, write_file, write_metadata, get_destination_location def test_write_metadata_bad_connection(): @@ -186,3 +186,90 @@ def test_destination_writer_does_not_call_write_file_without_rfau_column(monkeyp destination_writer(config, logger, "SomeTable", df) mock_write_file.assert_not_called() + + +# ── get_destination_location ────────────────────────────────────────────────── + +def test_get_destination_location_joins_destination_and_filename(): + import os + row = pd.Series({"RelativeFileArchiveURI": "/some/path/to/file.dcm"}) + result = get_destination_location(row, "/dest") + assert result == os.path.join("/dest", "file.dcm") + + +# ── write_file: None RFAU row ───────────────────────────────────────────────── + +def test_write_file_skips_copy_when_rfau_value_is_none(monkeypatch): + mock_copyfile = Mock() + monkeypatch.setattr("destination_writer.destination_writer.shutil.copyfile", mock_copyfile) + monkeypatch.setattr("destination_writer.destination_writer.os.makedirs", Mock()) + + config = {"destination": {"files": {"directory": "/dest"}}} + logger = Mock() + df = pd.DataFrame({"RelativeFileArchiveURI": [None]}) + + write_file(config, logger, df) + + mock_copyfile.assert_not_called() + + +# ── write_file: archive URI with ! ──────────────────────────────────────────── + +def test_write_file_copies_archive_source_when_rfau_contains_bang(monkeypatch): + mock_copyfile = Mock() + mock_get_dest = Mock(return_value="/dest/archive.7z") + monkeypatch.setattr("destination_writer.destination_writer.shutil.copyfile", mock_copyfile) + monkeypatch.setattr("destination_writer.destination_writer.get_destination_location", mock_get_dest) + monkeypatch.setattr("destination_writer.destination_writer.os.makedirs", Mock()) + + config = {"destination": {"files": {"directory": "/dest"}}} + logger = Mock() + df = pd.DataFrame({"RelativeFileArchiveURI": ["/src/archive.7z!file.dcm"]}) + + write_file(config, logger, df) + + mock_copyfile.assert_called_once_with("/src/archive.7z", "/dest/archive.7z") + + +# ── write_metadata: None RFAU value in row ──────────────────────────────────── + +def test_write_metadata_skips_get_destination_when_rfau_value_is_none(monkeypatch): + mock_get_dest = Mock() + monkeypatch.setattr( + "destination_writer.destination_writer.get_destination_location", mock_get_dest + ) + monkeypatch.setattr(pd.DataFrame, "to_sql", lambda *a, **kw: None) + + config = { + "destination": { + "files": {"directory": "/dest"}, + "metadata": {"dbType": "MSSQL", "connectionString": "fake", "index": "id"}, + } + } + logger = Mock() + df = pd.DataFrame({"RelativeFileArchiveURI": [None]}) + + write_metadata(config, logger, "TestTable", df) + + mock_get_dest.assert_not_called() + + +def test_write_metadata_skips_rfau_update_when_destination_location_is_none(monkeypatch): + monkeypatch.setattr( + "destination_writer.destination_writer.get_destination_location", + Mock(return_value=None), + ) + monkeypatch.setattr(pd.DataFrame, "to_sql", lambda *a, **kw: None) + + config = { + "destination": { + "files": {"directory": "/dest"}, + "metadata": {"dbType": "MSSQL", "connectionString": "fake", "index": "id"}, + } + } + logger = Mock() + df = pd.DataFrame({"RelativeFileArchiveURI": ["/src/file.dcm"]}) + + write_metadata(config, logger, "TestTable", df) + + logger.debug.assert_called() diff --git a/tests/test_metadata_processor.py b/tests/test_metadata_processor.py index 9f5c46b..278bcad 100644 --- a/tests/test_metadata_processor.py +++ b/tests/test_metadata_processor.py @@ -409,3 +409,82 @@ def test_metadata_processor_include_archives_adds_archive_extensions( assert "scan.dcm" in processed_files assert "backup.zip" in processed_files + + +# ─── get_modality_config: non-matching files in dir ────────────────────────── + +def test_get_modality_config_returns_none_when_files_present_but_none_match(tmp_path): + """Line 28->26: iterates files in templates dir that don't match the requested modality.""" + make_template(tmp_path / "templates", "MR", []) # MR.IT exists, but we request CT + + result = metadata_processor.get_modality_config_for_file("CT") + + assert result is None + + +# ─── _datasetProcess: except on ds.data_element() ──────────────────────────── + +def test_process_appends_none_when_data_element_raises(tmp_path): + """Lines 123-124: a valid DICOM keyword not present in the dataset causes ds[tag] to raise.""" + make_template(tmp_path / "templates", "CT", [{"TableName": "StudyTable", "Columns": [ + {"ColumnName": "PatientID"}, + {"ColumnName": "StudyDate"}, # valid keyword, not set in write_dicom -> KeyError + ]}]) + write_dicom(tmp_path / "image.dcm") # sets Modality, PatientID, StudyInstanceUID only + logger = Mock() + + records = metadata_processor.process("image.dcm", str(tmp_path), logger) + + values, _, _ = records[0] + assert values[0] == "patient-123" + assert values[1] is None + + +# ─── process: .7z path ─────────────────────────────────────────────────────── + +def test_process_handles_7z_archive_file(tmp_path, monkeypatch): + """Lines 135-139: .7z files are read from ./received_dicoms/friendly_name//.""" + make_template(tmp_path / "templates", "CT", [{"TableName": "StudyTable", "Columns": [ + {"ColumnName": "PatientID"}, + ]}]) + archive_dir = tmp_path / "received_dicoms" / "friendly_name" / "scan.7z" + archive_dir.mkdir(parents=True) + write_dicom(archive_dir / "image.dcm") + monkeypatch.chdir(tmp_path) + logger = Mock() + + records = metadata_processor.process("scan.7z", str(tmp_path), logger) + + assert len(records) == 1 + _, _, table_name = records[0] + assert table_name == "StudyTable" + + +# ─── metadata_processor: None result from process ──────────────────────────── + +def test_metadata_processor_handles_none_result_from_process( + tmp_path, patched_executor, mock_destination_writer, monkeypatch +): + """Line 180->179: process() returning None is skipped and does not increment file_count.""" + monkeypatch.setattr(metadata_processor, "process", lambda *a: None) + write_dicom(tmp_path / "a.dcm") + logger = Mock() + config = {"sources": {"scans": {"type": "filesystem", "directory": str(tmp_path)}}} + + metadata_processor.metadata_processor(config, logger) + + assert logger.info.call_args.args[0]["file_count"] == 0 + + +def test_metadata_processor_handles_none_record_in_result( + tmp_path, patched_executor, mock_destination_writer, monkeypatch +): + """Line 182->181: None entries within a process() result list are skipped.""" + monkeypatch.setattr(metadata_processor, "process", lambda *a: [None]) + write_dicom(tmp_path / "a.dcm") + logger = Mock() + config = {"sources": {"scans": {"type": "filesystem", "directory": str(tmp_path)}}} + + metadata_processor.metadata_processor(config, logger) + + assert logger.info.call_args.args[0]["file_count"] == 1