from __future__ import annotations from pathlib import Path import pytest from pydantic import ConfigDict from sync_state_machine.config import BaseDataSourceConfig, build_config_from_file, config_snapshot, register_datasource_type from sync_state_machine.config.run_presets import load_overrides_from_file from sync_state_machine.datasource import BaseDataSource def test_load_overrides_from_yaml_file(tmp_path: Path) -> None: pytest.importorskip("yaml") yaml_file = tmp_path / "profile.yaml" yaml_file.write_text( """ node_types: - company logging: initialize: true suppress_node_logs: true local_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/filtered_datasource/datasource/filtered """.strip(), encoding="utf-8", ) overrides = load_overrides_from_file(yaml_file, project_root=tmp_path) assert overrides["logging"]["suppress_node_logs"] is True assert str(tmp_path) in overrides["local_datasource"]["jsonl_dir"] def test_load_overrides_ignores_extensions_bootstraps(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: bootstrap_module = tmp_path / "profile_bootstrap_demo.py" bootstrap_module.write_text( "BOOTSTRAP_CALLED = False\n" "\n" "def register():\n" " global BOOTSTRAP_CALLED\n" " BOOTSTRAP_CALLED = True\n", encoding="utf-8", ) monkeypatch.syspath_prepend(str(tmp_path)) yaml_file = tmp_path / "profile_with_bootstrap.yaml" yaml_file.write_text( """ extensions: bootstraps: - profile_bootstrap_demo:register local_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/filtered_datasource/datasource/filtered remote_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/remote_datasource/datasource persist: db_path: ${PROJECT_ROOT}/runtime/test.db logging: initialize: false """.strip(), encoding="utf-8", ) load_overrides_from_file(yaml_file, project_root=tmp_path) import profile_bootstrap_demo assert profile_bootstrap_demo.BOOTSTRAP_CALLED is False class FileBackedConfig(BaseDataSourceConfig): model_config = ConfigDict(extra="forbid", validate_assignment=True) type: str = "file_backed" namespace: str = "demo" class FileBackedDatasource(BaseDataSource): def __init__(self, namespace: str): super().__init__() self.namespace = namespace def _build_file_backed_datasource(config, endpoint_name): return FileBackedDatasource(namespace=f"{endpoint_name}:{config.namespace}") def test_build_config_from_file_uses_registered_datasource_type(tmp_path: Path) -> None: register_datasource_type( "file_backed", config_model=FileBackedConfig, factory=_build_file_backed_datasource, replace=True, ) profile = tmp_path / "profile.yaml" profile.write_text( """ extensions: bootstraps: - config_from_file_bootstrap:register local_datasource: type: file_backed namespace: local-demo remote_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/remote_datasource/datasource persist: db_path: ${PROJECT_ROOT}/runtime/test.db logging: initialize: false """.strip(), encoding="utf-8", ) config = build_config_from_file(project_root=tmp_path, file_path=profile) assert config.local_datasource.type == "file_backed" assert getattr(config.local_datasource, "namespace") == "local-demo" def test_build_config_from_sparse_profile_resolves_full_strategy_defaults(tmp_path: Path) -> None: profile = tmp_path / "sparse_profile.yaml" profile.write_text( """ node_types: - project - production local_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/filtered_datasource/datasource/filtered remote_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/remote_datasource/datasource persist: db_path: ${PROJECT_ROOT}/runtime/test.db logging: initialize: false strategies: project: config: local_orphan_action: none post_check_ignore_fields: - code production: config: post_check_ignore_fields: - is_exist """.strip(), encoding="utf-8", ) config = build_config_from_file(project_root=tmp_path, file_path=profile) strategies = {item.node_type: item for item in config.strategies} project = strategies["project"].config assert project.auto_bind is False assert project.auto_bind_fields == [] assert project.depend_fields == {"company_id": "company"} assert project.local_orphan_action == "none" assert project.remote_orphan_action == "none" assert project.update_direction == "push" assert project.post_check_ignore_fields == ["code"] assert project.compare_ignore_fields == [] production = strategies["production"].config assert production.auto_bind_fields == ["project_id", "code"] assert production.depend_fields == {"project_id": "project"} assert production.post_check_ignore_fields == ["is_exist"] snapshot = config_snapshot(config) assert snapshot["strategies"][0]["config"]["compare_ignore_fields"] == [] assert snapshot["strategies"][0]["config"]["domain_option"] == { "pull_group_plan_production_date": False } def test_build_config_from_file_rejects_legacy_strategy_keys(tmp_path: Path) -> None: profile = tmp_path / "legacy_profile.yaml" profile.write_text( """ node_types: - supplier local_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/filtered_datasource/datasource/filtered remote_datasource: type: jsonl jsonl_dir: ${PROJECT_ROOT}/remote_datasource/datasource persist: db_path: ${PROJECT_ROOT}/runtime/test.db logging: initialize: false strategies: supplier: load_mode: persisted_only """.strip(), encoding="utf-8", ) with pytest.raises(ValueError, match="unsupported legacy keys: load_mode"): build_config_from_file(project_root=tmp_path, file_path=profile)