1- import json
21import unittest
32from unittest .mock import Mock
43
54from tests import IntegrationTestCase
65from tests .holodeck import Request
7- from twilio .base .api_v1_version import ApiV1Version
8- from twilio .base .exceptions import TwilioServiceException
6+ from twilio .base .version import Version
7+ from twilio .base .exceptions import TwilioRestException , TwilioServiceException
98from twilio .base .page import Page
109from twilio .http .response import Response
1110
@@ -127,11 +126,11 @@ def test_delete_not_found(self):
127126 self .assertIn ("Unable to delete record" , str (context .exception ))
128127
129128
130- class ExceptionTestCase (unittest .TestCase ):
131- """Test cases for ApiV1Version .exception() method"""
129+ class VersionExceptionTestCase (unittest .TestCase ):
130+ """Test cases for base Version .exception() method with RFC-9457 auto-detection """
132131
133- def test_exception_rfc9457_compliant (self ):
134- """Test that RFC-9457 compliant errors create TwilioServiceException"""
132+ def test_exception_rfc9457_auto_detection (self ):
133+ """Test that base Version auto-detects RFC-9457 errors and creates TwilioServiceException"""
135134 response = Mock (spec = Response )
136135 response .status_code = 400
137136 response .text = """{
@@ -143,9 +142,8 @@ def test_exception_rfc9457_compliant(self):
143142 "instance": "/api/v1/accounts/AC123/calls/CA456"
144143 }"""
145144
146- error_payload = json .loads (response .text )
147- exception = ApiV1Version .exception (
148- method = "POST" , uri = "/test" , response = response , error_payload = error_payload
145+ exception = Version .exception (
146+ method = "POST" , uri = "/test" , response = response , message = "Test error"
149147 )
150148
151149 self .assertIsInstance (exception , TwilioServiceException )
@@ -158,8 +156,26 @@ def test_exception_rfc9457_compliant(self):
158156 self .assertEqual (exception .method , "POST" )
159157 self .assertEqual (exception .uri , "/test" )
160158
161- def test_exception_rfc9457_with_validation_errors (self ):
162- """Test RFC-9457 error with validation errors array"""
159+ def test_exception_legacy_format_fallback (self ):
160+ """Test that base Version falls back to TwilioRestException for legacy errors"""
161+ response = Mock (spec = Response )
162+ response .status_code = 400
163+ response .text = """{
164+ "message": "Invalid phone number",
165+ "code": 21211
166+ }"""
167+
168+ exception = Version .exception (
169+ method = "POST" , uri = "/test" , response = response , message = "Test error"
170+ )
171+
172+ self .assertIsInstance (exception , TwilioRestException )
173+ self .assertEqual (exception .status , 400 )
174+ self .assertEqual (exception .code , 21211 )
175+ self .assertIn ("Invalid phone number" , exception .msg )
176+
177+ def test_exception_rfc9457_with_validation_errors_base_version (self ):
178+ """Test base Version handles RFC-9457 with validation errors array"""
163179 response = Mock (spec = Response )
164180 response .status_code = 422
165181 response .text = """{
@@ -174,182 +190,25 @@ def test_exception_rfc9457_with_validation_errors(self):
174190 ]
175191 }"""
176192
177- error_payload = json .loads (response .text )
178- exception = ApiV1Version .exception (
179- method = "POST" , uri = "/test" , response = response , error_payload = error_payload
193+ exception = Version .exception (
194+ method = "POST" , uri = "/test" , response = response , message = "Validation error"
180195 )
181196
182197 self .assertIsInstance (exception , TwilioServiceException )
183198 self .assertEqual (exception .title , "Validation failed" )
184199 self .assertEqual (len (exception .errors ), 2 )
185200 self .assertEqual (exception .errors [0 ]["detail" ], "must be a positive integer" )
186201 self .assertEqual (exception .errors [0 ]["pointer" ], "#/age" )
187- self .assertEqual (
188- exception .errors [1 ]["detail" ], "must be 'green', 'red' or 'blue'"
189- )
190- self .assertEqual (exception .errors [1 ]["pointer" ], "#/profile/color" )
191202
192- def test_exception_rfc9457_minimal (self ):
193- """Test RFC-9457 with only required fields """
203+ def test_exception_malformed_json_fallback (self ):
204+ """Test that base Version handles malformed JSON gracefully """
194205 response = Mock (spec = Response )
195- response .status_code = 404
196- response .text = """{
197- "type": "https://www.twilio.com/docs/api/errors/20404",
198- "title": "Resource not found",
199- "status": 404,
200- "code": 20404
201- }"""
202-
203- error_payload = json .loads (response .text )
204- exception = ApiV1Version .exception (
205- method = "GET" , uri = "/test" , response = response , error_payload = error_payload
206- )
207-
208- self .assertIsInstance (exception , TwilioServiceException )
209- self .assertEqual (exception .title , "Resource not found" )
210- self .assertIsNone (exception .detail )
211- self .assertIsNone (exception .instance )
212- self .assertEqual (exception .errors , [])
213-
214- def test_exception_with_error_payload_parameter (self ):
215- """Test passing error_payload directly to exception method"""
216- response = Mock (spec = Response )
217- response .status_code = 400
218- response .text = "This should be ignored"
219-
220- error_payload = {
221- "type" : "https://www.twilio.com/docs/api/errors/20001" ,
222- "title" : "Invalid parameter" ,
223- "status" : 400 ,
224- "code" : 20001 ,
225- "detail" : "Pre-parsed error payload" ,
226- }
206+ response .status_code = 500
207+ response .text = "This is not JSON"
227208
228- exception = ApiV1Version .exception (
229- method = "POST " , uri = "/test" , response = response , error_payload = error_payload
209+ exception = Version .exception (
210+ method = "GET " , uri = "/test" , response = response , message = "Server error"
230211 )
231212
232- self .assertIsInstance (exception , TwilioServiceException )
233- self .assertEqual (exception .title , "Invalid parameter" )
234- self .assertEqual (exception .detail , "Pre-parsed error payload" )
235-
236-
237- class ApiV1VersionParseTestCase (unittest .TestCase ):
238- """Test cases for ApiV1Version._parse_* methods with RFC-9457 error handling"""
239-
240- def test_parse_fetch_with_rfc9457_error (self ):
241- """Test that _parse_fetch raises TwilioServiceException for RFC-9457 errors"""
242- # Create a mock domain
243- domain = Mock ()
244- service_version = ApiV1Version (domain , "v1" )
245-
246- response = Mock (spec = Response )
247- response .status_code = 404
248- response .text = """{
249- "type": "https://www.twilio.com/docs/api/errors/20404",
250- "title": "Resource not found",
251- "status": 404,
252- "code": 20404,
253- "detail": "The requested resource does not exist"
254- }"""
255-
256- with self .assertRaises (TwilioServiceException ) as context :
257- service_version ._parse_fetch ("GET" , "/test" , response )
258-
259- exception = context .exception
260- self .assertEqual (exception .title , "Resource not found" )
261- self .assertEqual (exception .detail , "The requested resource does not exist" )
262- self .assertEqual (exception .status , 404 )
263-
264- def test_parse_update_with_rfc9457_error (self ):
265- """Test that _parse_update raises TwilioServiceException for RFC-9457 errors"""
266- domain = Mock ()
267- service_version = ApiV1Version (domain , "v1" )
268-
269- response = Mock (spec = Response )
270- response .status_code = 400
271- response .text = """{
272- "type": "https://www.twilio.com/docs/api/errors/20001",
273- "title": "Invalid parameter",
274- "status": 400,
275- "code": 20001
276- }"""
277-
278- with self .assertRaises (TwilioServiceException ) as context :
279- service_version ._parse_update ("PUT" , "/test" , response )
280-
281- exception = context .exception
282- self .assertEqual (exception .title , "Invalid parameter" )
283-
284- def test_parse_delete_with_rfc9457_error (self ):
285- """Test that _parse_delete raises TwilioServiceException for RFC-9457 errors"""
286- domain = Mock ()
287- service_version = ApiV1Version (domain , "v1" )
288-
289- response = Mock (spec = Response )
290- response .status_code = 403
291- response .text = """{
292- "type": "https://www.twilio.com/docs/api/errors/20403",
293- "title": "Forbidden",
294- "status": 403,
295- "code": 20403,
296- "detail": "You do not have permission to delete this resource"
297- }"""
298-
299- with self .assertRaises (TwilioServiceException ) as context :
300- service_version ._parse_delete ("DELETE" , "/test" , response )
301-
302- exception = context .exception
303- self .assertEqual (exception .title , "Forbidden" )
304- self .assertEqual (
305- exception .detail , "You do not have permission to delete this resource"
306- )
307-
308- def test_parse_create_with_rfc9457_error (self ):
309- """Test that _parse_create raises TwilioServiceException for RFC-9457 errors"""
310- domain = Mock ()
311- service_version = ApiV1Version (domain , "v1" )
312-
313- response = Mock (spec = Response )
314- response .status_code = 422
315- response .text = """{
316- "type": "https://www.twilio.com/docs/api/errors/20001",
317- "title": "Validation failed",
318- "status": 422,
319- "code": 20001,
320- "errors": [
321- {"detail": "must be a valid phone number", "pointer": "#/to"}
322- ]
323- }"""
324-
325- with self .assertRaises (TwilioServiceException ) as context :
326- service_version ._parse_create ("POST" , "/test" , response )
327-
328- exception = context .exception
329- self .assertEqual (exception .title , "Validation failed" )
330- self .assertEqual (len (exception .errors ), 1 )
331- self .assertEqual (exception .errors [0 ]["detail" ], "must be a valid phone number" )
332-
333- def test_parse_fetch_success (self ):
334- """Test that _parse_fetch returns data on success"""
335- domain = Mock ()
336- service_version = ApiV1Version (domain , "v1" )
337-
338- response = Mock (spec = Response )
339- response .status_code = 200
340- response .text = '{"data": "success"}'
341-
342- result = service_version ._parse_fetch ("GET" , "/test" , response )
343- self .assertEqual (result , {"data" : "success" })
344-
345- def test_parse_delete_success (self ):
346- """Test that _parse_delete returns True on success"""
347- domain = Mock ()
348- service_version = ApiV1Version (domain , "v1" )
349-
350- response = Mock (spec = Response )
351- response .status_code = 204
352- response .text = ""
353-
354- result = service_version ._parse_delete ("DELETE" , "/test" , response )
355- self .assertTrue (result )
213+ self .assertIsInstance (exception , TwilioRestException )
214+ self .assertEqual (exception .status , 500 )
0 commit comments