fix(api): bound run evidence batch receipts
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m39s
CD Pipeline / build-and-deploy (push) Successful in 10m32s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 13s
CD Pipeline / post-deploy-checks (push) Successful in 2m12s

This commit is contained in:
ogt
2026-07-14 17:05:30 +08:00
parent 1471f05f59
commit bf44f66fb3
2 changed files with 132 additions and 50 deletions

View File

@@ -29,19 +29,22 @@ class _FakeDb:
self,
rows: list[SimpleNamespace],
*,
error: Exception | None = None,
followup_rows: list[list[SimpleNamespace]] | None = None,
errors: list[Exception | None] | None = None,
) -> None:
self._rows = rows
self._error = error
self._row_batches = [rows, *(followup_rows or [])]
self._errors = errors or []
self.execute_calls = 0
self.statements: list[object] = []
async def execute(self, statement: object) -> _FakeResult:
self.execute_calls += 1
self.statements.append(statement)
if self._error is not None:
raise self._error
return _FakeResult(self._rows)
call_index = self.execute_calls - 1
if call_index < len(self._errors) and self._errors[call_index] is not None:
raise self._errors[call_index]
row_index = min(call_index, len(self._row_batches) - 1)
return _FakeResult(self._row_batches[row_index])
class _FakeDbContext:
@@ -84,15 +87,6 @@ def _history_row(incident_id: str, sequence: int) -> SimpleNamespace:
)
async def _empty_legacy_history(
_incident_ids: list[str],
*,
limit: int,
) -> dict[str, list[dict[str, object]]]:
assert limit > 0
return {}
@pytest.mark.asyncio
async def test_run_remediation_summaries_use_one_bounded_batch_query(
monkeypatch: pytest.MonkeyPatch,
@@ -114,7 +108,7 @@ async def test_run_remediation_summaries_use_one_bounded_batch_query(
for incident_id in incident_by_run.values()
for sequence in range(25)
]
db = _FakeDb(rows)
db = _FakeDb(rows, followup_rows=[[]])
enter_count = [0]
monkeypatch.setattr(
@@ -127,11 +121,6 @@ async def test_run_remediation_summaries_use_one_bounded_batch_query(
"_collect_run_incident_ids",
lambda *, run, inbound_events, outbound_messages: [incident_by_run[run.run_id]],
)
monkeypatch.setattr(
platform_operator_service,
"_fetch_legacy_mcp_by_incident_ids",
_empty_legacy_history,
)
async def forbidden_per_incident_history(
*_args: object, **_kwargs: object
@@ -154,8 +143,8 @@ async def test_run_remediation_summaries_use_one_bounded_batch_query(
)
assert enter_count == [0]
assert db.execute_calls == 1
assert len(db.statements) == 1
assert db.execute_calls == 2
assert len(db.statements) == 2
assert summaries[run_a.run_id]["total"] == 20
assert summaries[run_b.run_id]["total"] == 20
assert summaries[run_a.run_id]["status"] == "read_only_dry_run"
@@ -180,7 +169,11 @@ async def test_run_remediation_batch_failure_is_reported_for_each_incident(
run_a.run_id: "INC-20260714-A001",
run_b.run_id: "INC-20260714-B001",
}
db = _FakeDb([], error=RuntimeError("database pool exhausted"))
db = _FakeDb(
[],
followup_rows=[[]],
errors=[RuntimeError("database pool exhausted"), None],
)
enter_count = [0]
monkeypatch.setattr(
@@ -193,12 +186,6 @@ async def test_run_remediation_batch_failure_is_reported_for_each_incident(
"_collect_run_incident_ids",
lambda *, run, inbound_events, outbound_messages: [incident_by_run[run.run_id]],
)
monkeypatch.setattr(
platform_operator_service,
"_fetch_legacy_mcp_by_incident_ids",
_empty_legacy_history,
)
summaries = await platform_operator_service._build_run_remediation_summaries(
runs=[run_a, run_b],
inbound_by_run={},
@@ -207,10 +194,68 @@ async def test_run_remediation_batch_failure_is_reported_for_each_incident(
)
assert enter_count == [0]
assert db.execute_calls == 1
assert db.execute_calls == 2
for run in (run_a, run_b):
incident_id = incident_by_run[run.run_id]
assert summaries[run.run_id]["status"] == "no_evidence"
assert summaries[run.run_id]["errors"] == [
{"incident_id": incident_id, "error": "database pool exhausted"}
{
"incident_id": incident_id,
"error": "remediation_history_batch_fetch_failed:RuntimeError",
}
]
@pytest.mark.asyncio
async def test_run_remediation_batch_has_global_incident_and_row_limits() -> None:
requested_count = (
platform_operator_service._MAX_REMEDIATION_HISTORY_BATCH_INCIDENTS + 2
)
incident_ids = [f"INC-20260714-{index:04d}" for index in range(requested_count)]
db = _FakeDb([])
histories, errors = await (
platform_operator_service._fetch_run_remediation_histories_by_incident(
incident_ids,
db=db,
)
)
assert len(histories) == (
platform_operator_service._MAX_REMEDIATION_HISTORY_BATCH_INCIDENTS
)
assert all(items == [] for items in histories.values())
assert db.execute_calls == 1
statement = db.statements[0]
assert statement._limit_clause.value == (
platform_operator_service._MAX_REMEDIATION_HISTORY_BATCH_ROWS + 1
)
assert incident_ids[0] not in errors
assert errors[incident_ids[-1]]["error"] == (
"remediation_history_batch_incident_limit_reached"
)
@pytest.mark.asyncio
async def test_run_remediation_batch_reports_only_an_observed_row_truncation(
monkeypatch: pytest.MonkeyPatch,
) -> None:
incident_id = "INC-20260714-A001"
monkeypatch.setattr(
platform_operator_service,
"_MAX_REMEDIATION_HISTORY_BATCH_ROWS",
2,
)
db = _FakeDb([_history_row(incident_id, sequence) for sequence in range(3)])
_histories, errors = await (
platform_operator_service._fetch_run_remediation_histories_by_incident(
[incident_id],
db=db,
)
)
assert db.statements[0]._limit_clause.value == 3
assert errors[incident_id]["error"] == (
"remediation_history_batch_row_limit_reached"
)