From b8a86bf6dd9c0a26a5267d0059ad5f3577db7791 Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 16:17:45 -0800 Subject: [PATCH 1/8] cp dines --- src/runloop_api_client/sdk/__init__.py | 8 ++ src/runloop_api_client/sdk/_types.py | 15 +++ src/runloop_api_client/sdk/async_.py | 91 +++++++++++++++ .../sdk/async_gateway_config.py | 108 ++++++++++++++++++ src/runloop_api_client/sdk/gateway_config.py | 108 ++++++++++++++++++ src/runloop_api_client/sdk/sync.py | 91 +++++++++++++++ 6 files changed, 421 insertions(+) create mode 100644 src/runloop_api_client/sdk/async_gateway_config.py create mode 100644 src/runloop_api_client/sdk/gateway_config.py diff --git a/src/runloop_api_client/sdk/__init__.py b/src/runloop_api_client/sdk/__init__.py index 15f18f8b2..70ba60865 100644 --- a/src/runloop_api_client/sdk/__init__.py +++ b/src/runloop_api_client/sdk/__init__.py @@ -15,6 +15,7 @@ BenchmarkOps, BlueprintOps, NetworkPolicyOps, + GatewayConfigOps, StorageObjectOps, ) from .agent import Agent @@ -29,6 +30,7 @@ AsyncBenchmarkOps, AsyncBlueprintOps, AsyncNetworkPolicyOps, + AsyncGatewayConfigOps, AsyncStorageObjectOps, ) from .devbox import Devbox, NamedShell @@ -46,6 +48,7 @@ from .async_scenario import AsyncScenario from .async_snapshot import AsyncSnapshot from .network_policy import NetworkPolicy +from .gateway_config import GatewayConfig from .storage_object import StorageObject from .async_benchmark import AsyncBenchmark from .async_blueprint import AsyncBlueprint @@ -55,6 +58,7 @@ from .async_scenario_run import AsyncScenarioRun from .async_benchmark_run import AsyncBenchmarkRun from .async_network_policy import AsyncNetworkPolicy +from .async_gateway_config import AsyncGatewayConfig from .async_storage_object import AsyncStorageObject from .async_execution_result import AsyncExecutionResult from .async_scenario_builder import AsyncScenarioBuilder @@ -82,6 +86,8 @@ "AsyncStorageObjectOps", "NetworkPolicyOps", "AsyncNetworkPolicyOps", + "GatewayConfigOps", + "AsyncGatewayConfigOps", # Resource classes "Agent", "AsyncAgent", @@ -112,6 +118,8 @@ "AsyncStorageObject", "NetworkPolicy", "AsyncNetworkPolicy", + "GatewayConfig", + "AsyncGatewayConfig", "NamedShell", "AsyncNamedShell", ] diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index b7048a536..5838e32a0 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -20,6 +20,7 @@ BlueprintCreateParams, DevboxUploadFileParams, NetworkPolicyListParams, + GatewayConfigListParams, DevboxCreateTunnelParams, DevboxDownloadFileParams, DevboxEnableTunnelParams, @@ -27,6 +28,8 @@ DevboxSnapshotDiskParams, NetworkPolicyCreateParams, NetworkPolicyUpdateParams, + GatewayConfigCreateParams, + GatewayConfigUpdateParams, DevboxReadFileContentsParams, DevboxWriteFileContentsParams, BenchmarkRunListScenarioRunsParams, @@ -262,3 +265,15 @@ class SDKNetworkPolicyListParams(NetworkPolicyListParams, BaseRequestOptions): class SDKNetworkPolicyUpdateParams(NetworkPolicyUpdateParams, LongRequestOptions): pass + + +class SDKGatewayConfigCreateParams(GatewayConfigCreateParams, LongRequestOptions): + pass + + +class SDKGatewayConfigListParams(GatewayConfigListParams, BaseRequestOptions): + pass + + +class SDKGatewayConfigUpdateParams(GatewayConfigUpdateParams, LongRequestOptions): + pass diff --git a/src/runloop_api_client/sdk/async_.py b/src/runloop_api_client/sdk/async_.py index 98519f36f..214d1777a 100644 --- a/src/runloop_api_client/sdk/async_.py +++ b/src/runloop_api_client/sdk/async_.py @@ -27,7 +27,9 @@ SDKBlueprintCreateParams, SDKDiskSnapshotListParams, SDKNetworkPolicyListParams, + SDKGatewayConfigListParams, SDKNetworkPolicyCreateParams, + SDKGatewayConfigCreateParams, SDKDevboxCreateFromImageParams, ) from .._types import Timeout, NotGiven, not_given @@ -42,6 +44,7 @@ from .async_blueprint import AsyncBlueprint from ..lib.context_loader import TarFilter, build_directory_tar from .async_network_policy import AsyncNetworkPolicy +from .async_gateway_config import AsyncGatewayConfig from .async_storage_object import AsyncStorageObject from .async_scenario_builder import AsyncScenarioBuilder from ..types.object_create_params import ContentType @@ -924,6 +927,90 @@ async def list(self, **params: Unpack[SDKNetworkPolicyListParams]) -> list[Async return [AsyncNetworkPolicy(self._client, item.id) for item in page.network_policies] +class AsyncGatewayConfigOps: + """High-level async manager for creating and managing gateway configurations. + + Accessed via ``runloop.gateway_config`` from :class:`AsyncRunloopSDK`, provides + coroutines to create, retrieve, update, delete, and list gateway configs. Gateway configs + define how to proxy API requests through the credential gateway, enabling secure API + proxying without exposing API keys. + + Example: + >>> runloop = AsyncRunloopSDK() + >>> gateway_config = await runloop.gateway_config.create( + ... name="my-api-gateway", + ... endpoint="https://api.example.com", + ... auth_mechanism={"type": "bearer"}, + ... ) + >>> # Use with a devbox + >>> devbox = await runloop.devbox.create( + ... name="my-devbox", + ... gateways={ + ... "MY_API": { + ... "gateway": gateway_config.id, + ... "secret": "my-api-key-secret", + ... }, + ... }, + ... ) + """ + + def __init__(self, client: AsyncRunloop) -> None: + """Initialize AsyncGatewayConfigOps. + + :param client: AsyncRunloop client instance + :type client: AsyncRunloop + """ + self._client = client + + async def create(self, **params: Unpack[SDKGatewayConfigCreateParams]) -> AsyncGatewayConfig: + """Create a new gateway config. + + Example: + >>> gateway_config = await runloop.gateway_config.create( + ... name="my-gateway", + ... endpoint="https://api.example.com", + ... auth_mechanism={"type": "header", "key": "x-api-key"}, + ... description="Gateway for My API", + ... ) + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigCreateParams` for available parameters + :return: The newly created gateway config + :rtype: AsyncGatewayConfig + """ + response = await self._client.gateway_configs.create(**params) + return AsyncGatewayConfig(self._client, response.id) + + def from_id(self, gateway_config_id: str) -> AsyncGatewayConfig: + """Get an AsyncGatewayConfig instance for an existing gateway config ID. + + Example: + >>> gateway_config = runloop.gateway_config.from_id("gwc_1234567890") + >>> info = await gateway_config.get_info() + >>> print(f"Gateway Config name: {info.name}") + + :param gateway_config_id: ID of the gateway config + :type gateway_config_id: str + :return: AsyncGatewayConfig instance for the given ID + :rtype: AsyncGatewayConfig + """ + return AsyncGatewayConfig(self._client, gateway_config_id) + + async def list(self, **params: Unpack[SDKGatewayConfigListParams]) -> list[AsyncGatewayConfig]: + """List all gateway configs, optionally filtered by parameters. + + Example: + >>> configs = await runloop.gateway_config.list(limit=10) + >>> for config in configs: + ... print(config.id) + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigListParams` for available parameters + :return: List of gateway configs + :rtype: list[AsyncGatewayConfig] + """ + page = await self._client.gateway_configs.list(**params) + return [AsyncGatewayConfig(self._client, item.id) for item in page.gateway_configs] + + class AsyncRunloopSDK: """High-level asynchronous entry point for the Runloop SDK. @@ -951,6 +1038,8 @@ class AsyncRunloopSDK: :vartype storage_object: AsyncStorageObjectOps :ivar network_policy: High-level async interface for network policy management :vartype network_policy: AsyncNetworkPolicyOps + :ivar gateway_config: High-level async interface for gateway config management + :vartype gateway_config: AsyncGatewayConfigOps Example: >>> runloop = AsyncRunloopSDK() # Uses RUNLOOP_API_KEY env var @@ -965,6 +1054,7 @@ class AsyncRunloopSDK: benchmark: AsyncBenchmarkOps devbox: AsyncDevboxOps blueprint: AsyncBlueprintOps + gateway_config: AsyncGatewayConfigOps network_policy: AsyncNetworkPolicyOps scenario: AsyncScenarioOps scorer: AsyncScorerOps @@ -1013,6 +1103,7 @@ def __init__( self.benchmark = AsyncBenchmarkOps(self.api) self.devbox = AsyncDevboxOps(self.api) self.blueprint = AsyncBlueprintOps(self.api) + self.gateway_config = AsyncGatewayConfigOps(self.api) self.network_policy = AsyncNetworkPolicyOps(self.api) self.scenario = AsyncScenarioOps(self.api) self.scorer = AsyncScorerOps(self.api) diff --git a/src/runloop_api_client/sdk/async_gateway_config.py b/src/runloop_api_client/sdk/async_gateway_config.py new file mode 100644 index 000000000..6ccd670d0 --- /dev/null +++ b/src/runloop_api_client/sdk/async_gateway_config.py @@ -0,0 +1,108 @@ +"""GatewayConfig resource class for asynchronous operations.""" + +from __future__ import annotations + +from typing_extensions import Unpack, override + +from ._types import BaseRequestOptions, LongRequestOptions, SDKGatewayConfigUpdateParams +from .._client import AsyncRunloop +from ..types.gateway_config_view import GatewayConfigView + + +class AsyncGatewayConfig: + """Asynchronous wrapper around a gateway config resource. + + Gateway configs define how to proxy API requests through the credential gateway. + They specify the target endpoint and how credentials should be applied. Use with + devboxes to securely proxy requests to external APIs without exposing API keys. + + Example: + >>> runloop = AsyncRunloopSDK() + >>> gateway_config = await runloop.gateway_config.create( + ... name='my-api-gateway', + ... endpoint='https://api.example.com', + ... auth_mechanism={'type': 'bearer'}, + ... ) + >>> info = await gateway_config.get_info() + >>> print(f"Gateway Config: {info.name}") + """ + + def __init__( + self, + client: AsyncRunloop, + gateway_config_id: str, + ) -> None: + """Initialize the wrapper. + + :param client: Generated AsyncRunloop client + :type client: AsyncRunloop + :param gateway_config_id: GatewayConfig ID returned by the API + :type gateway_config_id: str + """ + self._client = client + self._id = gateway_config_id + + @override + def __repr__(self) -> str: + return f"" + + @property + def id(self) -> str: + """Return the gateway config ID. + + :return: Unique gateway config ID + :rtype: str + """ + return self._id + + async def get_info( + self, + **options: Unpack[BaseRequestOptions], + ) -> GatewayConfigView: + """Retrieve the latest gateway config details. + + Example: + >>> info = await gateway_config.get_info() + >>> print(f"Gateway Config: {info.name}, endpoint: {info.endpoint}") + + :param options: Optional request configuration + :return: API response describing the gateway config + :rtype: GatewayConfigView + """ + return await self._client.gateway_configs.retrieve( + self._id, + **options, + ) + + async def update(self, **params: Unpack[SDKGatewayConfigUpdateParams]) -> GatewayConfigView: + """Update the gateway config. + + Example: + >>> updated = await gateway_config.update( + ... name='updated-gateway-name', + ... description='Updated description', + ... ) + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigUpdateParams` for available parameters + :return: Updated gateway config view + :rtype: GatewayConfigView + """ + return await self._client.gateway_configs.update(self._id, **params) + + async def delete( + self, + **options: Unpack[LongRequestOptions], + ) -> GatewayConfigView: + """Delete the gateway config. This action is irreversible. + + Example: + >>> await gateway_config.delete() + + :param options: Optional long-running request configuration + :return: API response acknowledging deletion + :rtype: GatewayConfigView + """ + return await self._client.gateway_configs.delete( + self._id, + **options, + ) diff --git a/src/runloop_api_client/sdk/gateway_config.py b/src/runloop_api_client/sdk/gateway_config.py new file mode 100644 index 000000000..f9ed6e706 --- /dev/null +++ b/src/runloop_api_client/sdk/gateway_config.py @@ -0,0 +1,108 @@ +"""GatewayConfig resource class for synchronous operations.""" + +from __future__ import annotations + +from typing_extensions import Unpack, override + +from ._types import BaseRequestOptions, LongRequestOptions, SDKGatewayConfigUpdateParams +from .._client import Runloop +from ..types.gateway_config_view import GatewayConfigView + + +class GatewayConfig: + """Synchronous wrapper around a gateway config resource. + + Gateway configs define how to proxy API requests through the credential gateway. + They specify the target endpoint and how credentials should be applied. Use with + devboxes to securely proxy requests to external APIs without exposing API keys. + + Example: + >>> runloop = RunloopSDK() + >>> gateway_config = runloop.gateway_config.create( + ... name='my-api-gateway', + ... endpoint='https://api.example.com', + ... auth_mechanism={'type': 'bearer'}, + ... ) + >>> info = gateway_config.get_info() + >>> print(f"Gateway Config: {info.name}") + """ + + def __init__( + self, + client: Runloop, + gateway_config_id: str, + ) -> None: + """Initialize the wrapper. + + :param client: Generated Runloop client + :type client: Runloop + :param gateway_config_id: GatewayConfig ID returned by the API + :type gateway_config_id: str + """ + self._client = client + self._id = gateway_config_id + + @override + def __repr__(self) -> str: + return f"" + + @property + def id(self) -> str: + """Return the gateway config ID. + + :return: Unique gateway config ID + :rtype: str + """ + return self._id + + def get_info( + self, + **options: Unpack[BaseRequestOptions], + ) -> GatewayConfigView: + """Retrieve the latest gateway config details. + + Example: + >>> info = gateway_config.get_info() + >>> print(f"Gateway Config: {info.name}, endpoint: {info.endpoint}") + + :param options: Optional request configuration + :return: API response describing the gateway config + :rtype: GatewayConfigView + """ + return self._client.gateway_configs.retrieve( + self._id, + **options, + ) + + def update(self, **params: Unpack[SDKGatewayConfigUpdateParams]) -> GatewayConfigView: + """Update the gateway config. + + Example: + >>> updated = gateway_config.update( + ... name='updated-gateway-name', + ... description='Updated description', + ... ) + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigUpdateParams` for available parameters + :return: Updated gateway config view + :rtype: GatewayConfigView + """ + return self._client.gateway_configs.update(self._id, **params) + + def delete( + self, + **options: Unpack[LongRequestOptions], + ) -> GatewayConfigView: + """Delete the gateway config. This action is irreversible. + + Example: + >>> gateway_config.delete() + + :param options: Optional long-running request configuration + :return: API response acknowledging deletion + :rtype: GatewayConfigView + """ + return self._client.gateway_configs.delete( + self._id, + **options, + ) diff --git a/src/runloop_api_client/sdk/sync.py b/src/runloop_api_client/sdk/sync.py index aa5fa27fc..41333eb37 100644 --- a/src/runloop_api_client/sdk/sync.py +++ b/src/runloop_api_client/sdk/sync.py @@ -27,7 +27,9 @@ SDKBlueprintCreateParams, SDKDiskSnapshotListParams, SDKNetworkPolicyListParams, + SDKGatewayConfigListParams, SDKNetworkPolicyCreateParams, + SDKGatewayConfigCreateParams, SDKDevboxCreateFromImageParams, ) from .devbox import Devbox @@ -40,6 +42,7 @@ from .benchmark import Benchmark from .blueprint import Blueprint from .network_policy import NetworkPolicy +from .gateway_config import GatewayConfig from .storage_object import StorageObject from .scenario_builder import ScenarioBuilder from ..lib.context_loader import TarFilter, build_directory_tar @@ -949,6 +952,90 @@ def list(self, **params: Unpack[SDKNetworkPolicyListParams]) -> list[NetworkPoli return [NetworkPolicy(self._client, item.id) for item in page.network_policies] +class GatewayConfigOps: + """High-level manager for creating and managing gateway configurations. + + Accessed via ``runloop.gateway_config`` from :class:`RunloopSDK`, provides methods + to create, retrieve, update, delete, and list gateway configs. Gateway configs define + how to proxy API requests through the credential gateway, enabling secure API + proxying without exposing API keys. + + Example: + >>> runloop = RunloopSDK() + >>> gateway_config = runloop.gateway_config.create( + ... name="my-api-gateway", + ... endpoint="https://api.example.com", + ... auth_mechanism={"type": "bearer"}, + ... ) + >>> # Use with a devbox + >>> devbox = runloop.devbox.create( + ... name="my-devbox", + ... gateways={ + ... "MY_API": { + ... "gateway": gateway_config.id, + ... "secret": "my-api-key-secret", + ... }, + ... }, + ... ) + """ + + def __init__(self, client: Runloop) -> None: + """Initialize GatewayConfigOps. + + :param client: Runloop client instance + :type client: Runloop + """ + self._client = client + + def create(self, **params: Unpack[SDKGatewayConfigCreateParams]) -> GatewayConfig: + """Create a new gateway config. + + Example: + >>> gateway_config = runloop.gateway_config.create( + ... name="my-gateway", + ... endpoint="https://api.example.com", + ... auth_mechanism={"type": "header", "key": "x-api-key"}, + ... description="Gateway for My API", + ... ) + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigCreateParams` for available parameters + :return: The newly created gateway config + :rtype: GatewayConfig + """ + response = self._client.gateway_configs.create(**params) + return GatewayConfig(self._client, response.id) + + def from_id(self, gateway_config_id: str) -> GatewayConfig: + """Get a GatewayConfig instance for an existing gateway config ID. + + Example: + >>> gateway_config = runloop.gateway_config.from_id("gwc_1234567890") + >>> info = gateway_config.get_info() + >>> print(f"Gateway Config name: {info.name}") + + :param gateway_config_id: ID of the gateway config + :type gateway_config_id: str + :return: GatewayConfig instance for the given ID + :rtype: GatewayConfig + """ + return GatewayConfig(self._client, gateway_config_id) + + def list(self, **params: Unpack[SDKGatewayConfigListParams]) -> list[GatewayConfig]: + """List all gateway configs, optionally filtered by parameters. + + Example: + >>> configs = runloop.gateway_config.list(limit=10) + >>> for config in configs: + ... print(config.id) + + :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigListParams` for available parameters + :return: List of gateway configs + :rtype: list[GatewayConfig] + """ + page = self._client.gateway_configs.list(**params) + return [GatewayConfig(self._client, item.id) for item in page.gateway_configs] + + class RunloopSDK: """High-level synchronous entry point for the Runloop SDK. @@ -976,6 +1063,8 @@ class RunloopSDK: :vartype storage_object: StorageObjectOps :ivar network_policy: High-level interface for network policy management :vartype network_policy: NetworkPolicyOps + :ivar gateway_config: High-level interface for gateway config management + :vartype gateway_config: GatewayConfigOps Example: >>> runloop = RunloopSDK() # Uses RUNLOOP_API_KEY env var @@ -990,6 +1079,7 @@ class RunloopSDK: benchmark: BenchmarkOps devbox: DevboxOps blueprint: BlueprintOps + gateway_config: GatewayConfigOps network_policy: NetworkPolicyOps scenario: ScenarioOps scorer: ScorerOps @@ -1038,6 +1128,7 @@ def __init__( self.benchmark = BenchmarkOps(self.api) self.devbox = DevboxOps(self.api) self.blueprint = BlueprintOps(self.api) + self.gateway_config = GatewayConfigOps(self.api) self.network_policy = NetworkPolicyOps(self.api) self.scenario = ScenarioOps(self.api) self.scorer = ScorerOps(self.api) From 35a1ef245d17f799036da1344e86868e5bfce8ee Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 16:25:01 -0800 Subject: [PATCH 2/8] cp dines --- src/runloop_api_client/sdk/__init__.py | 8 +- src/runloop_api_client/sdk/_types.py | 6 +- src/runloop_api_client/sdk/async_.py | 6 +- src/runloop_api_client/sdk/sync.py | 6 +- tests/sdk/conftest.py | 27 +++++++ tests/sdk/test_async_gateway_config.py | 89 +++++++++++++++++++++ tests/sdk/test_async_ops.py | 79 +++++++++++++++++++ tests/sdk/test_gateway_config.py | 104 +++++++++++++++++++++++++ tests/sdk/test_ops.py | 75 ++++++++++++++++++ 9 files changed, 387 insertions(+), 13 deletions(-) create mode 100644 tests/sdk/test_async_gateway_config.py create mode 100644 tests/sdk/test_gateway_config.py diff --git a/src/runloop_api_client/sdk/__init__.py b/src/runloop_api_client/sdk/__init__.py index 70ba60865..edfe3f148 100644 --- a/src/runloop_api_client/sdk/__init__.py +++ b/src/runloop_api_client/sdk/__init__.py @@ -14,8 +14,8 @@ SnapshotOps, BenchmarkOps, BlueprintOps, - NetworkPolicyOps, GatewayConfigOps, + NetworkPolicyOps, StorageObjectOps, ) from .agent import Agent @@ -29,8 +29,8 @@ AsyncSnapshotOps, AsyncBenchmarkOps, AsyncBlueprintOps, - AsyncNetworkPolicyOps, AsyncGatewayConfigOps, + AsyncNetworkPolicyOps, AsyncStorageObjectOps, ) from .devbox import Devbox, NamedShell @@ -47,8 +47,8 @@ from .benchmark_run import BenchmarkRun from .async_scenario import AsyncScenario from .async_snapshot import AsyncSnapshot -from .network_policy import NetworkPolicy from .gateway_config import GatewayConfig +from .network_policy import NetworkPolicy from .storage_object import StorageObject from .async_benchmark import AsyncBenchmark from .async_blueprint import AsyncBlueprint @@ -57,8 +57,8 @@ from .scenario_builder import ScenarioBuilder from .async_scenario_run import AsyncScenarioRun from .async_benchmark_run import AsyncBenchmarkRun -from .async_network_policy import AsyncNetworkPolicy from .async_gateway_config import AsyncGatewayConfig +from .async_network_policy import AsyncNetworkPolicy from .async_storage_object import AsyncStorageObject from .async_execution_result import AsyncExecutionResult from .async_scenario_builder import AsyncScenarioBuilder diff --git a/src/runloop_api_client/sdk/_types.py b/src/runloop_api_client/sdk/_types.py index 5838e32a0..9eb0526bc 100644 --- a/src/runloop_api_client/sdk/_types.py +++ b/src/runloop_api_client/sdk/_types.py @@ -19,17 +19,17 @@ BenchmarkUpdateParams, BlueprintCreateParams, DevboxUploadFileParams, - NetworkPolicyListParams, GatewayConfigListParams, + NetworkPolicyListParams, DevboxCreateTunnelParams, DevboxDownloadFileParams, DevboxEnableTunnelParams, DevboxRemoveTunnelParams, DevboxSnapshotDiskParams, - NetworkPolicyCreateParams, - NetworkPolicyUpdateParams, GatewayConfigCreateParams, GatewayConfigUpdateParams, + NetworkPolicyCreateParams, + NetworkPolicyUpdateParams, DevboxReadFileContentsParams, DevboxWriteFileContentsParams, BenchmarkRunListScenarioRunsParams, diff --git a/src/runloop_api_client/sdk/async_.py b/src/runloop_api_client/sdk/async_.py index 214d1777a..c35ae98ae 100644 --- a/src/runloop_api_client/sdk/async_.py +++ b/src/runloop_api_client/sdk/async_.py @@ -26,10 +26,10 @@ SDKBenchmarkCreateParams, SDKBlueprintCreateParams, SDKDiskSnapshotListParams, - SDKNetworkPolicyListParams, SDKGatewayConfigListParams, - SDKNetworkPolicyCreateParams, + SDKNetworkPolicyListParams, SDKGatewayConfigCreateParams, + SDKNetworkPolicyCreateParams, SDKDevboxCreateFromImageParams, ) from .._types import Timeout, NotGiven, not_given @@ -43,8 +43,8 @@ from .async_benchmark import AsyncBenchmark from .async_blueprint import AsyncBlueprint from ..lib.context_loader import TarFilter, build_directory_tar -from .async_network_policy import AsyncNetworkPolicy from .async_gateway_config import AsyncGatewayConfig +from .async_network_policy import AsyncNetworkPolicy from .async_storage_object import AsyncStorageObject from .async_scenario_builder import AsyncScenarioBuilder from ..types.object_create_params import ContentType diff --git a/src/runloop_api_client/sdk/sync.py b/src/runloop_api_client/sdk/sync.py index 41333eb37..938576508 100644 --- a/src/runloop_api_client/sdk/sync.py +++ b/src/runloop_api_client/sdk/sync.py @@ -26,10 +26,10 @@ SDKBenchmarkCreateParams, SDKBlueprintCreateParams, SDKDiskSnapshotListParams, - SDKNetworkPolicyListParams, SDKGatewayConfigListParams, - SDKNetworkPolicyCreateParams, + SDKNetworkPolicyListParams, SDKGatewayConfigCreateParams, + SDKNetworkPolicyCreateParams, SDKDevboxCreateFromImageParams, ) from .devbox import Devbox @@ -41,8 +41,8 @@ from .snapshot import Snapshot from .benchmark import Benchmark from .blueprint import Blueprint -from .network_policy import NetworkPolicy from .gateway_config import GatewayConfig +from .network_policy import NetworkPolicy from .storage_object import StorageObject from .scenario_builder import ScenarioBuilder from ..lib.context_loader import TarFilter, build_directory_tar diff --git a/tests/sdk/conftest.py b/tests/sdk/conftest.py index 29085ed45..a67f25b27 100644 --- a/tests/sdk/conftest.py +++ b/tests/sdk/conftest.py @@ -28,6 +28,7 @@ "benchmark": "bmd_123", "benchmark_run": "bmr_123", "network_policy": "np_123", + "gateway_config": "gwc_123", } # Test URL constants @@ -179,6 +180,26 @@ class MockNetworkPolicyView: egress: MockEgress = field(default_factory=MockEgress) +@dataclass +class MockAuthMechanism: + """Mock AuthMechanism for testing.""" + + type: str = "bearer" + key: str | None = None + + +@dataclass +class MockGatewayConfigView: + """Mock GatewayConfigView for testing.""" + + id: str = TEST_IDS["gateway_config"] + name: str = "test-gateway-config" + endpoint: str = "https://api.example.com" + description: str | None = "Test gateway config description" + create_time_ms: int = 1234567890000 + auth_mechanism: MockAuthMechanism = field(default_factory=MockAuthMechanism) + + class AsyncIterableMock: """A simple async iterable mock for testing paginated responses.""" @@ -316,6 +337,12 @@ def network_policy_view() -> MockNetworkPolicyView: return MockNetworkPolicyView() +@pytest.fixture +def gateway_config_view() -> MockGatewayConfigView: + """Create a mock GatewayConfigView.""" + return MockGatewayConfigView() + + @pytest.fixture def mock_httpx_response() -> Mock: """Create a mock httpx.Response.""" diff --git a/tests/sdk/test_async_gateway_config.py b/tests/sdk/test_async_gateway_config.py new file mode 100644 index 000000000..8c93c88a2 --- /dev/null +++ b/tests/sdk/test_async_gateway_config.py @@ -0,0 +1,89 @@ +"""Comprehensive tests for async GatewayConfig class.""" + +from __future__ import annotations + +from unittest.mock import AsyncMock + +import pytest + +from tests.sdk.conftest import MockGatewayConfigView +from runloop_api_client.sdk import AsyncGatewayConfig + + +class TestAsyncGatewayConfig: + """Tests for AsyncGatewayConfig class.""" + + def test_init(self, mock_async_client: AsyncMock) -> None: + """Test AsyncGatewayConfig initialization.""" + gateway_config = AsyncGatewayConfig(mock_async_client, "gwc_123") + assert gateway_config.id == "gwc_123" + + def test_repr(self, mock_async_client: AsyncMock) -> None: + """Test AsyncGatewayConfig string representation.""" + gateway_config = AsyncGatewayConfig(mock_async_client, "gwc_123") + assert repr(gateway_config) == "" + + @pytest.mark.asyncio + async def test_get_info(self, mock_async_client: AsyncMock, gateway_config_view: MockGatewayConfigView) -> None: + """Test get_info method.""" + mock_async_client.gateway_configs.retrieve = AsyncMock(return_value=gateway_config_view) + + gateway_config = AsyncGatewayConfig(mock_async_client, "gwc_123") + result = await gateway_config.get_info( + extra_headers={"X-Custom": "value"}, + extra_query={"param": "value"}, + extra_body={"key": "value"}, + timeout=30.0, + ) + + assert result == gateway_config_view + mock_async_client.gateway_configs.retrieve.assert_awaited_once() + + @pytest.mark.asyncio + async def test_update(self, mock_async_client: AsyncMock, gateway_config_view: MockGatewayConfigView) -> None: + """Test update method.""" + mock_async_client.gateway_configs.update = AsyncMock(return_value=gateway_config_view) + + gateway_config = AsyncGatewayConfig(mock_async_client, "gwc_123") + result = await gateway_config.update( + name="updated-gateway", + description="Updated description", + endpoint="https://api.updated.com", + auth_mechanism={"type": "header", "key": "x-api-key"}, + extra_headers={"X-Custom": "value"}, + timeout=30.0, + ) + + assert result == gateway_config_view + mock_async_client.gateway_configs.update.assert_awaited_once() + + @pytest.mark.asyncio + async def test_update_partial( + self, mock_async_client: AsyncMock, gateway_config_view: MockGatewayConfigView + ) -> None: + """Test update method with partial fields.""" + mock_async_client.gateway_configs.update = AsyncMock(return_value=gateway_config_view) + + gateway_config = AsyncGatewayConfig(mock_async_client, "gwc_123") + result = await gateway_config.update( + name="renamed-gateway", + ) + + assert result == gateway_config_view + mock_async_client.gateway_configs.update.assert_awaited_once() + + @pytest.mark.asyncio + async def test_delete(self, mock_async_client: AsyncMock, gateway_config_view: MockGatewayConfigView) -> None: + """Test delete method.""" + mock_async_client.gateway_configs.delete = AsyncMock(return_value=gateway_config_view) + + gateway_config = AsyncGatewayConfig(mock_async_client, "gwc_123") + result = await gateway_config.delete( + extra_headers={"X-Custom": "value"}, + extra_query={"param": "value"}, + extra_body={"key": "value"}, + timeout=30.0, + ) + + assert result == gateway_config_view + mock_async_client.gateway_configs.delete.assert_awaited_once() diff --git a/tests/sdk/test_async_ops.py b/tests/sdk/test_async_ops.py index 432cb706d..b3f02ab5c 100644 --- a/tests/sdk/test_async_ops.py +++ b/tests/sdk/test_async_ops.py @@ -19,6 +19,7 @@ MockSnapshotView, MockBenchmarkView, MockBlueprintView, + MockGatewayConfigView, MockNetworkPolicyView, create_mock_httpx_response, ) @@ -38,8 +39,10 @@ AsyncSnapshotOps, AsyncBenchmarkOps, AsyncBlueprintOps, + AsyncGatewayConfig, AsyncNetworkPolicy, AsyncStorageObject, + AsyncGatewayConfigOps, AsyncNetworkPolicyOps, AsyncStorageObjectOps, ) @@ -1313,6 +1316,81 @@ async def test_list_multiple(self, mock_async_client: AsyncMock) -> None: mock_async_client.network_policies.list.assert_awaited_once() +class TestAsyncGatewayConfigOps: + """Tests for AsyncGatewayConfigOps class.""" + + @pytest.mark.asyncio + async def test_create(self, mock_async_client: AsyncMock, gateway_config_view: MockGatewayConfigView) -> None: + """Test create method.""" + mock_async_client.gateway_configs.create = AsyncMock(return_value=gateway_config_view) + + ops = AsyncGatewayConfigOps(mock_async_client) + gateway_config = await ops.create( + name="test-gateway-config", + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + assert isinstance(gateway_config, AsyncGatewayConfig) + assert gateway_config.id == "gwc_123" + mock_async_client.gateway_configs.create.assert_awaited_once() + + def test_from_id(self, mock_async_client: AsyncMock) -> None: + """Test from_id method.""" + ops = AsyncGatewayConfigOps(mock_async_client) + gateway_config = ops.from_id("gwc_123") + + assert isinstance(gateway_config, AsyncGatewayConfig) + assert gateway_config.id == "gwc_123" + + @pytest.mark.asyncio + async def test_list_empty(self, mock_async_client: AsyncMock) -> None: + """Test list method with empty results.""" + page = SimpleNamespace(gateway_configs=[]) + mock_async_client.gateway_configs.list = AsyncMock(return_value=page) + + ops = AsyncGatewayConfigOps(mock_async_client) + gateway_configs = await ops.list(limit=10) + + assert len(gateway_configs) == 0 + mock_async_client.gateway_configs.list.assert_awaited_once() + + @pytest.mark.asyncio + async def test_list_single(self, mock_async_client: AsyncMock, gateway_config_view: MockGatewayConfigView) -> None: + """Test list method with single result.""" + page = SimpleNamespace(gateway_configs=[gateway_config_view]) + mock_async_client.gateway_configs.list = AsyncMock(return_value=page) + + ops = AsyncGatewayConfigOps(mock_async_client) + gateway_configs = await ops.list( + limit=10, + starting_after="gwc_000", + ) + + assert len(gateway_configs) == 1 + assert isinstance(gateway_configs[0], AsyncGatewayConfig) + assert gateway_configs[0].id == "gwc_123" + mock_async_client.gateway_configs.list.assert_awaited_once() + + @pytest.mark.asyncio + async def test_list_multiple(self, mock_async_client: AsyncMock) -> None: + """Test list method with multiple results.""" + gateway_config_view1 = MockGatewayConfigView(id="gwc_001", name="gateway-1") + gateway_config_view2 = MockGatewayConfigView(id="gwc_002", name="gateway-2") + page = SimpleNamespace(gateway_configs=[gateway_config_view1, gateway_config_view2]) + mock_async_client.gateway_configs.list = AsyncMock(return_value=page) + + ops = AsyncGatewayConfigOps(mock_async_client) + gateway_configs = await ops.list(limit=10) + + assert len(gateway_configs) == 2 + assert isinstance(gateway_configs[0], AsyncGatewayConfig) + assert isinstance(gateway_configs[1], AsyncGatewayConfig) + assert gateway_configs[0].id == "gwc_001" + assert gateway_configs[1].id == "gwc_002" + mock_async_client.gateway_configs.list.assert_awaited_once() + + class TestAsyncRunloopSDK: """Tests for AsyncRunloopSDK class.""" @@ -1323,6 +1401,7 @@ def test_init(self) -> None: assert isinstance(runloop.agent, AsyncAgentOps) assert isinstance(runloop.benchmark, AsyncBenchmarkOps) assert isinstance(runloop.devbox, AsyncDevboxOps) + assert isinstance(runloop.gateway_config, AsyncGatewayConfigOps) assert isinstance(runloop.network_policy, AsyncNetworkPolicyOps) assert isinstance(runloop.scorer, AsyncScorerOps) assert isinstance(runloop.snapshot, AsyncSnapshotOps) diff --git a/tests/sdk/test_gateway_config.py b/tests/sdk/test_gateway_config.py new file mode 100644 index 000000000..5f515fe97 --- /dev/null +++ b/tests/sdk/test_gateway_config.py @@ -0,0 +1,104 @@ +"""Comprehensive tests for sync GatewayConfig class.""" + +from __future__ import annotations + +from unittest.mock import Mock + +from tests.sdk.conftest import MockGatewayConfigView +from runloop_api_client.sdk import GatewayConfig + + +class TestGatewayConfig: + """Tests for GatewayConfig class.""" + + def test_init(self, mock_client: Mock) -> None: + """Test GatewayConfig initialization.""" + gateway_config = GatewayConfig(mock_client, "gwc_123") + assert gateway_config.id == "gwc_123" + + def test_repr(self, mock_client: Mock) -> None: + """Test GatewayConfig string representation.""" + gateway_config = GatewayConfig(mock_client, "gwc_123") + assert repr(gateway_config) == "" + + def test_get_info(self, mock_client: Mock, gateway_config_view: MockGatewayConfigView) -> None: + """Test get_info method.""" + mock_client.gateway_configs.retrieve.return_value = gateway_config_view + + gateway_config = GatewayConfig(mock_client, "gwc_123") + result = gateway_config.get_info( + extra_headers={"X-Custom": "value"}, + extra_query={"param": "value"}, + extra_body={"key": "value"}, + timeout=30.0, + ) + + assert result == gateway_config_view + mock_client.gateway_configs.retrieve.assert_called_once_with( + "gwc_123", + extra_headers={"X-Custom": "value"}, + extra_query={"param": "value"}, + extra_body={"key": "value"}, + timeout=30.0, + ) + + def test_update(self, mock_client: Mock, gateway_config_view: MockGatewayConfigView) -> None: + """Test update method.""" + mock_client.gateway_configs.update.return_value = gateway_config_view + + gateway_config = GatewayConfig(mock_client, "gwc_123") + result = gateway_config.update( + name="updated-gateway", + description="Updated description", + endpoint="https://api.updated.com", + auth_mechanism={"type": "header", "key": "x-api-key"}, + extra_headers={"X-Custom": "value"}, + timeout=30.0, + ) + + assert result == gateway_config_view + mock_client.gateway_configs.update.assert_called_once_with( + "gwc_123", + name="updated-gateway", + description="Updated description", + endpoint="https://api.updated.com", + auth_mechanism={"type": "header", "key": "x-api-key"}, + extra_headers={"X-Custom": "value"}, + timeout=30.0, + ) + + def test_update_partial(self, mock_client: Mock, gateway_config_view: MockGatewayConfigView) -> None: + """Test update method with partial fields.""" + mock_client.gateway_configs.update.return_value = gateway_config_view + + gateway_config = GatewayConfig(mock_client, "gwc_123") + result = gateway_config.update( + name="renamed-gateway", + ) + + assert result == gateway_config_view + mock_client.gateway_configs.update.assert_called_once_with( + "gwc_123", + name="renamed-gateway", + ) + + def test_delete(self, mock_client: Mock, gateway_config_view: MockGatewayConfigView) -> None: + """Test delete method.""" + mock_client.gateway_configs.delete.return_value = gateway_config_view + + gateway_config = GatewayConfig(mock_client, "gwc_123") + result = gateway_config.delete( + extra_headers={"X-Custom": "value"}, + extra_query={"param": "value"}, + extra_body={"key": "value"}, + timeout=30.0, + ) + + assert result == gateway_config_view + mock_client.gateway_configs.delete.assert_called_once_with( + "gwc_123", + extra_headers={"X-Custom": "value"}, + extra_query={"param": "value"}, + extra_body={"key": "value"}, + timeout=30.0, + ) diff --git a/tests/sdk/test_ops.py b/tests/sdk/test_ops.py index ff4075e77..3ff648adc 100644 --- a/tests/sdk/test_ops.py +++ b/tests/sdk/test_ops.py @@ -19,6 +19,7 @@ MockSnapshotView, MockBenchmarkView, MockBlueprintView, + MockGatewayConfigView, MockNetworkPolicyView, create_mock_httpx_response, ) @@ -38,8 +39,10 @@ SnapshotOps, BenchmarkOps, BlueprintOps, + GatewayConfig, NetworkPolicy, StorageObject, + GatewayConfigOps, NetworkPolicyOps, StorageObjectOps, ) @@ -1228,6 +1231,77 @@ def test_list_multiple(self, mock_client: Mock) -> None: mock_client.network_policies.list.assert_called_once() +class TestGatewayConfigOps: + """Tests for GatewayConfigOps class.""" + + def test_create(self, mock_client: Mock, gateway_config_view: MockGatewayConfigView) -> None: + """Test create method.""" + mock_client.gateway_configs.create.return_value = gateway_config_view + + ops = GatewayConfigOps(mock_client) + gateway_config = ops.create( + name="test-gateway-config", + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + assert isinstance(gateway_config, GatewayConfig) + assert gateway_config.id == "gwc_123" + mock_client.gateway_configs.create.assert_called_once() + + def test_from_id(self, mock_client: Mock) -> None: + """Test from_id method.""" + ops = GatewayConfigOps(mock_client) + gateway_config = ops.from_id("gwc_123") + + assert isinstance(gateway_config, GatewayConfig) + assert gateway_config.id == "gwc_123" + + def test_list_empty(self, mock_client: Mock) -> None: + """Test list method with empty results.""" + page = SimpleNamespace(gateway_configs=[]) + mock_client.gateway_configs.list.return_value = page + + ops = GatewayConfigOps(mock_client) + gateway_configs = ops.list(limit=10) + + assert len(gateway_configs) == 0 + mock_client.gateway_configs.list.assert_called_once() + + def test_list_single(self, mock_client: Mock, gateway_config_view: MockGatewayConfigView) -> None: + """Test list method with single result.""" + page = SimpleNamespace(gateway_configs=[gateway_config_view]) + mock_client.gateway_configs.list.return_value = page + + ops = GatewayConfigOps(mock_client) + gateway_configs = ops.list( + limit=10, + starting_after="gwc_000", + ) + + assert len(gateway_configs) == 1 + assert isinstance(gateway_configs[0], GatewayConfig) + assert gateway_configs[0].id == "gwc_123" + mock_client.gateway_configs.list.assert_called_once() + + def test_list_multiple(self, mock_client: Mock) -> None: + """Test list method with multiple results.""" + gateway_config_view1 = MockGatewayConfigView(id="gwc_001", name="gateway-1") + gateway_config_view2 = MockGatewayConfigView(id="gwc_002", name="gateway-2") + page = SimpleNamespace(gateway_configs=[gateway_config_view1, gateway_config_view2]) + mock_client.gateway_configs.list.return_value = page + + ops = GatewayConfigOps(mock_client) + gateway_configs = ops.list(limit=10) + + assert len(gateway_configs) == 2 + assert isinstance(gateway_configs[0], GatewayConfig) + assert isinstance(gateway_configs[1], GatewayConfig) + assert gateway_configs[0].id == "gwc_001" + assert gateway_configs[1].id == "gwc_002" + mock_client.gateway_configs.list.assert_called_once() + + class TestRunloopSDK: """Tests for RunloopSDK class.""" @@ -1238,6 +1312,7 @@ def test_init(self) -> None: assert isinstance(runloop.agent, AgentOps) assert isinstance(runloop.benchmark, BenchmarkOps) assert isinstance(runloop.devbox, DevboxOps) + assert isinstance(runloop.gateway_config, GatewayConfigOps) assert isinstance(runloop.network_policy, NetworkPolicyOps) assert isinstance(runloop.scorer, ScorerOps) assert isinstance(runloop.snapshot, SnapshotOps) From 046680343d85291d195bed728bbc7913d4e50938 Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 17:03:05 -0800 Subject: [PATCH 3/8] cp dines --- src/runloop_api_client/sdk/async_gateway_config.py | 10 +++++----- src/runloop_api_client/sdk/gateway_config.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/runloop_api_client/sdk/async_gateway_config.py b/src/runloop_api_client/sdk/async_gateway_config.py index 6ccd670d0..423e8c3be 100644 --- a/src/runloop_api_client/sdk/async_gateway_config.py +++ b/src/runloop_api_client/sdk/async_gateway_config.py @@ -19,9 +19,9 @@ class AsyncGatewayConfig: Example: >>> runloop = AsyncRunloopSDK() >>> gateway_config = await runloop.gateway_config.create( - ... name='my-api-gateway', - ... endpoint='https://api.example.com', - ... auth_mechanism={'type': 'bearer'}, + ... name="my-api-gateway", + ... endpoint="https://api.example.com", + ... auth_mechanism={"type": "bearer"}, ... ) >>> info = await gateway_config.get_info() >>> print(f"Gateway Config: {info.name}") @@ -79,8 +79,8 @@ async def update(self, **params: Unpack[SDKGatewayConfigUpdateParams]) -> Gatewa Example: >>> updated = await gateway_config.update( - ... name='updated-gateway-name', - ... description='Updated description', + ... name="updated-gateway-name", + ... description="Updated description", ... ) :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigUpdateParams` for available parameters diff --git a/src/runloop_api_client/sdk/gateway_config.py b/src/runloop_api_client/sdk/gateway_config.py index f9ed6e706..0a067fb48 100644 --- a/src/runloop_api_client/sdk/gateway_config.py +++ b/src/runloop_api_client/sdk/gateway_config.py @@ -19,9 +19,9 @@ class GatewayConfig: Example: >>> runloop = RunloopSDK() >>> gateway_config = runloop.gateway_config.create( - ... name='my-api-gateway', - ... endpoint='https://api.example.com', - ... auth_mechanism={'type': 'bearer'}, + ... name="my-api-gateway", + ... endpoint="https://api.example.com", + ... auth_mechanism={"type": "bearer"}, ... ) >>> info = gateway_config.get_info() >>> print(f"Gateway Config: {info.name}") @@ -79,8 +79,8 @@ def update(self, **params: Unpack[SDKGatewayConfigUpdateParams]) -> GatewayConfi Example: >>> updated = gateway_config.update( - ... name='updated-gateway-name', - ... description='Updated description', + ... name="updated-gateway-name", + ... description="Updated description", ... ) :param params: See :typeddict:`~runloop_api_client.sdk._types.SDKGatewayConfigUpdateParams` for available parameters From c0d0130ee9ec2ba40470b08d14b6cb94d5948bad Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 17:08:59 -0800 Subject: [PATCH 4/8] cp dines --- scripts/lint | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/lint b/scripts/lint index 36ff12baf..b6390e3ae 100755 --- a/scripts/lint +++ b/scripts/lint @@ -13,5 +13,8 @@ uv run mypy . echo "==> Running lints" uv run ruff check . "$@" +echo "==> Running format check" +uv run ruff format --check . + echo "==> Making sure it imports" uv run python -c 'import runloop_api_client' From e4979d2ce167e6fa547a89c64106cbf1dae92e6e Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 17:16:16 -0800 Subject: [PATCH 5/8] cp dines --- .../sdk/test_async_gateway_config.py | 261 ++++++++++++++++++ tests/smoketests/sdk/test_gateway_config.py | 261 ++++++++++++++++++ 2 files changed, 522 insertions(+) create mode 100644 tests/smoketests/sdk/test_async_gateway_config.py create mode 100644 tests/smoketests/sdk/test_gateway_config.py diff --git a/tests/smoketests/sdk/test_async_gateway_config.py b/tests/smoketests/sdk/test_async_gateway_config.py new file mode 100644 index 000000000..138e8a717 --- /dev/null +++ b/tests/smoketests/sdk/test_async_gateway_config.py @@ -0,0 +1,261 @@ +"""Asynchronous SDK smoke tests for Gateway Config operations.""" + +from __future__ import annotations + +import pytest + +from runloop_api_client.sdk import AsyncRunloopSDK +from tests.smoketests.utils import unique_name + +pytestmark = [pytest.mark.smoketest, pytest.mark.asyncio] + +THIRTY_SECOND_TIMEOUT = 30 + + +class TestAsyncGatewayConfigLifecycle: + """Test basic async gateway config lifecycle operations.""" + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_create(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test creating a gateway config.""" + name = unique_name("sdk-async-gateway-config") + gateway_config = await async_sdk_client.gateway_config.create( + name=name, + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + description="SDK async smoke test gateway config", + ) + + try: + assert gateway_config is not None + assert gateway_config.id is not None + assert len(gateway_config.id) > 0 + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_get_info(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test retrieving gateway config information.""" + name = unique_name("sdk-async-gateway-config-info") + gateway_config = await async_sdk_client.gateway_config.create( + name=name, + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + description="Async test gateway config for get_info", + ) + + try: + info = await gateway_config.get_info() + + assert info.id == gateway_config.id + assert info.name == name + assert info.endpoint == "https://api.example.com" + assert info.description == "Async test gateway config for get_info" + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "bearer" + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_update(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test updating a gateway config.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-update"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + description="Original async description", + ) + + try: + # Update the gateway config + updated_name = unique_name("sdk-async-gateway-config-updated") + result = await gateway_config.update( + name=updated_name, + description="Updated async description", + ) + + assert result is not None + assert result.name == updated_name + assert result.description == "Updated async description" + + # Verify update persisted + info = await gateway_config.get_info() + assert info.name == updated_name + assert info.description == "Updated async description" + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_update_endpoint(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test updating gateway config endpoint.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-endpoint"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + result = await gateway_config.update( + endpoint="https://api.updated-example.com", + ) + + assert result.endpoint == "https://api.updated-example.com" + + # Verify update persisted + info = await gateway_config.get_info() + assert info.endpoint == "https://api.updated-example.com" + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_update_auth_mechanism(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test updating gateway config auth mechanism.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-auth"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + result = await gateway_config.update( + auth_mechanism={"type": "header", "key": "x-api-key"}, + ) + + assert result.auth_mechanism.type == "header" + assert result.auth_mechanism.key == "x-api-key" + + # Verify update persisted + info = await gateway_config.get_info() + assert info.auth_mechanism.type == "header" + assert info.auth_mechanism.key == "x-api-key" + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_delete(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test deleting a gateway config.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-delete"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + result = await gateway_config.delete() + + assert result is not None + + +class TestAsyncGatewayConfigAuthMechanisms: + """Test different async gateway config auth mechanism types.""" + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_with_bearer_auth(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test creating a gateway config with bearer auth.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-bearer"), + endpoint="https://api.bearer-test.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + info = await gateway_config.get_info() + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "bearer" + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_with_header_auth(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test creating a gateway config with header auth.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-header"), + endpoint="https://api.header-test.com", + auth_mechanism={"type": "header", "key": "x-api-key"}, + ) + + try: + info = await gateway_config.get_info() + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "header" + assert info.auth_mechanism.key == "x-api-key" + finally: + await gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_gateway_config_with_authorization_header(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test creating a gateway config with Authorization header.""" + gateway_config = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-auth-header"), + endpoint="https://api.auth-header-test.com", + auth_mechanism={"type": "header", "key": "Authorization"}, + ) + + try: + info = await gateway_config.get_info() + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "header" + assert info.auth_mechanism.key == "Authorization" + finally: + await gateway_config.delete() + + +class TestAsyncGatewayConfigListing: + """Test async gateway config listing and retrieval operations.""" + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_list_gateway_configs(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test listing gateway configs.""" + configs = await async_sdk_client.gateway_config.list(limit=10) + + assert isinstance(configs, list) + # List might be empty or have system configs, that's okay + assert len(configs) >= 0 + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_get_gateway_config_by_id(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test retrieving gateway config by ID.""" + # Create a gateway config + created = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-retrieve"), + endpoint="https://api.retrieve-test.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + # Retrieve it by ID + retrieved = async_sdk_client.gateway_config.from_id(created.id) + assert retrieved.id == created.id + + # Verify it's the same gateway config + info = await retrieved.get_info() + assert info.id == created.id + finally: + await created.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + async def test_list_gateway_configs_with_limit(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test listing gateway configs with a limit.""" + # Create two gateway configs + config1 = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-list-1"), + endpoint="https://api.list-test-1.com", + auth_mechanism={"type": "bearer"}, + ) + config2 = await async_sdk_client.gateway_config.create( + name=unique_name("sdk-async-gateway-config-list-2"), + endpoint="https://api.list-test-2.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + # List with limit + configs = await async_sdk_client.gateway_config.list(limit=100) + + assert isinstance(configs, list) + # Should find our gateway configs + config_ids = [c.id for c in configs] + assert config1.id in config_ids + assert config2.id in config_ids + finally: + await config1.delete() + await config2.delete() diff --git a/tests/smoketests/sdk/test_gateway_config.py b/tests/smoketests/sdk/test_gateway_config.py new file mode 100644 index 000000000..34a43961d --- /dev/null +++ b/tests/smoketests/sdk/test_gateway_config.py @@ -0,0 +1,261 @@ +"""Synchronous SDK smoke tests for Gateway Config operations.""" + +from __future__ import annotations + +import pytest + +from runloop_api_client.sdk import RunloopSDK +from tests.smoketests.utils import unique_name + +pytestmark = [pytest.mark.smoketest] + +THIRTY_SECOND_TIMEOUT = 30 + + +class TestGatewayConfigLifecycle: + """Test basic gateway config lifecycle operations.""" + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_create(self, sdk_client: RunloopSDK) -> None: + """Test creating a gateway config.""" + name = unique_name("sdk-gateway-config") + gateway_config = sdk_client.gateway_config.create( + name=name, + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + description="SDK smoke test gateway config", + ) + + try: + assert gateway_config is not None + assert gateway_config.id is not None + assert len(gateway_config.id) > 0 + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_get_info(self, sdk_client: RunloopSDK) -> None: + """Test retrieving gateway config information.""" + name = unique_name("sdk-gateway-config-info") + gateway_config = sdk_client.gateway_config.create( + name=name, + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + description="Test gateway config for get_info", + ) + + try: + info = gateway_config.get_info() + + assert info.id == gateway_config.id + assert info.name == name + assert info.endpoint == "https://api.example.com" + assert info.description == "Test gateway config for get_info" + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "bearer" + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_update(self, sdk_client: RunloopSDK) -> None: + """Test updating a gateway config.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-update"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + description="Original description", + ) + + try: + # Update the gateway config + updated_name = unique_name("sdk-gateway-config-updated") + result = gateway_config.update( + name=updated_name, + description="Updated description", + ) + + assert result is not None + assert result.name == updated_name + assert result.description == "Updated description" + + # Verify update persisted + info = gateway_config.get_info() + assert info.name == updated_name + assert info.description == "Updated description" + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_update_endpoint(self, sdk_client: RunloopSDK) -> None: + """Test updating gateway config endpoint.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-endpoint"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + result = gateway_config.update( + endpoint="https://api.updated-example.com", + ) + + assert result.endpoint == "https://api.updated-example.com" + + # Verify update persisted + info = gateway_config.get_info() + assert info.endpoint == "https://api.updated-example.com" + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_update_auth_mechanism(self, sdk_client: RunloopSDK) -> None: + """Test updating gateway config auth mechanism.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-auth"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + result = gateway_config.update( + auth_mechanism={"type": "header", "key": "x-api-key"}, + ) + + assert result.auth_mechanism.type == "header" + assert result.auth_mechanism.key == "x-api-key" + + # Verify update persisted + info = gateway_config.get_info() + assert info.auth_mechanism.type == "header" + assert info.auth_mechanism.key == "x-api-key" + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_delete(self, sdk_client: RunloopSDK) -> None: + """Test deleting a gateway config.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-delete"), + endpoint="https://api.example.com", + auth_mechanism={"type": "bearer"}, + ) + + result = gateway_config.delete() + + assert result is not None + + +class TestGatewayConfigAuthMechanisms: + """Test different gateway config auth mechanism types.""" + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_with_bearer_auth(self, sdk_client: RunloopSDK) -> None: + """Test creating a gateway config with bearer auth.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-bearer"), + endpoint="https://api.bearer-test.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + info = gateway_config.get_info() + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "bearer" + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_with_header_auth(self, sdk_client: RunloopSDK) -> None: + """Test creating a gateway config with header auth.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-header"), + endpoint="https://api.header-test.com", + auth_mechanism={"type": "header", "key": "x-api-key"}, + ) + + try: + info = gateway_config.get_info() + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "header" + assert info.auth_mechanism.key == "x-api-key" + finally: + gateway_config.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_gateway_config_with_authorization_header(self, sdk_client: RunloopSDK) -> None: + """Test creating a gateway config with Authorization header.""" + gateway_config = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-auth-header"), + endpoint="https://api.auth-header-test.com", + auth_mechanism={"type": "header", "key": "Authorization"}, + ) + + try: + info = gateway_config.get_info() + assert info.auth_mechanism is not None + assert info.auth_mechanism.type == "header" + assert info.auth_mechanism.key == "Authorization" + finally: + gateway_config.delete() + + +class TestGatewayConfigListing: + """Test gateway config listing and retrieval operations.""" + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_list_gateway_configs(self, sdk_client: RunloopSDK) -> None: + """Test listing gateway configs.""" + configs = sdk_client.gateway_config.list(limit=10) + + assert isinstance(configs, list) + # List might be empty or have system configs, that's okay + assert len(configs) >= 0 + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_get_gateway_config_by_id(self, sdk_client: RunloopSDK) -> None: + """Test retrieving gateway config by ID.""" + # Create a gateway config + created = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-retrieve"), + endpoint="https://api.retrieve-test.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + # Retrieve it by ID + retrieved = sdk_client.gateway_config.from_id(created.id) + assert retrieved.id == created.id + + # Verify it's the same gateway config + info = retrieved.get_info() + assert info.id == created.id + finally: + created.delete() + + @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) + def test_list_gateway_configs_with_limit(self, sdk_client: RunloopSDK) -> None: + """Test listing gateway configs with a limit.""" + # Create two gateway configs + config1 = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-list-1"), + endpoint="https://api.list-test-1.com", + auth_mechanism={"type": "bearer"}, + ) + config2 = sdk_client.gateway_config.create( + name=unique_name("sdk-gateway-config-list-2"), + endpoint="https://api.list-test-2.com", + auth_mechanism={"type": "bearer"}, + ) + + try: + # List with limit + configs = sdk_client.gateway_config.list(limit=100) + + assert isinstance(configs, list) + # Should find our gateway configs + config_ids = [c.id for c in configs] + assert config1.id in config_ids + assert config2.id in config_ids + finally: + config1.delete() + config2.delete() From 8ec1f0fff9fd9fa1c0c6203e0bbe89fad06a1b45 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Wed, 4 Feb 2026 01:56:39 +0000 Subject: [PATCH 6/8] test: consolidate auth mechanism smoketests Consolidate redundant auth mechanism tests into parameterized tests: - Replace 3 separate test methods with 1 parameterized test per file - Remove duplicate test_gateway_config_with_authorization_header - Maintain equivalent test coverage with less code duplication Addresses reviewer feedback on PR #736. Co-Authored-By: Claude Sonnet 4.5 --- .../sdk/test_async_gateway_config.py | 57 ++++++------------- tests/smoketests/sdk/test_gateway_config.py | 57 ++++++------------- 2 files changed, 34 insertions(+), 80 deletions(-) diff --git a/tests/smoketests/sdk/test_async_gateway_config.py b/tests/smoketests/sdk/test_async_gateway_config.py index 138e8a717..c374ea1c7 100644 --- a/tests/smoketests/sdk/test_async_gateway_config.py +++ b/tests/smoketests/sdk/test_async_gateway_config.py @@ -149,52 +149,29 @@ class TestAsyncGatewayConfigAuthMechanisms: """Test different async gateway config auth mechanism types.""" @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_with_bearer_auth(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test creating a gateway config with bearer auth.""" + @pytest.mark.parametrize( + "auth_mechanism,expected_type,expected_key", + [ + ({"type": "bearer"}, "bearer", None), + ({"type": "header", "key": "x-api-key"}, "header", "x-api-key"), + ], + ) + async def test_gateway_config_auth_mechanisms( + self, async_sdk_client: AsyncRunloopSDK, auth_mechanism: dict, expected_type: str, expected_key: str | None + ) -> None: + """Test creating gateway configs with different auth mechanisms.""" gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-bearer"), - endpoint="https://api.bearer-test.com", - auth_mechanism={"type": "bearer"}, + name=unique_name(f"sdk-async-gateway-{expected_type}"), + endpoint=f"https://api.{expected_type}-test.com", + auth_mechanism=auth_mechanism, ) try: info = await gateway_config.get_info() assert info.auth_mechanism is not None - assert info.auth_mechanism.type == "bearer" - finally: - await gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_with_header_auth(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test creating a gateway config with header auth.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-header"), - endpoint="https://api.header-test.com", - auth_mechanism={"type": "header", "key": "x-api-key"}, - ) - - try: - info = await gateway_config.get_info() - assert info.auth_mechanism is not None - assert info.auth_mechanism.type == "header" - assert info.auth_mechanism.key == "x-api-key" - finally: - await gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_with_authorization_header(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test creating a gateway config with Authorization header.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-auth-header"), - endpoint="https://api.auth-header-test.com", - auth_mechanism={"type": "header", "key": "Authorization"}, - ) - - try: - info = await gateway_config.get_info() - assert info.auth_mechanism is not None - assert info.auth_mechanism.type == "header" - assert info.auth_mechanism.key == "Authorization" + assert info.auth_mechanism.type == expected_type + if expected_key is not None: + assert info.auth_mechanism.key == expected_key finally: await gateway_config.delete() diff --git a/tests/smoketests/sdk/test_gateway_config.py b/tests/smoketests/sdk/test_gateway_config.py index 34a43961d..2643852d0 100644 --- a/tests/smoketests/sdk/test_gateway_config.py +++ b/tests/smoketests/sdk/test_gateway_config.py @@ -149,52 +149,29 @@ class TestGatewayConfigAuthMechanisms: """Test different gateway config auth mechanism types.""" @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_with_bearer_auth(self, sdk_client: RunloopSDK) -> None: - """Test creating a gateway config with bearer auth.""" + @pytest.mark.parametrize( + "auth_mechanism,expected_type,expected_key", + [ + ({"type": "bearer"}, "bearer", None), + ({"type": "header", "key": "x-api-key"}, "header", "x-api-key"), + ], + ) + def test_gateway_config_auth_mechanisms( + self, sdk_client: RunloopSDK, auth_mechanism: dict, expected_type: str, expected_key: str | None + ) -> None: + """Test creating gateway configs with different auth mechanisms.""" gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-bearer"), - endpoint="https://api.bearer-test.com", - auth_mechanism={"type": "bearer"}, + name=unique_name(f"sdk-gateway-{expected_type}"), + endpoint=f"https://api.{expected_type}-test.com", + auth_mechanism=auth_mechanism, ) try: info = gateway_config.get_info() assert info.auth_mechanism is not None - assert info.auth_mechanism.type == "bearer" - finally: - gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_with_header_auth(self, sdk_client: RunloopSDK) -> None: - """Test creating a gateway config with header auth.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-header"), - endpoint="https://api.header-test.com", - auth_mechanism={"type": "header", "key": "x-api-key"}, - ) - - try: - info = gateway_config.get_info() - assert info.auth_mechanism is not None - assert info.auth_mechanism.type == "header" - assert info.auth_mechanism.key == "x-api-key" - finally: - gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_with_authorization_header(self, sdk_client: RunloopSDK) -> None: - """Test creating a gateway config with Authorization header.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-auth-header"), - endpoint="https://api.auth-header-test.com", - auth_mechanism={"type": "header", "key": "Authorization"}, - ) - - try: - info = gateway_config.get_info() - assert info.auth_mechanism is not None - assert info.auth_mechanism.type == "header" - assert info.auth_mechanism.key == "Authorization" + assert info.auth_mechanism.type == expected_type + if expected_key is not None: + assert info.auth_mechanism.key == expected_key finally: gateway_config.delete() From 65377134a25eefc231444799021b7d5ae28c237f Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 18:03:49 -0800 Subject: [PATCH 7/8] cp dines --- tests/smoketests/sdk/test_async_gateway_config.py | 3 ++- tests/smoketests/sdk/test_gateway_config.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/smoketests/sdk/test_async_gateway_config.py b/tests/smoketests/sdk/test_async_gateway_config.py index c374ea1c7..5d2e67e50 100644 --- a/tests/smoketests/sdk/test_async_gateway_config.py +++ b/tests/smoketests/sdk/test_async_gateway_config.py @@ -6,6 +6,7 @@ from runloop_api_client.sdk import AsyncRunloopSDK from tests.smoketests.utils import unique_name +from runloop_api_client.types.gateway_config_create_params import AuthMechanism pytestmark = [pytest.mark.smoketest, pytest.mark.asyncio] @@ -157,7 +158,7 @@ class TestAsyncGatewayConfigAuthMechanisms: ], ) async def test_gateway_config_auth_mechanisms( - self, async_sdk_client: AsyncRunloopSDK, auth_mechanism: dict, expected_type: str, expected_key: str | None + self, async_sdk_client: AsyncRunloopSDK, auth_mechanism: AuthMechanism, expected_type: str, expected_key: str | None ) -> None: """Test creating gateway configs with different auth mechanisms.""" gateway_config = await async_sdk_client.gateway_config.create( diff --git a/tests/smoketests/sdk/test_gateway_config.py b/tests/smoketests/sdk/test_gateway_config.py index 2643852d0..e57dd0931 100644 --- a/tests/smoketests/sdk/test_gateway_config.py +++ b/tests/smoketests/sdk/test_gateway_config.py @@ -6,6 +6,7 @@ from runloop_api_client.sdk import RunloopSDK from tests.smoketests.utils import unique_name +from runloop_api_client.types.gateway_config_create_params import AuthMechanism pytestmark = [pytest.mark.smoketest] @@ -157,7 +158,7 @@ class TestGatewayConfigAuthMechanisms: ], ) def test_gateway_config_auth_mechanisms( - self, sdk_client: RunloopSDK, auth_mechanism: dict, expected_type: str, expected_key: str | None + self, sdk_client: RunloopSDK, auth_mechanism: AuthMechanism, expected_type: str, expected_key: str | None ) -> None: """Test creating gateway configs with different auth mechanisms.""" gateway_config = sdk_client.gateway_config.create( From d6ce334d28d77d5ed5272e3c6b02b299062b59f6 Mon Sep 17 00:00:00 2001 From: Alexander Dines Date: Tue, 3 Feb 2026 18:10:43 -0800 Subject: [PATCH 8/8] cp dines --- .../sdk/test_async_gateway_config.py | 179 +++--------------- tests/smoketests/sdk/test_gateway_config.py | 179 +++--------------- 2 files changed, 52 insertions(+), 306 deletions(-) diff --git a/tests/smoketests/sdk/test_async_gateway_config.py b/tests/smoketests/sdk/test_async_gateway_config.py index 5d2e67e50..1ff4959d1 100644 --- a/tests/smoketests/sdk/test_async_gateway_config.py +++ b/tests/smoketests/sdk/test_async_gateway_config.py @@ -6,7 +6,6 @@ from runloop_api_client.sdk import AsyncRunloopSDK from tests.smoketests.utils import unique_name -from runloop_api_client.types.gateway_config_create_params import AuthMechanism pytestmark = [pytest.mark.smoketest, pytest.mark.asyncio] @@ -14,11 +13,12 @@ class TestAsyncGatewayConfigLifecycle: - """Test basic async gateway config lifecycle operations.""" + """Test async gateway config lifecycle operations.""" @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_create(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test creating a gateway config.""" + async def test_gateway_config_full_lifecycle(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test complete gateway config lifecycle: create, get_info, update, delete.""" + # Create name = unique_name("sdk-async-gateway-config") gateway_config = await async_sdk_client.gateway_config.create( name=name, @@ -31,188 +31,57 @@ async def test_gateway_config_create(self, async_sdk_client: AsyncRunloopSDK) -> assert gateway_config is not None assert gateway_config.id is not None assert len(gateway_config.id) > 0 - finally: - await gateway_config.delete() - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_get_info(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test retrieving gateway config information.""" - name = unique_name("sdk-async-gateway-config-info") - gateway_config = await async_sdk_client.gateway_config.create( - name=name, - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - description="Async test gateway config for get_info", - ) - - try: + # Get info info = await gateway_config.get_info() - assert info.id == gateway_config.id assert info.name == name assert info.endpoint == "https://api.example.com" - assert info.description == "Async test gateway config for get_info" + assert info.description == "SDK async smoke test gateway config" assert info.auth_mechanism is not None assert info.auth_mechanism.type == "bearer" - finally: - await gateway_config.delete() - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_update(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test updating a gateway config.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-config-update"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - description="Original async description", - ) - - try: - # Update the gateway config + # Update name and description updated_name = unique_name("sdk-async-gateway-config-updated") result = await gateway_config.update( name=updated_name, description="Updated async description", ) - - assert result is not None assert result.name == updated_name assert result.description == "Updated async description" - # Verify update persisted - info = await gateway_config.get_info() - assert info.name == updated_name - assert info.description == "Updated async description" - finally: - await gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_update_endpoint(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test updating gateway config endpoint.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-config-endpoint"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - ) - - try: + # Update endpoint result = await gateway_config.update( endpoint="https://api.updated-example.com", ) - assert result.endpoint == "https://api.updated-example.com" - # Verify update persisted - info = await gateway_config.get_info() - assert info.endpoint == "https://api.updated-example.com" - finally: - await gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_update_auth_mechanism(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test updating gateway config auth mechanism.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-config-auth"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - ) - - try: + # Update auth mechanism result = await gateway_config.update( auth_mechanism={"type": "header", "key": "x-api-key"}, ) - assert result.auth_mechanism.type == "header" assert result.auth_mechanism.key == "x-api-key" - # Verify update persisted + # Verify all updates persisted info = await gateway_config.get_info() + assert info.name == updated_name + assert info.description == "Updated async description" + assert info.endpoint == "https://api.updated-example.com" assert info.auth_mechanism.type == "header" assert info.auth_mechanism.key == "x-api-key" finally: - await gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_gateway_config_delete(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test deleting a gateway config.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-config-delete"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - ) - - result = await gateway_config.delete() - - assert result is not None - - -class TestAsyncGatewayConfigAuthMechanisms: - """Test different async gateway config auth mechanism types.""" - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - @pytest.mark.parametrize( - "auth_mechanism,expected_type,expected_key", - [ - ({"type": "bearer"}, "bearer", None), - ({"type": "header", "key": "x-api-key"}, "header", "x-api-key"), - ], - ) - async def test_gateway_config_auth_mechanisms( - self, async_sdk_client: AsyncRunloopSDK, auth_mechanism: AuthMechanism, expected_type: str, expected_key: str | None - ) -> None: - """Test creating gateway configs with different auth mechanisms.""" - gateway_config = await async_sdk_client.gateway_config.create( - name=unique_name(f"sdk-async-gateway-{expected_type}"), - endpoint=f"https://api.{expected_type}-test.com", - auth_mechanism=auth_mechanism, - ) - - try: - info = await gateway_config.get_info() - assert info.auth_mechanism is not None - assert info.auth_mechanism.type == expected_type - if expected_key is not None: - assert info.auth_mechanism.key == expected_key - finally: - await gateway_config.delete() + # Delete + result = await gateway_config.delete() + assert result is not None class TestAsyncGatewayConfigListing: """Test async gateway config listing and retrieval operations.""" @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_list_gateway_configs(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test listing gateway configs.""" - configs = await async_sdk_client.gateway_config.list(limit=10) - - assert isinstance(configs, list) - # List might be empty or have system configs, that's okay - assert len(configs) >= 0 - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_get_gateway_config_by_id(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test retrieving gateway config by ID.""" - # Create a gateway config - created = await async_sdk_client.gateway_config.create( - name=unique_name("sdk-async-gateway-config-retrieve"), - endpoint="https://api.retrieve-test.com", - auth_mechanism={"type": "bearer"}, - ) - - try: - # Retrieve it by ID - retrieved = async_sdk_client.gateway_config.from_id(created.id) - assert retrieved.id == created.id - - # Verify it's the same gateway config - info = await retrieved.get_info() - assert info.id == created.id - finally: - await created.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - async def test_list_gateway_configs_with_limit(self, async_sdk_client: AsyncRunloopSDK) -> None: - """Test listing gateway configs with a limit.""" + async def test_gateway_config_list_and_retrieve(self, async_sdk_client: AsyncRunloopSDK) -> None: + """Test listing gateway configs and retrieving by ID.""" # Create two gateway configs config1 = await async_sdk_client.gateway_config.create( name=unique_name("sdk-async-gateway-config-list-1"), @@ -222,18 +91,22 @@ async def test_list_gateway_configs_with_limit(self, async_sdk_client: AsyncRunl config2 = await async_sdk_client.gateway_config.create( name=unique_name("sdk-async-gateway-config-list-2"), endpoint="https://api.list-test-2.com", - auth_mechanism={"type": "bearer"}, + auth_mechanism={"type": "header", "key": "x-api-key"}, ) try: - # List with limit + # List gateway configs configs = await async_sdk_client.gateway_config.list(limit=100) - assert isinstance(configs, list) - # Should find our gateway configs config_ids = [c.id for c in configs] assert config1.id in config_ids assert config2.id in config_ids + + # Retrieve by ID + retrieved = async_sdk_client.gateway_config.from_id(config1.id) + assert retrieved.id == config1.id + info = await retrieved.get_info() + assert info.id == config1.id finally: await config1.delete() await config2.delete() diff --git a/tests/smoketests/sdk/test_gateway_config.py b/tests/smoketests/sdk/test_gateway_config.py index e57dd0931..eac45a122 100644 --- a/tests/smoketests/sdk/test_gateway_config.py +++ b/tests/smoketests/sdk/test_gateway_config.py @@ -6,7 +6,6 @@ from runloop_api_client.sdk import RunloopSDK from tests.smoketests.utils import unique_name -from runloop_api_client.types.gateway_config_create_params import AuthMechanism pytestmark = [pytest.mark.smoketest] @@ -14,11 +13,12 @@ class TestGatewayConfigLifecycle: - """Test basic gateway config lifecycle operations.""" + """Test gateway config lifecycle operations.""" @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_create(self, sdk_client: RunloopSDK) -> None: - """Test creating a gateway config.""" + def test_gateway_config_full_lifecycle(self, sdk_client: RunloopSDK) -> None: + """Test complete gateway config lifecycle: create, get_info, update, delete.""" + # Create name = unique_name("sdk-gateway-config") gateway_config = sdk_client.gateway_config.create( name=name, @@ -31,188 +31,57 @@ def test_gateway_config_create(self, sdk_client: RunloopSDK) -> None: assert gateway_config is not None assert gateway_config.id is not None assert len(gateway_config.id) > 0 - finally: - gateway_config.delete() - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_get_info(self, sdk_client: RunloopSDK) -> None: - """Test retrieving gateway config information.""" - name = unique_name("sdk-gateway-config-info") - gateway_config = sdk_client.gateway_config.create( - name=name, - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - description="Test gateway config for get_info", - ) - - try: + # Get info info = gateway_config.get_info() - assert info.id == gateway_config.id assert info.name == name assert info.endpoint == "https://api.example.com" - assert info.description == "Test gateway config for get_info" + assert info.description == "SDK smoke test gateway config" assert info.auth_mechanism is not None assert info.auth_mechanism.type == "bearer" - finally: - gateway_config.delete() - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_update(self, sdk_client: RunloopSDK) -> None: - """Test updating a gateway config.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-config-update"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - description="Original description", - ) - - try: - # Update the gateway config + # Update name and description updated_name = unique_name("sdk-gateway-config-updated") result = gateway_config.update( name=updated_name, description="Updated description", ) - - assert result is not None assert result.name == updated_name assert result.description == "Updated description" - # Verify update persisted - info = gateway_config.get_info() - assert info.name == updated_name - assert info.description == "Updated description" - finally: - gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_update_endpoint(self, sdk_client: RunloopSDK) -> None: - """Test updating gateway config endpoint.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-config-endpoint"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - ) - - try: + # Update endpoint result = gateway_config.update( endpoint="https://api.updated-example.com", ) - assert result.endpoint == "https://api.updated-example.com" - # Verify update persisted - info = gateway_config.get_info() - assert info.endpoint == "https://api.updated-example.com" - finally: - gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_update_auth_mechanism(self, sdk_client: RunloopSDK) -> None: - """Test updating gateway config auth mechanism.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-config-auth"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - ) - - try: + # Update auth mechanism result = gateway_config.update( auth_mechanism={"type": "header", "key": "x-api-key"}, ) - assert result.auth_mechanism.type == "header" assert result.auth_mechanism.key == "x-api-key" - # Verify update persisted + # Verify all updates persisted info = gateway_config.get_info() + assert info.name == updated_name + assert info.description == "Updated description" + assert info.endpoint == "https://api.updated-example.com" assert info.auth_mechanism.type == "header" assert info.auth_mechanism.key == "x-api-key" finally: - gateway_config.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_gateway_config_delete(self, sdk_client: RunloopSDK) -> None: - """Test deleting a gateway config.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-config-delete"), - endpoint="https://api.example.com", - auth_mechanism={"type": "bearer"}, - ) - - result = gateway_config.delete() - - assert result is not None - - -class TestGatewayConfigAuthMechanisms: - """Test different gateway config auth mechanism types.""" - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - @pytest.mark.parametrize( - "auth_mechanism,expected_type,expected_key", - [ - ({"type": "bearer"}, "bearer", None), - ({"type": "header", "key": "x-api-key"}, "header", "x-api-key"), - ], - ) - def test_gateway_config_auth_mechanisms( - self, sdk_client: RunloopSDK, auth_mechanism: AuthMechanism, expected_type: str, expected_key: str | None - ) -> None: - """Test creating gateway configs with different auth mechanisms.""" - gateway_config = sdk_client.gateway_config.create( - name=unique_name(f"sdk-gateway-{expected_type}"), - endpoint=f"https://api.{expected_type}-test.com", - auth_mechanism=auth_mechanism, - ) - - try: - info = gateway_config.get_info() - assert info.auth_mechanism is not None - assert info.auth_mechanism.type == expected_type - if expected_key is not None: - assert info.auth_mechanism.key == expected_key - finally: - gateway_config.delete() + # Delete + result = gateway_config.delete() + assert result is not None class TestGatewayConfigListing: """Test gateway config listing and retrieval operations.""" @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_list_gateway_configs(self, sdk_client: RunloopSDK) -> None: - """Test listing gateway configs.""" - configs = sdk_client.gateway_config.list(limit=10) - - assert isinstance(configs, list) - # List might be empty or have system configs, that's okay - assert len(configs) >= 0 - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_get_gateway_config_by_id(self, sdk_client: RunloopSDK) -> None: - """Test retrieving gateway config by ID.""" - # Create a gateway config - created = sdk_client.gateway_config.create( - name=unique_name("sdk-gateway-config-retrieve"), - endpoint="https://api.retrieve-test.com", - auth_mechanism={"type": "bearer"}, - ) - - try: - # Retrieve it by ID - retrieved = sdk_client.gateway_config.from_id(created.id) - assert retrieved.id == created.id - - # Verify it's the same gateway config - info = retrieved.get_info() - assert info.id == created.id - finally: - created.delete() - - @pytest.mark.timeout(THIRTY_SECOND_TIMEOUT) - def test_list_gateway_configs_with_limit(self, sdk_client: RunloopSDK) -> None: - """Test listing gateway configs with a limit.""" + def test_gateway_config_list_and_retrieve(self, sdk_client: RunloopSDK) -> None: + """Test listing gateway configs and retrieving by ID.""" # Create two gateway configs config1 = sdk_client.gateway_config.create( name=unique_name("sdk-gateway-config-list-1"), @@ -222,18 +91,22 @@ def test_list_gateway_configs_with_limit(self, sdk_client: RunloopSDK) -> None: config2 = sdk_client.gateway_config.create( name=unique_name("sdk-gateway-config-list-2"), endpoint="https://api.list-test-2.com", - auth_mechanism={"type": "bearer"}, + auth_mechanism={"type": "header", "key": "x-api-key"}, ) try: - # List with limit + # List gateway configs configs = sdk_client.gateway_config.list(limit=100) - assert isinstance(configs, list) - # Should find our gateway configs config_ids = [c.id for c in configs] assert config1.id in config_ids assert config2.id in config_ids + + # Retrieve by ID + retrieved = sdk_client.gateway_config.from_id(config1.id) + assert retrieved.id == config1.id + info = retrieved.get_info() + assert info.id == config1.id finally: config1.delete() config2.delete()