22from unittest import TestCase
33from unittest .mock import Mock , patch
44
5- from harmony_service_lib .exceptions import ForbiddenException , ServerException
5+ from harmony_service_lib .exceptions import (
6+ ForbiddenException ,
7+ HarmonyException ,
8+ NoRetryException ,
9+ ServerException ,
10+ )
611from harmony_service_lib .util import config
712
8- from hoss .exceptions import UrlAccessFailed
13+ from hoss .exceptions import (
14+ CustomError ,
15+ CustomNoRetryError ,
16+ UrlAccessFailed ,
17+ UrlAccessForbidden ,
18+ )
919from hoss .harmony_log_context import set_logger
1020from hoss .utilities import (
1121 download_url ,
1626 get_opendap_nc4 ,
1727 get_value_or_default ,
1828 move_downloaded_nc4 ,
29+ raise_from_hoss_exception ,
1930 unexecuted_url_requested ,
2031)
2132
@@ -99,12 +110,15 @@ def test_download_url(self, mock_util_download):
99110 )
100111 mock_util_download .reset_mock ()
101112
102- with self .subTest ('500 error is caught and handled.' ):
113+ with self .subTest ('500 error is caught and handled, and is retryable .' ):
103114 mock_util_download .side_effect = [self .harmony_500_error , http_response ]
104115
105- with self .assertRaises (UrlAccessFailed ):
116+ with self .assertRaises (UrlAccessFailed ) as context :
106117 download_url (test_url , output_directory , access_token , self .config )
107118
119+ self .assertIsInstance (context .exception , CustomError )
120+ self .assertNotIsInstance (context .exception , CustomNoRetryError )
121+
108122 mock_util_download .assert_called_once_with (
109123 test_url ,
110124 output_directory ,
@@ -115,12 +129,36 @@ def test_download_url(self, mock_util_download):
115129 )
116130 mock_util_download .reset_mock ()
117131
118- with self .subTest ('Non-500 error does not retry, and is re-raised .' ):
132+ with self .subTest ('A 403 error (forbidden) is not retried .' ):
119133 mock_util_download .side_effect = [self .harmony_auth_error , http_response ]
120134
121- with self .assertRaises (UrlAccessFailed ):
135+ with self .assertRaises (UrlAccessForbidden ) as context :
136+ download_url (test_url , output_directory , access_token , self .config )
137+
138+ self .assertIsInstance (context .exception , CustomNoRetryError )
139+
140+ mock_util_download .assert_called_once_with (
141+ test_url ,
142+ output_directory ,
143+ self .logger ,
144+ access_token = access_token ,
145+ data = None ,
146+ cfg = self .config ,
147+ )
148+ mock_util_download .reset_mock ()
149+
150+ with self .subTest ('Unknown/transient error is caught and is retryable.' ):
151+ mock_util_download .side_effect = [
152+ Exception ('something broke' ),
153+ http_response ,
154+ ]
155+
156+ with self .assertRaises (UrlAccessFailed ) as context :
122157 download_url (test_url , output_directory , access_token , self .config )
123158
159+ self .assertIsInstance (context .exception , CustomError )
160+ self .assertNotIsInstance (context .exception , CustomNoRetryError )
161+
124162 mock_util_download .assert_called_once_with (
125163 test_url ,
126164 output_directory ,
@@ -335,3 +373,23 @@ def test_unexecuted_url_requested(self):
335373
336374 with self .subTest ('Format type is None' ):
337375 self .assertFalse (unexecuted_url_requested (None ))
376+
377+ def test_raise_from_hoss_exception (self ):
378+ """Ensure that HOSS exceptions are correctly converted to Harmony
379+ exceptions. CustomNoRetryError subclasses should become
380+ NoRetryException, while CustomError subclasses should become a
381+ retryable HarmonyException.
382+
383+ """
384+ test_url = 'fake_website.com'
385+
386+ with self .subTest ('UrlAccessForbidden (no-retry) raises NoRetryException.' ):
387+ forbidden_exception = UrlAccessForbidden (test_url , 403 )
388+ with self .assertRaises (NoRetryException ):
389+ raise_from_hoss_exception (forbidden_exception )
390+
391+ with self .subTest ('UrlAccessFailed (retryable) raises HarmonyException.' ):
392+ failed_exception = UrlAccessFailed (test_url , 500 )
393+ with self .assertRaises (HarmonyException ) as context :
394+ raise_from_hoss_exception (failed_exception )
395+ self .assertNotIsInstance (context .exception , NoRetryException )
0 commit comments