规范了update data的构建方式,统一使用build_update_request_data方法来处理更新数据的构建和过滤逻辑。这个方法会根据提供的原始数据、更新数据和对应的schema来生成最终的更新请求数据,同时会自动排除掉未变化的字段和空值字段,从而确保只发送必要的更新信息到后端接口。这种方式不仅简化了代码逻辑,还提高了代码的可维护性和一致性。
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from sync_state_machine.datasource.api.handler import BaseApiHandler
|
||||
from sync_state_machine.datasource.task_result import TaskResult
|
||||
|
||||
|
||||
class _UpdateSchema(BaseModel):
|
||||
id: str
|
||||
required_value: float
|
||||
optional_text: str | None = None
|
||||
|
||||
|
||||
class _DummyApiHandler(BaseApiHandler[BaseModel]):
|
||||
_node_type = "dummy"
|
||||
_node_class = object # type: ignore[assignment]
|
||||
_schema = BaseModel
|
||||
|
||||
async def create_all(self, nodes):
|
||||
return [TaskResult.success(node_id="dummy")]
|
||||
|
||||
async def update_all(self, nodes):
|
||||
return [TaskResult.success(node_id="dummy")]
|
||||
|
||||
async def delete_all(self, nodes):
|
||||
return [TaskResult.success(node_id="dummy")]
|
||||
|
||||
async def load(self):
|
||||
return []
|
||||
|
||||
|
||||
def test_get_update_schema_diff_falls_back_to_schema_fields_when_origin_is_incomplete() -> None:
|
||||
handler = _DummyApiHandler()
|
||||
|
||||
diff = handler.get_update_schema_diff(
|
||||
_UpdateSchema,
|
||||
{"id": "node-1", "required_value": 4.0, "optional_text": "ok"},
|
||||
{"id": "node-1"},
|
||||
node_id="node-1",
|
||||
)
|
||||
|
||||
assert diff == {"required_value": 4.0, "optional_text": "ok"}
|
||||
|
||||
|
||||
def test_build_update_request_data_uses_same_schema_projection_as_diff() -> None:
|
||||
handler = _DummyApiHandler()
|
||||
|
||||
payload = handler.build_update_request_data(
|
||||
{"id": "node-1", "required_value": 4.0, "optional_text": "ok", "ignored": "x"},
|
||||
{"id": "node-1"},
|
||||
schema=_UpdateSchema,
|
||||
)
|
||||
|
||||
assert payload == {"id": "node-1", "required_value": 4.0, "optional_text": "ok"}
|
||||
|
||||
|
||||
def test_schema_changed_fields_matches_update_payload_comparison() -> None:
|
||||
handler = _DummyApiHandler()
|
||||
|
||||
changed_fields = handler._schema_changed_fields(
|
||||
_UpdateSchema,
|
||||
{"id": "node-1", "required_value": 4.0, "optional_text": "ok"},
|
||||
{"id": "node-1", "required_value": 4.0},
|
||||
)
|
||||
|
||||
assert changed_fields == ["optional_text"]
|
||||
|
||||
|
||||
def test_update_schema_payload_filters_empty_values() -> None:
|
||||
handler = _DummyApiHandler()
|
||||
|
||||
payload = handler._get_update_schema_payload(
|
||||
_UpdateSchema,
|
||||
{"id": "node-1", "required_value": 4.0, "optional_text": ""},
|
||||
require_valid=False,
|
||||
)
|
||||
|
||||
assert payload == {"id": "node-1", "required_value": 4.0}
|
||||
|
||||
|
||||
def test_get_update_schema_diff_skips_schema_when_new_data_is_still_incomplete() -> None:
|
||||
handler = _DummyApiHandler()
|
||||
|
||||
diff = handler.get_update_schema_diff(
|
||||
_UpdateSchema,
|
||||
{"id": "node-1"},
|
||||
{"id": "node-1", "required_value": 2.0},
|
||||
node_id="node-1",
|
||||
)
|
||||
|
||||
assert diff is None
|
||||
@@ -31,6 +31,7 @@ class _StubStrategy:
|
||||
def __init__(self, node_type: str):
|
||||
self.node_type = node_type
|
||||
self.skip_sync = False
|
||||
self.skip_post_check = False
|
||||
self.config = SimpleNamespace(
|
||||
depend_fields={},
|
||||
update_direction=None,
|
||||
@@ -193,4 +194,55 @@ async def test_evaluate_consistency_merges_compare_and_post_check_ignore_fields(
|
||||
full_sync_pipeline_module.evaluate_consistency_for_node_type = original
|
||||
|
||||
assert captured["ignore_fields"] == ["updated_at", "code", "audit_status"]
|
||||
assert result["ignore_fields"] == ["updated_at", "code", "audit_status"]
|
||||
assert result["ignore_fields"] == ["updated_at", "code", "audit_status"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_phase_post_check_skips_configured_node_types(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
supplier_strategy = _StubStrategy("supplier")
|
||||
supplier_strategy.skip_post_check = True
|
||||
project_strategy = _StubStrategy("project")
|
||||
|
||||
pipeline = FullSyncPipeline(
|
||||
local_datasource=_FakeDataSource(),
|
||||
remote_datasource=_FakeDataSource(),
|
||||
strategies=[supplier_strategy, project_strategy],
|
||||
persistence=_FakePersistence(),
|
||||
local_collection=DataCollection("local"),
|
||||
remote_collection=DataCollection("remote"),
|
||||
binding_manager=BindingManager(_FakePersistence(), auto_persist=False),
|
||||
node_types=["supplier", "project"],
|
||||
load_order=["supplier", "project"],
|
||||
sync_order=["supplier", "project"],
|
||||
enable_persistence=False,
|
||||
)
|
||||
|
||||
reload_calls: list[str] = []
|
||||
evaluated: list[str] = []
|
||||
|
||||
async def fake_reload(node_type: str, *, reason: str) -> None:
|
||||
del reason
|
||||
reload_calls.append(node_type)
|
||||
|
||||
async def fake_evaluate(node_type: str):
|
||||
evaluated.append(node_type)
|
||||
return {"ok": True, "checked_pairs": 1, "mismatch_count": 0}
|
||||
|
||||
async def fake_print_summary() -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(pipeline, "_reload_node_type", fake_reload)
|
||||
monkeypatch.setattr(pipeline, "_evaluate_consistency_for_node_type", fake_evaluate)
|
||||
monkeypatch.setattr(pipeline, "print_summary", fake_print_summary)
|
||||
|
||||
result = await pipeline.phase_post_check()
|
||||
|
||||
assert result is True
|
||||
assert reload_calls == ["project"]
|
||||
assert evaluated == ["project"]
|
||||
assert pipeline._consistency_by_type["supplier"] == {
|
||||
"ok": True,
|
||||
"checked_pairs": 0,
|
||||
"mismatch_count": 0,
|
||||
"skipped": True,
|
||||
}
|
||||
@@ -8,6 +8,7 @@ def test_strategy_runtime_config_skip_sync_defaults_false() -> None:
|
||||
runtime_config = StrategyRuntimeConfig(node_type="project")
|
||||
|
||||
assert runtime_config.skip_sync is False
|
||||
assert runtime_config.skip_post_check is False
|
||||
|
||||
|
||||
def test_apply_strategy_overrides_allows_skip_sync_override() -> None:
|
||||
@@ -16,6 +17,7 @@ def test_apply_strategy_overrides_allows_skip_sync_override() -> None:
|
||||
strategy_overrides={
|
||||
"project": {
|
||||
"skip_sync": True,
|
||||
"skip_post_check": True,
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -23,6 +25,7 @@ def test_apply_strategy_overrides_allows_skip_sync_override() -> None:
|
||||
assert len(resolved) == 1
|
||||
assert resolved[0].node_type == "project"
|
||||
assert resolved[0].skip_sync is True
|
||||
assert resolved[0].skip_post_check is True
|
||||
|
||||
|
||||
def test_strategy_config_supports_both_post_check_ignore_fields() -> None:
|
||||
@@ -30,8 +33,10 @@ def test_strategy_config_supports_both_post_check_ignore_fields() -> None:
|
||||
{
|
||||
"post_check_ignore_fields": ["code"],
|
||||
"post_check_ignore_list_item_fields": {"plan_node": ["is_audit"]},
|
||||
"allow_many_to_one_auto_bind": True,
|
||||
}
|
||||
)
|
||||
|
||||
assert config.post_check_ignore_fields == ["code"]
|
||||
assert config.post_check_ignore_list_item_fields == {"plan_node": ["is_audit"]}
|
||||
assert config.post_check_ignore_list_item_fields == {"plan_node": ["is_audit"]}
|
||||
assert config.allow_many_to_one_auto_bind is True
|
||||
Reference in New Issue
Block a user