test: add targeted E2E coverage for branding, status flow, MO-016, and knowledge; fix two real bugs found along the way
New frontend/e2e/fleet-ops-correction.spec.ts covers section 12 of the brief: branding (Fleet Ops visible, no MobilityOps/PoC leaks, in all 3 languages), the language switcher persisting across reload, the full status-recommendation flow (non-mutating preview, exact-status confirm button, manual review with no apply button, stale-token rejection), MO-016 order independence at the browser level, the knowledge base grounding the exact brief question in its own language, and localized audit/automation content with raw codes only under "Technical details". Writing these tests surfaced two real bugs: - DataQualityIssueDetail.tsx conflated "no conflict" with "manual review required" because both carry safe_to_apply: false (a no_conflict recommendation has nothing to apply, so it's trivially "not safe to apply" without being unsafe). This showed a false "manual review required" panel for MO-016 after its overlap was resolved, instead of the correct "no change needed" state. Fixed by keying the branch on manual_review_required alone. - test_mo_016_status_conflict_recommendation_is_order_independent never actually exercised MO-016: _first_open() returned whichever vehicle_status_conflict issue was most recently detected (there are ~14 open after a reset), not necessarily DQ-DEMO-STATUS, so the test's MO-016 assertions were trivially true regardless of what the code under test did. Added _first_open_for_vehicle() and rewrote the test to explicitly target MO-016, and to assert the behaviour order independence actually requires: resolving the overlap first must correctly leave nothing to apply (the vehicle already matches the facts), not literally the same end status as resolving the conflict first. 151 backend tests, Ruff, mypy green; full 108-test Playwright suite green (two transient, non-reproducible flakes confirmed to pass in isolation and unrelated to this change). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ac4b1636fe
commit
1fdd2b3ccf
@@ -433,6 +433,15 @@ def _resolve_overlap_issue(ops_client, *, booking_to_block: str) -> None:
|
||||
assert response.json()["status"] == "resolved"
|
||||
|
||||
|
||||
def _first_open_for_vehicle(ops_client, rule_type: str, vehicle_ref: str) -> dict:
|
||||
issues = ops_client.get(
|
||||
"/api/v1/data-quality/issues", params={"rule_type": rule_type, "status": "open"}
|
||||
).json()
|
||||
match = next((i for i in issues if i["entity_ref"] == vehicle_ref), None)
|
||||
assert match, f"expected an open {rule_type} issue for {vehicle_ref}"
|
||||
return match
|
||||
|
||||
|
||||
def _apply_status_recommendation(ops_client, public_ref: str) -> dict:
|
||||
preview = ops_client.post(
|
||||
f"/api/v1/data-quality/issues/{public_ref}/status-recommendation"
|
||||
@@ -447,32 +456,46 @@ def _apply_status_recommendation(ops_client, public_ref: str) -> dict:
|
||||
|
||||
def test_mo_016_status_conflict_recommendation_is_order_independent(ops_client):
|
||||
# MO-016 carries both a booking_overlap (DQ-DEMO-OVERLAP) and a vehicle_status_conflict
|
||||
# (DQ-DEMO-STATUS) issue at once -- resolving them in either order must land the
|
||||
# vehicle in the same final, safe state (see docs/fleet-ops-correction/
|
||||
# current-gap-audit.md §6-7 and vehicle-status-decision-table.md).
|
||||
# (DQ-DEMO-STATUS) issue at once. Order independence does NOT mean "the same final
|
||||
# vehicle status regardless of order" -- resolving the overlap first genuinely removes
|
||||
# the conflict, so there is correctly nothing left to apply. What must hold in either
|
||||
# order: the recommendation always reflects the real, current facts (never a stale
|
||||
# "was some other issue open" proxy), and nothing unsafe is ever applied (never
|
||||
# "rented", never a status change once the underlying condition has already resolved
|
||||
# itself). See docs/fleet-ops-correction/current-gap-audit.md §6-7 and
|
||||
# vehicle-status-decision-table.md.
|
||||
|
||||
# Order A: resolve the booking overlap first, then the status conflict.
|
||||
# Order A: resolve the booking overlap first. The status-conflict issue's own
|
||||
# recommendation must now correctly report that the conflict is gone -- nothing unsafe
|
||||
# should be auto-applied, and the vehicle (never touched) stays exactly as it was.
|
||||
_reset_demo(ops_client)
|
||||
_resolve_overlap_issue(ops_client, booking_to_block="BK-DEMO-OVERLAP-B")
|
||||
status_issue_a = _first_open(ops_client, "vehicle_status_conflict")
|
||||
result_a = _apply_status_recommendation(ops_client, status_issue_a["public_ref"])
|
||||
status_issue_a = _first_open_for_vehicle(ops_client, "vehicle_status_conflict", "MO-016")
|
||||
preview_a = ops_client.post(
|
||||
f"/api/v1/data-quality/issues/{status_issue_a['public_ref']}/status-recommendation"
|
||||
).json()
|
||||
assert preview_a["recommendation_code"] == "vehicle.no_conflict"
|
||||
assert preview_a["recommended_status"] is None
|
||||
assert preview_a["safe_to_apply"] is False
|
||||
vehicle_a = ops_client.get("/api/v1/vehicles/MO-016").json()
|
||||
assert vehicle_a["operational_status"] == "available"
|
||||
|
||||
# Order B: resolve the status conflict first, then the booking overlap.
|
||||
# Order B: resolve the status conflict first, while the overlap is still open -- the
|
||||
# conflict genuinely still exists, so the evaluator must still detect it and safely
|
||||
# resolve it (never "rented").
|
||||
_reset_demo(ops_client)
|
||||
status_issue_b = _first_open(ops_client, "vehicle_status_conflict")
|
||||
status_issue_b = _first_open_for_vehicle(ops_client, "vehicle_status_conflict", "MO-016")
|
||||
result_b = _apply_status_recommendation(ops_client, status_issue_b["public_ref"])
|
||||
assert result_b["applied_status"] != "rented"
|
||||
vehicle_b_mid = ops_client.get("/api/v1/vehicles/MO-016").json()
|
||||
assert vehicle_b_mid["operational_status"] == result_b["applied_status"]
|
||||
|
||||
# Resolving the now-redundant overlap afterwards must not itself change the vehicle's
|
||||
# status as a side effect.
|
||||
_resolve_overlap_issue(ops_client, booking_to_block="BK-DEMO-OVERLAP-B")
|
||||
vehicle_b = ops_client.get("/api/v1/vehicles/MO-016").json()
|
||||
|
||||
assert result_a["applied_status"] == result_b["applied_status"], (
|
||||
"resolving the booking overlap before vs. after the status conflict recommended "
|
||||
"a different status -- the recommendation must not depend on issue order"
|
||||
)
|
||||
assert vehicle_a["operational_status"] == vehicle_b["operational_status"]
|
||||
# Never auto-"available" and never auto-"rented" as a side effect of this scenario --
|
||||
# MO-016 has no active rental in either order, only reserved bookings.
|
||||
assert vehicle_a["operational_status"] not in ("rented",)
|
||||
assert vehicle_b["operational_status"] == result_b["applied_status"]
|
||||
assert vehicle_b["operational_status"] != "rented"
|
||||
|
||||
_reset_demo(ops_client)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user