sched: require proven ownership at poll/admin/recovery entries (F5)
This commit is contained in:
@@ -15,6 +15,7 @@ from wf_artifacts.runs.store import FileRunStore
|
||||
from wf_scheduling import recovery as sched_recovery
|
||||
from wf_scheduling.calendar import OneShotSource
|
||||
from wf_scheduling.models import Schedule
|
||||
from wf_scheduling.ownership import SchedulerOwnership
|
||||
from wf_scheduling.poll import Scheduler
|
||||
from wf_scheduling.prepare import SchedulePreparer
|
||||
from wf_scheduling.store import FileScheduleStore
|
||||
@@ -57,9 +58,16 @@ def test_recovery_materializes_missing_view_as_pending(tmp_path: Path) -> None:
|
||||
resolved_input={},
|
||||
max_steps=None,
|
||||
)
|
||||
diags = sched_recovery.recover(
|
||||
schedule_store=sched_store, run_store=run_store, now=ts(2026, 9, 8, 12, 0)
|
||||
)
|
||||
ownership = SchedulerOwnership(tmp_path / "sched", owner="test").acquire()
|
||||
try:
|
||||
diags = sched_recovery.recover(
|
||||
schedule_store=sched_store,
|
||||
run_store=run_store,
|
||||
now=ts(2026, 9, 8, 12, 0),
|
||||
ownership=ownership,
|
||||
)
|
||||
finally:
|
||||
ownership.release()
|
||||
assert any("pending-dispatch" in d for d in diags)
|
||||
assert run_store.get_run(admission.id).status.value == "admitted"
|
||||
assert sched_recovery._is_pending(run_store, admission.id)
|
||||
@@ -88,9 +96,16 @@ def test_recovery_fails_abandoned_admitted_without_replay(tmp_path: Path) -> Non
|
||||
schedule_revision=1,
|
||||
)
|
||||
materialize_admitted_view(store=run_store, admission=admission)
|
||||
diags = sched_recovery.recover(
|
||||
schedule_store=sched_store, run_store=run_store, now=ts(2026, 9, 8, 12, 0)
|
||||
)
|
||||
ownership = SchedulerOwnership(tmp_path / "sched", owner="test").acquire()
|
||||
try:
|
||||
diags = sched_recovery.recover(
|
||||
schedule_store=sched_store,
|
||||
run_store=run_store,
|
||||
now=ts(2026, 9, 8, 12, 0),
|
||||
ownership=ownership,
|
||||
)
|
||||
finally:
|
||||
ownership.release()
|
||||
assert any("failed-closed" in d for d in diags)
|
||||
assert run_store.get_run(admission.id).status.value == "failed"
|
||||
|
||||
@@ -118,25 +133,37 @@ def test_recovery_never_executes_pending_until_poll(tmp_path: Path) -> None:
|
||||
schedule_id="a",
|
||||
schedule_revision=1,
|
||||
)
|
||||
diags = sched_recovery.recover(
|
||||
schedule_store=sched_store, run_store=run_store, now=ts(2026, 9, 8, 12, 0)
|
||||
)
|
||||
ownership = SchedulerOwnership(tmp_path / "sched", owner="test").acquire()
|
||||
try:
|
||||
diags = sched_recovery.recover(
|
||||
schedule_store=sched_store,
|
||||
run_store=run_store,
|
||||
now=ts(2026, 9, 8, 12, 0),
|
||||
ownership=ownership,
|
||||
)
|
||||
finally:
|
||||
ownership.release()
|
||||
assert any("pending-dispatch" in d for d in diags)
|
||||
# Recovery itself produced no terminal history; the poll sweep dispatches.
|
||||
assert sched_store.list_occurrences("a", limit=100)["total"] == 0
|
||||
sources = {"a": OneShotSource(ts(2026, 9, 8, 12, 0))}
|
||||
sched = Scheduler(
|
||||
schedule_store=sched_store,
|
||||
run_store=run_store,
|
||||
sources=sources, # type: ignore[arg-type]
|
||||
capacity=4,
|
||||
preparer=SchedulePreparer(
|
||||
DictDeployments({"dep-1": {"rev": 1, "required": []}}),
|
||||
fixture_environment,
|
||||
),
|
||||
dispatcher=ScriptedDispatcher({"*": "complete"}),
|
||||
)
|
||||
sched_store.save_consumed("a", ts(2026, 9, 8, 12, 0))
|
||||
sched.poll(ts(2026, 9, 8, 12, 1))
|
||||
ownership = SchedulerOwnership(tmp_path / "sched", owner="test").acquire()
|
||||
try:
|
||||
sched = Scheduler(
|
||||
schedule_store=sched_store,
|
||||
run_store=run_store,
|
||||
sources=sources, # type: ignore[arg-type]
|
||||
capacity=4,
|
||||
preparer=SchedulePreparer(
|
||||
DictDeployments({"dep-1": {"rev": 1, "required": []}}),
|
||||
fixture_environment,
|
||||
),
|
||||
dispatcher=ScriptedDispatcher({"*": "complete"}),
|
||||
ownership=ownership,
|
||||
)
|
||||
sched_store.save_consumed("a", ts(2026, 9, 8, 12, 0))
|
||||
sched.poll(ts(2026, 9, 8, 12, 1))
|
||||
finally:
|
||||
ownership.release()
|
||||
assert run_store.get_run(admission.id).status.value == "completed"
|
||||
assert not sched_recovery._is_pending(run_store, admission.id)
|
||||
|
||||
Reference in New Issue
Block a user