20
20
21
21
from tests .utils .object_assertions import assert_match_object , assert_object_lists_match
22
22
from typesense import exceptions
23
- from typesense .api_call import ApiCall
23
+ from typesense .api_call import ApiCall , RequestHandler
24
24
from typesense .configuration import Configuration , Node
25
25
from typesense .logger import logger
26
26
@@ -76,8 +76,8 @@ def test_initialization(
76
76
) -> None :
77
77
"""Test the initialization of the ApiCall object."""
78
78
assert api_call .config == config
79
- assert_object_lists_match (api_call .nodes , config .nodes )
80
- assert api_call .node_index == 0
79
+ assert_object_lists_match (api_call .node_manager . nodes , config .nodes )
80
+ assert api_call .node_manager . node_index == 0
81
81
82
82
83
83
def test_node_due_for_health_check (
@@ -86,14 +86,14 @@ def test_node_due_for_health_check(
86
86
"""Test that it correctly identifies if a node is due for health check."""
87
87
node = Node (host = "localhost" , port = 8108 , protocol = "http" , path = " " )
88
88
node .last_access_ts = time .time () - 61
89
- assert api_call .node_due_for_health_check (node ) is True
89
+ assert api_call .node_manager . _is_due_for_health_check (node ) is True
90
90
91
91
92
92
def test_get_node_nearest_healthy (
93
93
api_call : ApiCall ,
94
94
) -> None :
95
95
"""Test that it correctly selects the nearest node if it is healthy."""
96
- node = api_call .get_node ()
96
+ node = api_call .node_manager . get_node ()
97
97
assert_match_object (node , api_call .config .nearest_node )
98
98
99
99
@@ -102,8 +102,8 @@ def test_get_node_nearest_not_healthy(
102
102
) -> None :
103
103
"""Test that it selects the next available node if the nearest node is not healthy."""
104
104
api_call .config .nearest_node .healthy = False
105
- node = api_call .get_node ()
106
- assert_match_object (node , api_call .nodes [0 ])
105
+ node = api_call .node_manager . get_node ()
106
+ assert_match_object (node , api_call .node_manager . nodes [0 ])
107
107
108
108
109
109
def test_get_node_round_robin_selection (
@@ -114,34 +114,34 @@ def test_get_node_round_robin_selection(
114
114
api_call .config .nearest_node = None
115
115
mocker .patch ("time.time" , return_value = 100 )
116
116
117
- node1 = api_call .get_node ()
117
+ node1 = api_call .node_manager . get_node ()
118
118
assert_match_object (node1 , api_call .config .nodes [0 ])
119
119
120
- node2 = api_call .get_node ()
120
+ node2 = api_call .node_manager . get_node ()
121
121
assert_match_object (node2 , api_call .config .nodes [1 ])
122
122
123
- node3 = api_call .get_node ()
123
+ node3 = api_call .node_manager . get_node ()
124
124
assert_match_object (node3 , api_call .config .nodes [2 ])
125
125
126
126
127
127
def test_get_exception () -> None :
128
128
"""Test that it correctly returns the exception class for a given status code."""
129
- assert ApiCall . get_exception (0 ) == exceptions .HTTPStatus0Error
130
- assert ApiCall . get_exception (400 ) == exceptions .RequestMalformed
131
- assert ApiCall . get_exception (401 ) == exceptions .RequestUnauthorized
132
- assert ApiCall . get_exception (403 ) == exceptions .RequestForbidden
133
- assert ApiCall . get_exception (404 ) == exceptions .ObjectNotFound
134
- assert ApiCall . get_exception (409 ) == exceptions .ObjectAlreadyExists
135
- assert ApiCall . get_exception (422 ) == exceptions .ObjectUnprocessable
136
- assert ApiCall . get_exception (500 ) == exceptions .ServerError
137
- assert ApiCall . get_exception (503 ) == exceptions .ServiceUnavailable
138
- assert ApiCall . get_exception (999 ) == exceptions .TypesenseClientError
129
+ assert RequestHandler . _get_exception (0 ) == exceptions .HTTPStatus0Error
130
+ assert RequestHandler . _get_exception (400 ) == exceptions .RequestMalformed
131
+ assert RequestHandler . _get_exception (401 ) == exceptions .RequestUnauthorized
132
+ assert RequestHandler . _get_exception (403 ) == exceptions .RequestForbidden
133
+ assert RequestHandler . _get_exception (404 ) == exceptions .ObjectNotFound
134
+ assert RequestHandler . _get_exception (409 ) == exceptions .ObjectAlreadyExists
135
+ assert RequestHandler . _get_exception (422 ) == exceptions .ObjectUnprocessable
136
+ assert RequestHandler . _get_exception (500 ) == exceptions .ServerError
137
+ assert RequestHandler . _get_exception (503 ) == exceptions .ServiceUnavailable
138
+ assert RequestHandler . _get_exception (999 ) == exceptions .TypesenseClientError
139
139
140
140
141
141
def test_normalize_params_with_booleans () -> None :
142
142
"""Test that it correctly normalizes boolean values to strings."""
143
143
parameter_dict : typing .Dict [str , str | bool ] = {"key1" : True , "key2" : False }
144
- ApiCall .normalize_params (parameter_dict )
144
+ RequestHandler .normalize_params (parameter_dict )
145
145
146
146
assert parameter_dict == {"key1" : "true" , "key2" : "false" }
147
147
@@ -151,13 +151,13 @@ def test_normalize_params_with_non_dict() -> None:
151
151
parameter_non_dict = "string"
152
152
153
153
with pytest .raises (ValueError ):
154
- ApiCall .normalize_params (parameter_non_dict )
154
+ RequestHandler .normalize_params (parameter_non_dict )
155
155
156
156
157
157
def test_normalize_params_with_mixed_types () -> None :
158
158
"""Test that it correctly normalizes boolean values to strings."""
159
159
parameter_dict = {"key1" : True , "key2" : False , "key3" : "value" , "key4" : 123 }
160
- ApiCall .normalize_params (parameter_dict )
160
+ RequestHandler .normalize_params (parameter_dict )
161
161
assert parameter_dict == {
162
162
"key1" : "true" ,
163
163
"key2" : "false" ,
@@ -169,14 +169,14 @@ def test_normalize_params_with_mixed_types() -> None:
169
169
def test_normalize_params_with_empty_dict () -> None :
170
170
"""Test that it correctly normalizes an empty dictionary."""
171
171
parameter_dict : typing .Dict [str , str ] = {}
172
- ApiCall .normalize_params (parameter_dict )
172
+ RequestHandler .normalize_params (parameter_dict )
173
173
assert not parameter_dict
174
174
175
175
176
176
def test_normalize_params_with_no_booleans () -> None :
177
177
"""Test that it correctly normalizes a dictionary with no boolean values."""
178
178
parameter_dict = {"key1" : "value" , "key2" : 123 }
179
- ApiCall .normalize_params (parameter_dict )
179
+ RequestHandler .normalize_params (parameter_dict )
180
180
assert parameter_dict == {"key1" : "value" , "key2" : 123 }
181
181
182
182
@@ -191,7 +191,7 @@ def test_make_request_as_json(api_call: ApiCall) -> None:
191
191
status_code = 200 ,
192
192
)
193
193
194
- response = api_call .make_request (
194
+ response = api_call ._execute_request (
195
195
session .get ,
196
196
"/test" ,
197
197
as_json = True ,
@@ -211,7 +211,7 @@ def test_make_request_as_text(api_call: ApiCall) -> None:
211
211
status_code = 200 ,
212
212
)
213
213
214
- response = api_call .make_request (
214
+ response = api_call ._execute_request (
215
215
session .get ,
216
216
"/test" ,
217
217
as_json = False ,
@@ -387,7 +387,7 @@ def test_raise_custom_exception_with_header(
387
387
)
388
388
389
389
with pytest .raises (exceptions .RequestMalformed ) as exception :
390
- api_call .make_request (
390
+ api_call ._execute_request (
391
391
requests .get ,
392
392
"/test" ,
393
393
as_json = True ,
@@ -408,7 +408,7 @@ def test_raise_custom_exception_without_header(
408
408
)
409
409
410
410
with pytest .raises (exceptions .RequestMalformed ) as exception :
411
- api_call .make_request (
411
+ api_call ._execute_request (
412
412
requests .get ,
413
413
"/test" ,
414
414
as_json = True ,
@@ -456,24 +456,30 @@ def test_get_node_no_healthy_nodes(
456
456
caplog : pytest .LogCaptureFixture ,
457
457
) -> None :
458
458
"""Test that it logs a message if no healthy nodes are found."""
459
- for api_node in api_call .nodes :
459
+ for api_node in api_call .node_manager . nodes :
460
460
api_node .healthy = False
461
461
462
462
api_call .config .nearest_node .healthy = False
463
463
464
- mocker .patch .object (api_call , "node_due_for_health_check" , return_value = False )
464
+ mocker .patch .object (
465
+ api_call .node_manager ,
466
+ "_is_due_for_health_check" ,
467
+ return_value = False ,
468
+ )
465
469
466
470
# Need to set the logger level to DEBUG to capture the message
467
471
logger .setLevel (logging .DEBUG )
468
472
469
- selected_node = api_call .get_node ()
473
+ selected_node = api_call .node_manager . get_node ()
470
474
471
475
with caplog .at_level (logging .DEBUG ):
472
476
assert "No healthy nodes were found. Returning the next node." in caplog .text
473
477
474
- assert selected_node == api_call .nodes [api_call .node_index ]
478
+ assert (
479
+ selected_node == api_call .node_manager .nodes [api_call .node_manager .node_index ]
480
+ )
475
481
476
- assert api_call .node_index == 0
482
+ assert api_call .node_manager . node_index == 0
477
483
478
484
479
485
def test_raises_if_no_nodes_are_healthy_with_the_last_exception (
@@ -579,3 +585,19 @@ def test_uses_nearest_node_if_present_and_healthy( # noqa: WPS213
579
585
assert request_mocker .request_history [11 ].url == "http://nearest:8108/"
580
586
assert request_mocker .request_history [12 ].url == "http://nearest:8108/"
581
587
assert request_mocker .request_history [13 ].url == "http://nearest:8108/"
588
+
589
+
590
+ def test_max_retries_no_last_exception (api_call : ApiCall ) -> None :
591
+ """Test that it raises if the maximum number of retries is reached."""
592
+ with pytest .raises (
593
+ exceptions .TypesenseClientError ,
594
+ match = "All nodes are unhealthy" ,
595
+ ):
596
+ api_call ._execute_request (
597
+ requests .get ,
598
+ "/" ,
599
+ as_json = True ,
600
+ entity_type = typing .Dict [str , str ],
601
+ num_retries = 10 ,
602
+ last_exception = None ,
603
+ )
0 commit comments