Files
ecm_sync_system/tests/unit/test_run_presets_yaml_loader.py
T
2026-04-01 11:52:57 +08:00

209 lines
5.8 KiB
Python

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)