@@ -302,3 +302,89 @@ def test_check_connector(
302302 assert result == expect_success
303303 except AirbyteError as e :
304304 pytest .fail (f"API call failed: { e } " )
305+
306+
307+ @pytest .mark .parametrize (
308+ "bogus_api_root" ,
309+ [
310+ pytest .param (
311+ "https://bogus.invalid.example.com/api/v1" ,
312+ id = "completely_invalid_host" ,
313+ ),
314+ pytest .param (
315+ "https://httpbin.org/status/404" ,
316+ id = "httpbin_404_endpoint" ,
317+ ),
318+ ],
319+ )
320+ def test_bogus_api_root_error_includes_url_context (bogus_api_root : str ) -> None :
321+ """Test that API errors include the request URL in context for debugging.
322+
323+ This test validates that when an API call fails due to a bogus base URL,
324+ the error message includes the full request URL that was attempted. This
325+ helps debug URL construction issues like those seen with custom API roots.
326+
327+ Note: This test does not require credentials since it's testing error
328+ behavior with invalid URLs that will fail before authentication.
329+ """
330+ fake_bearer_token = SecretString ("fake-token-for-testing" )
331+ fake_workspace_id = "00000000-0000-0000-0000-000000000000"
332+
333+ with pytest .raises (Exception ) as exc_info :
334+ api_util .list_sources (
335+ workspace_id = fake_workspace_id ,
336+ api_root = bogus_api_root ,
337+ client_id = None ,
338+ client_secret = None ,
339+ bearer_token = fake_bearer_token ,
340+ )
341+
342+ error = exc_info .value
343+ error_str = str (error )
344+
345+ print (f"\n Bogus API root: { bogus_api_root } " )
346+ print (f"Error type: { type (error ).__name__ } " )
347+ print (f"Error message: { error_str } " )
348+
349+ if isinstance (error , AirbyteError ) and hasattr (error , "context" ):
350+ context = error .context or {}
351+ print (f"Error context: { context } " )
352+ if "request_url" in context :
353+ request_url = str (context ["request_url" ])
354+ print (f"Request URL from context: { request_url } " )
355+ host_from_bogus = bogus_api_root .split ("/" )[2 ]
356+ assert host_from_bogus in request_url , (
357+ f"Expected request_url to contain host '{ host_from_bogus } ' "
358+ f"from bogus_api_root '{ bogus_api_root } ', but got '{ request_url } '"
359+ )
360+
361+
362+ def test_url_construction_with_path_prefix () -> None :
363+ """Test that the SDK correctly preserves path prefixes in the base URL.
364+
365+ This test uses httpbin.org/anything which echoes back the request details,
366+ allowing us to verify the actual URL that was constructed and sent.
367+
368+ This test validates that when using a custom API root with a path prefix
369+ (like https://host/api/public/v1), the SDK correctly appends endpoints
370+ to that path rather than replacing it.
371+ """
372+ base_url_with_path = "https://httpbin.org/anything/api/public/v1"
373+ fake_bearer_token = SecretString ("fake-token-for-testing" )
374+ fake_workspace_id = "00000000-0000-0000-0000-000000000000"
375+
376+ result = api_util .list_sources (
377+ workspace_id = fake_workspace_id ,
378+ api_root = base_url_with_path ,
379+ client_id = None ,
380+ client_secret = None ,
381+ bearer_token = fake_bearer_token ,
382+ )
383+
384+ print (f"\n Base URL with path prefix: { base_url_with_path } " )
385+ print (f"Result type: { type (result )} " )
386+ print (f"Result: { result } " )
387+
388+ assert result == [], (
389+ f"Expected empty list from httpbin (no real sources), but got: { result } "
390+ )
0 commit comments