Implement ACTIVITY-WP-0021 production automation reliability
Root-cause IssueSink 503 (dead Forgejo PAT on issue-core), add state-hub task sink path B, log runs before emit, harden sync_schedules, deterministic SBOM/triage reports, DB probe thrash fix, and prod automation-status helper.
This commit is contained in:
parent
1209ff6973
commit
98e8aa83bd
15 changed files with 638 additions and 63 deletions
|
|
@ -11,11 +11,18 @@ from activity_core.rules.models import TaskRef, TaskSpec
|
|||
|
||||
|
||||
class DummyResponse:
|
||||
def __init__(self, payload: dict[str, Any]) -> None:
|
||||
def __init__(self, payload: dict[str, Any], status_code: int = 200) -> None:
|
||||
self.payload = payload
|
||||
self.status_code = status_code
|
||||
self.text = ""
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
return None
|
||||
if self.status_code >= 400:
|
||||
raise httpx.HTTPStatusError(
|
||||
"error",
|
||||
request=httpx.Request("POST", "http://issue-core.test/issues/"),
|
||||
response=httpx.Response(self.status_code),
|
||||
)
|
||||
|
||||
def json(self) -> dict[str, Any]:
|
||||
return self.payload
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ async def test_sync_schedule_rows_reports_drift_counts_and_preserves_one_shots(
|
|||
"upserted": 2,
|
||||
"paused": 1,
|
||||
"deleted_orphans": 1,
|
||||
"errors": 0,
|
||||
}
|
||||
assert upserted == [
|
||||
(new_id, True, "cron"),
|
||||
|
|
@ -124,3 +125,71 @@ async def test_sync_schedule_rows_reports_drift_counts_and_preserves_one_shots(
|
|||
(one_shot_id, True, "scheduled"),
|
||||
]
|
||||
assert deleted == [str(orphan_id)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_schedule_rows_continues_after_upsert_error(monkeypatch) -> None:
|
||||
"""ACTIVITY-WP-0021-T06: one ScheduleAlreadyRunningError must not abort later rows."""
|
||||
ok_id = uuid.uuid4()
|
||||
bad_id = uuid.uuid4()
|
||||
later_id = uuid.uuid4()
|
||||
upserted: list[uuid.UUID] = []
|
||||
|
||||
async def fake_upsert_schedule(client: object, defn: object) -> None:
|
||||
if defn.id == bad_id:
|
||||
raise RuntimeError("ScheduleAlreadyRunningError: simulated")
|
||||
upserted.append(defn.id)
|
||||
|
||||
async def fake_list_schedules(client: object) -> list[dict[str, str]]:
|
||||
return []
|
||||
|
||||
async def fake_delete_schedule(client: object, activity_id: str) -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(sync_schedules, "upsert_schedule", fake_upsert_schedule)
|
||||
monkeypatch.setattr(sync_schedules, "list_schedules", fake_list_schedules)
|
||||
monkeypatch.setattr(sync_schedules, "delete_schedule", fake_delete_schedule)
|
||||
|
||||
result = await sync_schedules.sync_schedule_rows(
|
||||
object(),
|
||||
[
|
||||
_row(
|
||||
activity_id=ok_id,
|
||||
enabled=True,
|
||||
trigger_config={
|
||||
"trigger_type": "cron",
|
||||
"cron_expression": "0 * * * *",
|
||||
"timezone": "UTC",
|
||||
"misfire_policy": "skip",
|
||||
},
|
||||
),
|
||||
_row(
|
||||
activity_id=bad_id,
|
||||
enabled=True,
|
||||
trigger_config={
|
||||
"trigger_type": "cron",
|
||||
"cron_expression": "0 9 * * *",
|
||||
"timezone": "UTC",
|
||||
"misfire_policy": "skip",
|
||||
},
|
||||
),
|
||||
_row(
|
||||
activity_id=later_id,
|
||||
enabled=False,
|
||||
trigger_config={
|
||||
"trigger_type": "cron",
|
||||
"cron_expression": "30 8 * * *",
|
||||
"timezone": "Europe/Berlin",
|
||||
"misfire_policy": "skip",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
assert result.upserted == 1
|
||||
assert result.paused == 1
|
||||
assert result.errors == 1
|
||||
assert ok_id in upserted
|
||||
assert later_id in upserted
|
||||
assert bad_id not in upserted
|
||||
assert result.error_details and "ScheduleAlreadyRunningError" in result.error_details[0]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue