207 lines
5.8 KiB
Python
207 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"]["field_direction_overrides"] == {}
|
|
|
|
|
|
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)
|