更新了部分测试

This commit is contained in:
strepsiades
2026-03-24 09:25:37 +08:00
parent b51ed0175b
commit 7d92b3f1fe
19 changed files with 115 additions and 200 deletions
+1
View File
@@ -17,6 +17,7 @@
## 设计原则
- 不绕过 schema/状态机校验;配置错误必须显式失败。
- 单元测试聚焦纯逻辑,集成测试覆盖流水线与边界行为。
- 优先保留行为测试,避免只验证转发器、setter 或仅供测试存在的空壳包装层。
- 所有测试均从仓库根路径解析配置,避免目录迁移导致路径脆弱。
- 保留高价值回归测试,移除仅重复路径或无断言价值的历史脚本型测试(脚本保留在 `scripts/`,不纳入 pytest)。
@@ -112,7 +112,7 @@ async def pipeline_bundle():
local_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
remote_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
local_ds.register_handler(
local_ds.add_handler(
InMemoryNodeHandler(
node_type="test_project",
node_class=TestProjectNode,
@@ -120,7 +120,7 @@ async def pipeline_bundle():
load_nodes=local_project_nodes,
)
)
local_ds.register_handler(
local_ds.add_handler(
InMemoryNodeHandler(
node_type="test_contract",
node_class=TestContractNode,
@@ -129,7 +129,7 @@ async def pipeline_bundle():
)
)
remote_ds.register_handler(
remote_ds.add_handler(
InMemoryNodeHandler(
node_type="test_project",
node_class=TestProjectNode,
@@ -143,7 +143,7 @@ async def pipeline_bundle():
update_mode_by_code={"UPD-1": "success"},
)
)
remote_ds.register_handler(
remote_ds.add_handler(
InMemoryNodeHandler(
node_type="test_contract",
node_class=TestContractNode,
@@ -453,7 +453,7 @@ async def test_pipeline_create_then_update_completes_in_single_run_with_reload()
local_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
remote_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
local_ds.register_handler(
local_ds.add_handler(
InMemoryNodeHandler(
node_type="test_project",
node_class=TestProjectNode,
@@ -464,7 +464,7 @@ async def test_pipeline_create_then_update_completes_in_single_run_with_reload()
)
)
remote_handler = _MutableProjectRemoteHandler()
remote_ds.register_handler(remote_handler)
remote_ds.add_handler(remote_handler)
project_strategy = TestProjectStrategy("test_project", local_collection, remote_collection, binding_manager)
project_strategy.update_config(
@@ -73,7 +73,7 @@ async def test_full_pipeline_multinode_mock_persisted_db_snapshot() -> None:
local_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
remote_ds = InMemoryDataSource(poll_max_retries=2, poll_interval=0)
local_ds.register_handler(
local_ds.add_handler(
InMemoryNodeHandler(
node_type="test_project",
node_class=TestProjectNode,
@@ -81,7 +81,7 @@ async def test_full_pipeline_multinode_mock_persisted_db_snapshot() -> None:
load_nodes=local_project_nodes,
)
)
local_ds.register_handler(
local_ds.add_handler(
InMemoryNodeHandler(
node_type="test_contract",
node_class=TestContractNode,
@@ -90,7 +90,7 @@ async def test_full_pipeline_multinode_mock_persisted_db_snapshot() -> None:
)
)
remote_ds.register_handler(
remote_ds.add_handler(
InMemoryNodeHandler(
node_type="test_project",
node_class=TestProjectNode,
@@ -104,7 +104,7 @@ async def test_full_pipeline_multinode_mock_persisted_db_snapshot() -> None:
update_mode_by_code={"UPD-1": "success"},
)
)
remote_ds.register_handler(
remote_ds.add_handler(
InMemoryNodeHandler(
node_type="test_contract",
node_class=TestContractNode,
@@ -97,8 +97,8 @@ class _DS(BaseDataSource):
@pytest.mark.asyncio
async def test_load_all_continue_when_one_handler_fails(capsys) -> None:
ds = _DS()
ds.register_handler(_FailHandler())
ds.register_handler(_OkHandler())
ds.add_handler(_FailHandler())
ds.add_handler(_OkHandler())
ds.set_collection(DataCollection("local"))
await ds.load_all(order=["fail", "ok"])
@@ -2,6 +2,7 @@ from __future__ import annotations
from pathlib import Path
import pytest
from pydantic import BaseModel
from sync_state_machine.common.registry import DomainRegistry
@@ -17,7 +18,7 @@ from sync_state_machine.config import (
from sync_state_machine.datasource import BaseDataSource
from sync_state_machine.datasource.handler import BaseNodeHandler
from sync_state_machine.datasource.jsonl import BaseJsonlHandler, JsonlDataSource
from sync_state_machine.pipeline.factory import _register_handlers
from sync_state_machine.pipeline.factory import create_pipeline_from_config
from sync_state_machine.sync_system.strategy import DefaultSyncStrategy
@@ -79,7 +80,8 @@ def _register_test_db_datasource_type() -> None:
)
def test_register_handlers_uses_db_handler_for_db_datasource(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_register_handlers_uses_db_handler_for_db_datasource(tmp_path: Path) -> None:
_register_test_db_datasource_type()
if not DomainRegistry.is_registered("test_db_handler"):
@@ -104,10 +106,9 @@ def test_register_handlers_uses_db_handler_for_db_datasource(tmp_path: Path) ->
logging=LoggingConfig(initialize=False),
)
local_ds = BaseDataSource()
remote_ds = JsonlDataSource(tmp_path)
_register_handlers(config, local_ds, remote_ds, ["test_db_handler"])
assert isinstance(local_ds.get_handler("test_db_handler"), TestDbHandler)
assert isinstance(remote_ds.get_handler("test_db_handler"), TestJsonlHandler)
pipeline = await create_pipeline_from_config(config)
try:
assert isinstance(pipeline.local_datasource.get_handler("test_db_handler"), TestDbHandler)
assert isinstance(pipeline.remote_datasource.get_handler("test_db_handler"), TestJsonlHandler)
finally:
await pipeline.close()
-86
View File
@@ -1,86 +0,0 @@
from __future__ import annotations
from typing import Any
from pydantic import BaseModel
from sync_state_machine.common.sync_node import SyncNode
from sync_state_machine.datasource.api.handler import BaseApiHandler
from sync_state_machine.datasource.handler import BaseNodeHandler, CompatibleNodeHandler
from sync_state_machine.datasource.task_result import TaskResult
class SharedConfigSchema(BaseModel):
__test__ = False
id: str
class SharedConfigNode(SyncNode[SharedConfigSchema]):
__test__ = False
node_type = "shared_config_demo"
schema = SharedConfigSchema
class SharedConfigBaseHandler(BaseNodeHandler[SharedConfigSchema]):
__test__ = False
def __init__(self) -> None:
super().__init__("shared_config_demo", SharedConfigNode, SharedConfigSchema)
async def load(self):
return []
async def create_all(self, nodes):
return []
async def update_all(self, nodes):
return []
def extract_id(self, data: dict[str, Any]) -> str:
return str(data.get("id", ""))
class SharedConfigApiHandler(BaseApiHandler[SharedConfigSchema]):
__test__ = False
def __init__(self) -> None:
super().__init__()
self._node_type = "shared_api_demo"
self._node_class = SharedConfigNode
self._schema = SharedConfigSchema
async def load(self):
return []
async def create_all(self, nodes):
return [TaskResult.success(node_id=node.node_id) for node in nodes]
async def update_all(self, nodes):
return [TaskResult.success(node_id=node.node_id) for node in nodes]
async def delete_all(self, nodes):
return [TaskResult.success(node_id=node.node_id) for node in nodes]
def test_base_handler_supports_shared_handler_config() -> None:
handler = SharedConfigBaseHandler()
handler.set_handler_config({"mode": "local", "batch_size": 10})
assert handler.handler_config == {"mode": "local", "batch_size": 10}
def test_compatible_handler_forwards_shared_handler_config() -> None:
handler = CompatibleNodeHandler(SharedConfigBaseHandler())
handler.set_handler_config({"mode": "remote"})
assert handler.handler_config == {"mode": "remote"}
def test_api_handler_supports_shared_handler_config() -> None:
handler = SharedConfigApiHandler()
handler.set_handler_config({"filter_project_id": ["p1", "p2"]})
assert handler.handler_config == {"filter_project_id": ["p1", "p2"]}
@@ -35,14 +35,18 @@ class _OkStrategy(BaseSyncStrategy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bound = False
self.create_calls = 0
self.update_calls = 0
async def bind(self, **kwargs):
self.bound = True
async def create(self):
self.create_calls += 1
return []
async def update(self):
self.update_calls += 1
return []
@@ -87,6 +91,52 @@ async def test_phase_sync_by_node_type_continues_after_strategy_error(tmp_path:
await pipeline.phase_sync_by_node_type()
assert ok_strategy.bound is True
assert ok_strategy.create_calls == 0
assert ok_strategy.update_calls == 0
assert pipeline.stats["failed"] >= 1
await pipeline.close()
@pytest.mark.asyncio
async def test_phase_sync_by_node_type_skip_sync_keeps_bind_but_skips_writes(tmp_path: Path) -> None:
db_path = tmp_path / "state.db"
persistence = PersistenceBackend(str(db_path))
await persistence.initialize()
local_collection = DataCollection("local", persistence=persistence, auto_persist=False)
remote_collection = DataCollection("remote", persistence=persistence, auto_persist=False)
binding_manager = BindingManager(persistence, auto_persist=False)
strategy = _OkStrategy("ok", local_collection, remote_collection, binding_manager)
strategy.skip_sync = True
pipeline = FullSyncPipeline(
local_datasource=_DS(),
remote_datasource=_DS(),
strategies=[strategy],
persistence=persistence,
local_collection=local_collection,
remote_collection=remote_collection,
binding_manager=binding_manager,
node_types=["ok"],
load_order=["ok"],
sync_order=["ok"],
enable_persistence=False,
)
load_calls: list[tuple[str, str]] = []
async def _record_load(node_type: str, reason: str = "") -> None:
load_calls.append((node_type, reason))
pipeline._load_node_type = _record_load # type: ignore[method-assign]
await pipeline.phase_sync_by_node_type()
assert load_calls == [("ok", "pre-strategy refresh")]
assert strategy.bound is True
assert strategy.create_calls == 0
assert strategy.update_calls == 0
await pipeline.close()
@@ -45,7 +45,7 @@ async def test_jsonl_project_load_applies_target_project_filter(tmp_path):
datasource = JsonlDataSource(tmp_path)
handler = BaseJsonlHandler(datasource, "project", _ProjectNode, _ProjectSchema)
handler.set_handler_config({"data_id_filter": ["p1"]})
datasource.register_handler(handler)
datasource.add_handler(handler)
datasource.set_collection(DataCollection("local"))
await datasource.load_all(order=["project"])
@@ -70,7 +70,7 @@ async def test_jsonl_non_project_load_filters_rows_by_project_id(tmp_path):
datasource = JsonlDataSource(tmp_path)
handler = BaseJsonlHandler(datasource, "contract", _BizNode, _BizSchema)
handler.set_handler_config({"data_id_filter": ["p1"]})
datasource.register_handler(handler)
datasource.add_handler(handler)
datasource.set_collection(DataCollection("local"))
await datasource.load_all(order=["contract"])
@@ -104,8 +104,8 @@ async def test_jsonl_non_project_load_inherits_project_filter_from_loaded_projec
project_handler = BaseJsonlHandler(datasource, "project", _ProjectNode, _ProjectSchema)
project_handler.set_handler_config({"data_id_filter": ["p1"]})
contract_handler = BaseJsonlHandler(datasource, "contract", _BizNode, _BizSchema)
datasource.register_handler(project_handler)
datasource.register_handler(contract_handler)
datasource.add_handler(project_handler)
datasource.add_handler(contract_handler)
datasource.set_collection(DataCollection("local"))
await datasource.load_all(order=["project", "contract"])
@@ -27,7 +27,7 @@ async def test_units_jsonl_handler_loads_units_filename(tmp_path):
)
datasource = JsonlDataSource(tmp_path)
datasource.register_handler(UnitsJsonlHandler(datasource))
datasource.add_handler(UnitsJsonlHandler(datasource))
datasource.set_collection(DataCollection("local"))
await datasource.load_all(order=["units"])