@@ -166,3 +166,92 @@ def test_given_both_from_exception_and_as_sanitized_airbyte_message_with_stream_
166166 )
167167 message = traced_exc .as_sanitized_airbyte_message (stream_descriptor = _ANOTHER_STREAM_DESCRIPTOR )
168168 assert message .trace .error .stream_descriptor == _A_STREAM_DESCRIPTOR
169+
170+
171+ class TestAirbyteTracedExceptionStr :
172+ """Tests proving that __str__ returns user-facing message instead of internal_message."""
173+
174+ def test_str_returns_user_facing_message_when_both_set (self ) -> None :
175+ exc = AirbyteTracedException (
176+ internal_message = "raw API error: 401 Unauthorized" ,
177+ message = "Authentication credentials are invalid." ,
178+ )
179+ assert str (exc ) == "Authentication credentials are invalid."
180+
181+ def test_str_falls_back_to_internal_message_when_message_is_none (self ) -> None :
182+ exc = AirbyteTracedException (internal_message = "an internal error" )
183+ assert str (exc ) == "an internal error"
184+
185+ def test_str_returns_empty_string_when_both_none (self ) -> None :
186+ exc = AirbyteTracedException ()
187+ assert str (exc ) == ""
188+
189+ def test_str_returns_message_when_internal_message_is_none (self ) -> None :
190+ exc = AirbyteTracedException (message = "A user-friendly error occurred." )
191+ assert str (exc ) == "A user-friendly error occurred."
192+
193+ def test_str_used_in_fstring_returns_user_facing_message (self ) -> None :
194+ exc = AirbyteTracedException (
195+ internal_message = "internal detail" ,
196+ message = "Connection timed out." ,
197+ )
198+ assert f"Error: { exc } " == "Error: Connection timed out."
199+
200+ def test_str_used_in_logging_format_returns_user_facing_message (self ) -> None :
201+ exc = AirbyteTracedException (
202+ internal_message = "socket.timeout: read timed out" ,
203+ message = "Request timed out." ,
204+ )
205+ assert "Error: %s" % exc == "Error: Request timed out."
206+
207+ def test_args_still_contains_internal_message (self ) -> None :
208+ """Verify args[0] is still internal_message for traceback formatting."""
209+ exc = AirbyteTracedException (
210+ internal_message = "internal detail" ,
211+ message = "user-facing message" ,
212+ )
213+ assert exc .args [0 ] == "internal detail"
214+
215+ def test_str_on_subclass_inherits_behavior (self ) -> None :
216+ """Verify subclasses inherit the __str__ override without needing their own."""
217+
218+ class CustomTracedException (AirbyteTracedException ):
219+ pass
220+
221+ exc = CustomTracedException (
222+ internal_message = "raw error" ,
223+ message = "User-friendly error." ,
224+ )
225+ assert str (exc ) == "User-friendly error."
226+
227+ def test_str_with_from_exception_factory (self ) -> None :
228+ original = ValueError ("original error" )
229+ exc = AirbyteTracedException .from_exception (
230+ original , message = "A validation error occurred."
231+ )
232+ assert str (exc ) == "A validation error occurred."
233+ assert exc .internal_message == "original error"
234+
235+ def test_str_with_from_exception_without_message (self ) -> None :
236+ original = RuntimeError ("runtime failure" )
237+ exc = AirbyteTracedException .from_exception (original )
238+ assert str (exc ) == "runtime failure"
239+
240+ def test_stack_trace_uses_str_representation (self ) -> None :
241+ """Verify traceback one-liner uses __str__ (user-facing message)."""
242+ exc = AirbyteTracedException (
243+ internal_message = "internal detail for traceback" ,
244+ message = "User sees this." ,
245+ )
246+ airbyte_message = exc .as_airbyte_message ()
247+ assert "User sees this." in airbyte_message .trace .error .stack_trace
248+
249+ def test_internal_message_preserved_in_trace_error (self ) -> None :
250+ """Verify internal_message is still available in the trace error for debugging."""
251+ exc = AirbyteTracedException (
252+ internal_message = "raw API error: 401" ,
253+ message = "Authentication failed." ,
254+ )
255+ airbyte_message = exc .as_airbyte_message ()
256+ assert airbyte_message .trace .error .internal_message == "raw API error: 401"
257+ assert airbyte_message .trace .error .message == "Authentication failed."
0 commit comments