Files
resolutionflow/backend/tests/test_survey.py
chihlasm 199cf315c6 feat: admin survey responses page with expandable detail and CSV export
- Backend: GET /admin/survey-responses (list with stats, invite join)
- Backend: GET /admin/survey-responses/export (CSV download)
- Frontend: SurveyResponsesPage with expandable row detail
- Two-column Q&A grid with typed answer rendering (chips, ranked lists, quote blocks)
- Stats cards (total responses, this week)
- CSV export button with blob download
- Sidebar nav + route wiring
- Also: updated Q14 from product domain ranking to diagnostic prioritization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 07:55:49 -05:00

172 lines
5.7 KiB
Python

"""Tests for the public survey submission endpoint."""
import pytest
@pytest.mark.asyncio
async def test_submit_survey_success(client):
"""POST valid survey data returns 200 with id."""
payload = {
"respondent_name": "Test Engineer",
"responses": {
"prereqs": ["Who's affected", "What changed recently"],
"verify_fix": "Have the user confirm it's working",
"steps_at_a_time": "5 steps",
},
}
response = await client.post("/api/v1/survey/submit", json=payload)
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["message"] == "Thank you for your response!"
@pytest.mark.asyncio
async def test_submit_survey_anonymous(client):
"""Survey works without respondent_name."""
payload = {
"responses": {
"first_step": "Check if it's one user or many",
},
}
response = await client.post("/api/v1/survey/submit", json=payload)
assert response.status_code == 200
assert "id" in response.json()
@pytest.mark.asyncio
async def test_submit_survey_missing_responses(client):
"""Missing responses field returns 422."""
payload = {"respondent_name": "Test"}
response = await client.post("/api/v1/survey/submit", json=payload)
assert response.status_code == 422
@pytest.mark.asyncio
async def test_submit_survey_empty_body(client):
"""Empty body returns 422."""
response = await client.post("/api/v1/survey/submit", json={})
assert response.status_code == 422
@pytest.mark.asyncio
async def test_check_invite_status_not_found(client):
"""Invalid token returns 404."""
response = await client.get("/api/v1/survey/invite/nonexistent")
assert response.status_code == 404
@pytest.mark.asyncio
async def test_submit_with_completed_token_returns_409(client, admin_auth_headers):
"""Submitting with an already-used token returns 409."""
# Create invite
create_res = await client.post(
"/api/v1/admin/survey-invites",
json={"recipient_name": "Test User"},
headers=admin_auth_headers,
)
assert create_res.status_code == 200
token = create_res.json()["token"]
# First submit succeeds
submit_res = await client.post(
"/api/v1/survey/submit",
json={"responses": {"q1": "answer"}, "token": token},
)
assert submit_res.status_code == 200
# Second submit with same token returns 409
submit_res2 = await client.post(
"/api/v1/survey/submit",
json={"responses": {"q1": "answer"}, "token": token},
)
assert submit_res2.status_code == 409
@pytest.mark.asyncio
async def test_create_invite_requires_admin(client, auth_headers):
"""Non-admin users cannot create invites."""
response = await client.post(
"/api/v1/admin/survey-invites",
json={"recipient_name": "Test"},
headers=auth_headers,
)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_create_and_list_invites(client, admin_auth_headers):
"""Admin can create and list invites."""
# Create
create_res = await client.post(
"/api/v1/admin/survey-invites",
json={"recipient_name": "John Smith", "recipient_email": "john@msp.example.com"},
headers=admin_auth_headers,
)
assert create_res.status_code == 200
data = create_res.json()
assert data["recipient_name"] == "John Smith"
assert data["status"] == "pending"
assert "survey_url" in data
# List
list_res = await client.get("/api/v1/admin/survey-invites", headers=admin_auth_headers)
assert list_res.status_code == 200
invites = list_res.json()
assert len(invites) >= 1
@pytest.mark.asyncio
async def test_list_survey_responses_admin(client, admin_auth_headers):
"""Admin can list survey responses."""
await client.post(
"/api/v1/survey/submit",
json={"respondent_name": "Tester", "responses": {"q1": "answer"}},
)
res = await client.get("/api/v1/admin/survey-responses", headers=admin_auth_headers)
assert res.status_code == 200
data = res.json()
assert "responses" in data
assert "total" in data
assert "this_week" in data
assert data["total"] >= 1
assert data["responses"][0]["respondent_name"] == "Tester"
assert data["responses"][0]["source"] == "direct"
@pytest.mark.asyncio
async def test_list_survey_responses_requires_admin(client, auth_headers):
"""Non-admin cannot list survey responses."""
res = await client.get("/api/v1/admin/survey-responses", headers=auth_headers)
assert res.status_code == 403
@pytest.mark.asyncio
async def test_export_survey_responses_csv(client, admin_auth_headers):
"""Admin can export survey responses as CSV."""
await client.post(
"/api/v1/survey/submit",
json={
"respondent_name": "CSV Tester",
"responses": {
"prereqs": ["Who's affected", "What changed recently"],
"verify_fix": "Have the user confirm",
"steps_at_a_time": "5 steps",
"prioritization": ["Likelihood", "Speed", "Blast radius"],
},
},
)
res = await client.get("/api/v1/admin/survey-responses/export", headers=admin_auth_headers)
assert res.status_code == 200
assert "text/csv" in res.headers["content-type"]
assert "attachment" in res.headers.get("content-disposition", "")
body = res.text
assert "CSV Tester" in body
assert "Respondent" in body
@pytest.mark.asyncio
async def test_export_survey_responses_requires_admin(client, auth_headers):
"""Non-admin cannot export survey responses."""
res = await client.get("/api/v1/admin/survey-responses/export", headers=auth_headers)
assert res.status_code == 403