File size: 12,692 Bytes
cce43bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
"""
Unit tests for APIClient class
"""
import pytest
import requests
import json
from unittest.mock import Mock, patch, MagicMock
from api_client import APIClient


class TestAPIClient:
    """Test cases for APIClient class"""

    def setup_method(self):
        """Set up test fixtures before each test method."""
        self.base_url = "http://localhost:8000"
        self.client = APIClient(self.base_url, timeout=30)

    def test_init(self):
        """Test APIClient initialization"""
        client = APIClient("http://localhost:8000/", timeout=45)
        assert client.base_url == "http://localhost:8000"  # Trailing slash removed
        assert client.timeout == 45
        assert client.endpoints['process_text'] == '/api/process-text'
        assert client.endpoints['health'] == '/api/health'

    @patch('requests.post')
    def test_send_query_success_with_data(self, mock_post):
        """Test successful query with data response"""
        # Mock successful response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "sql": "SELECT * FROM employees",
            "rows": [{"id": 1, "name": "John Doe"}],
            "heading": "Employee Details",
            "summary": "List of employees",
            "chart": None
        }
        mock_post.return_value = mock_response

        result = self.client.send_query("Show me all employees")

        assert result["error"] == False
        assert result["message"] == "Employee Details"
        assert len(result["rows"]) == 1
        assert result["rows"][0]["name"] == "John Doe"
        assert result["sql"] == "SELECT * FROM employees"

    @patch('requests.post')
    def test_send_query_success_with_message_only(self, mock_post):
        """Test successful query with message-only response"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "message": "I understand your question about banking services.",
            "model_used": "gpt-4"
        }
        mock_post.return_value = mock_response

        result = self.client.send_query("What services do you offer?")

        assert result["error"] == False
        assert result["message"] == "I understand your question about banking services."
        assert result["rows"] == []
        assert result["model_used"] == "gpt-4"
        assert result["status"] == "message"

    @patch('requests.post')
    def test_send_query_with_model_parameter(self, mock_post):
        """Test query with specific model parameter"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message": "Success"}
        mock_post.return_value = mock_response

        self.client.send_query("Test question", model="gpt-4-turbo")

        # Verify the payload includes model_name
        args, kwargs = mock_post.call_args
        payload = kwargs['json']
        assert payload["question"] == "Test question"
        assert payload["model_name"] == "gpt-4-turbo"

    @patch('requests.post')
    def test_send_query_connection_error(self, mock_post):
        """Test handling of connection errors"""
        mock_post.side_effect = requests.exceptions.ConnectionError("Connection failed")

        result = self.client.send_query("Test question")

        assert result["error"] == True
        assert "Cannot connect to the server" in result["message"]
        assert result["rows"] == []

    @patch('requests.post')
    def test_send_query_request_exception(self, mock_post):
        """Test handling of general request exceptions"""
        mock_post.side_effect = requests.exceptions.RequestException("Request failed")

        result = self.client.send_query("Test question")

        assert result["error"] == True
        assert "Request failed" in result["message"]
        assert result["rows"] == []

    @patch('requests.post')
    def test_send_query_unexpected_exception(self, mock_post):
        """Test handling of unexpected exceptions"""
        mock_post.side_effect = ValueError("Unexpected error")

        result = self.client.send_query("Test question")

        assert result["error"] == True
        assert "Exception" in result["message"]
        assert "Unexpected error" in result["message"]

    def test_process_response_success_with_data(self):
        """Test _process_response with successful data response"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "sql": "SELECT * FROM branches",
            "rows": [{"id": 1, "name": "Main Branch"}, {"id": 2, "name": "Downtown"}],
            "heading": "Branch Information",
            "summary": "List of all branches"
        }

        result = self.client._process_response(mock_response)

        assert result["error"] == False
        assert result["message"] == "Branch Information"
        assert len(result["rows"]) == 2
        assert result["heading"] == "Branch Information"

    def test_process_response_with_empty_heading(self):
        """Test _process_response with empty heading"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "rows": [{"id": 1, "name": "Test"}],
            "heading": "",
            "summary": ""
        }

        result = self.client._process_response(mock_response)

        assert result["message"] == "Here are the 1 results I found:"

    def test_process_response_no_data_no_heading(self):
        """Test _process_response with no data and no heading"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "rows": [],
            "heading": "",
            "summary": ""
        }

        result = self.client._process_response(mock_response)

        assert result["message"] == "I could not find matching records for your query."

    def test_process_response_error_status(self):
        """Test _process_response with error status code"""
        mock_response = Mock()
        mock_response.status_code = 400
        mock_response.json.return_value = {
            "detail": "Bad request error"
        }

        result = self.client._process_response(mock_response)

        assert result["error"] == True
        assert "Error: Bad request error" in result["message"]

    def test_process_response_invalid_json(self):
        """Test _process_response with invalid JSON"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.side_effect = ValueError("Invalid JSON")
        mock_response.text = "Invalid response text"

        result = self.client._process_response(mock_response)

        # Should handle the JSON parsing error gracefully
        assert "detail" in result or "message" in result

    @patch('requests.get')
    def test_check_health_healthy(self, mock_get):
        """Test health check with healthy status"""
        mock_response = Mock()
        mock_response.json.return_value = {"status": "healthy"}
        mock_get.return_value = mock_response

        status, message, level = self.client.check_health()

        assert status == "🟒 Active"
        assert message == "Online"
        assert level == "success"

    @patch('requests.get')
    def test_check_health_degraded(self, mock_get):
        """Test health check with degraded status"""
        mock_response = Mock()
        mock_response.status_code = 503
        mock_response.json.return_value = {"status": "degraded"}
        mock_get.return_value = mock_response

        status, message, level = self.client.check_health()

        assert status == "🟑 Degraded"
        assert message == "Some Issues"
        assert level == "warning"

    @patch('requests.get')
    @patch('socket.create_connection')
    def test_check_health_connection_error_with_socket_fallback(self, mock_socket, mock_get):
        """Test health check with connection error but socket reachable"""
        mock_get.side_effect = requests.exceptions.RequestException("Connection failed")
        mock_socket.return_value.close.return_value = None

        status, message, level = self.client.check_health()

        assert status == "🟑 Reachable"
        assert message == "Port Open"
        assert level == "warning"

    @patch('requests.get')
    @patch('socket.create_connection')
    def test_check_health_completely_offline(self, mock_socket, mock_get):
        """Test health check when completely offline"""
        mock_get.side_effect = requests.exceptions.RequestException("Connection failed")
        mock_socket.side_effect = Exception("Socket connection failed")

        status, message, level = self.client.check_health()

        assert status == "πŸ”΄ Offline"
        assert message == "Connection Failed"
        assert level == "error"

    @patch('requests.get')
    def test_get_detailed_health_success(self, mock_get):
        """Test get_detailed_health with successful response"""
        expected_health = {
            "status": "healthy",
            "checks": {"database": "ok", "api": "ok"}
        }
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = expected_health
        mock_get.return_value = mock_response

        result = self.client.get_detailed_health()

        assert result == expected_health

    @patch('requests.get')
    def test_get_detailed_health_error(self, mock_get):
        """Test get_detailed_health with error response"""
        mock_response = Mock()
        mock_response.status_code = 500
        mock_get.return_value = mock_response

        result = self.client.get_detailed_health()

        assert result["status"] == "error"
        assert "500" in result["message"]

    @patch('requests.get')
    def test_get_method_success(self, mock_get):
        """Test generic GET method with success"""
        expected_data = {"models": ["gpt-4", "gpt-3.5"]}
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = expected_data
        mock_get.return_value = mock_response

        result = self.client.get("/models")

        assert result == expected_data

    @patch('requests.get')
    def test_get_method_error(self, mock_get):
        """Test generic GET method with error"""
        mock_response = Mock()
        mock_response.status_code = 404
        mock_get.return_value = mock_response

        result = self.client.get("/models")

        assert result is None

    @patch('requests.post')
    def test_post_method_success(self, mock_post):
        """Test generic POST method with success"""
        expected_data = {"result": "success"}
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = expected_data
        mock_post.return_value = mock_response

        result = self.client.post("/change-model", {"model": "gpt-4"})

        assert result == expected_data

    @patch('requests.post')
    def test_post_method_error(self, mock_post):
        """Test generic POST method with error"""
        mock_response = Mock()
        mock_response.status_code = 400
        mock_post.return_value = mock_response

        result = self.client.post("/change-model", {"model": "invalid"})

        assert result is None

    def test_url_construction(self):
        """Test URL construction for different endpoints"""
        client = APIClient("http://localhost:8000/")
        
        # Test that trailing slash is removed
        assert client.base_url == "http://localhost:8000"
        
        # Test endpoint URLs
        process_url = f"{client.base_url}{client.endpoints['process_text']}"
        assert process_url == "http://localhost:8000/api/process-text"

    @patch('requests.post')
    def test_send_query_with_legacy_agent_parameter(self, mock_post):
        """Test that legacy agent parameter is handled correctly"""
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message": "Success"}
        mock_post.return_value = mock_response

        # Agent parameter should be ignored in payload
        self.client.send_query("Test", model="gpt-4", agent="legacy_agent")

        args, kwargs = mock_post.call_args
        payload = kwargs['json']
        assert "agent" not in payload  # Agent should not be in payload
        assert payload["model_name"] == "gpt-4"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])