fix(sp41): trailing newlines + edge-case tests for rebill admin endpoints
This commit is contained in:
@@ -38,6 +38,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
|
|
||||||
from cyclone.auth.deps import matrix_gate
|
from cyclone.auth.deps import matrix_gate
|
||||||
|
from cyclone.rebill.run import run_rebill
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -133,8 +134,6 @@ def post_rebill(req: RebillRequest) -> dict[str, Any]:
|
|||||||
envelope with CORS headers. Per-file failures land in the summary
|
envelope with CORS headers. Per-file failures land in the summary
|
||||||
CSV (and the ``counts`` dict) rather than raising here.
|
CSV (and the ``counts`` dict) rather than raising here.
|
||||||
"""
|
"""
|
||||||
from cyclone.rebill.run import run_rebill # local import — keeps --help fast
|
|
||||||
|
|
||||||
start_str, end_str = req.window.split("..", 1)
|
start_str, end_str = req.window.split("..", 1)
|
||||||
# Validator already proved these parse cleanly.
|
# Validator already proved these parse cleanly.
|
||||||
window_start = date.fromisoformat(start_str)
|
window_start = date.fromisoformat(start_str)
|
||||||
@@ -212,7 +211,7 @@ def get_rebill_status(
|
|||||||
counts: dict[str, int] = {}
|
counts: dict[str, int] = {}
|
||||||
with summary_path.open(newline="") as f:
|
with summary_path.open(newline="") as f:
|
||||||
for row in csv.DictReader(f):
|
for row in csv.DictReader(f):
|
||||||
disp = (row.get("disposition") or "UNKNOWN").strip() or "UNKNOWN"
|
disp = (row.get("disposition", "UNKNOWN") or "").strip() or "UNKNOWN"
|
||||||
counts[disp] = counts.get(disp, 0) + 1
|
counts[disp] = counts.get(disp, 0) + 1
|
||||||
recent.append({
|
recent.append({
|
||||||
"as_of": d.name,
|
"as_of": d.name,
|
||||||
|
|||||||
@@ -191,3 +191,36 @@ def test_get_rebill_status_returns_recent_runs(client, tmp_path, monkeypatch):
|
|||||||
# Each disposition surfaced exactly once — matches the 2-row CSV.
|
# Each disposition surfaced exactly once — matches the 2-row CSV.
|
||||||
assert counts.get("REBILLED_B") == 1, counts
|
assert counts.get("REBILLED_B") == 1, counts
|
||||||
assert counts.get("EXCLUDED_TIMELY_FILING") == 1, counts
|
assert counts.get("EXCLUDED_TIMELY_FILING") == 1, counts
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_rebill_rejects_malformed_window(client):
|
||||||
|
"""Pydantic window validator should 422 on bad shapes (no `..`, non-dates, reversed)."""
|
||||||
|
bad_windows = [
|
||||||
|
"2026-01-01", # no `..` separator
|
||||||
|
"foo..bar", # non-ISO dates
|
||||||
|
"2026-12-01..2026-01-01", # start after end
|
||||||
|
]
|
||||||
|
for w in bad_windows:
|
||||||
|
resp = client.post(
|
||||||
|
"/api/admin/rebill-from-835",
|
||||||
|
json={"window": w},
|
||||||
|
)
|
||||||
|
assert resp.status_code == 422, (
|
||||||
|
f"expected 422 for window={w!r}, got {resp.status_code}: {resp.text}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_rebill_status_returns_empty_when_no_runs(client, tmp_path, monkeypatch):
|
||||||
|
"""GET /status on an empty rebills dir must return {"recent_runs": []}, not 500.
|
||||||
|
|
||||||
|
Monkeypatches REBILLS_DIR to a fresh tmp_path subdir so the test
|
||||||
|
doesn't depend on (or pollute) any pre-existing dev/rebills/ tree.
|
||||||
|
"""
|
||||||
|
from cyclone.api_routers import rebill
|
||||||
|
|
||||||
|
monkeypatch.setattr(rebill, "REBILLS_DIR", tmp_path / "rebills_empty")
|
||||||
|
|
||||||
|
resp = client.get("/api/admin/rebill-from-835/status")
|
||||||
|
assert resp.status_code == 200, resp.text
|
||||||
|
assert resp.json() == {"recent_runs": []}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user