@@ -48,6 +48,20 @@ def get_resource_does_not_exist_exception():
4848 return requests .exceptions .HTTPError (response = response )
4949
5050
51+ def get_temporarily_unavailable_exception ():
52+ response = requests .Response ()
53+ response .status_code = 503
54+ response ._content = ('{{"error_code": "{}"}}' .format (api .DbfsErrorCodes .TEMPORARILY_UNAVAILABLE )).encode () # NOQA
55+ return requests .exceptions .HTTPError (response = response )
56+
57+
58+ def get_partial_delete_exception (message = "[...] operation has deleted 10 files [...]" ):
59+ response = requests .Response ()
60+ response .status_code = 503
61+ response ._content = ('{{"error_code": "{}","message": "{}"}}' .format (api .DbfsErrorCodes .PARTIAL_DELETE , message )).encode () # NOQA
62+ return requests .exceptions .HTTPError (response = response )
63+
64+
5165class TestFileInfo (object ):
5266 def test_to_row_not_long_form_not_absolute (self ):
5367 file_info = api .FileInfo (TEST_DBFS_PATH , False , 1 )
@@ -165,3 +179,52 @@ def test_cat(self, dbfs_api):
165179 with mock .patch ('databricks_cli.dbfs.api.click' ) as click_mock :
166180 dbfs_api .cat ('dbfs:/whatever-doesnt-matter' )
167181 click_mock .echo .assert_called_with ('a' , nl = False )
182+
183+ def test_partial_delete (self , dbfs_api ):
184+ e_partial_delete = get_partial_delete_exception ()
185+ e_temporarily_unavailable = get_temporarily_unavailable_exception ()
186+ # Simulate partial deletes and 503 exceptions followed by a full successful delete
187+ exception_sequence = \
188+ [e_temporarily_unavailable , e_partial_delete , e_partial_delete ] + \
189+ [e_temporarily_unavailable ] * api .DELETE_MAX_CONSECUTIVE_503_RETRIES + \
190+ [e_partial_delete , None ]
191+ dbfs_api .client .delete = mock .Mock (side_effect = exception_sequence )
192+ dbfs_api .delete_retry_delay_millis = 1
193+ # Should succeed
194+ dbfs_api .delete (DbfsPath ('dbfs:/whatever-doesnt-matter' ), recursive = True )
195+
196+ def test_partial_delete_service_unavailable (self , dbfs_api ):
197+ e_partial_delete = get_partial_delete_exception ()
198+ e_temporarily_unavailable = get_temporarily_unavailable_exception ()
199+ # Simulate more than api.DELETE_MAX_CONSECUTIVE_503_ERRORS 503 errors that are not partial
200+ # deletes (error_code != PARTIAL_DELETE)
201+ exception_sequence = \
202+ [e_partial_delete ] + \
203+ [e_temporarily_unavailable ] * (api .DELETE_MAX_CONSECUTIVE_503_RETRIES + 1 ) + \
204+ [e_partial_delete , None ]
205+ dbfs_api .client .delete = mock .Mock (side_effect = exception_sequence )
206+ dbfs_api .delete_retry_delay_millis = 1
207+ with pytest .raises (e_temporarily_unavailable .__class__ ) as thrown :
208+ dbfs_api .delete (DbfsPath ('dbfs:/whatever-doesnt-matter' ), recursive = True )
209+ # Should raise the same e_temporarily_unavailable exception instance
210+ assert thrown .value == e_temporarily_unavailable
211+
212+ def test_partial_delete_exception_message_parse_error (self , dbfs_api ):
213+ message = "unexpected partial delete exception message"
214+ e_partial_delete = get_partial_delete_exception (message )
215+ dbfs_api .client .delete = mock .Mock (side_effect = [e_partial_delete , None ])
216+ dbfs_api .delete_retry_delay_millis = 1
217+ # Should succeed
218+ dbfs_api .delete (DbfsPath ('dbfs:/whatever-doesnt-matter' ), recursive = True )
219+
220+ def test_get_num_files_deleted (self ):
221+ e_partial_delete = get_partial_delete_exception ()
222+ # Should succeed
223+ api .DbfsApi .get_num_files_deleted (e_partial_delete )
224+
225+ def test_get_num_files_deleted_parse_error (self ):
226+ message = "unexpected partial delete exception message"
227+ e_partial_delete = get_partial_delete_exception (message )
228+ # Should raise api.ParseException
229+ with pytest .raises (api .ParseException ):
230+ api .DbfsApi .get_num_files_deleted (e_partial_delete )
0 commit comments