audit_events already stored before_json/after_json, but the API and UI only ever surfaced metadata -- the audit trail could say something happened but never show what changed. Add before/after to AuditEventOut, resolve a safe entity_ref/entity_link for vehicle/booking/data-quality-issue entities (customer stays label-only; no customer detail route exists in this PoC), and render a human-readable change summary in the UI with the raw before/after/metadata JSON kept behind a <details> disclosure rather than shown by default.
75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
from sqlalchemy import select
|
|
|
|
from app.core.db import SessionLocal
|
|
from app.models.booking import Booking
|
|
from app.models.vehicle import Vehicle
|
|
|
|
|
|
def test_demo_login_is_audited(ops_client):
|
|
response = ops_client.get("/api/v1/audit", params={"action": "demo_login"})
|
|
assert response.status_code == 200
|
|
events = response.json()
|
|
assert len(events) >= 1
|
|
assert events[0]["action"] == "demo_login"
|
|
|
|
|
|
def test_audit_requires_authentication(client):
|
|
response = client.get("/api/v1/audit")
|
|
assert response.status_code == 401
|
|
|
|
|
|
def test_audit_requires_operations_manager(employee_client):
|
|
response = employee_client.get("/api/v1/audit")
|
|
assert response.status_code == 403
|
|
|
|
|
|
def _activate_booking(vehicle_ref: str, start_odometer_km: int) -> str:
|
|
db = SessionLocal()
|
|
try:
|
|
vehicle = db.scalar(select(Vehicle).where(Vehicle.public_ref == vehicle_ref))
|
|
booking = db.scalar(
|
|
select(Booking).where(Booking.vehicle_id == vehicle.id, Booking.status == "returned")
|
|
)
|
|
booking.status = "active"
|
|
booking.start_odometer_km = start_odometer_km
|
|
booking.end_odometer_km = None
|
|
db.commit()
|
|
return booking.public_ref
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def test_return_registered_audit_event_exposes_before_after_and_link(ops_client):
|
|
booking_ref = _activate_booking("MO-015", start_odometer_km=17000)
|
|
vehicle_before = ops_client.get("/api/v1/vehicles/MO-015").json()
|
|
ops_client.post(
|
|
f"/api/v1/bookings/{booking_ref}/return",
|
|
json={
|
|
"end_odometer_km": vehicle_before["odometer_km"] + 10,
|
|
"fuel_level_percent": 50,
|
|
"cleanliness_ok": True,
|
|
"damage_reported": False,
|
|
"technical_warning": False,
|
|
},
|
|
headers={"Idempotency-Key": "test-audit-before-after-001"},
|
|
)
|
|
|
|
key = "test-audit-before-after-001"
|
|
events = ops_client.get(
|
|
"/api/v1/audit", params={"action": "return_registered"}
|
|
).json()
|
|
event = next(e for e in events if e["metadata"]["idempotency_key"] == key)
|
|
assert event["before"] == {"status": "active"}
|
|
assert event["after"]["status"] == "returned"
|
|
assert event["entity_ref"] == booking_ref
|
|
assert event["entity_link"] == f"/bookings/{booking_ref}"
|
|
|
|
vehicle_events = ops_client.get(
|
|
"/api/v1/audit",
|
|
params={"action": "vehicle_status_changed", "correlation_id": event["correlation_id"]},
|
|
).json()
|
|
assert len(vehicle_events) == 1
|
|
assert vehicle_events[0]["entity_ref"] == "MO-015"
|
|
assert vehicle_events[0]["entity_link"] == "/vehicles/MO-015"
|
|
assert vehicle_events[0]["before"]["odometer_km"] == vehicle_before["odometer_km"]
|