4
4
from optimizely .cmab .cmab_client import DefaultCmabClient , CmabRetryConfig
5
5
from requests .exceptions import RequestException
6
6
from optimizely .helpers .enums import Errors
7
+ from optimizely .exceptions import CmabFetchError , CmabInvalidResponseError
7
8
8
9
9
10
class TestDefaultCmabClient_do_fetch (unittest .TestCase ):
@@ -20,40 +21,52 @@ def test_do_fetch_success(self):
20
21
}
21
22
self .mock_http_client .post .return_value = mock_response
22
23
23
- result = self .client ._do_fetch ('http://fake-url' , {'some' : 'data' })
24
+ result = self .client ._do_fetch ('http://fake-url' , {'some' : 'data' }, 1.0 )
24
25
self .assertEqual (result , 'abc123' )
25
26
26
27
def test_do_fetch_http_exception (self ):
27
28
self .mock_http_client .post .side_effect = RequestException ('Connection error' )
28
- result = self .client ._do_fetch ('http://fake-url' , {'some' : 'data' })
29
- self .assertIsNone (result )
30
- self .mock_logger .exception .assert_called_with (Errors .CMAB_FETCH_FAILED .format ('Connection error' ))
29
+
30
+ with self .assertRaises (CmabFetchError ) as context :
31
+ self .client ._do_fetch ('http://fake-url' , {'some' : 'data' }, 1.0 )
32
+
33
+ self .mock_logger .error .assert_called_with (Errors .CMAB_FETCH_FAILED .format ('Connection error' ))
34
+ self .assertIn ('Connection error' , str (context .exception ))
31
35
32
36
def test_do_fetch_non_2xx_status (self ):
33
37
mock_response = MagicMock ()
34
38
mock_response .status_code = 500
35
39
self .mock_http_client .post .return_value = mock_response
36
- result = self .client ._do_fetch ('http://fake-url' , {'some' : 'data' })
37
- self .assertIsNone (result )
38
- self .mock_logger .exception .assert_called_with (Errors .CMAB_FETCH_FAILED .format (str (mock_response .status_code )))
40
+
41
+ with self .assertRaises (CmabFetchError ) as context :
42
+ self .client ._do_fetch ('http://fake-url' , {'some' : 'data' }, 1.0 )
43
+
44
+ self .mock_logger .error .assert_called_with (Errors .CMAB_FETCH_FAILED .format (str (mock_response .status_code )))
45
+ self .assertIn (str (mock_response .status_code ), str (context .exception ))
39
46
40
47
def test_do_fetch_invalid_json (self ):
41
48
mock_response = MagicMock ()
42
49
mock_response .status_code = 200
43
50
mock_response .json .side_effect = json .JSONDecodeError ("Expecting value" , "" , 0 )
44
51
self .mock_http_client .post .return_value = mock_response
45
- result = self .client ._do_fetch ('http://fake-url' , {'some' : 'data' })
46
- self .assertIsNone (result )
47
- self .mock_logger .exception .assert_called_with (Errors .INVALID_CMAB_FETCH_RESPONSE )
52
+
53
+ with self .assertRaises (CmabInvalidResponseError ) as context :
54
+ self .client ._do_fetch ('http://fake-url' , {'some' : 'data' }, 1.0 )
55
+
56
+ self .mock_logger .error .assert_called_with (Errors .INVALID_CMAB_FETCH_RESPONSE )
57
+ self .assertIn (Errors .INVALID_CMAB_FETCH_RESPONSE , str (context .exception ))
48
58
49
59
def test_do_fetch_invalid_response_structure (self ):
50
60
mock_response = MagicMock ()
51
61
mock_response .status_code = 200
52
62
mock_response .json .return_value = {'no_predictions' : []}
53
63
self .mock_http_client .post .return_value = mock_response
54
- result = self .client ._do_fetch ('http://fake-url' , {'some' : 'data' })
55
- self .assertIsNone (result )
56
- self .mock_logger .exception .assert_called_with (Errors .INVALID_CMAB_FETCH_RESPONSE )
64
+
65
+ with self .assertRaises (CmabInvalidResponseError ) as context :
66
+ self .client ._do_fetch ('http://fake-url' , {'some' : 'data' }, 1.0 )
67
+
68
+ self .mock_logger .error .assert_called_with (Errors .INVALID_CMAB_FETCH_RESPONSE )
69
+ self .assertIn (Errors .INVALID_CMAB_FETCH_RESPONSE , str (context .exception ))
57
70
58
71
59
72
class TestDefaultCmabClientWithRetry (unittest .TestCase ):
@@ -76,7 +89,7 @@ def test_do_fetch_with_retry_success_on_first_try(self, _):
76
89
}
77
90
self .mock_http_client .post .return_value = mock_response
78
91
79
- result = self .client ._do_fetch_with_retry ("http://fake-url" , {}, self .retry_config )
92
+ result = self .client ._do_fetch_with_retry ("http://fake-url" , {}, self .retry_config , 1.0 )
80
93
self .assertEqual (result , "abc123" )
81
94
self .assertEqual (self .mock_http_client .post .call_count , 1 )
82
95
@@ -97,7 +110,7 @@ def test_do_fetch_with_retry_success_on_retry(self, _):
97
110
success_response
98
111
]
99
112
100
- result = self .client ._do_fetch_with_retry ("http://fake-url" , {}, self .retry_config )
113
+ result = self .client ._do_fetch_with_retry ("http://fake-url" , {}, self .retry_config , 1.0 )
101
114
self .assertEqual (result , "xyz456" )
102
115
self .assertEqual (self .mock_http_client .post .call_count , 2 )
103
116
self .mock_logger .info .assert_called_with ("Retrying CMAB request (attempt: 1) after 0.01 seconds..." )
@@ -109,8 +122,9 @@ def test_do_fetch_with_retry_exhausts_all_attempts(self, _):
109
122
110
123
self .mock_http_client .post .return_value = failure_response
111
124
112
- result = self .client ._do_fetch_with_retry ("http://fake-url" , {}, self .retry_config )
113
- self .assertIsNone (result )
125
+ with self .assertRaises (CmabFetchError ):
126
+ self .client ._do_fetch_with_retry ("http://fake-url" , {}, self .retry_config , 1.0 )
127
+
114
128
self .assertEqual (self .mock_http_client .post .call_count , 3 ) # 1 original + 2 retries
115
129
self .mock_logger .error .assert_called_with (
116
130
Errors .CMAB_FETCH_FAILED .format ("Exhausted all retries for CMAB request." ))
@@ -124,7 +138,7 @@ def setUp(self):
124
138
self .client = DefaultCmabClient (
125
139
http_client = self .mock_http_client ,
126
140
logger = self .mock_logger ,
127
- retry_config = self . retry_config
141
+ retry_config = None
128
142
)
129
143
self .rule_id = 'test_rule'
130
144
self .user_id = 'user123'
@@ -150,12 +164,6 @@ def test_fetch_decision_success_with_retry(self, mock_do_fetch_with_retry):
150
164
151
165
@patch .object (DefaultCmabClient , '_do_fetch' , side_effect = RequestException ("Network error" ))
152
166
def test_fetch_decision_request_exception (self , mock_do_fetch ):
153
- result = self .client . fetch_decision ( self . rule_id , self . user_id , self . attributes , self . cmab_uuid )
154
- self .assertIsNone ( result )
167
+ with self .assertRaises ( CmabFetchError ):
168
+ self .client . fetch_decision ( self . rule_id , self . user_id , self . attributes , self . cmab_uuid )
155
169
self .mock_logger .error .assert_called_with (Errors .CMAB_FETCH_FAILED .format ("Network error" ))
156
-
157
- @patch .object (DefaultCmabClient , '_do_fetch' , return_value = None )
158
- def test_fetch_decision_invalid_response (self , mock_do_fetch ):
159
- result = self .client .fetch_decision (self .rule_id , self .user_id , self .attributes , self .cmab_uuid )
160
- self .assertIsNone (result )
161
- self .mock_logger .error .assert_called_once ()
0 commit comments