Spaces:
Running
Running
File size: 8,820 Bytes
0497d92 |
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 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
"""
Tests specifically for 502 Bad Gateway error prevention.
"""
import pytest
import httpx
from unittest.mock import patch, MagicMock
from starlette.testclient import TestClient
from app.main import app
from tests.test_services import StubAsyncClient, StubAsyncResponse
client = TestClient(app)
class Test502BadGatewayPrevention:
"""Test that 502 Bad Gateway errors are prevented and handled properly."""
@pytest.mark.integration
def test_no_502_for_timeout_errors(self):
"""Test that timeout errors return 504 instead of 502."""
with patch('httpx.AsyncClient', return_value=StubAsyncClient(post_exc=httpx.TimeoutException("Timeout"))):
resp = client.post(
"/api/v1/summarize/",
json={"text": "Test text that will timeout"}
)
# Should return 504 Gateway Timeout, not 502 Bad Gateway
assert resp.status_code == 504
assert resp.status_code != 502
data = resp.json()
assert "timeout" in data["detail"].lower()
assert "text may be too long" in data["detail"].lower()
@pytest.mark.integration
def test_large_text_gets_extended_timeout(self):
"""Test that large text gets extended timeout to prevent 502 errors."""
large_text = "A" * 10000 # 10,000 characters
with patch('httpx.AsyncClient') as mock_client:
mock_client.return_value = StubAsyncClient(post_result=StubAsyncResponse())
resp = client.post(
"/api/v1/summarize/",
json={"text": large_text, "max_tokens": 256}
)
# Verify extended timeout was used
mock_client.assert_called_once()
call_args = mock_client.call_args
expected_timeout = 120 + (10000 - 1000) // 1000 * 10 # 210 seconds
assert call_args[1]['timeout'] == expected_timeout
@pytest.mark.integration
def test_very_large_text_gets_capped_timeout(self):
"""Test that very large text gets capped timeout to prevent infinite waits."""
very_large_text = "A" * 100000 # 100,000 characters
with patch('httpx.AsyncClient') as mock_client:
mock_client.return_value = StubAsyncClient(post_result=StubAsyncResponse())
resp = client.post(
"/api/v1/summarize/",
json={"text": very_large_text, "max_tokens": 256}
)
# Verify timeout is capped at 300 seconds
mock_client.assert_called_once()
call_args = mock_client.call_args
assert call_args[1]['timeout'] == 300 # Maximum cap
@pytest.mark.integration
def test_small_text_uses_base_timeout(self):
"""Test that small text uses base timeout (30 seconds)."""
small_text = "Short text"
with patch('httpx.AsyncClient') as mock_client:
mock_client.return_value = StubAsyncClient(post_result=StubAsyncResponse())
resp = client.post(
"/api/v1/summarize/",
json={"text": small_text, "max_tokens": 256}
)
# Verify base timeout was used
mock_client.assert_called_once()
call_args = mock_client.call_args
assert call_args[1]['timeout'] == 120 # Base timeout
@pytest.mark.integration
def test_medium_text_gets_appropriate_timeout(self):
"""Test that medium-sized text gets appropriate timeout."""
medium_text = "A" * 5000 # 5,000 characters
with patch('httpx.AsyncClient') as mock_client:
mock_client.return_value = StubAsyncClient(post_result=StubAsyncResponse())
resp = client.post(
"/api/v1/summarize/",
json={"text": medium_text, "max_tokens": 256}
)
# Verify appropriate timeout was used
mock_client.assert_called_once()
call_args = mock_client.call_args
expected_timeout = 120 + (5000 - 1000) // 1000 * 10 # 160 seconds
assert call_args[1]['timeout'] == expected_timeout
@pytest.mark.integration
def test_timeout_error_has_helpful_message(self):
"""Test that timeout errors provide helpful guidance."""
with patch('httpx.AsyncClient', return_value=StubAsyncClient(post_exc=httpx.TimeoutException("Timeout"))):
resp = client.post(
"/api/v1/summarize/",
json={"text": "Test text"}
)
assert resp.status_code == 504
data = resp.json()
# Check for helpful error message
assert "timeout" in data["detail"].lower()
assert "text may be too long" in data["detail"].lower()
assert "reduce" in data["detail"].lower()
assert "max_tokens" in data["detail"].lower()
@pytest.mark.integration
def test_http_errors_still_return_502(self):
"""Test that actual HTTP errors still return 502 (this is correct behavior)."""
http_error = httpx.HTTPStatusError("Bad Request", request=MagicMock(), response=MagicMock())
with patch('httpx.AsyncClient', return_value=StubAsyncClient(post_exc=http_error)):
resp = client.post(
"/api/v1/summarize/",
json={"text": "Test text"}
)
# HTTP errors should still return 502
assert resp.status_code == 502
data = resp.json()
assert "Summarization failed" in data["detail"]
@pytest.mark.integration
def test_unexpected_errors_return_500(self):
"""Test that unexpected errors return 500 Internal Server Error."""
with patch('httpx.AsyncClient', return_value=StubAsyncClient(post_exc=Exception("Unexpected error"))):
resp = client.post(
"/api/v1/summarize/",
json={"text": "Test text"}
)
assert resp.status_code == 500
data = resp.json()
assert "Internal server error" in data["detail"]
@pytest.mark.integration
def test_successful_large_text_processing(self):
"""Test that large text can be processed successfully with extended timeout."""
large_text = "A" * 5000 # 5,000 characters
mock_response = {
"response": "This is a summary of the large text.",
"eval_count": 25,
"done": True
}
with patch('httpx.AsyncClient', return_value=StubAsyncClient(post_result=StubAsyncResponse(json_data=mock_response))):
resp = client.post(
"/api/v1/summarize/",
json={"text": large_text, "max_tokens": 256}
)
# Should succeed with 200
assert resp.status_code == 200
data = resp.json()
assert data["summary"] == mock_response["response"]
assert data["model"] == "llama3.2:latest"
assert data["tokens_used"] == mock_response["eval_count"]
assert "latency_ms" in data
@pytest.mark.integration
def test_dynamic_timeout_calculation_formula(self):
"""Test the exact formula for dynamic timeout calculation."""
test_cases = [
(500, 120), # Small text: base timeout (120s)
(1000, 120), # Exactly 1000 chars: base timeout (120s)
(1500, 120), # 1500 chars: 120 + (500//1000)*10 = 120 + 0*10 = 120
(2000, 130), # 2000 chars: 120 + (1000//1000)*10 = 120 + 1*10 = 130
(5000, 160), # 5000 chars: 120 + (4000//1000)*10 = 120 + 4*10 = 160
(10000, 210), # 10000 chars: 120 + (9000//1000)*10 = 120 + 9*10 = 210
(50000, 300), # Very large: should be capped at 300
]
for text_length, expected_timeout in test_cases:
test_text = "A" * text_length
with patch('httpx.AsyncClient') as mock_client:
mock_client.return_value = StubAsyncClient(post_result=StubAsyncResponse())
resp = client.post(
"/api/v1/summarize/",
json={"text": test_text, "max_tokens": 256}
)
# Verify timeout calculation
mock_client.assert_called_once()
call_args = mock_client.call_args
actual_timeout = call_args[1]['timeout']
assert actual_timeout == expected_timeout, f"Text length {text_length} should have timeout {expected_timeout}, got {actual_timeout}"
|