fix: finalize semantic repair and test updates

This commit is contained in:
2026-03-21 15:07:06 +03:00
parent 005797334b
commit 9b47b9b667
99 changed files with 2484 additions and 985 deletions

View File

@@ -22,6 +22,7 @@ from src.core.migration.archive_parser import MigrationArchiveParser
# [DEF:test_extract_objects_from_zip_collects_all_types:Function]
# @RELATION: BINDS_TO -> TestArchiveParser
# @PURPOSE: Verify archive parser collects dashboard/chart/dataset YAML objects into typed buckets.
def test_extract_objects_from_zip_collects_all_types():
parser = MigrationArchiveParser()
with tempfile.TemporaryDirectory() as td:
@@ -33,11 +34,19 @@ def test_extract_objects_from_zip_collects_all_types():
(src_dir / "datasets").mkdir(parents=True)
with open(src_dir / "dashboards" / "dash.yaml", "w") as file_obj:
yaml.dump({"uuid": "dash-u1", "dashboard_title": "D1", "json_metadata": "{}"}, file_obj)
yaml.dump(
{"uuid": "dash-u1", "dashboard_title": "D1", "json_metadata": "{}"},
file_obj,
)
with open(src_dir / "charts" / "chart.yaml", "w") as file_obj:
yaml.dump({"uuid": "chart-u1", "slice_name": "C1", "viz_type": "bar"}, file_obj)
yaml.dump(
{"uuid": "chart-u1", "slice_name": "C1", "viz_type": "bar"}, file_obj
)
with open(src_dir / "datasets" / "dataset.yaml", "w") as file_obj:
yaml.dump({"uuid": "ds-u1", "table_name": "orders", "database_uuid": "db-u1"}, file_obj)
yaml.dump(
{"uuid": "ds-u1", "table_name": "orders", "database_uuid": "db-u1"},
file_obj,
)
with zipfile.ZipFile(zip_path, "w") as zip_obj:
for root, _, files in os.walk(src_dir):
@@ -61,5 +70,5 @@ def test_extract_objects_from_zip_collects_all_types():
raise AssertionError("dataset uuid mismatch")
# [/DEF:TestArchiveParser:Module]
# [/DEF:test_extract_objects_from_zip_collects_all_types:Function]
# [/DEF:TestArchiveParser:Module]

View File

@@ -3,7 +3,7 @@
# @COMPLEXITY: 3
# @PURPOSE: Unit tests for MigrationDryRunService diff and risk computation contracts.
# @LAYER: Domain
# @RELATION: VERIFIES -> backend.src.core.migration.dry_run_orchestrator
# @RELATION: VERIFIES -> [MigrationDryRunOrchestratorModule]
#
import json
import sys
@@ -24,16 +24,21 @@ from src.models.mapping import Base
# [DEF:_load_fixture:Function]
# @RELATION: BINDS_TO -> TestDryRunOrchestrator
# @RELATION: BINDS_TO -> [TestDryRunOrchestrator]
# @PURPOSE: Load canonical migration dry-run fixture payload used by deterministic orchestration assertions.
def _load_fixture() -> dict:
fixture_path = Path(__file__).parents[2] / "fixtures" / "migration_dry_run_fixture.json"
fixture_path = (
Path(__file__).parents[2] / "fixtures" / "migration_dry_run_fixture.json"
)
return json.loads(fixture_path.read_text())
# [/DEF:_load_fixture:Function]
# [DEF:_make_session:Function]
# @RELATION: BINDS_TO -> TestDryRunOrchestrator
# @RELATION: BINDS_TO -> [TestDryRunOrchestrator]
# @PURPOSE: Build isolated in-memory SQLAlchemy session for dry-run service tests.
def _make_session():
engine = create_engine(
"sqlite:///:memory:",
@@ -47,8 +52,10 @@ def _make_session():
# [/DEF:_make_session:Function]
# [DEF:test_migration_dry_run_service_builds_diff_and_risk:Function]
# @RELATION: BINDS_TO -> TestDryRunOrchestrator
# @RELATION: BINDS_TO -> [TestDryRunOrchestrator]
# @PURPOSE: Verify dry-run orchestration returns stable diff summary and required risk codes.
def test_migration_dry_run_service_builds_diff_and_risk():
# @TEST_CONTRACT: dry_run_result_contract -> {
# required_fields: {diff: object, summary: object, risk: object},
@@ -68,7 +75,9 @@ def test_migration_dry_run_service_builds_diff_and_risk():
)
source_client = MagicMock()
source_client.get_dashboards_summary.return_value = fixture["source_dashboard_summary"]
source_client.get_dashboards_summary.return_value = fixture[
"source_dashboard_summary"
]
source_client.export_dashboard.return_value = (b"PK\x03\x04", "source.zip")
target_client = MagicMock()
@@ -117,5 +126,5 @@ def test_migration_dry_run_service_builds_diff_and_risk():
raise AssertionError("breaking_reference risk is not detected")
# [/DEF:TestDryRunOrchestrator:Module]
# [/DEF:test_migration_dry_run_service_builds_diff_and_risk:Function]
# [/DEF:TestDryRunOrchestrator:Module]

View File

