@@ -120,6 +120,47 @@ def test_nlu_api_failure_raises_exception_for_celery_retry(self):
120120 )
121121 )
122122
123+ def test_turn_api_failure_raises_exception_for_retry (self ):
124+ """
125+ Tests that if NLU succeeds but the Turn API fails,
126+ a RequestException is rraised to trigger a retry.
127+ """
128+ nlu_success_mock = mock .Mock (status_code = 200 )
129+ nlu_success_mock .json .return_value = {
130+ "intent" : self .expected_intent ,
131+ "model_version" : "2025-09-29-v1" ,
132+ "parent_label" : "FEEDBACK" ,
133+ "probability" : 0.3805 ,
134+ "review_status" : "NEEDS_REVIEW" ,
135+ }
136+ nlu_success_mock .raise_for_status .return_value = None
137+
138+ turn_fail_mock = mock .Mock (status_code = 500 )
139+ error = requests .exceptions .HTTPError ("500 Server Error" )
140+ turn_fail_mock .raise_for_status .side_effect = error
141+
142+ with (
143+ mock .patch ("requests.get" ) as mock_get ,
144+ mock .patch ("requests.post" ) as mock_post ,
145+ self .assertLogs (
146+ "nluclassifier.tasks" , level = "WARNING" ) as log_context ,
147+ ):
148+ mock_get .return_value = nlu_success_mock
149+ mock_post .return_value = turn_fail_mock
150+
151+ with self .assertRaises (requests .exceptions .HTTPError ):
152+ process_feedback_for_labeling (
153+ self .message_id , self .inbound_message )
154+
155+ self .assertEqual (mock_get .call_count , 1 )
156+
157+ self .assertEqual (mock_post .call_count , 1 )
158+
159+ self .assertTrue (
160+ any ("Turn API failed to label message" in output
161+ for output in log_context .output )
162+ )
163+
123164 def test_no_label_applied_on_unhandled_intent (self ):
124165 """
125166 Tests that if NLU returns an intent that is not
0 commit comments