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