1
1
# docker-compose -f local.yml run --rm django pytest sde_collections/tests/api_tests.py
2
+ import json
2
3
from unittest .mock import MagicMock , patch
3
4
4
5
import pytest
12
13
13
14
@pytest .mark .django_db
14
15
class TestApiClass :
16
+ """
17
+ Test suite for the Sinequa API integration.
18
+ Tests cover authentication, query construction, response processing,
19
+ and error handling across different server configurations.
20
+ """
21
+
15
22
@pytest .fixture
16
23
def collection (self ):
17
24
"""Fixture to create a collection object for testing."""
@@ -25,7 +32,10 @@ def collection(self):
25
32
26
33
@pytest .fixture
27
34
def api_instance (self ):
28
- """Fixture to create an Api instance with mocked server configs."""
35
+ """
36
+ Fixture to create an Api instance with mocked server configs.
37
+ Provides a consistent test environment with predefined credentials.
38
+ """
29
39
with patch (
30
40
"sde_collections.sinequa_api.server_configs" ,
31
41
{
@@ -41,35 +51,71 @@ def api_instance(self):
41
51
42
52
@patch ("requests.post" )
43
53
def test_process_response_success (self , mock_post , api_instance ):
44
- """Test that process_response handles successful responses."""
54
+ """
55
+ Test that process_response successfully handles and parses API responses.
56
+ Verifies:
57
+ 1. Correct HTTP request processing
58
+ 2. JSON response parsing
59
+ 3. Return value structure
60
+ """
45
61
mock_response = MagicMock ()
46
62
mock_response .status_code = 200
47
63
mock_response .json .return_value = {"key" : "value" }
48
64
mock_post .return_value = mock_response
49
65
50
66
response = api_instance .process_response ("http://example.com" , payload = {"test" : "data" })
51
67
assert response == {"key" : "value" }
68
+ mock_post .assert_called_once ()
52
69
53
70
@patch ("requests.post" )
54
71
def test_process_response_failure (self , mock_post , api_instance ):
55
- """Test that process_response raises an exception on failure."""
72
+ """
73
+ Test that process_response properly handles failed API requests.
74
+ Verifies appropriate exception raising and error messaging.
75
+ """
56
76
mock_response = MagicMock ()
57
77
mock_response .status_code = 500
58
78
mock_post .return_value = mock_response
59
- mock_response .raise_for_status .side_effect = Exception ("Internal Server Error" )
79
+ mock_response .raise_for_status .side_effect = requests . RequestException ("Internal Server Error" )
60
80
61
- with pytest .raises (Exception , match = "Internal Server Error" ):
81
+ with pytest .raises (requests . RequestException , match = "Internal Server Error" ):
62
82
api_instance .process_response ("http://example.com" , payload = {"test" : "data" })
63
83
84
+ def test_missing_token_for_sql_query (self , api_instance ):
85
+ """
86
+ Test that attempting SQL queries without a token raises an appropriate error.
87
+ Verifies token validation before query execution.
88
+ """
89
+ api_instance ._provided_token = None
90
+ with pytest .raises (ValueError , match = "Token is required" ):
91
+ api_instance ._execute_sql_query ("SELECT * FROM test" )
92
+
64
93
@patch ("sde_collections.sinequa_api.Api.process_response" )
65
94
def test_query (self , mock_process_response , api_instance ):
66
- """Test that query sends correct payload and processes response."""
95
+ """
96
+ Test that query method:
97
+ 1. Constructs the correct URL and payload based on input parameters
98
+ 2. Processes API response correctly
99
+ 3. Returns expected data structure
100
+ """
67
101
mock_process_response .return_value = {"result" : "success" }
68
102
response = api_instance .query (page = 1 , collection_config_folder = "folder" )
69
103
assert response == {"result" : "success" }
70
104
105
+ # Verify payload construction
106
+ mock_process_response .assert_called_once ()
107
+ call_args = mock_process_response .call_args
108
+ assert "folder" in str (call_args ) # Verify collection folder is included
109
+ assert "page" in str (call_args ) # Verify pagination parameters
110
+
71
111
def test_process_rows_to_records (self , api_instance ):
72
- """Test processing row data into record dictionaries."""
112
+ """
113
+ Test processing of raw SQL row data into structured record dictionaries.
114
+ Verifies:
115
+ 1. Correct parsing of valid input data
116
+ 2. Error handling for malformed rows
117
+ 3. Output format consistency
118
+ """
73
119
# Test valid input
74
120
valid_rows = [["http://example.com/1" , "Text 1" , "Title 1" ], ["http://example.com/2" , "Text 2" , "Title 2" ]]
75
121
expected_output = [
@@ -85,7 +131,13 @@ def test_process_rows_to_records(self, api_instance):
85
131
86
132
@patch ("sde_collections.sinequa_api.Api.process_response" )
87
133
def test_execute_sql_query (self , mock_process_response , api_instance ):
88
- """Test SQL query execution."""
134
+ """
135
+ Test SQL query execution with token-based authentication.
136
+ Verifies:
137
+ 1. Query construction
138
+ 2. Token validation
139
+ 3. Response processing
140
+ """
89
141
mock_process_response .return_value = {"Rows" : [], "TotalRowCount" : 0 }
90
142
91
143
# Test successful query
@@ -99,7 +151,13 @@ def test_execute_sql_query(self, mock_process_response, api_instance):
99
151
100
152
@patch ("sde_collections.sinequa_api.Api._execute_sql_query" )
101
153
def test_get_full_texts_pagination (self , mock_execute_sql , api_instance ):
102
- """Test that get_full_texts correctly handles pagination."""
154
+ """
155
+ Test pagination handling in get_full_texts method.
156
+ Verifies:
157
+ 1. Correct batch processing
158
+ 2. Accurate record counting
159
+ 3. Proper iteration termination
160
+ """
103
161
# Mock responses for two pages of results
104
162
mock_execute_sql .side_effect = [
105
163
{
@@ -117,17 +175,18 @@ def test_get_full_texts_pagination(self, mock_execute_sql, api_instance):
117
175
assert len (batches [0 ]) == 2 # First batch has 2 records
118
176
assert len (batches [1 ]) == 1 # Second batch has 1 record
119
177
120
- # Verify content of first batch
178
+ # Verify content of batches
121
179
assert batches [0 ] == [
122
180
{"url" : "http://example.com/1" , "full_text" : "Text 1" , "title" : "Title 1" },
123
181
{"url" : "http://example.com/2" , "full_text" : "Text 2" , "title" : "Title 2" },
124
182
]
125
-
126
- # Verify content of second batch
127
183
assert batches [1 ] == [{"url" : "http://example.com/3" , "full_text" : "Text 3" , "title" : "Title 3" }]
128
184
129
185
def test_get_full_texts_missing_index (self , api_instance ):
130
- """Test that get_full_texts raises error when index is missing from config."""
186
+ """
187
+ Test error handling when index configuration is missing.
188
+ Verifies appropriate error message and exception type.
189
+ """
131
190
api_instance .config .pop ("index" , None )
132
191
with pytest .raises (ValueError , match = "Index not defined for server" ):
133
192
next (api_instance .get_full_texts ("test_folder" ))
@@ -141,7 +200,13 @@ def test_get_full_texts_missing_index(self, api_instance):
141
200
)
142
201
@patch ("requests.post" )
143
202
def test_query_authentication (self , mock_post , server_name , expect_auth , api_instance ):
144
- """Test authentication handling for different server types."""
203
+ """
204
+ Test authentication handling for different server types.
205
+ Verifies:
206
+ 1. Dev servers require authentication
207
+ 2. Production servers skip authentication
208
+ 3. Correct credential handling
209
+ """
145
210
api_instance .server_name = server_name
146
211
mock_post .return_value = MagicMock (status_code = 200 , json = lambda : {"result" : "success" })
147
212
@@ -154,7 +219,10 @@ def test_query_authentication(self, mock_post, server_name, expect_auth, api_ins
154
219
155
220
@patch ("requests.post" )
156
221
def test_query_dev_server_missing_credentials (self , mock_post , api_instance ):
157
- """Test that dev servers raise error when credentials are missing."""
222
+ """
223
+ Test error handling for dev servers with missing credentials.
224
+ Verifies appropriate error messages and authentication requirements.
225
+ """
158
226
api_instance .server_name = "xli"
159
227
api_instance ._provided_user = None
160
228
api_instance ._provided_password = None
@@ -164,7 +232,13 @@ def test_query_dev_server_missing_credentials(self, mock_post, api_instance):
164
232
165
233
@patch ("sde_collections.sinequa_api.Api._execute_sql_query" )
166
234
def test_get_full_texts_batch_size_reduction (self , mock_execute_sql , api_instance ):
167
- """Test that batch size reduces appropriately on failure and continues processing."""
235
+ """
236
+ Test batch size reduction logic when queries fail.
237
+ Verifies:
238
+ 1. Progressive batch size reduction
239
+ 2. Retry mechanism
240
+ 3. Successful recovery
241
+ """
168
242
# Mock first query to fail, then succeed with smaller batch
169
243
mock_execute_sql .side_effect = [
170
244
requests .RequestException ("Query too large" ), # First attempt fails
@@ -181,7 +255,7 @@ def test_get_full_texts_batch_size_reduction(self, mock_execute_sql, api_instanc
181
255
assert len (batches [0 ]) == 1
182
256
assert batches [0 ][0 ]["url" ] == "http://example.com/1"
183
257
184
- # Verify the calls made - first with original size, then with reduced size
258
+ # Verify batch size reduction logic
185
259
assert mock_execute_sql .call_count == 2
186
260
first_call = mock_execute_sql .call_args_list [0 ][0 ][0 ]
187
261
second_call = mock_execute_sql .call_args_list [1 ][0 ][0 ]
@@ -190,24 +264,88 @@ def test_get_full_texts_batch_size_reduction(self, mock_execute_sql, api_instanc
190
264
191
265
@patch ("sde_collections.sinequa_api.Api._execute_sql_query" )
192
266
def test_get_full_texts_minimum_batch_size (self , mock_execute_sql , api_instance ):
193
- """Test behavior when reaching minimum batch size."""
267
+ """
268
+ Test behavior when reaching minimum batch size.
269
+ Verifies error handling at minimum batch size threshold.
270
+ """
194
271
mock_execute_sql .side_effect = requests .RequestException ("Query failed" )
195
272
196
273
# Start with batch_size=4, min_batch_size=1
197
- # Should try: 4 -> 2 -> 1 -> raise error
198
274
with pytest .raises (ValueError , match = "Failed to process batch even at minimum size 1" ):
199
275
list (api_instance .get_full_texts ("test_folder" , batch_size = 4 , min_batch_size = 1 ))
200
276
201
- # Should have tried 3 times before giving up
277
+ # Verify retry attempts
202
278
assert mock_execute_sql .call_count == 3
203
279
calls = mock_execute_sql .call_args_list
204
280
assert "COUNT 4" in calls [0 ][0 ][0 ] # First try with 4
205
281
assert "COUNT 2" in calls [1 ][0 ][0 ] # Second try with 2
206
282
assert "COUNT 1" in calls [2 ][0 ][0 ] # Final try with 1
207
283
284
+ @patch ("requests.post" )
285
+ def test_sql_query_construction (self , mock_post , api_instance ):
286
+ """
287
+ Test direct SQL query execution with specific URL and payload validation.
288
+ Verifies:
289
+ 1. Correct URL construction
290
+ 2. Proper payload formatting
291
+ 3. Token-based authentication
292
+ """
293
+ mock_response = MagicMock ()
294
+ mock_response .status_code = 200
295
+ mock_response .json .return_value = {"Rows" : [["http://example.com" , "sample text" , "sample title" ]]}
296
+ mock_post .return_value = mock_response
297
+
298
+ sql = "SELECT url1, text, title FROM test_index WHERE collection = '/SDE/sample_folder/'"
299
+ api_instance ._execute_sql_query (sql )
300
+
301
+ # Verify URL and payload construction
302
+ mock_post .assert_called_once ()
303
+ call_args = mock_post .call_args
304
+
305
+ # Get the actual payload from the call arguments
306
+ _ , kwargs = call_args
307
+ payload = json .loads (kwargs .get ("data" , "{}" ))
308
+
309
+ # Verify each component separately
310
+ assert "engine.sql" in call_args [0 ][0 ] # Verify endpoint
311
+ assert kwargs ["headers" ]["Authorization" ] == "Bearer test_token" # Verify token usage
312
+ assert payload ["sql" ] == sql # Verify SQL query inclusion
313
+
314
+ def test_process_full_text_response (self , api_instance ):
315
+ """
316
+ Test static method for processing full text response data.
317
+ Verifies:
318
+ 1. Correct parsing of raw response data
319
+ 2. Proper dictionary structure creation
320
+ 3. Error handling for invalid response format
321
+ """
322
+ # Test valid response processing
323
+ raw_response = {
324
+ "Rows" : [
325
+ ["http://example.com/article1" , "Full text 1" , "Title 1" ],
326
+ ["http://example.com/article2" , "Full text 2" , "Title 2" ],
327
+ ]
328
+ }
329
+ expected = [
330
+ {"url" : "http://example.com/article1" , "full_text" : "Full text 1" , "title" : "Title 1" },
331
+ {"url" : "http://example.com/article2" , "full_text" : "Full text 2" , "title" : "Title 2" },
332
+ ]
333
+ processed = Api ._process_full_text_response (raw_response )
334
+ assert processed == expected
335
+
336
+ # Test invalid response format
337
+ with pytest .raises (ValueError , match = "Invalid response format" ):
338
+ Api ._process_full_text_response ({"wrong_key" : []})
339
+
208
340
@patch ("sde_collections.sinequa_api.Api._execute_sql_query" )
209
341
def test_get_full_texts_batch_size_progression (self , mock_execute_sql , api_instance ):
210
- """Test multiple batch size reductions followed by successful query."""
342
+ """
343
+ Test multiple batch size reductions followed by successful query.
344
+ Verifies:
345
+ 1. Progressive batch size reduction steps
346
+ 2. Recovery after multiple failures
347
+ 3. Final successful query execution
348
+ """
211
349
mock_execute_sql .side_effect = [
212
350
requests .RequestException ("First failure" ),
213
351
requests .RequestException ("Second failure" ),
0 commit comments