diff --git a/backend/tests/test_feedback.py b/backend/tests/test_feedback.py new file mode 100644 index 00000000..5f00ea7d --- /dev/null +++ b/backend/tests/test_feedback.py @@ -0,0 +1,128 @@ +import pytest +from unittest.mock import patch, AsyncMock + + +@pytest.mark.asyncio +async def test_submit_feedback(client, auth_headers): + """Test successful feedback submission — saves to DB and sends emails.""" + with patch("app.api.endpoints.feedback.settings") as mock_settings, \ + patch("app.api.endpoints.feedback.EmailService") as mock_email: + mock_settings.FEEDBACK_EMAIL = "support@test.com" + mock_email.send_feedback_email = AsyncMock(return_value=True) + mock_email.send_feedback_confirmation_email = AsyncMock(return_value=True) + + response = await client.post( + "/api/v1/feedback", + json={ + "email": "test@example.com", + "feedback_type": "Bug Report", + "message": "Something is broken in the tree editor when I try to save.", + }, + headers=auth_headers, + ) + + assert response.status_code == 200 + data = response.json() + assert data["success"] is True + assert "submitted" in data["message"].lower() + + # Verify both emails were called + mock_email.send_feedback_email.assert_called_once() + mock_email.send_feedback_confirmation_email.assert_called_once() + + +@pytest.mark.asyncio +async def test_submit_feedback_saves_to_db_even_if_email_fails(client, auth_headers, test_db): + """Test that feedback is persisted even when email sending fails.""" + from sqlalchemy import select, func + from app.models.feedback import Feedback + + with patch("app.api.endpoints.feedback.settings") as mock_settings, \ + patch("app.api.endpoints.feedback.EmailService") as mock_email: + mock_settings.FEEDBACK_EMAIL = "support@test.com" + mock_email.send_feedback_email = AsyncMock(return_value=False) + mock_email.send_feedback_confirmation_email = AsyncMock(return_value=False) + + response = await client.post( + "/api/v1/feedback", + json={ + "email": "test@example.com", + "feedback_type": "Feature Request", + "message": "Please add dark mode to the export preview screen.", + }, + headers=auth_headers, + ) + + # Should still succeed — DB write happened + assert response.status_code == 200 + assert response.json()["success"] is True + + # Verify it was saved to the database + result = await test_db.execute(select(func.count()).select_from(Feedback)) + count = result.scalar() + assert count >= 1 + + +@pytest.mark.asyncio +async def test_submit_feedback_not_configured(client, auth_headers): + """Test 503 when FEEDBACK_EMAIL is not set.""" + with patch("app.api.endpoints.feedback.settings") as mock_settings: + mock_settings.FEEDBACK_EMAIL = None + + response = await client.post( + "/api/v1/feedback", + json={ + "email": "test@example.com", + "feedback_type": "General Feedback", + "message": "This is a general feedback message for testing.", + }, + headers=auth_headers, + ) + + assert response.status_code == 503 + + +@pytest.mark.asyncio +async def test_submit_feedback_validation_message_too_short(client, auth_headers): + """Test validation — message too short.""" + response = await client.post( + "/api/v1/feedback", + json={ + "email": "test@example.com", + "feedback_type": "Bug Report", + "message": "short", + }, + headers=auth_headers, + ) + + assert response.status_code == 422 + + +@pytest.mark.asyncio +async def test_submit_feedback_invalid_type(client, auth_headers): + """Test validation — invalid feedback type.""" + response = await client.post( + "/api/v1/feedback", + json={ + "email": "test@example.com", + "feedback_type": "Invalid Type", + "message": "This should fail because the type is invalid.", + }, + headers=auth_headers, + ) + + assert response.status_code == 422 + + +@pytest.mark.asyncio +async def test_submit_feedback_requires_auth(client): + """Test that unauthenticated requests are rejected.""" + response = await client.post( + "/api/v1/feedback", + json={ + "email": "anon@example.com", + "feedback_type": "General Feedback", + "message": "This should fail because I'm not logged in.", + }, + ) + assert response.status_code == 401