From e3ab68ab19b0009f9e82cc2825abbe9899e9c57f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:23:44 +0000 Subject: [PATCH] fix: raise AirbyteTracedException with config_error in SelectiveAuthenticator Replace ValueError with AirbyteTracedException in SelectiveAuthenticator for both missing config path and unrecognized authenticator key cases. - Sets failure_type=config_error (was system_error via generic fallback) - Provides clear user-facing message naming the specific config field - Includes detailed internal_message for debugging - Fixes the generic 'Something went wrong in the connector' fallback Co-Authored-By: bot_apk --- .../auth/selective_authenticator.py | 20 +++++--- .../auth/test_selective_authenticator.py | 46 +++++++++++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/airbyte_cdk/sources/declarative/auth/selective_authenticator.py b/airbyte_cdk/sources/declarative/auth/selective_authenticator.py index 3a84150bf..c7ed9a18c 100644 --- a/airbyte_cdk/sources/declarative/auth/selective_authenticator.py +++ b/airbyte_cdk/sources/declarative/auth/selective_authenticator.py @@ -7,7 +7,9 @@ import dpath +from airbyte_cdk.models import FailureType from airbyte_cdk.sources.declarative.auth.declarative_authenticator import DeclarativeAuthenticator +from airbyte_cdk.utils.traced_exception import AirbyteTracedException @dataclass @@ -27,6 +29,7 @@ def __new__( # type: ignore[misc] *arg: Any, **kwargs: Any, ) -> DeclarativeAuthenticator: + dotted_path = ".".join(authenticator_selection_path) try: selected_key = str( dpath.get( @@ -35,11 +38,16 @@ def __new__( # type: ignore[misc] ) ) except KeyError as err: - raise ValueError( - "The path from `authenticator_selection_path` is not found in the config." + raise AirbyteTracedException( + message=f'Required field "{dotted_path}" is missing from connector configuration.', + internal_message=f"SelectiveAuthenticator could not find the path {authenticator_selection_path} in the config. Available top-level config keys: {list(config.keys())}", + failure_type=FailureType.config_error, ) from err - try: - return authenticators[selected_key] - except KeyError as err: - raise ValueError(f"The authenticator `{selected_key}` is not found.") from err + if selected_key not in authenticators: + raise AirbyteTracedException( + message=f'Configuration field "{dotted_path}" contains unrecognized value "{selected_key}".', + internal_message=f'SelectiveAuthenticator received key "{selected_key}" but available authenticators are: {list(authenticators.keys())}', + failure_type=FailureType.config_error, + ) + return authenticators[selected_key] diff --git a/unit_tests/sources/declarative/auth/test_selective_authenticator.py b/unit_tests/sources/declarative/auth/test_selective_authenticator.py index 1e43f1e66..00ad4d44a 100644 --- a/unit_tests/sources/declarative/auth/test_selective_authenticator.py +++ b/unit_tests/sources/declarative/auth/test_selective_authenticator.py @@ -4,7 +4,9 @@ import pytest +from airbyte_cdk.models import FailureType from airbyte_cdk.sources.declarative.auth.selective_authenticator import SelectiveAuthenticator +from airbyte_cdk.utils.traced_exception import AirbyteTracedException def test_authenticator_selected(mocker): @@ -22,21 +24,59 @@ def test_selection_path_not_found(mocker): authenticators = {"one": mocker.Mock(), "two": mocker.Mock()} with pytest.raises( - ValueError, match="The path from `authenticator_selection_path` is not found in the config" - ): + AirbyteTracedException, + match='Required field "auth_type" is missing from connector configuration', + ) as exc_info: _ = SelectiveAuthenticator( config={"auth": {"type": "one"}}, authenticators=authenticators, authenticator_selection_path=["auth_type"], ) + assert exc_info.value.failure_type == FailureType.config_error + def test_selected_auth_not_found(mocker): authenticators = {"one": mocker.Mock(), "two": mocker.Mock()} - with pytest.raises(ValueError, match="The authenticator `unknown` is not found"): + with pytest.raises( + AirbyteTracedException, match='contains unrecognized value "unknown"' + ) as exc_info: _ = SelectiveAuthenticator( config={"auth": {"type": "unknown"}}, authenticators=authenticators, authenticator_selection_path=["auth", "type"], ) + + assert exc_info.value.failure_type == FailureType.config_error + + +def test_selection_path_not_found_includes_dotted_path(mocker): + authenticators = {"one": mocker.Mock(), "two": mocker.Mock()} + + with pytest.raises( + AirbyteTracedException, match='Required field "credentials.auth_type" is missing' + ) as exc_info: + _ = SelectiveAuthenticator( + config={"credentials": {}}, + authenticators=authenticators, + authenticator_selection_path=["credentials", "auth_type"], + ) + + assert exc_info.value.failure_type == FailureType.config_error + + +def test_selected_auth_not_found_lists_valid_options(mocker): + authenticators = {"Client": mocker.Mock(), "Service": mocker.Mock()} + + with pytest.raises(AirbyteTracedException) as exc_info: + _ = SelectiveAuthenticator( + config={"credentials": {"auth_type": "BadValue"}}, + authenticators=authenticators, + authenticator_selection_path=["credentials", "auth_type"], + ) + + assert exc_info.value.failure_type == FailureType.config_error + assert "BadValue" in str(exc_info.value.message) + assert "Client" in str(exc_info.value.internal_message) + assert "Service" in str(exc_info.value.internal_message)