import uuid from app.core.config import get_settings def _callback_headers(event_id: str, token: str | None = None): settings = get_settings() return { "Idempotency-Key": event_id, "X-Service-Token": token if token is not None else settings.n8n_callback_token, } def test_callback_rejects_wrong_service_token(client): response = client.post( "/api/v1/integrations/n8n/return-callback", json={"follow_up": "cleaning"}, headers=_callback_headers(str(uuid.uuid4()), token="wrong-token"), ) assert response.status_code == 401 def test_callback_unknown_event_returns_404(client): response = client.post( "/api/v1/integrations/n8n/return-callback", json={"follow_up": "cleaning"}, headers=_callback_headers(str(uuid.uuid4())), ) assert response.status_code == 404 def test_callback_is_idempotent_by_event_id(client, ops_client): from sqlalchemy import select from app.core.db import SessionLocal from app.models.booking import Booking from app.models.outbox import OutboxEvent db = SessionLocal() try: booking = db.scalar(select(Booking).limit(1)) event = OutboxEvent( event_id=uuid.uuid4(), event_type="vehicle.returned.v1", aggregate_type="booking", aggregate_id=booking.id, payload_json={ "correlation_id": str(uuid.uuid4()), "aggregate": { "type": "booking", "id": str(booking.id), "public_ref": booking.public_ref, }, "data": {}, "aggregate_ref": booking.public_ref, }, occurred_at=db.execute(select(Booking.starts_at).limit(1)).scalar(), delivery_status="delivering", attempts=1, ) db.add(event) db.commit() event_id = str(event.event_id) finally: db.close() first = client.post( "/api/v1/integrations/n8n/return-callback", json={"follow_up": "cleaning", "summary": "test"}, headers=_callback_headers(event_id), ) second = client.post( "/api/v1/integrations/n8n/return-callback", json={"follow_up": "cleaning", "summary": "test"}, headers=_callback_headers(event_id), ) assert first.status_code == 200 assert second.status_code == 200 audit_events = ops_client.get( "/api/v1/audit", params={"action": "n8n_return_followup_recorded"} ).json() matching = [e for e in audit_events if e["metadata"]["event_id"] == event_id] assert len(matching) == 1