champ-chatbot / tests /api /test_feedback_post.py
qyle's picture
deployment
8b9e569 verified
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch
from constants import MAX_COMMENT_LENGTH, MAX_RESPONSE_LENGTH
from main import app
client = TestClient(app)
class TestFeedbackEndpoint:
"""Consolidated tests for POST /feedback"""
@pytest.fixture
def base_payload(self):
"""Standard valid payload structure"""
return {
"user_id": "test-user-123",
"participant_id": "participant-456",
"session_id": "test-session-123",
"consent": True,
"age_group": "25-34",
"gender": "M",
"roles": ["patient"],
"message_index": 5,
"rating": "like",
"reply_content": "Helpful response",
"comment": "Clear advice"
}
# ==================== Logic & Happy Path ====================
def test_feedback_success_and_logging(self, base_payload):
"""Tests the full happy path and ensures background tasks/logging are triggered"""
with patch("main.log_event") as mock_log, \
patch("main.BackgroundTasks.add_task") as mock_task:
response = client.post("/feedback", json=base_payload)
assert response.status_code == 200
assert mock_task.called
@pytest.mark.parametrize("rating", ["like", "dislike", "mixed"])
def test_valid_ratings(self, base_payload, rating):
"""Consolidated: Tests all valid rating strings"""
base_payload["rating"] = rating
response = client.post("/feedback", json=base_payload)
assert response.status_code == 200
def test_comment_optionality(self, base_payload):
"""Tests that comment can be empty but must exist as a key"""
base_payload["comment"] = ""
response = client.post("/feedback", json=base_payload)
assert response.status_code == 200
# ==================== Integer Constraints (The New Fixes) ====================
@pytest.mark.parametrize("index, expected_status", [
(0, 200), # Lower boundary
(10000, 200), # Upper boundary
(-1, 422), # Out of bounds (low)
(10001, 422), # Out of bounds (high)
])
def test_message_index_constraints(self, base_payload, index, expected_status):
"""Verifies ge=0 and le=10000 constraints"""
base_payload["message_index"] = index
response = client.post("/feedback", json=base_payload)
assert response.status_code == expected_status
# ==================== String & Security ====================
def test_html_sanitization(self, base_payload):
"""Ensures XSS tags are stripped (Relies on nh3 in your model)"""
base_payload["comment"] = "<script>alert('xss')</script>Safe Text"
# We assume 200 here; the real check would be inspecting the DB/Log
# to ensure the tags were removed.
response = client.post("/feedback", json=base_payload)
assert response.status_code == 200
@pytest.mark.parametrize("field, length", [
("comment", MAX_COMMENT_LENGTH + 1),
("reply_content", MAX_RESPONSE_LENGTH + 1),
])
def test_string_max_lengths(self, base_payload, field, length):
"""Verifies length constraints for strings"""
base_payload[field] = "x" * length
response = client.post("/feedback", json=base_payload)
assert response.status_code == 422
# ==================== Rate Limiting ====================
@pytest.mark.enable_rate_limit
def test_feedback_rate_limiting(self, base_payload):
"""Verifies the 20 requests per minute limit"""
# Create a fresh client to ensure limit starts at 0
with TestClient(app) as limit_client:
for _ in range(20):
limit_client.post("/feedback", json=base_payload)
over_limit_response = limit_client.post("/feedback", json=base_payload)
assert over_limit_response.status_code == 429