整理update逻辑

This commit is contained in:
strepsiades
2026-03-31 08:48:34 +08:00
parent bdc7bd5069
commit 14c20a6123
5 changed files with 210 additions and 2 deletions
@@ -0,0 +1,59 @@
from __future__ import annotations
from typing import Any
import pytest
from sync_state_machine.datasource.task_result import TaskStatus
from sync_state_machine.domain.contract.api_handler import ContractApiHandler
from sync_state_machine.domain.contract.sync_node import ContractSyncNode
class _MockApiClient:
def __init__(self) -> None:
self.requests: list[dict[str, Any]] = []
async def request(self, method: str, endpoint: str, **kwargs: Any) -> dict[str, Any]:
self.requests.append({"method": method, "endpoint": endpoint, **kwargs})
return {"code": 200, "message": "OK", "data": {"biz_id": "contract-created-1"}}
@pytest.mark.asyncio
async def test_contract_create_passes_name_and_qt_code_to_api() -> None:
handler = ContractApiHandler()
handler.api_client = _MockApiClient()
handler.set_poll_mode("sync")
node = ContractSyncNode(
node_id="contract-node-1",
data={
"id": "contract-local-1",
"project_id": "project-1",
"name": "电缆采购合同",
"short_name": "电缆采购合同",
"code": "QT",
"contract_type": "product",
"currency": "CNY",
"manager_name": "张三",
"manager_phone": "13800000000",
"sign_company": "测试供应商",
"company_id": "company-1",
"sign_date": "2026-03-01",
"credit_code": "91350000123456789X",
"total_price": 1200.5,
"sub_name": None,
},
context={"project_id": "project-1"},
)
results = await handler.create_all([node])
assert len(results) == 1
assert results[0].status == TaskStatus.SUCCESS
assert results[0].data_id == "contract-created-1"
request = handler.api_client.requests[0]
assert request["endpoint"] == "/contract/create"
assert request["data"]["data"]["name"] == "电缆采购合同"
assert request["data"]["data"]["code"] == "QT"
assert request["data"]["data"]["project_id"] == "project-1"
@@ -0,0 +1,57 @@
from __future__ import annotations
import pytest
from sync_state_machine.common.collection import DataCollection
from sync_state_machine.domain.project.api_handler import ProjectApiHandler
class _FakeProjectResponse:
def __init__(self, project_id: str):
self.id = project_id
def model_dump(self) -> dict[str, str]:
return {"id": self.id, "name": f"project-{self.id}"}
@pytest.mark.asyncio
async def test_project_handler_purge_project_data_requires_data_id_filter() -> None:
handler = ProjectApiHandler()
handler.set_handler_config({"purge_project_data": True})
with pytest.raises(ValueError, match="requires non-empty data_id_filter"):
await handler.load()
@pytest.mark.asyncio
async def test_project_handler_purge_project_data_calls_remote_endpoint(monkeypatch: pytest.MonkeyPatch) -> None:
handler = ProjectApiHandler()
handler.set_handler_config(
{
"purge_project_data": True,
"data_id_filter": ["project-1"],
}
)
handler.api_client = object()
handler.set_collection(DataCollection("remote"))
purged: list[str] = []
async def fake_purge(client, project_id: str):
purged.append(project_id)
return {"project_id": project_id}
async def fake_get_project(client, project_id: str):
return _FakeProjectResponse(project_id)
async def fake_get_all_info(cls, client, project_id: str):
return {}
monkeypatch.setattr("sync_state_machine.domain.project.api_handler.api_purge_project_data", fake_purge)
monkeypatch.setattr("sync_state_machine.domain.project.api_handler.api_get_project", fake_get_project)
monkeypatch.setattr(ProjectApiHandler, "get_all_info", classmethod(fake_get_all_info))
nodes = await handler.load()
assert purged == ["project-1"]
assert [node.data_id for node in nodes] == ["project-1"]
+44
View File
@@ -4,6 +4,8 @@ from sync_state_machine.domain.company.sync_strategy import CompanySyncStrategy
from sync_state_machine.sync_system.config import UpdateDirection
from sync_state_machine.sync_system.strategy_ops.update_ops import run_update
from sync_state_machine.domain.construction.sync_strategy import ConstructionTaskSyncStrategy
from sync_state_machine.domain.project_detail.sync_strategy import ProjectDetailSyncStrategy
from schemas.project_extensions.project_detail import ProjectDetailKey
@pytest.mark.asyncio
async def test_run_update_with_direction_overrides():
@@ -107,3 +109,45 @@ def test_default_strategy_exposes_normalize_compare_payload():
}
assert strategy.normalize_compare_payload(local_payload) == strategy.normalize_compare_payload(remote_payload)
def test_project_detail_compare_payload_ignores_response_only_year_and_is_audit_noise():
strategy = ProjectDetailSyncStrategy(
"project_detail_data",
MagicMock(),
MagicMock(),
MagicMock(),
)
local_payload = {
"project_id": "project-1",
"key": "actual_construction",
"value": [{"date": "2023-08-23", "capacity": 4.0, "is_audit": False, "year": None}],
}
remote_payload = {
"project_id": "project-1",
"key": "actual_construction",
"value": [{"date": "2023-08-23", "capacity": 4.0, "is_audit": True, "year": 2023}],
}
assert strategy.normalize_compare_payload(local_payload) == strategy.normalize_compare_payload(remote_payload)
def test_project_detail_compare_payload_normalizes_enum_key_values():
strategy = ProjectDetailSyncStrategy(
"project_detail_data",
MagicMock(),
MagicMock(),
MagicMock(),
)
remote_payload = {
"project_id": "project-1",
"key": ProjectDetailKey.PLAN_CONSTRUCTION,
"value": [{"date": "2023-06-28", "capacity": 4.0, "year": 2023, "is_audit": True}],
}
normalized = strategy.normalize_compare_payload(remote_payload)
assert normalized["key"] == "plan_construction"
assert normalized["value"] == [{"date": "2023-06-28", "capacity": 4.0}]