From f67f058480077d85f66b2b90b364652d0e213555 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:42:01 +0000 Subject: [PATCH 1/3] fix(cloud): validate connection_id is a UUID in get_connection A malformed connection ID previously surfaced as an opaque HTTP 500 from the Cloud API. CloudWorkspace.get_connection now raises AirbyteLibInputError before any request is made. Co-Authored-By: AJ Steers --- airbyte/cloud/workspaces.py | 11 +++++++++++ tests/unit_tests/test_cloud_connections.py | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/airbyte/cloud/workspaces.py b/airbyte/cloud/workspaces.py index 202e9ec12..31c1d2caf 100644 --- a/airbyte/cloud/workspaces.py +++ b/airbyte/cloud/workspaces.py @@ -36,6 +36,7 @@ from __future__ import annotations import contextlib +import uuid from dataclasses import dataclass, field from functools import cached_property from pathlib import Path @@ -484,7 +485,17 @@ def get_connection( This method does not fetch data from the API. It returns a `CloudConnection` object, which will be loaded lazily as needed. + + Raises: + AirbyteLibInputError: If `connection_id` is not a valid UUID. """ + try: + uuid.UUID(connection_id) + except ValueError: + raise exc.AirbyteLibInputError( + message="`connection_id` must be a UUID.", + context={"connection_id": connection_id}, + ) from None return CloudConnection( workspace=self, connection_id=connection_id, diff --git a/tests/unit_tests/test_cloud_connections.py b/tests/unit_tests/test_cloud_connections.py index e68d175d8..c609b5ec6 100644 --- a/tests/unit_tests/test_cloud_connections.py +++ b/tests/unit_tests/test_cloud_connections.py @@ -73,6 +73,24 @@ def _connection() -> CloudConnection: return CloudConnection(workspace=workspace, connection_id="connection-id") +def test_get_connection_validates_connection_id_uuid() -> None: + """`get_connection` rejects malformed IDs before any API call.""" + workspace = CloudWorkspace( + workspace_id="workspace-id", + bearer_token="token", + api_root="https://api.airbyte.com/v1", + ) + + with pytest.raises(AirbyteLibInputError) as exc_info: + workspace.get_connection("not-a-uuid") + + assert "`connection_id` must be a UUID." in str(exc_info.value) + + connection_id = "12345678-1234-1234-1234-1234567890ab" + connection = workspace.get_connection(connection_id) + assert connection.connection_id == connection_id + + def _scheduled_connection_response(*, basic: bool = False) -> models.ConnectionResponse: """Create a response with a real public API schedule model.""" return models.ConnectionResponse( From 5136fe00d2fd7f5e2329b94e23541fa07473a920 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 21:59:19 +0000 Subject: [PATCH 2/3] test(mcp): use a real UUID for connection_id in connection schedule tests Co-Authored-By: AJ Steers --- tests/unit_tests/test_mcp_connection_schedules.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests/test_mcp_connection_schedules.py b/tests/unit_tests/test_mcp_connection_schedules.py index d1849040e..f52b0d37d 100644 --- a/tests/unit_tests/test_mcp_connection_schedules.py +++ b/tests/unit_tests/test_mcp_connection_schedules.py @@ -30,7 +30,7 @@ def _connection_response() -> models.ConnectionResponse: """Create a complete SDK response for the network boundary.""" return models.ConnectionResponse( - connection_id="connection-id", + connection_id="12345678-1234-1234-1234-1234567890ab", created_at=0, destination_id="destination-id", name="name", @@ -70,7 +70,7 @@ def schedule_backend( with responses.RequestsMock(assert_all_requests_are_fired=False) as http: http.post( "https://config.example/custom/web_backend/connections/update", - json={"connectionId": "connection-id", "scheduleType": "basic"}, + json={"connectionId": "12345678-1234-1234-1234-1234567890ab", "scheduleType": "basic"}, ) http.post("https://api.segment.io/v1/batch", json={"success": True}) if request.param: @@ -93,7 +93,7 @@ async def _update(server: FastMCP, **arguments: object) -> str: """Call the registered tool through the serialized MCP boundary.""" async with Client(server) as client: result = await client.call_tool( - "update_cloud_connection", {"connection_id": "connection-id", **arguments} + "update_cloud_connection", {"connection_id": "12345678-1234-1234-1234-1234567890ab", **arguments} ) return str(result) @@ -150,7 +150,7 @@ def test_direct_update_rejects_invalid_intervals_before_status_change( with pytest.raises(AirbyteLibInputError, match="positive whole number"): cloud_mcp.update_cloud_connection( ctx=None, - connection_id="connection-id", + connection_id="12345678-1234-1234-1234-1234567890ab", enabled=True, interval_hours=interval_hours, cron_expression=None, @@ -189,7 +189,7 @@ def test_update_interval_schedule_serialized( "https://config.example/custom/web_backend/connections/update" ) assert json.loads(calls[0].request.body) == { - "connectionId": "connection-id", + "connectionId": "12345678-1234-1234-1234-1234567890ab", "scheduleType": "basic", "scheduleData": {"basicSchedule": {"timeUnit": "hours", "units": 24}}, "skipReset": True, From bc987856f48f5a239b679516c293a2cf63f39e77 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:02:08 +0000 Subject: [PATCH 3/3] style(mcp): apply ruff format to connection schedule tests Co-Authored-By: AJ Steers --- tests/unit_tests/test_mcp_connection_schedules.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_mcp_connection_schedules.py b/tests/unit_tests/test_mcp_connection_schedules.py index f52b0d37d..c8f0d2bfa 100644 --- a/tests/unit_tests/test_mcp_connection_schedules.py +++ b/tests/unit_tests/test_mcp_connection_schedules.py @@ -70,7 +70,10 @@ def schedule_backend( with responses.RequestsMock(assert_all_requests_are_fired=False) as http: http.post( "https://config.example/custom/web_backend/connections/update", - json={"connectionId": "12345678-1234-1234-1234-1234567890ab", "scheduleType": "basic"}, + json={ + "connectionId": "12345678-1234-1234-1234-1234567890ab", + "scheduleType": "basic", + }, ) http.post("https://api.segment.io/v1/batch", json={"success": True}) if request.param: @@ -93,7 +96,8 @@ async def _update(server: FastMCP, **arguments: object) -> str: """Call the registered tool through the serialized MCP boundary.""" async with Client(server) as client: result = await client.call_tool( - "update_cloud_connection", {"connection_id": "12345678-1234-1234-1234-1234567890ab", **arguments} + "update_cloud_connection", + {"connection_id": "12345678-1234-1234-1234-1234567890ab", **arguments}, ) return str(result)