Skip to content

Commit 5642779

Browse files
committed
fix: integration tests with explicit config
1 parent aba8fa3 commit 5642779

5 files changed

Lines changed: 79 additions & 64 deletions

File tree

‎src/sap_cloud_sdk/adms/client.py‎

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
)
5959
from sap_cloud_sdk.adms._token_cache import TokenCache
6060
from sap_cloud_sdk.adms.config import AdmsConfig, _make_config_factory
61+
from sap_cloud_sdk.adms.exceptions import ConfigError
6162

6263

6364
# ---------------------------------------------------------------------------
@@ -171,12 +172,15 @@ def create_client(
171172
raise ValueError(
172173
"instance must not be an empty string; omit it to use 'default'"
173174
)
174-
if config is not None:
175-
token_fetcher = IasTokenFetcher(config=config, cache=token_cache)
176-
else:
177-
token_fetcher = IasTokenFetcher(
178-
config=_make_config_factory(instance), cache=token_cache
179-
)
175+
try:
176+
if config is not None:
177+
token_fetcher = IasTokenFetcher(config=config, cache=token_cache)
178+
else:
179+
token_fetcher = IasTokenFetcher(
180+
config=_make_config_factory(instance), cache=token_cache
181+
)
182+
except RuntimeError as exc:
183+
raise ConfigError(str(exc)) from exc
180184
http = AdmsHttp(
181185
config=token_fetcher._config, token_fetcher=token_fetcher, user_jwt=user_jwt
182186
)
@@ -212,12 +216,15 @@ def create_async_client(
212216
raise ValueError(
213217
"instance must not be an empty string; omit it to use 'default'"
214218
)
215-
if config is not None:
216-
token_fetcher = IasTokenFetcher(config=config, cache=token_cache)
217-
else:
218-
token_fetcher = IasTokenFetcher(
219-
config=_make_config_factory(instance), cache=token_cache
220-
)
219+
try:
220+
if config is not None:
221+
token_fetcher = IasTokenFetcher(config=config, cache=token_cache)
222+
else:
223+
token_fetcher = IasTokenFetcher(
224+
config=_make_config_factory(instance), cache=token_cache
225+
)
226+
except RuntimeError as exc:
227+
raise ConfigError(str(exc)) from exc
221228
http = AsyncAdmsHttp(
222229
config=token_fetcher._config,
223230
token_fetcher=token_fetcher,

‎tests/adms/integration/conftest.py‎

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,42 +24,30 @@
2424
AsyncAdmsClient,
2525
create_async_client,
2626
)
27-
from sap_cloud_sdk.adms.config import AdmsConfig, load_from_env_or_mount
2827
from sap_cloud_sdk.adms.exceptions import ConfigError
2928

3029

3130
# ---------------------------------------------------------------------------
32-
# Configuration fixture
31+
# Client fixtures
3332
# ---------------------------------------------------------------------------
3433

3534

3635
@pytest.fixture(scope="session")
37-
def adms_config() -> AdmsConfig:
38-
"""Resolve AdmsConfig from env/secret-mount.
39-
40-
Skips the entire integration suite when required credentials are missing.
41-
"""
36+
def adms_client() -> AdmsClient:
37+
"""Sync AdmsClient wired to the real ADM instance via ConfigFactory."""
4238
try:
43-
return load_from_env_or_mount("default")
39+
return create_client()
4440
except ConfigError as exc:
4541
pytest.skip(f"ADMS integration tests skipped — missing config: {exc}")
4642

4743

48-
# ---------------------------------------------------------------------------
49-
# Client fixtures
50-
# ---------------------------------------------------------------------------
51-
52-
53-
@pytest.fixture(scope="session")
54-
def adms_client(adms_config: AdmsConfig) -> AdmsClient:
55-
"""Sync AdmsClient wired to the real ADM instance."""
56-
return create_client(config=adms_config)
57-
58-
5944
@pytest.fixture(scope="function")
60-
def async_adms_client(adms_config: AdmsConfig) -> AsyncAdmsClient:
61-
"""Async AdmsClient wired to the real ADM instance."""
62-
return create_async_client(config=adms_config)
45+
def async_adms_client() -> AsyncAdmsClient:
46+
"""Async AdmsClient wired to the real ADM instance via ConfigFactory."""
47+
try:
48+
return create_async_client()
49+
except ConfigError as exc:
50+
pytest.skip(f"ADMS integration tests skipped — missing config: {exc}")
6351

6452

6553
# ---------------------------------------------------------------------------

‎tests/adms/unit/test_client.py‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,19 +152,27 @@ def test_raises_config_error_on_missing_binding(self):
152152
create_client(instance="nonexistent-instance")
153153

154154
def test_unexpected_exception_propagates_as_is(self):
155-
"""Real bugs (e.g. ``RuntimeError`` from internal logic) must surface
156-
as themselves rather than being silently wrapped — wrapping makes
157-
debugging harder and previously masked SDK programming errors as
158-
"client creation failed".
159-
"""
160-
factory = MagicMock(side_effect=RuntimeError("unexpected"))
155+
"""Exceptions other than RuntimeError (e.g. programming errors) must
156+
surface as themselves rather than being silently swallowed."""
157+
factory = MagicMock(side_effect=ValueError("unexpected"))
161158
with patch(
162159
"sap_cloud_sdk.adms.client._make_config_factory",
163160
return_value=factory,
164161
):
165-
with pytest.raises(RuntimeError, match="unexpected"):
162+
with pytest.raises(ValueError, match="unexpected"):
166163
create_client(instance="bad-instance")
167164

165+
def test_runtime_error_from_secret_resolver_becomes_config_error(self):
166+
"""RuntimeError from ConfigFactory (missing secrets) must be wrapped as
167+
ConfigError so callers only need to handle one exception type."""
168+
factory = MagicMock(side_effect=RuntimeError("env var not found: CLOUD_SDK_CFG_ADMS_DEFAULT_CLIENTID"))
169+
with patch(
170+
"sap_cloud_sdk.adms.client._make_config_factory",
171+
return_value=factory,
172+
):
173+
with pytest.raises(ConfigError):
174+
create_client(instance="missing")
175+
168176
def test_returns_adms_client_on_success(self):
169177
mock_config = AdmsConfig(
170178
service_url="https://adm.example.com",

‎tests/objectstore/integration/conftest.py‎

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ def integration_env() -> Dict[str, str]:
4848
missing_vars.append(var)
4949

5050
if missing_vars:
51-
pytest.skip(f"Missing required environment variables for cloud integration tests: {missing_vars}")
51+
pytest.skip(
52+
f"Missing required environment variables for cloud integration tests: {missing_vars}"
53+
)
5254

5355
# Ensure SSL is enabled for cloud services
5456
env_vars["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SSL_ENABLED"] = os.getenv(
@@ -66,19 +68,17 @@ def integration_env() -> Dict[str, str]:
6668

6769
@pytest.fixture(scope="session")
6870
def objectstore_client(integration_env):
69-
"""Create an ObjectStore client for cloud testing using explicit configuration."""
71+
"""Create an ObjectStore client via ConfigFactory (reads CLOUD_SDK_CFG_* env vars)."""
7072
try:
71-
config = ObjectStoreBindingData(
72-
host=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_HOST"],
73-
access_key_id=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_ACCESS_KEY_ID"],
74-
secret_access_key=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SECRET_ACCESS_KEY"],
75-
bucket=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_BUCKET"],
76-
)
77-
disable_ssl = integration_env.get("CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SSL_ENABLED", "true").lower() in ("false", "0")
78-
client = create_client("default", config=config, disable_ssl=disable_ssl)
73+
disable_ssl = integration_env.get(
74+
"CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SSL_ENABLED", "true"
75+
).lower() in ("false", "0")
76+
client = create_client("default", disable_ssl=disable_ssl)
7977
return client
8078
except Exception as e:
81-
pytest.fail(f"Failed to create ObjectStore client for cloud integration tests: {e}")
79+
pytest.fail(
80+
f"Failed to create ObjectStore client for cloud integration tests: {e}"
81+
)
8282

8383

8484
@pytest.fixture
@@ -89,6 +89,7 @@ def test_prefix() -> str:
8989

9090
# ===== CLEANUP INFRASTRUCTURE =====
9191

92+
9293
def cleanup_by_prefix(client, prefix: str, timeout: float = 10.0) -> bool:
9394
"""Timeout-controlled cleanup with eventual consistency handling."""
9495
start_time = time.time()
@@ -103,7 +104,9 @@ def cleanup_by_prefix(client, prefix: str, timeout: float = 10.0) -> bool:
103104

104105
# Check timeout
105106
if time.time() - start_time > timeout:
106-
logger.warning(f"Cleanup timeout reached after {timeout}s, cleaned {cleaned_count} objects")
107+
logger.warning(
108+
f"Cleanup timeout reached after {timeout}s, cleaned {cleaned_count} objects"
109+
)
107110
break
108111

109112
if cleaned_count > 0:
@@ -126,8 +129,12 @@ def cleanup_all_test_objects():
126129
try:
127130
objects = objectstore_client.list_objects("sdk-python-integration-tests/")
128131
if objects:
129-
logger.info(f"Found {len(objects)} leftover integration test objects, cleaning up...")
130-
cleanup_by_prefix(objectstore_client, "sdk-python-integration-tests/", timeout=30.0)
132+
logger.info(
133+
f"Found {len(objects)} leftover integration test objects, cleaning up..."
134+
)
135+
cleanup_by_prefix(
136+
objectstore_client, "sdk-python-integration-tests/", timeout=30.0
137+
)
131138
logger.info("Session cleanup completed")
132139
except Exception as e:
133140
logger.warning(f"Session cleanup failed: {e}")
@@ -166,7 +173,9 @@ def register_object(object_name: str):
166173

167174
# Respect timeout
168175
if time.time() - start_time > 10.0:
169-
logger.warning(f"Object cleanup timeout reached, cleaned {cleaned_count}/{len(created_objects)} objects")
176+
logger.warning(
177+
f"Object cleanup timeout reached, cleaned {cleaned_count}/{len(created_objects)} objects"
178+
)
170179
break
171180

172181
except Exception as e:
@@ -182,11 +191,17 @@ def failure_simulation(integration_env):
182191
"""Utilities for simulating various failure conditions using explicit configuration."""
183192
base_config = ObjectStoreBindingData(
184193
host=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_HOST"],
185-
access_key_id=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_ACCESS_KEY_ID"],
186-
secret_access_key=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SECRET_ACCESS_KEY"],
194+
access_key_id=integration_env[
195+
"CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_ACCESS_KEY_ID"
196+
],
197+
secret_access_key=integration_env[
198+
"CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SECRET_ACCESS_KEY"
199+
],
187200
bucket=integration_env["CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_BUCKET"],
188201
)
189-
disable_ssl = integration_env.get("CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SSL_ENABLED", "true").lower() in ("false", "0")
202+
disable_ssl = integration_env.get(
203+
"CLOUD_SDK_CFG_OBJECTSTORE_DEFAULT_SSL_ENABLED", "true"
204+
).lower() in ("false", "0")
190205

191206
class FailureSimulator:
192207
def create_client_with_network_failure(self):
@@ -219,10 +234,7 @@ def setup_intermittent_failure(self):
219234
# Configure pytest markers for integration tests
220235
def pytest_configure(config):
221236
"""Configure pytest markers."""
222-
config.addinivalue_line(
223-
"markers",
224-
"integration: mark test as integration test"
225-
)
237+
config.addinivalue_line("markers", "integration: mark test as integration test")
226238

227239

228240
def pytest_collection_modifyitems(config, items):

‎uv.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)