fix(sector-radar): 增加安全源契约诊断
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import logging
|
||||
from collections.abc import Sequence
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from zhixing_server.modules.sector_radar.application.build import (
|
||||
BuildSectorRadar,
|
||||
BuildSectorRadarCommand,
|
||||
@@ -19,6 +22,7 @@ from zhixing_server.modules.sector_radar.domain.source import (
|
||||
MoneyflowDcRow,
|
||||
SectorIndexRow,
|
||||
SectorMemberRow,
|
||||
SourceContractError,
|
||||
SourceResult,
|
||||
SourceSnapshot,
|
||||
StockBasicRow,
|
||||
@@ -284,6 +288,37 @@ def test_successful_build_is_idempotent_and_failed_retry_preserves_last_good() -
|
||||
assert any(item.status is PublicationStatus.FAILED for item in repository.publications.values())
|
||||
|
||||
|
||||
def test_source_contract_failure_is_logged_with_safe_build_context(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
class InvalidDailySource(FakeRadarSource):
|
||||
def fetch_daily(self, trade_date: date) -> SourceResult[DailyRow]:
|
||||
self.calls.append("daily")
|
||||
raise SourceContractError("daily returned duplicate business keys")
|
||||
|
||||
repository = InMemorySectorRadarRepository()
|
||||
caplog.set_level(
|
||||
logging.ERROR,
|
||||
logger="zhixing_server.modules.sector_radar.application.build",
|
||||
)
|
||||
|
||||
failed = BuildSectorRadar(
|
||||
InvalidDailySource(),
|
||||
repository,
|
||||
today=TARGET_DATE,
|
||||
now_fn=lambda: NOW,
|
||||
).execute(BuildSectorRadarCommand(trade_date=TARGET_DATE))
|
||||
|
||||
messages = "\n".join(record.getMessage() for record in caplog.records)
|
||||
assert failed.status == "failed"
|
||||
assert failed.outcomes[0].error_message == "input or source contract validation failed"
|
||||
assert "sector_radar_source_group_contract_failed" in messages
|
||||
assert "source_group=daily" in messages
|
||||
assert "publication_id=radar-20260828-running-" in messages
|
||||
assert "validation=daily returned duplicate business keys" in messages
|
||||
assert len(caplog.records) == 1
|
||||
|
||||
|
||||
def test_partial_coverage_and_lock_have_distinct_exit_codes() -> None:
|
||||
repository = InMemorySectorRadarRepository()
|
||||
partial = BuildSectorRadar(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
from datetime import UTC, date, datetime
|
||||
from decimal import Decimal
|
||||
@@ -132,6 +133,86 @@ def test_non_finite_source_values_are_rejected() -> None:
|
||||
make_adapter(client).fetch_daily(TARGET_DATE)
|
||||
|
||||
|
||||
def test_contract_failure_log_identifies_member_partition_without_payload(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setitem(source_module.ROW_LIMITS, "dc_member", 2)
|
||||
client = QueryClient(
|
||||
{
|
||||
(
|
||||
"dc_member",
|
||||
"",
|
||||
): (
|
||||
{
|
||||
"trade_date": "20260828",
|
||||
"ts_code": "BK0001.DC",
|
||||
"con_code": "000001.SZ",
|
||||
"name": "private-payload-marker",
|
||||
},
|
||||
{
|
||||
"trade_date": "20260828",
|
||||
"ts_code": "BK0001.DC",
|
||||
"con_code": "000002.SZ",
|
||||
"name": "private-payload-marker",
|
||||
},
|
||||
),
|
||||
(
|
||||
"dc_member",
|
||||
"BK0001.DC",
|
||||
): (
|
||||
{
|
||||
"trade_date": "20260828",
|
||||
"ts_code": "BK9999.DC",
|
||||
"con_code": "000001.SZ",
|
||||
"name": "private-payload-marker",
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
caplog.set_level(
|
||||
logging.ERROR,
|
||||
logger="zhixing_server.modules.sector_radar.infrastructure.tushare",
|
||||
)
|
||||
|
||||
with pytest.raises(SourceContractError, match="different sector"):
|
||||
make_adapter(client).fetch_sector_members(TARGET_DATE, ("BK0001.DC",))
|
||||
|
||||
messages = "\n".join(record.getMessage() for record in caplog.records)
|
||||
assert "sector_radar_source_contract_failed" in messages
|
||||
assert "api_name=dc_member" in messages
|
||||
assert "partition_key=BK0001.DC" in messages
|
||||
assert "validation=dc_member partition returned a different sector" in messages
|
||||
assert "private-payload-marker" not in messages
|
||||
assert len(caplog.records) == 1
|
||||
|
||||
|
||||
def test_merged_member_contract_failure_has_one_interface_level_log(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
duplicate = {
|
||||
"trade_date": "20260828",
|
||||
"ts_code": "BK0001.DC",
|
||||
"con_code": "000001.SZ",
|
||||
"name": "private-payload-marker",
|
||||
}
|
||||
client = QueryClient({("dc_member", ""): (duplicate, duplicate)})
|
||||
caplog.set_level(
|
||||
logging.ERROR,
|
||||
logger="zhixing_server.modules.sector_radar.infrastructure.tushare",
|
||||
)
|
||||
|
||||
with pytest.raises(SourceContractError, match="duplicate business keys"):
|
||||
make_adapter(client).fetch_sector_members(TARGET_DATE, ("BK0001.DC",))
|
||||
|
||||
messages = "\n".join(record.getMessage() for record in caplog.records)
|
||||
assert "api_name=dc_member" in messages
|
||||
assert "partition_key=merged" in messages
|
||||
assert "validation=dc_member returned duplicate business keys" in messages
|
||||
assert "private-payload-marker" not in messages
|
||||
assert len(caplog.records) == 1
|
||||
|
||||
|
||||
def test_dc_member_reloads_by_sector_when_the_all_market_call_hits_limit(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user