diff --git a/airbyte_cdk/models/airbyte_protocol.py b/airbyte_cdk/models/airbyte_protocol.py index 8702b83701..ec48e1ad53 100644 --- a/airbyte_cdk/models/airbyte_protocol.py +++ b/airbyte_cdk/models/airbyte_protocol.py @@ -89,6 +89,8 @@ class OauthConnectorInputSpecification: scope: Optional[str] = None scopes: Optional[List[Dict[str, Any]]] = None optional_scopes: Optional[List[Dict[str, Any]]] = None + # Stored as str (not ScopesJoinStrategy enum) because spec.py converts the enum + # to its .value before serialization. The protocol layer only sees plain strings. scopes_join_strategy: Optional[str] = None access_token_headers: Optional[Dict[str, Any]] = None access_token_params: Optional[Dict[str, Any]] = None diff --git a/unit_tests/sources/declarative/spec/test_spec.py b/unit_tests/sources/declarative/spec/test_spec.py index 13c0c1c07a..6516c146d1 100644 --- a/unit_tests/sources/declarative/spec/test_spec.py +++ b/unit_tests/sources/declarative/spec/test_spec.py @@ -2,6 +2,8 @@ # Copyright (c) 2025 Airbyte, Inc., all rights reserved. # +import dataclasses + import pytest from airbyte_cdk.models import ( @@ -271,3 +273,45 @@ def test_given_invalid_config_value_when_validating_then_exception_is_raised() - with pytest.raises(Exception): spec.validate_config(input_config) + + +@pytest.mark.parametrize( + "upstream_class_name, override_class_name", + [ + ("OauthConnectorInputSpecification", "OauthConnectorInputSpecification"), + ("OAuthConfigSpecification", "OAuthConfigSpecification"), + ("AdvancedAuth", "AdvancedAuth"), + ("ConnectorSpecification", "ConnectorSpecification"), + ], + ids=[ + "OauthConnectorInputSpecification", + "OAuthConfigSpecification", + "AdvancedAuth", + "ConnectorSpecification", + ], +) +def test_protocol_override_fields_in_sync( + upstream_class_name: str, override_class_name: str +) -> None: + """Ensure protocol override dataclasses stay compatible with their upstream counterparts. + + The airbyte_protocol.py file redeclares several dataclasses to add fields that the + upstream airbyte_protocol_dataclasses package does not yet include (e.g. scopes, + optional_scopes, scopes_join_strategy). If the upstream package adds new fields, this + test will fail to remind us to update the local overrides. + """ + import airbyte_protocol_dataclasses.models as upstream_models + + import airbyte_cdk.models.airbyte_protocol as override_models + + upstream_cls = getattr(upstream_models, upstream_class_name) + override_cls = getattr(override_models, override_class_name) + + upstream_fields = {f.name for f in dataclasses.fields(upstream_cls)} + override_fields = {f.name for f in dataclasses.fields(override_cls)} + + missing = upstream_fields - override_fields + assert not missing, ( + f"Upstream protocol added fields {missing} to {upstream_class_name} " + f"that are missing from the airbyte_protocol.py override. Update the override to match." + )