chore: tighten scheduling review coverage
This commit is contained in:
@@ -10,6 +10,7 @@ inventing a run.
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import typing
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
@@ -105,9 +106,9 @@ def test_production_scheduler_has_no_fixture_seams() -> None:
|
||||
# Collaborators are protocols consumed here, implemented elsewhere.
|
||||
assert "InvocationPreparer" in source
|
||||
assert "RunDispatcher" in source
|
||||
assert isinstance(
|
||||
poll_module.Scheduler.__init__.__annotations__["preparer"], object
|
||||
)
|
||||
hints = typing.get_type_hints(poll_module.Scheduler.__init__)
|
||||
assert hints["preparer"] is InvocationPreparer
|
||||
assert hints["dispatcher"] is RunDispatcher
|
||||
|
||||
|
||||
def test_preparer_contract_is_a_protocol() -> None:
|
||||
@@ -117,52 +118,59 @@ def test_preparer_contract_is_a_protocol() -> None:
|
||||
|
||||
def test_occurrence_bindings_resolve_into_admission(tmp_path: Path) -> None:
|
||||
sched, store, runs = _scheduler(tmp_path, script={"*": "hang"})
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
model = _sched_model(
|
||||
"b",
|
||||
input_bindings=[
|
||||
{
|
||||
"target": "team",
|
||||
"expression": {"kind": "literal", "value": "engineering"},
|
||||
},
|
||||
{
|
||||
"target": "report_time",
|
||||
"expression": {"kind": "occurrence", "field": "scheduled_at"},
|
||||
},
|
||||
{
|
||||
"target": "which",
|
||||
"expression": {"kind": "occurrence", "field": "schedule_id"},
|
||||
},
|
||||
],
|
||||
)
|
||||
store.create_schedule(model)
|
||||
store.save_consumed("b", intended - timedelta(hours=1))
|
||||
sched.sources["b"] = OneShotSource(intended)
|
||||
result = sched.poll(intended)
|
||||
assert result["b"].startswith("admit:run-")
|
||||
run_id = result["b"].split(":", 1)[1]
|
||||
admission = runs.get_admission(run_id)
|
||||
assert admission.resolved_input["team"] == "engineering"
|
||||
assert admission.resolved_input["report_time"] == intended.isoformat()
|
||||
assert admission.resolved_input["which"] == "b"
|
||||
assert admission.deployment_revision == 3
|
||||
assert admission.schedule_revision == 1
|
||||
sched.ownership.release()
|
||||
try:
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
model = _sched_model(
|
||||
"b",
|
||||
input_bindings=[
|
||||
{
|
||||
"target": "team",
|
||||
"expression": {"kind": "literal", "value": "engineering"},
|
||||
},
|
||||
{
|
||||
"target": "report_time",
|
||||
"expression": {
|
||||
"kind": "occurrence",
|
||||
"field": "scheduled_at",
|
||||
},
|
||||
},
|
||||
{
|
||||
"target": "which",
|
||||
"expression": {"kind": "occurrence", "field": "schedule_id"},
|
||||
},
|
||||
],
|
||||
)
|
||||
store.create_schedule(model)
|
||||
store.save_consumed("b", intended - timedelta(hours=1))
|
||||
sched.sources["b"] = OneShotSource(intended)
|
||||
result = sched.poll(intended)
|
||||
assert result["b"].startswith("admit:run-")
|
||||
run_id = result["b"].split(":", 1)[1]
|
||||
admission = runs.get_admission(run_id)
|
||||
assert admission.resolved_input["team"] == "engineering"
|
||||
assert admission.resolved_input["report_time"] == intended.isoformat()
|
||||
assert admission.resolved_input["which"] == "b"
|
||||
assert admission.deployment_revision == 3
|
||||
assert admission.schedule_revision == 1
|
||||
finally:
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_unknown_deployment_rejects_without_a_run(tmp_path: Path) -> None:
|
||||
sched, store, runs = _scheduler(tmp_path)
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
store.create_schedule(_sched_model("gone", deployment_id="dep-missing"))
|
||||
store.save_consumed("gone", intended - timedelta(hours=1))
|
||||
sched.sources["gone"] = OneShotSource(intended)
|
||||
assert sched.poll(intended) == {"gone": "admit:None"}
|
||||
assert runs.list_runs() == []
|
||||
assert runs.list_admissions() == []
|
||||
page = store.list_occurrences("gone", limit=100)
|
||||
kinds = [r["kind"] for r in cast(list[dict[str, Any]], page["occurrences"])]
|
||||
assert kinds == ["preflight-rejected"]
|
||||
sched.ownership.release()
|
||||
try:
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
store.create_schedule(_sched_model("gone", deployment_id="dep-missing"))
|
||||
store.save_consumed("gone", intended - timedelta(hours=1))
|
||||
sched.sources["gone"] = OneShotSource(intended)
|
||||
assert sched.poll(intended) == {"gone": "admit:None"}
|
||||
assert runs.list_runs() == []
|
||||
assert runs.list_admissions() == []
|
||||
page = store.list_occurrences("gone", limit=100)
|
||||
kinds = [r["kind"] for r in cast(list[dict[str, Any]], page["occurrences"])]
|
||||
assert kinds == ["preflight-rejected"]
|
||||
finally:
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_deployment_revision_mismatch_rejects_pinned_environment() -> None:
|
||||
@@ -198,46 +206,50 @@ def test_missing_required_input_rejects_without_a_run(tmp_path: Path) -> None:
|
||||
dispatcher=ScriptedDispatcher({"*": "hang"}),
|
||||
ownership=SchedulerOwnership(tmp_path, owner="test").acquire(),
|
||||
)
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
sched_store.create_schedule(_sched_model("need"))
|
||||
sched_store.save_consumed("need", intended - timedelta(hours=1))
|
||||
sched.sources["need"] = OneShotSource(intended)
|
||||
assert sched.poll(intended) == {"need": "admit:None"}
|
||||
assert run_store.list_runs() == []
|
||||
page = sched_store.list_occurrences("need", limit=100)
|
||||
entry = cast(list[dict[str, Any]], page["occurrences"])[0]
|
||||
assert entry["kind"] == "preflight-rejected"
|
||||
assert "missing-input" in entry["reason"]
|
||||
sched.ownership.release()
|
||||
try:
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
sched_store.create_schedule(_sched_model("need"))
|
||||
sched_store.save_consumed("need", intended - timedelta(hours=1))
|
||||
sched.sources["need"] = OneShotSource(intended)
|
||||
assert sched.poll(intended) == {"need": "admit:None"}
|
||||
assert run_store.list_runs() == []
|
||||
page = sched_store.list_occurrences("need", limit=100)
|
||||
entry = cast(list[dict[str, Any]], page["occurrences"])[0]
|
||||
assert entry["kind"] == "preflight-rejected"
|
||||
assert "missing-input" in entry["reason"]
|
||||
finally:
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_conflicting_schedule_targets_reject_without_a_run(tmp_path: Path) -> None:
|
||||
sched, store, runs = _scheduler(tmp_path)
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
store.create_schedule(
|
||||
_sched_model(
|
||||
"conflict",
|
||||
input_bindings=[
|
||||
{
|
||||
"target": "a",
|
||||
"expression": {"kind": "literal", "value": 1},
|
||||
},
|
||||
{
|
||||
"target": "a.b",
|
||||
"expression": {"kind": "literal", "value": 2},
|
||||
},
|
||||
],
|
||||
try:
|
||||
intended = ts(2026, 9, 8, 12, 0)
|
||||
store.create_schedule(
|
||||
_sched_model(
|
||||
"conflict",
|
||||
input_bindings=[
|
||||
{
|
||||
"target": "a",
|
||||
"expression": {"kind": "literal", "value": 1},
|
||||
},
|
||||
{
|
||||
"target": "a.b",
|
||||
"expression": {"kind": "literal", "value": 2},
|
||||
},
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
store.save_consumed("conflict", intended - timedelta(hours=1))
|
||||
sched.sources["conflict"] = OneShotSource(intended)
|
||||
assert sched.poll(intended) == {"conflict": "admit:None"}
|
||||
assert runs.list_runs() == []
|
||||
page = store.list_occurrences("conflict", limit=100)
|
||||
entry = cast(list[dict[str, Any]], page["occurrences"])[0]
|
||||
assert entry["kind"] == "preflight-rejected"
|
||||
assert entry["reason"].startswith("invalid-input:")
|
||||
sched.ownership.release()
|
||||
store.save_consumed("conflict", intended - timedelta(hours=1))
|
||||
sched.sources["conflict"] = OneShotSource(intended)
|
||||
assert sched.poll(intended) == {"conflict": "admit:None"}
|
||||
assert runs.list_runs() == []
|
||||
page = store.list_occurrences("conflict", limit=100)
|
||||
entry = cast(list[dict[str, Any]], page["occurrences"])[0]
|
||||
assert entry["kind"] == "preflight-rejected"
|
||||
assert entry["reason"].startswith("invalid-input:")
|
||||
finally:
|
||||
sched.ownership.release()
|
||||
|
||||
|
||||
def test_preparer_rejection_type_shape() -> None:
|
||||
|
||||
Reference in New Issue
Block a user