From 43e3c8a0fb8344fb4dbff167c3a158b3a387e709 Mon Sep 17 00:00:00 2001 From: Torsten Kilias Date: Tue, 12 May 2026 15:31:28 +0200 Subject: [PATCH] Wrap generated OpenAPI models behind facade --- exasol/saas/client/__init__.py | 2 +- exasol/saas/client/api_access.py | 146 +++------ exasol/saas/client/openapi_facade.py | 363 +++++++++++++++++++++ exasol/saas/client/openapi_facade_types.py | 129 ++++++++ test/integration/conftest.py | 4 +- test/integration/test_databases.py | 2 +- test/unit/test_api_access.py | 35 +- test/unit/test_ensure_type.py | 13 +- 8 files changed, 577 insertions(+), 117 deletions(-) create mode 100644 exasol/saas/client/openapi_facade.py create mode 100644 exasol/saas/client/openapi_facade_types.py diff --git a/exasol/saas/client/__init__.py b/exasol/saas/client/__init__.py index b6e0c0a..8324215 100644 --- a/exasol/saas/client/__init__.py +++ b/exasol/saas/client/__init__.py @@ -7,7 +7,7 @@ ) from typing import Final -from exasol.saas.client.openapi.models.status import Status +from exasol.saas.client.openapi_facade_types import Status SAAS_HOST = "https://cloud.exasol.com" diff --git a/exasol/saas/client/api_access.py b/exasol/saas/client/api_access.py index 2c0d8a4..9dc6aa6 100644 --- a/exasol/saas/client/api_access.py +++ b/exasol/saas/client/api_access.py @@ -28,31 +28,17 @@ wait_fixed, ) -from exasol.saas.client import ( - Limits, - openapi, -) -from exasol.saas.client.openapi.api.clusters import ( - get_cluster_connection, - list_clusters, -) -from exasol.saas.client.openapi.api.databases import ( - create_database, - delete_database, - get_database, - list_databases, -) -from exasol.saas.client.openapi.api.security import ( - add_allowed_ip, - delete_allowed_ip, - list_allowed_i_ps, -) -from exasol.saas.client.openapi.models import ( +from exasol.saas.client import Limits +from exasol.saas.client.openapi_facade import ( + AllowedIP, ApiError, + AuthenticatedClient, + Cluster, + ClusterConnection, ExasolDatabase, Status, ) -from exasol.saas.client.openapi.types import UNSET +from exasol.saas.client.openapi_facade import OpenApiFacade as OpenApiClient LOG = logging.getLogger(__name__) LOG.setLevel(logging.INFO) @@ -138,34 +124,23 @@ def create_saas_client( host: str, pat: str, raise_on_unexpected_status: bool = True, -) -> openapi.AuthenticatedClient: - return openapi.AuthenticatedClient( - base_url=host, - token=pat, +) -> AuthenticatedClient: + return OpenApiClient( + host=host, + pat=pat, raise_on_unexpected_status=raise_on_unexpected_status, ) def _get_database_id( account_id: str, - client: openapi.AuthenticatedClient, + client: AuthenticatedClient, database_name: str, ) -> str: """ Finds the database id, given the database name. """ - dbs = list_databases.sync(account_id, client=client) - dbs = list( - filter( - lambda db: (db.name == database_name) # type: ignore - and (db.deleted_at is UNSET) # type: ignore - and (db.deleted_by is UNSET), - dbs, # type: ignore - ) - ) # type: ignore - if not dbs: - raise RuntimeError(f"SaaS database {database_name} was not found.") - return dbs[0].id + return client.find_database_id(account_id, database_name) def get_database_id( @@ -223,15 +198,18 @@ def get_connection_params( database_id = _get_database_id( account_id, client, database_name=database_name ) - clusters = list_clusters.sync(account_id, database_id, client=client) + clusters = ensure_type( + list, + client.list_clusters(account_id, database_id), + "Failed to list clusters of " + f"host {host}, account {account_id}, database {database_id}", + ) cluster_id = next( filter(lambda cl: cl.main_cluster, clusters) # type: ignore ).id - resp = get_cluster_connection.sync( - account_id, database_id, cluster_id, client=client - ) + resp = client.get_cluster_connection(account_id, database_id, cluster_id) connection = ensure_type( - openapi.models.ClusterConnection, + ClusterConnection, resp, "Failed to get the connection data to" f" host {host}, account {account_id}," @@ -251,7 +229,7 @@ class OpenApiAccess: planned to only use fixture ``saas_database_id()``. """ - def __init__(self, client: openapi.AuthenticatedClient, account_id: str): + def __init__(self, client: AuthenticatedClient, account_id: str): self._client = client self._account_id = account_id @@ -262,29 +240,14 @@ def create_database( region: str = "eu-central-1", idle_time: timedelta | None = None, ) -> ExasolDatabase | None: - def minutes(x: timedelta) -> int: - return x.seconds // 60 - idle_time = idle_time or Limits.AUTOSTOP_MIN_IDLE_TIME - cluster_spec = openapi.models.CreateDatabaseInitialCluster( - name="my-cluster", - size=cluster_size, - auto_stop=openapi.models.AutoStop( - enabled=True, - idle_time=minutes(idle_time), - ), - ) LOG.info("Creating database %s", name) - resp = create_database.sync( + resp = self._client.create_database( self._account_id, - client=self._client, - body=openapi.models.CreateDatabase( - name=name, - initial_cluster=cluster_spec, - provider="aws", - region=region, - stream_type="innovation-release", - ), + name=name, + cluster_size=cluster_size, + region=region, + idle_time=idle_time, ) database = ensure_type( ExasolDatabase, resp, f"Failed to create database {name}" @@ -341,10 +304,9 @@ def is_retry(resp: ApiError) -> bool: ) def delete_with_retry() -> None: LOG.info("- Trying to delete ...") - resp = delete_database.sync( + resp = self._client.delete_database( self._account_id, database_id, - client=self._client, ) if not isinstance(resp, ApiError): # success @@ -366,7 +328,7 @@ def delete_with_retry() -> None: raise DatabaseDeleteError(msg) from ex def list_database_ids(self) -> Iterable[str]: - resp = list_databases.sync(self._account_id, client=self._client) or [] + resp = self._client.list_databases(self._account_id) or [] # actually list[ExasolDatabase] dbs = ensure_type(list, resp, "Failed to list databases") return (db.id for db in dbs) @@ -397,11 +359,7 @@ def get_database( self, database_id: str, ) -> ExasolDatabase | None: - resp = get_database.sync( - self._account_id, - database_id, - client=self._client, - ) + resp = self._client.get_database(self._account_id, database_id) return ensure_type( ExasolDatabase, resp, f"Failed to get database {database_id}" ) @@ -430,16 +388,9 @@ def poll_status() -> Status: def clusters( self, database_id: str, - ) -> list[openapi.models.Cluster] | None: - resp = ( - list_clusters.sync( - self._account_id, - database_id, - client=self._client, - ) - or [] - ) - # actually list[openapi.models.Cluster] + ) -> list[Cluster] | None: + resp = self._client.list_clusters(self._account_id, database_id) or [] + # actually list[Cluster] return ensure_type( list, resp, f"Failed to list clusters of database {database_id}" ) @@ -448,54 +399,47 @@ def get_connection( self, database_id: str, cluster_id: str, - ) -> openapi.models.ClusterConnection | None: - resp = get_cluster_connection.sync( - self._account_id, - database_id, - cluster_id, - client=self._client, + ) -> ClusterConnection | None: + resp = self._client.get_cluster_connection( + self._account_id, database_id, cluster_id ) return ensure_type( - openapi.models.ClusterConnection, + ClusterConnection, resp, "Failed to retrieve a connection to " f"database {database_id} cluster {cluster_id}", ) def list_allowed_ip_ids(self) -> Iterable[str]: - resp = list_allowed_i_ps.sync(self._account_id, client=self._client) or [] - # actually list[openapi.models.AllowedIP] + resp = self._client.list_allowed_ips(self._account_id) or [] + # actually list[AllowedIP] ips = ensure_type(list, resp, "Failed to retrieve the list of allowed ips") return (x.id for x in ips) def add_allowed_ip( self, cidr_ip: str = "0.0.0.0/0", - ) -> openapi.models.AllowedIP | None: + ) -> AllowedIP | None: """ Suggested values for cidr_ip: * 185.17.207.78/32 * 0.0.0.0/0 = all ipv4 * ::/0 = all ipv6 """ - rule = openapi.models.CreateAllowedIP( - name=timestamp_name(), - cidr_ip=cidr_ip, - ) - resp = add_allowed_ip.sync( + resp = self._client.add_allowed_ip( self._account_id, - client=self._client, - body=rule, + cidr_ip=cidr_ip, + name=timestamp_name(), ) return ensure_type( - openapi.models.AllowedIP, + AllowedIP, resp, f"Failed to add allowed IP address {cidr_ip}", ) def delete_allowed_ip(self, id: str, ignore_failures=False) -> Any | None: with self._ignore_failures(ignore_failures) as client: - return delete_allowed_ip.sync(self._account_id, id, client=client) + return client.delete_allowed_ip(self._account_id, id) @contextmanager def allowed_ip( diff --git a/exasol/saas/client/openapi_facade.py b/exasol/saas/client/openapi_facade.py new file mode 100644 index 0000000..a9ce4d4 --- /dev/null +++ b/exasol/saas/client/openapi_facade.py @@ -0,0 +1,363 @@ +from __future__ import annotations + +from datetime import timedelta +from typing import ( + Any, + Callable, + TypeAlias, + TypeVar, +) + +from exasol.saas.client import openapi +from exasol.saas.client.openapi.api.clusters import ( + get_cluster_connection, + list_clusters, +) +from exasol.saas.client.openapi.api.databases import ( + create_database, + delete_database, + get_database, + list_databases, +) +from exasol.saas.client.openapi.api.security import ( + add_allowed_ip, + delete_allowed_ip, + list_allowed_i_ps, +) +from exasol.saas.client.openapi.models import ( + AllowedIP as GeneratedAllowedIP, +) +from exasol.saas.client.openapi.models import ( + ApiError as GeneratedApiError, +) +from exasol.saas.client.openapi.models import ( + AutoStop as GeneratedAutoStop, +) +from exasol.saas.client.openapi.models import ( + Cluster as GeneratedCluster, +) +from exasol.saas.client.openapi.models import ( + ClusterConnection as GeneratedClusterConnection, +) +from exasol.saas.client.openapi.models import ( + ClusterSettings as GeneratedClusterSettings, +) +from exasol.saas.client.openapi.models import ( + ConnectionIPs as GeneratedConnectionIPs, +) +from exasol.saas.client.openapi.models import ( + CreateAllowedIP, + CreateDatabase, + CreateDatabaseInitialCluster, +) +from exasol.saas.client.openapi.models import ( + ExasolDatabase as GeneratedExasolDatabase, +) +from exasol.saas.client.openapi.models import ( + ExasolDatabaseClusters as GeneratedExasolDatabaseClusters, +) +from exasol.saas.client.openapi.types import UNSET +from exasol.saas.client.openapi_facade_types import ( + AllowedIP, + ApiError, + AutoStop, + Cluster, + ClusterConnection, + ClusterSettings, + ConnectionIPs, + ExasolDatabase, + ExasolDatabaseClusters, + Status, +) + + +def _minutes(duration: timedelta) -> int: + return duration.seconds // 60 + + +def _optional(value: Any) -> Any | None: + return None if value is UNSET else value + + +def _convert_status(value: Any) -> Status: + return Status(str(value)) + + +def _convert_api_error(error: GeneratedApiError) -> ApiError: + return ApiError( + status=error.status, + message=error.message, + request_id=error.request_id, + path=error.path, + method=error.method, + log_id=error.log_id, + handler=error.handler, + timestamp=error.timestamp, + causes=_optional(error.causes), + ) + + +def _convert_database_clusters( + clusters: GeneratedExasolDatabaseClusters, +) -> ExasolDatabaseClusters: + return ExasolDatabaseClusters( + total=clusters.total, + running=clusters.running, + ) + + +def _convert_database(database: GeneratedExasolDatabase) -> ExasolDatabase: + integrations = _optional(database.integrations) + return ExasolDatabase( + status=_convert_status(database.status), + id=database.id, + name=database.name, + clusters=_convert_database_clusters(database.clusters), + provider=database.provider, + region=database.region, + created_at=database.created_at, + created_by=database.created_by, + integrations=integrations, + deleted_by=_optional(database.deleted_by), + deleted_at=_optional(database.deleted_at), + ) + + +def _convert_cluster_settings(settings: GeneratedClusterSettings) -> ClusterSettings: + return ClusterSettings( + offload_enabled=settings.offload_enabled, + offload_timeout_min=settings.offload_timeout_min, + ) + + +def _convert_auto_stop(auto_stop: GeneratedAutoStop) -> AutoStop: + return AutoStop( + enabled=auto_stop.enabled, + idle_time=auto_stop.idle_time, + ) + + +def _convert_cluster(cluster: GeneratedCluster) -> Cluster: + auto_stop = _optional(cluster.auto_stop) + return Cluster( + status=_convert_status(cluster.status), + id=cluster.id, + name=cluster.name, + size=cluster.size, + family_name=cluster.family_name, + created_at=cluster.created_at, + created_by=cluster.created_by, + main_cluster=cluster.main_cluster, + settings=_convert_cluster_settings(cluster.settings), + deleted_at=_optional(cluster.deleted_at), + deleted_by=_optional(cluster.deleted_by), + auto_stop=_convert_auto_stop(auto_stop) if auto_stop else None, + ) + + +def _convert_connection_ips(ips: GeneratedConnectionIPs) -> ConnectionIPs: + return ConnectionIPs(private=ips.private, public=ips.public) + + +def _convert_cluster_connection( + connection: GeneratedClusterConnection, +) -> ClusterConnection: + return ClusterConnection( + dns=connection.dns, + port=connection.port, + jdbc=connection.jdbc, + ips=_convert_connection_ips(connection.ips), + db_username=connection.db_username, + ) + + +def _convert_allowed_ip(allowed_ip: GeneratedAllowedIP) -> AllowedIP: + return AllowedIP( + id=allowed_ip.id, + name=allowed_ip.name, + cidr_ip=allowed_ip.cidr_ip, + created_at=allowed_ip.created_at, + created_by=allowed_ip.created_by, + deleted_by=_optional(allowed_ip.deleted_by), + deleted_at=_optional(allowed_ip.deleted_at), + ) + + +TGenerated = TypeVar("TGenerated") +TConverted = TypeVar("TConverted") + + +def _map_result( + result: TGenerated | GeneratedApiError | None, + converter: Callable[[TGenerated], TConverted], +) -> TConverted | ApiError | None: + if result is None: + return None + if isinstance(result, GeneratedApiError): + return _convert_api_error(result) + return converter(result) + + +class OpenApiFacade: + def __init__( + self, + host: str, + pat: str, + raise_on_unexpected_status: bool = True, + ): + self._client = openapi.AuthenticatedClient( + base_url=host, + token=pat, + raise_on_unexpected_status=raise_on_unexpected_status, + ) + + @property + def raise_on_unexpected_status(self) -> bool: + return self._client.raise_on_unexpected_status + + @raise_on_unexpected_status.setter + def raise_on_unexpected_status(self, value: bool) -> None: + self._client.raise_on_unexpected_status = value + + def __enter__(self) -> OpenApiFacade: + self._client.__enter__() + return self + + def __exit__(self, *args, **kwargs) -> None: + self._client.__exit__(*args, **kwargs) + + def list_databases( + self, account_id: str + ) -> list[ExasolDatabase] | ApiError | None: + result = list_databases.sync(account_id, client=self._client) + if isinstance(result, list): + return [_convert_database(item) for item in result] + return _map_result(result, _convert_database) + + def find_database_id(self, account_id: str, database_name: str) -> str: + databases = self.list_databases(account_id) + if not isinstance(databases, list): + raise RuntimeError(f"SaaS database {database_name} was not found.") + + matches = [ + db + for db in databases + if db.name == database_name + and db.deleted_at is None + and db.deleted_by is None + ] + if not matches: + raise RuntimeError(f"SaaS database {database_name} was not found.") + return matches[0].id + + def create_database( + self, + account_id: str, + name: str, + cluster_size: str, + region: str, + idle_time: timedelta, + ) -> ExasolDatabase | ApiError | None: + cluster_spec = CreateDatabaseInitialCluster( + name="my-cluster", + size=cluster_size, + auto_stop=GeneratedAutoStop( + enabled=True, + idle_time=_minutes(idle_time), + ), + ) + body = CreateDatabase( + name=name, + initial_cluster=cluster_spec, + provider="aws", + region=region, + stream_type="innovation-release", + ) + result = create_database.sync( + account_id, + client=self._client, + body=body, + ) + return _map_result(result, _convert_database) + + def get_database( + self, account_id: str, database_id: str + ) -> ExasolDatabase | ApiError | None: + result = get_database.sync(account_id, database_id, client=self._client) + return _map_result(result, _convert_database) + + def delete_database( + self, account_id: str, database_id: str + ) -> Any | ApiError | None: + result = delete_database.sync(account_id, database_id, client=self._client) + if isinstance(result, GeneratedApiError): + return _convert_api_error(result) + return result + + def list_clusters( + self, account_id: str, database_id: str + ) -> list[Cluster] | ApiError | None: + result = list_clusters.sync(account_id, database_id, client=self._client) + if isinstance(result, list): + return [_convert_cluster(item) for item in result] + return _map_result(result, _convert_cluster) + + def get_cluster_connection( + self, account_id: str, database_id: str, cluster_id: str + ) -> ClusterConnection | ApiError | None: + result = get_cluster_connection.sync( + account_id, database_id, cluster_id, client=self._client + ) + return _map_result(result, _convert_cluster_connection) + + def list_allowed_ips( + self, account_id: str + ) -> list[AllowedIP] | ApiError | None: + result = list_allowed_i_ps.sync(account_id, client=self._client) + if isinstance(result, list): + return [_convert_allowed_ip(item) for item in result] + return _map_result(result, _convert_allowed_ip) + + def add_allowed_ip( + self, + account_id: str, + cidr_ip: str, + name: str, + ) -> AllowedIP | ApiError | None: + rule = CreateAllowedIP(name=name, cidr_ip=cidr_ip) + result = add_allowed_ip.sync( + account_id, + client=self._client, + body=rule, + ) + return _map_result(result, _convert_allowed_ip) + + def delete_allowed_ip( + self, account_id: str, allowed_ip_id: str + ) -> Any | ApiError | None: + result = delete_allowed_ip.sync( + account_id, + allowed_ip_id, + client=self._client, + ) + if isinstance(result, GeneratedApiError): + return _convert_api_error(result) + return result + + +AuthenticatedClient: TypeAlias = OpenApiFacade + +__all__ = ( + "AllowedIP", + "ApiError", + "AuthenticatedClient", + "AutoStop", + "Cluster", + "ClusterConnection", + "ClusterSettings", + "ConnectionIPs", + "ExasolDatabase", + "ExasolDatabaseClusters", + "OpenApiFacade", + "Status", +) diff --git a/exasol/saas/client/openapi_facade_types.py b/exasol/saas/client/openapi_facade_types.py new file mode 100644 index 0000000..4caf85c --- /dev/null +++ b/exasol/saas/client/openapi_facade_types.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime +from enum import Enum +from typing import Any + + +class Status(str, Enum): + CREATING = "creating" + DELETED = "deleted" + DELETING = "deleting" + ERROR = "error" + MAINTENANCE = "maintenance" + RUNNING = "running" + SCALING = "scaling" + STARTING = "starting" + STOPPED = "stopped" + STOPPING = "stopping" + TOCREATE = "tocreate" + TODELETE = "todelete" + TOSCALE = "toscale" + TOSTART = "tostart" + TOSTOP = "tostop" + + def __str__(self) -> str: + return str(self.value) + + +@dataclass(frozen=True) +class ApiError: + status: float + message: str + request_id: str + path: str + method: str + log_id: str + handler: str + timestamp: str + causes: Any | None = None + + +@dataclass(frozen=True) +class ExasolDatabaseClusters: + total: int + running: int + + +@dataclass(frozen=True) +class ExasolDatabase: + status: Status + id: str + name: str + clusters: ExasolDatabaseClusters + provider: str + region: str + created_at: datetime + created_by: str + integrations: list[Any] | None = None + deleted_by: str | None = None + deleted_at: datetime | None = None + + +@dataclass(frozen=True) +class ClusterSettings: + offload_enabled: bool + offload_timeout_min: int + + +@dataclass(frozen=True) +class AutoStop: + enabled: bool + idle_time: int + + +@dataclass(frozen=True) +class Cluster: + status: Status + id: str + name: str + size: str + family_name: str + created_at: datetime + created_by: str + main_cluster: bool + settings: ClusterSettings + deleted_at: datetime | None = None + deleted_by: str | None = None + auto_stop: AutoStop | None = None + + +@dataclass(frozen=True) +class ConnectionIPs: + private: list[str] + public: list[str] + + +@dataclass(frozen=True) +class ClusterConnection: + dns: str + port: int + jdbc: str + ips: ConnectionIPs + db_username: str + + +@dataclass(frozen=True) +class AllowedIP: + id: str + name: str + cidr_ip: str + created_at: datetime + created_by: str + deleted_by: str | None = None + deleted_at: datetime | None = None + + +__all__ = ( + "AllowedIP", + "ApiError", + "AutoStop", + "Cluster", + "ClusterConnection", + "ClusterSettings", + "ConnectionIPs", + "ExasolDatabase", + "ExasolDatabaseClusters", + "Status", +) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 272ba1b..6d1cf7a 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -2,12 +2,12 @@ import pytest -from exasol.saas.client import openapi from exasol.saas.client.api_access import ( OpenApiAccess, create_saas_client, timestamp_name, ) +from exasol.saas.client.openapi_facade import ExasolDatabase def _env(var: str) -> str: @@ -41,7 +41,7 @@ def api_access(saas_host, saas_pat, saas_account_id) -> OpenApiAccess: @pytest.fixture(scope="session") def saas_database( api_access, database_name -) -> openapi.models.exasol_database.ExasolDatabase: +) -> ExasolDatabase: """ Note: The SaaS instance database returned by this fixture initially will not be operational. The startup takes about 20 minutes. diff --git a/test/integration/test_databases.py b/test/integration/test_databases.py index f338799..941c828 100644 --- a/test/integration/test_databases.py +++ b/test/integration/test_databases.py @@ -11,7 +11,7 @@ from exasol.saas.client.api_access import ( timestamp_name, ) -from exasol.saas.client.openapi.models.exasol_database import ExasolDatabase +from exasol.saas.client.openapi_facade import ExasolDatabase LOG = logging.getLogger(__name__) logging.basicConfig( diff --git a/test/unit/test_api_access.py b/test/unit/test_api_access.py index 0aa4164..a0256b4 100644 --- a/test/unit/test_api_access.py +++ b/test/unit/test_api_access.py @@ -7,9 +7,13 @@ from exasol.saas.client.api_access import ( DatabaseDeleteError, OpenApiAccess, + create_saas_client, timestamp_name, ) -from exasol.saas.client.openapi.models.api_error import ApiError +from exasol.saas.client.openapi_facade import ( + ApiError, + OpenApiFacade, +) def response(status_code: int, message: str, spec=None): @@ -17,7 +21,16 @@ def response(status_code: int, message: str, spec=None): def api_error(status_code: int, message: str): - return response(status_code, message, spec=ApiError) + return ApiError( + status=status_code, + message=message, + request_id="r1", + path="/path", + method="DELETE", + log_id="l1", + handler="handler", + timestamp="now", + ) RETRY = api_error( @@ -31,11 +44,9 @@ def api_mock(): return OpenApiAccess(Mock(), account_id="A1") -def delete_mock(monkeypatch, side_effect) -> Mock: - from exasol.saas.client.api_access import delete_database as api - +def delete_mock(api_mock, side_effect) -> Mock: mock = Mock(side_effect=side_effect) - monkeypatch.setattr(api, "sync", mock) + api_mock._client.delete_database = mock return mock @@ -69,8 +80,8 @@ def retry_timings() -> dict[str, timedelta]: ), ], ) -def test_delete_fail(api_mock, monkeypatch, side_effect, retry_timings) -> None: - delete_mock(monkeypatch, side_effect) +def test_delete_fail(api_mock, side_effect, retry_timings) -> None: + delete_mock(api_mock, side_effect) with pytest.raises(DatabaseDeleteError): api_mock.delete_database("123", **retry_timings) @@ -97,11 +108,10 @@ def test_delete_success( ignore_failures, expected_log_message, api_mock, - monkeypatch, retry_timings, caplog, ) -> None: - delete = delete_mock(monkeypatch, side_effect) + delete = delete_mock(api_mock, side_effect) with not_raises(Exception): api_mock.delete_database( database_id="123", @@ -123,3 +133,8 @@ def test_timestamp_name() -> None: assert len(set(suffixes)) == 3 # the provided tag should follow the hacky timestamp. assert all(tag == "TEST" for tag in tags) + + +def test_create_saas_client_returns_facade() -> None: + client = create_saas_client("https://example.org", "pat") + assert isinstance(client, OpenApiFacade) diff --git a/test/unit/test_ensure_type.py b/test/unit/test_ensure_type.py index 959c12f..4fae61f 100644 --- a/test/unit/test_ensure_type.py +++ b/test/unit/test_ensure_type.py @@ -7,7 +7,7 @@ OpenApiError, ensure_type, ) -from exasol.saas.client.openapi.models.api_error import ApiError +from exasol.saas.client.openapi_facade import ApiError class MyClass: @@ -25,7 +25,16 @@ def test_ensure_type_success(): "object, suffix", [ pytest.param( - Mock(ApiError, message="inner error"), + ApiError( + status=400, + message="inner error", + request_id="r1", + path="/path", + method="GET", + log_id="l1", + handler="handler", + timestamp="now", + ), ": inner error.", id="api_error", ),