Publish curated MobilityOps source snapshot
This commit is contained in:
commit
7adc072f5e
317 files changed
+48851
No files matched your search
@@ -0,0 +1,296 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from app.core.db import SessionLocal
|
||||
from app.models.audit import AuditEvent
|
||||
from app.models.booking import Booking
|
||||
from app.models.customer import Customer
|
||||
from app.models.data_quality import DataQualityIssue
|
||||
from app.models.outbox import DEMO_SCENARIO_ERROR_CODE, OutboxEvent
|
||||
from app.models.user import User
|
||||
from app.models.vehicle import Vehicle
|
||||
from app.seed_loader import SEED_AUTHORED_ANCHOR, reset_and_seed
|
||||
|
||||
|
||||
def test_seed_counts_match_deterministic_dataset():
|
||||
# Other test modules mutate shared demo state (returns, resets), so this test
|
||||
# re-seeds immediately before asserting counts rather than trusting whatever
|
||||
# order pytest happened to run modules in.
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
assert db.scalar(select(func.count()).select_from(Vehicle)) == 50
|
||||
assert db.scalar(select(func.count()).select_from(Customer)) == 180
|
||||
# 246 original plus 8 (BK-T-001..008) added so "Today's movements" reads as a
|
||||
# real day of traffic rather than the same fixed 4 rows on every reset.
|
||||
assert db.scalar(select(func.count()).select_from(Booking)) == 254
|
||||
# 21 from the CSV (15 original + 6 giving every unexplained blocked vehicle a
|
||||
# real open issue) plus a deterministic set discovered by the post-seed scan. The
|
||||
# shared vehicle-status evaluator (app.services.vehicle_status) also catches
|
||||
# MO-024: an active/return-pending booking (BK-DEMO-RETURN) on a vehicle that has
|
||||
# already crossed its service-due odometer threshold -- a genuine conflict the
|
||||
# previous hand-rolled scanner never checked for.
|
||||
assert db.scalar(select(func.count()).select_from(DataQualityIssue)) == 33
|
||||
assert db.scalar(select(func.count()).select_from(OutboxEvent)) == 20
|
||||
assert db.scalar(select(func.count()).select_from(User)) == 2
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_demo_scenarios_present():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
|
||||
booking = db.scalar(select(Booking).where(Booking.public_ref == "BK-DEMO-RETURN"))
|
||||
assert booking is not None
|
||||
assert booking.status == "active"
|
||||
|
||||
duplicate_customer = db.scalar(select(Customer).where(Customer.public_ref == "CUS-0178"))
|
||||
assert duplicate_customer is not None
|
||||
|
||||
duplicate_issue = db.scalar(
|
||||
select(DataQualityIssue).where(DataQualityIssue.public_ref == "DQ-DEMO-DUPLICATE")
|
||||
)
|
||||
assert duplicate_issue is not None
|
||||
assert duplicate_issue.rule_type == "possible_duplicate_customer"
|
||||
|
||||
failed_run = db.scalar(select(OutboxEvent).where(OutboxEvent.delivery_status == "failed"))
|
||||
assert failed_run is not None
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _by_ref(db, model, ref):
|
||||
return db.scalar(select(model).where(model.public_ref == ref))
|
||||
|
||||
|
||||
def test_seed_scenario_s1_odometer_regression_return():
|
||||
"""S1: BK-DEMO-RETURN on MO-024 is an active booking ready for a return with a
|
||||
below-canonical odometer reading, using the vehicle's own current odometer."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
booking = _by_ref(db, Booking, "BK-DEMO-RETURN")
|
||||
vehicle = _by_ref(db, Vehicle, "MO-024")
|
||||
assert booking is not None and vehicle is not None
|
||||
assert booking.vehicle_id == vehicle.id
|
||||
assert booking.status == "active"
|
||||
assert booking.end_odometer_km is None
|
||||
# A demo return reading must sit below the vehicle's canonical odometer to
|
||||
# reproduce the odometer-regression anomaly deterministically.
|
||||
assert vehicle.odometer_km > 0
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_scenario_s2_duplicate_customer_pair():
|
||||
"""S2: CUS-0012/CUS-0178 form a possible-duplicate pair with a matching open issue."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
primary = _by_ref(db, Customer, "CUS-0012")
|
||||
duplicate = _by_ref(db, Customer, "CUS-0178")
|
||||
assert primary is not None and duplicate is not None
|
||||
assert primary.email == duplicate.email
|
||||
assert duplicate.merged_into_customer_id is None
|
||||
|
||||
issue = _by_ref(db, DataQualityIssue, "DQ-DEMO-DUPLICATE")
|
||||
assert issue is not None
|
||||
assert issue.rule_type == "possible_duplicate_customer"
|
||||
assert issue.status == "open"
|
||||
related = issue.evidence_json.get("related_refs", [])
|
||||
assert "CUS-0012" in related or "CUS-0178" in related
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_scenario_s4_booking_overlap():
|
||||
"""S4: MO-016 carries two overlapping reservations plus a matching open issue."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
vehicle = _by_ref(db, Vehicle, "MO-016")
|
||||
booking_a = _by_ref(db, Booking, "BK-DEMO-OVERLAP-A")
|
||||
booking_b = _by_ref(db, Booking, "BK-DEMO-OVERLAP-B")
|
||||
assert vehicle is not None and booking_a is not None and booking_b is not None
|
||||
assert booking_a.vehicle_id == vehicle.id
|
||||
assert booking_b.vehicle_id == vehicle.id
|
||||
assert booking_a.starts_at < booking_b.ends_at
|
||||
assert booking_b.starts_at < booking_a.ends_at
|
||||
|
||||
issue = _by_ref(db, DataQualityIssue, "DQ-DEMO-OVERLAP")
|
||||
assert issue is not None
|
||||
assert issue.rule_type == "booking_overlap"
|
||||
assert issue.status == "open"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_odometer_issues_expose_only_the_regressing_booking_as_correctable():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
issue = _by_ref(db, DataQualityIssue, "DQ-0007")
|
||||
assert issue is not None
|
||||
assert issue.evidence_json["source_type"] == "return"
|
||||
assert issue.evidence_json["related_refs"] == [
|
||||
"INSP-0057",
|
||||
"BK-H-0007",
|
||||
"INSP-0007",
|
||||
]
|
||||
assert issue.evidence_json["correctable_booking_refs"] == ["BK-H-0007"]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_scenario_s5_failed_workflow_run():
|
||||
"""S5: one seeded outbox event is durably 'failed' (terminal, retryable), not merely
|
||||
pending, so the background dispatcher never silently auto-heals it away."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
failed = db.scalar(
|
||||
select(OutboxEvent).where(
|
||||
OutboxEvent.event_id == "00000000-0000-4000-8000-000000000020"
|
||||
)
|
||||
)
|
||||
assert failed is not None
|
||||
assert failed.delivery_status == "failed"
|
||||
assert failed.attempts >= 1
|
||||
assert failed.last_error
|
||||
# Coded as a prepared demo scenario, not as a real connectionError: the whole
|
||||
# point of this row is to demonstrate retry and audit, so nothing downstream
|
||||
# may read it as evidence that the n8n integration is unhealthy.
|
||||
assert failed.last_error_code == DEMO_SCENARIO_ERROR_CODE
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_dates_are_anchored_to_reset_moment():
|
||||
"""Every reset shifts seeded dates by (real today - authored anchor), so scenario
|
||||
bookings stay 'today'/'near-future' relative to whenever the reset actually ran,
|
||||
instead of decaying back to the fixed 2026-08-01 authoring date."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
result = reset_and_seed(db)
|
||||
today = datetime.now(UTC).date()
|
||||
assert result.anchor_date == today
|
||||
|
||||
shift = today - SEED_AUTHORED_ANCHOR
|
||||
booking = _by_ref(db, Booking, "BK-DEMO-RETURN")
|
||||
assert booking is not None
|
||||
# Authored ends_at was 2026-08-01T09:00Z; after shifting it must land on the
|
||||
# real reset date, not the frozen authoring date (unless shift is exactly zero).
|
||||
assert booking.ends_at.date() == today or shift.days == 0
|
||||
|
||||
marker = db.scalar(
|
||||
select(AuditEvent)
|
||||
.where(AuditEvent.action == "demo_data_seeded")
|
||||
.order_by(AuditEvent.occurred_at.desc())
|
||||
)
|
||||
assert marker is not None
|
||||
assert marker.metadata_json["anchor_date"] == today.isoformat()
|
||||
assert marker.metadata_json["seed_authored_anchor"] == SEED_AUTHORED_ANCHOR.isoformat()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_today_movements_are_a_credible_mix():
|
||||
"""A fresh reset must not land on a thin, always-identical 'Today's movements'
|
||||
dashboard section: a real day of fleet traffic (>=5 departures, >=5 returns, across
|
||||
more than 4 distinct vehicles) should fall on the reset day, mirroring the same
|
||||
status/date rule the dashboard router uses to build the today list."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
today = datetime.now(UTC).date()
|
||||
bookings = db.scalars(select(Booking)).all()
|
||||
departures = [
|
||||
b
|
||||
for b in bookings
|
||||
if b.starts_at.date() == today and b.status in ("reserved", "active")
|
||||
]
|
||||
returns = [
|
||||
b for b in bookings if b.ends_at.date() == today and b.status in ("active", "returned")
|
||||
]
|
||||
assert len(departures) >= 5
|
||||
assert len(returns) >= 5
|
||||
vehicles_involved = {b.vehicle_id for b in departures} | {b.vehicle_id for b in returns}
|
||||
assert len(vehicles_involved) > 4
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_every_blocked_vehicle_has_a_real_open_issue():
|
||||
"""A live reviewer found blocked vehicles with no explanation anywhere in the UI --
|
||||
5 with zero quality issues at all, one (MO-049) with only a resolved one. Every
|
||||
vehicle seeded as 'blocked' must now have at least one real, currently open
|
||||
DataQualityIssue an operator can click through to."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
blocked = db.scalars(select(Vehicle).where(Vehicle.operational_status == "blocked")).all()
|
||||
assert len(blocked) > 0
|
||||
for vehicle in blocked:
|
||||
open_issue = db.scalar(
|
||||
select(DataQualityIssue).where(
|
||||
DataQualityIssue.entity_type == "vehicle",
|
||||
DataQualityIssue.entity_id == vehicle.id,
|
||||
DataQualityIssue.status == "open",
|
||||
)
|
||||
)
|
||||
assert open_issue is not None, f"{vehicle.public_ref} is blocked with no open issue"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_scenario_mo024_service_conflict_is_flagged():
|
||||
"""Regression lock-in for the live-reported MO-024 defect: 'rented' with a real
|
||||
active booking (BK-DEMO-RETURN) yet already 14,820 km past its service threshold,
|
||||
with nothing surfacing the contradiction. The shared vehicle-status evaluator
|
||||
already catches this (a real conflict, not a fabricated third status) -- this test
|
||||
exists so a future change can't silently regress it back to unexplained."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
vehicle = _by_ref(db, Vehicle, "MO-024")
|
||||
assert vehicle is not None
|
||||
assert vehicle.operational_status == "rented"
|
||||
assert vehicle.odometer_km >= vehicle.next_service_km
|
||||
|
||||
issue = db.scalar(
|
||||
select(DataQualityIssue).where(
|
||||
DataQualityIssue.entity_type == "vehicle",
|
||||
DataQualityIssue.entity_id == vehicle.id,
|
||||
DataQualityIssue.status == "open",
|
||||
DataQualityIssue.rule_type == "vehicle_status_conflict",
|
||||
)
|
||||
)
|
||||
assert issue is not None
|
||||
signals = issue.evidence_json.get("signals", [])
|
||||
assert any(s["code"] == "vehicle.manual_review_required" for s in signals)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_seed_evidence_has_no_placeholder_summary():
|
||||
"""Every seed-only data-quality issue used to carry the vacuous evidence
|
||||
'Synthetic deterministic seed issue' with no structured signal at all -- a visitor
|
||||
had no way to understand why it needed attention. Every issue must now carry a real
|
||||
summary and at least one localizable signal (code + params)."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
reset_and_seed(db)
|
||||
issues = db.scalars(select(DataQualityIssue)).all()
|
||||
assert len(issues) > 0
|
||||
for issue in issues:
|
||||
summary = issue.evidence_json.get("summary", "")
|
||||
assert summary != "Synthetic deterministic seed issue", (
|
||||
f"{issue.public_ref} still has the meaningless placeholder summary"
|
||||
)
|
||||
signals = issue.evidence_json.get("signals", [])
|
||||
assert len(signals) > 0, f"{issue.public_ref} has no structured evidence signal"
|
||||
finally:
|
||||
db.close()
|
||||
Reference in new issue
Block a user