@@ -3,7 +3,7 @@
# @COMPLEXITY: 3
# @PURPOSE: Unit tests for MigrationEngine's cross-filter patching algorithms.
# @LAYER: Domain
# @RELATION: VERIFIES -> backend.src.core.migration_engine
# @RELATION: VERIFIES -> [src.core.migration_engine:Module]
#
import pytest
import tempfile
@@ -28,7 +28,7 @@ from src.models.mapping import ResourceType
# [DEF:MockMappingService:Class]
# @RELATION: BINDS_TO ->[TestMigrationEngine]
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @COMPLEXITY: 2
# @PURPOSE: Deterministic mapping service double for native filter ID remapping scenarios.
# @INVARIANT: Returns mappings only for requested UUID keys present in seeded map.
@@ -50,7 +50,8 @@ class MockMappingService:
# [DEF:_write_dashboard_yaml:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Serialize dashboard metadata into YAML fixture with json_metadata payload for patch tests.
def _write_dashboard_yaml(dir_path: Path, metadata: dict) -> Path:
"""Helper: writes a dashboard YAML file with json_metadata."""
file_path = dir_path / "dash.yaml"
@@ -65,7 +66,8 @@ def _write_dashboard_yaml(dir_path: Path, metadata: dict) -> Path:
# [DEF:test_patch_dashboard_metadata_replaces_chart_ids:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Verify native filter target chartId values are remapped via mapping service results.
def test_patch_dashboard_metadata_replaces_chart_ids():
"""Verifies that chartId values are replaced using the mapping service."""
mock_service = MockMappingService({"uuid-chart-A": 999})
@@ -91,7 +93,8 @@ def test_patch_dashboard_metadata_replaces_chart_ids():
# [DEF:test_patch_dashboard_metadata_replaces_dataset_ids:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Verify native filter target datasetId values are remapped via mapping service results.
def test_patch_dashboard_metadata_replaces_dataset_ids():
"""Verifies that datasetId values are replaced using the mapping service."""
mock_service = MockMappingService({"uuid-ds-B": 500})
@@ -118,7 +121,8 @@ def test_patch_dashboard_metadata_replaces_dataset_ids():
# [DEF:test_patch_dashboard_metadata_skips_when_no_metadata:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Ensure dashboard files without json_metadata are left unchanged by metadata patching.
def test_patch_dashboard_metadata_skips_when_no_metadata():
"""Verifies early return when json_metadata key is absent."""
mock_service = MockMappingService({})
@@ -140,7 +144,8 @@ def test_patch_dashboard_metadata_skips_when_no_metadata():
# [DEF:test_patch_dashboard_metadata_handles_missing_targets:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Verify patching updates mapped targets while preserving unmapped native filter IDs.
def test_patch_dashboard_metadata_handles_missing_targets():
"""When some source IDs have no target mapping, patches what it can and leaves the rest."""
mock_service = MockMappingService({"uuid-A": 100}) # Only uuid-A maps
@@ -173,7 +178,8 @@ def test_patch_dashboard_metadata_handles_missing_targets():
# [DEF:test_extract_chart_uuids_from_archive:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Verify chart archive scan returns complete local chart id-to-uuid mapping.
def test_extract_chart_uuids_from_archive():
"""Verifies that chart YAML files are parsed for id->uuid mappings."""
engine = MigrationEngine()
@@ -201,7 +207,8 @@ def test_extract_chart_uuids_from_archive():
# [DEF:test_transform_yaml_replaces_database_uuid:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Ensure dataset YAML database_uuid fields are replaced when source UUID mapping exists.
def test_transform_yaml_replaces_database_uuid():
"""Verifies that database_uuid in a dataset YAML is replaced."""
engine = MigrationEngine()
@@ -223,7 +230,8 @@ def test_transform_yaml_replaces_database_uuid():
# [DEF:test_transform_yaml_ignores_unmapped_uuid:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Ensure transform_yaml leaves dataset files untouched when database_uuid is not mapped.
def test_transform_yaml_ignores_unmapped_uuid():
"""Verifies no changes when UUID is not in the mapping."""
engine = MigrationEngine()
@@ -247,7 +255,8 @@ def test_transform_yaml_ignores_unmapped_uuid():
# [DEF:test_transform_zip_end_to_end:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Validate full ZIP transform pipeline remaps datasets and dashboard cross-filter chart IDs.
def test_transform_zip_end_to_end():
"""Verifies full orchestration: extraction, transformation, patching, and re-packaging."""
mock_service = MockMappingService({"char-uuid": 101, "ds-uuid": 202})
@@ -327,7 +336,8 @@ def test_transform_zip_end_to_end():
# [DEF:test_transform_zip_invalid_path:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Verify transform_zip returns False when source archive path does not exist.
def test_transform_zip_invalid_path():
"""@PRE: Verify behavior (False) on invalid ZIP path."""
engine = MigrationEngine()
@@ -339,7 +349,8 @@ def test_transform_zip_invalid_path():
# [DEF:test_transform_yaml_nonexistent_file:Function]
# @RELATION: BINDS_TO -> TestMigrationEngine
# @RELATION: BINDS_TO -> [TestMigrationEngine:Module]
# @PURPOSE: Verify transform_yaml raises FileNotFoundError for missing YAML source files.
def test_transform_yaml_nonexistent_file():
"""@PRE: Verify behavior on non-existent YAML file."""
engine = MigrationEngine()
@@ -349,5 +360,5 @@ def test_transform_yaml_nonexistent_file():
engine._transform_yaml(Path("non_existent.yaml"), {})
# [/DEF:TestMigrationEngine:Module]
# [/DEF:test_transform_yaml_nonexistent_file:Function]
# [/DEF:TestMigrationEngine:Module]