- 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>
172 lines
5.7 KiB
Python
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
|