plan(SP22): self-review fixes — phase4_at, browser check via shared client, dead code
This commit is contained in:
@@ -2392,13 +2392,32 @@ async def test_phase1_preflight_passes_when_backend_healthy(tmp_path: Path):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@respx.mock
|
||||||
async def test_phase1_preflight_fails_fast_if_browser_down(tmp_path: Path):
|
async def test_phase1_preflight_fails_fast_if_browser_down(tmp_path: Path):
|
||||||
"""If the Vite dev server is not reachable, phase 1 fails-fast
|
"""If the Vite dev server is not reachable, phase 1 fails-fast
|
||||||
with BrowserNotReachableError before phase 2 even tries."""
|
with BrowserNotReachableError before phase 2 even tries."""
|
||||||
from cyclone_pipeline.exceptions import BrowserNotReachableError
|
from cyclone_pipeline.exceptions import BrowserNotReachableError
|
||||||
|
respx.get("http://127.0.0.1:8000/api/health").mock(
|
||||||
|
return_value=Response(200, json={
|
||||||
|
"status": "ok", "version": "0.1.0",
|
||||||
|
"db": {"ok": True},
|
||||||
|
"scheduler": {"running": True, "interval_s": 60},
|
||||||
|
"pubsub": {}, "batch": {},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
respx.get("http://127.0.0.1:8000/api/admin/scheduler/status").mock(
|
||||||
|
return_value=Response(200, json={
|
||||||
|
"running": True, "interval_s": 60,
|
||||||
|
"sftp_block": "co_medicaid",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
# Make the head request fail with a connection error
|
||||||
|
respx.head("http://127.0.0.1:9999/").mock(
|
||||||
|
side_effect=httpx.ConnectError("Connection refused")
|
||||||
|
)
|
||||||
cfg = PipelineConfig(
|
cfg = PipelineConfig(
|
||||||
api_base="http://127.0.0.1:8000",
|
api_base="http://127.0.0.1:8000",
|
||||||
browser_base="http://127.0.0.1:9999", # nothing listens here
|
browser_base="http://127.0.0.1:9999",
|
||||||
run_dir=tmp_path / "runs",
|
run_dir=tmp_path / "runs",
|
||||||
headless=True,
|
headless=True,
|
||||||
)
|
)
|
||||||
@@ -2526,11 +2545,15 @@ class CyclonePipeline:
|
|||||||
f"Backend unhealthy: {snap.model_dump()}"
|
f"Backend unhealthy: {snap.model_dump()}"
|
||||||
)
|
)
|
||||||
sched = await self._client.scheduler_status()
|
sched = await self._client.scheduler_status()
|
||||||
# Verify the browser-side URL is reachable
|
# Verify the browser-side URL is reachable. We use the same
|
||||||
|
# httpx.AsyncClient as the API calls so tests can mock the
|
||||||
|
# head request via `respx`.
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=5.0) as http:
|
assert self._client._http is not None # type: ignore[attr-defined]
|
||||||
r = await http.head(self.cfg.browser_base + "/")
|
r = await self._client._http.head( # type: ignore[attr-defined]
|
||||||
r.raise_for_status()
|
self.cfg.browser_base + "/"
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
except httpx.HTTPError as e:
|
except httpx.HTTPError as e:
|
||||||
raise BrowserNotReachableError(self.cfg.browser_base) from e
|
raise BrowserNotReachableError(self.cfg.browser_base) from e
|
||||||
result = PhaseResult(
|
result = PhaseResult(
|
||||||
@@ -2950,6 +2973,10 @@ Append to `cyclone_pipeline/pipeline.py`:
|
|||||||
self.run_state.pending_data["submission_filename"] = (
|
self.run_state.pending_data["submission_filename"] = (
|
||||||
result_obj.filename
|
result_obj.filename
|
||||||
)
|
)
|
||||||
|
# Record the moment phase 4 completed; phase 7 uses this as
|
||||||
|
# the `since` for `processed-files` so we only see inbound
|
||||||
|
# files from the MFT scheduler *after* our submit.
|
||||||
|
self.run_state.pending_data["phase4_at"] = _now_iso()
|
||||||
log.info(
|
log.info(
|
||||||
"phase4_submit_ok",
|
"phase4_submit_ok",
|
||||||
submission_id=result_obj.submission_id,
|
submission_id=result_obj.submission_id,
|
||||||
@@ -3418,7 +3445,7 @@ Append to `cyclone_pipeline/pipeline.py`:
|
|||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
assert self._client is not None
|
assert self._client is not None
|
||||||
since = self.run_state.pending_data.get(
|
since = self.run_state.pending_data.get(
|
||||||
"submission_filename_set_at", self.run_state.started_at
|
"phase4_at", self.run_state.started_at
|
||||||
)
|
)
|
||||||
files = await self._client.processed_files(since=since)
|
files = await self._client.processed_files(since=since)
|
||||||
html_files = [f for f in files if f.kind == "html"]
|
html_files = [f for f in files if f.kind == "html"]
|
||||||
@@ -3442,10 +3469,8 @@ Append to `cyclone_pipeline/pipeline.py`:
|
|||||||
result_detail=self._result_detail(),
|
result_detail=self._result_detail(),
|
||||||
started_at=self.run_state.started_at,
|
started_at=self.run_state.started_at,
|
||||||
finished_at=_now_iso(),
|
finished_at=_now_iso(),
|
||||||
duration_s=(
|
duration_s=sum(
|
||||||
_iso_to_monotonic(self.run_state.started_at) -
|
p.duration_s for p in self.run_state.completed_phases
|
||||||
_iso_to_monotonic(self.run_state.started_at) +
|
|
||||||
sum(p.duration_s for p in self.run_state.completed_phases)
|
|
||||||
),
|
),
|
||||||
phases=list(self.run_state.completed_phases),
|
phases=list(self.run_state.completed_phases),
|
||||||
expected_835_by=expected_835,
|
expected_835_by=expected_835,
|
||||||
@@ -3548,18 +3573,6 @@ def _next_monday_iso() -> str:
|
|||||||
return now_mt.date().isoformat()
|
return now_mt.date().isoformat()
|
||||||
days_ahead = (7 - weekday) % 7 or 7
|
days_ahead = (7 - weekday) % 7 or 7
|
||||||
return (now_mt + timedelta(days=days_ahead)).date().isoformat()
|
return (now_mt + timedelta(days=days_ahead)).date().isoformat()
|
||||||
|
|
||||||
|
|
||||||
def _iso_to_monotonic(iso: str) -> float:
|
|
||||||
"""Best-effort ISO→monotonic. Returns 0.0 on parse error so the
|
|
||||||
report's `duration_s` is at least non-negative."""
|
|
||||||
try:
|
|
||||||
from datetime import datetime
|
|
||||||
return datetime.fromisoformat(
|
|
||||||
iso.replace("Z", "+00:00")
|
|
||||||
).timestamp()
|
|
||||||
except (ValueError, AttributeError):
|
|
||||||
return 0.0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Run test to verify it passes**
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|||||||
Reference in New Issue
Block a user