File size: 4,139 Bytes
8b9e569
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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