import os import uuid import pytest from fastapi.testclient import TestClient # Ensure required env vars for app import/config os.environ.setdefault("SECRET_KEY", "x" * 32) os.environ.setdefault("DATABASE_URL", "sqlite:////tmp/delphi_test.sqlite") from app.main import app # noqa: E402 from app.auth.security import get_current_user, get_admin_user # noqa: E402 from tests.helpers import assert_validation_error, assert_http_error # noqa: E402 @pytest.fixture(scope="module") def client(): class _Admin: def __init__(self): self.id = 1 self.username = "admin" self.is_admin = True self.is_active = True self.first_name = "Admin" self.last_name = "User" # For public create, current_user is optional; override admin endpoints app.dependency_overrides[get_admin_user] = lambda: _Admin() app.dependency_overrides[get_current_user] = lambda: _Admin() try: yield TestClient(app) finally: app.dependency_overrides.pop(get_admin_user, None) app.dependency_overrides.pop(get_current_user, None) def test_create_ticket_validation_errors(client: TestClient): # Missing required fields resp = client.post("/api/support/tickets", json={}) assert_validation_error(resp, "subject") # Too short subject/description and invalid email payload = { "subject": "Hey", "description": "short", "contact_name": "", "contact_email": "not-an-email", } resp = client.post("/api/support/tickets", json=payload) assert_validation_error(resp, "subject") assert_validation_error(resp, "description") assert_validation_error(resp, "contact_name") assert_validation_error(resp, "contact_email") def _valid_ticket_payload() -> dict: token = uuid.uuid4().hex[:6] return { "subject": f"Support issue {token}", "description": "A reproducible problem description long enough", "category": "bug_report", "priority": "medium", "contact_name": "John Tester", "contact_email": f"john.{token}@example.com", "current_page": "/dashboard", "browser_info": "pytest-agent", } def test_ticket_lifecycle_and_404s_with_audit(client: TestClient): # Create ticket (public) payload = _valid_ticket_payload() resp = client.post("/api/support/tickets", json=payload) assert resp.status_code == 200 body = resp.json() ticket_id = body["ticket_id"] assert body["status"] == "created" # Get ticket as admin resp = client.get(f"/api/support/tickets/{ticket_id}") assert resp.status_code == 200 detail = resp.json() assert detail["status"] == "open" assert isinstance(detail.get("responses"), list) # 404 on missing ticket get/update/respond resp = client.get("/api/support/tickets/999999") assert_http_error(resp, 404, "Ticket not found") resp = client.put("/api/support/tickets/999999", json={"status": "in_progress"}) assert_http_error(resp, 404, "Ticket not found") resp = client.post("/api/support/tickets/999999/responses", json={"message": "x"}) assert_http_error(resp, 404, "Ticket not found") # State transitions: open -> in_progress -> resolved resp = client.put(f"/api/support/tickets/{ticket_id}", json={"status": "in_progress"}) assert resp.status_code == 200 resp = client.get(f"/api/support/tickets/{ticket_id}") assert resp.status_code == 200 assert resp.json()["status"] == "in_progress" # Add public response resp = client.post( f"/api/support/tickets/{ticket_id}/responses", json={"message": "We are working on it", "is_internal": False}, ) assert resp.status_code == 200 # Resolve ticket resp = client.put(f"/api/support/tickets/{ticket_id}", json={"status": "resolved"}) assert resp.status_code == 200 resp = client.get(f"/api/support/tickets/{ticket_id}") data = resp.json() assert data["status"] == "resolved" assert data["resolved_at"] is not None # Basic list with pagination params should 200 resp = client.get("/api/support/tickets", params={"skip": 0, "limit": 10}) assert resp.status_code == 200 assert isinstance(resp.json(), list)