diff --git a/src/bioetl/domain/ports/workflow_foreign_key_reconciliation.py b/src/bioetl/domain/ports/workflow_foreign_key_reconciliation.py index 27d1e43a2c..eb0febf49a 100644 --- a/src/bioetl/domain/ports/workflow_foreign_key_reconciliation.py +++ b/src/bioetl/domain/ports/workflow_foreign_key_reconciliation.py @@ -27,6 +27,10 @@ class ForeignKeyReconciliationRequest: nulls_equal: bool = False def __post_init__(self) -> None: + self._validate_required_fields() + self._validate_composite_keys() + + def _validate_required_fields(self) -> None: if not self.source_table.strip(): raise ValueError("source_table cannot be empty") if not self.reference_table.strip(): @@ -37,24 +41,20 @@ def __post_init__(self) -> None: raise ValueError("reference_key cannot be empty") if not self.primary_keys: raise ValueError("primary_keys cannot be empty") + + def _validate_composite_keys(self) -> None: if self.source_keys is None and self.reference_keys is None: return if self.source_keys is None or self.reference_keys is None: - raise ValueError( - "source_keys and reference_keys must be provided together" - ) + raise ValueError("source_keys and reference_keys must be provided together") if not self.source_keys or not self.reference_keys: raise ValueError("source_keys and reference_keys cannot be empty") if len(self.source_keys) != len(self.reference_keys): - raise ValueError( - "source_keys and reference_keys must have the same length" - ) + raise ValueError("source_keys and reference_keys must have the same length") if self.source_keys[0].strip() != self.source_key.strip(): raise ValueError("source_key must match the first source_keys entry") if self.reference_keys[0].strip() != self.reference_key.strip(): - raise ValueError( - "reference_key must match the first reference_keys entry" - ) + raise ValueError("reference_key must match the first reference_keys entry") @property def effective_source_keys(self) -> tuple[str, ...]: diff --git a/src/bioetl/interfaces/cli/commands/domains/health/observability_backend_process.py b/src/bioetl/interfaces/cli/commands/domains/health/observability_backend_process.py index 5a6c38f08e..1208f4d1b0 100644 --- a/src/bioetl/interfaces/cli/commands/domains/health/observability_backend_process.py +++ b/src/bioetl/interfaces/cli/commands/domains/health/observability_backend_process.py @@ -136,7 +136,7 @@ def _build_detached_backend_env( current_env: dict[str, str] | None = None, ) -> dict[str, str]: """Ensure detached backend subprocess can import the src-layout package.""" - env = dict(current_env if current_env is not None else getattr(os, "environ")) + env = dict(current_env if current_env is not None else os.environ) src_root = Path(__file__).resolve().parents[6] existing_pythonpath = env.get("PYTHONPATH", "").strip() pythonpath_parts = [str(src_root)] diff --git a/tests/unit/infrastructure/adapters/common/test_response_shapes.py b/tests/unit/infrastructure/adapters/common/test_response_shapes.py index 999874b855..2285a8691d 100644 --- a/tests/unit/infrastructure/adapters/common/test_response_shapes.py +++ b/tests/unit/infrastructure/adapters/common/test_response_shapes.py @@ -2,6 +2,8 @@ from __future__ import annotations +from collections import OrderedDict + from bioetl.infrastructure.adapters.common.response_shapes import ( extract_response_items, extract_response_mapping, @@ -40,3 +42,11 @@ def test_extract_response_text_returns_only_string_values() -> None: assert extract_response_text({"nextCursor": "cursor-1"}, "nextCursor") == "cursor-1" assert extract_response_text({"nextCursor": 10}, "nextCursor") is None assert extract_response_text({}, "nextCursor") is None + + +def test_extract_response_mapping_handles_various_edge_cases() -> None: + assert extract_response_mapping({"meta": None}, "meta") is None + assert extract_response_mapping({"meta": "string_not_mapping"}, "meta") is None + + ordered_map = OrderedDict([("a", 1), ("b", 2)]) + assert extract_response_mapping({"meta": ordered_map}, "meta") == ordered_map