Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env_integration_tests.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ CLOUD_SDK_CFG_DESTINATION_DEFAULT_IDENTITYZONE=your-identity-zone-here
CLOUD_SDK_CFG_SDM_DEFAULT_URI=https://your-sdm-api-uri-here
CLOUD_SDK_CFG_SDM_DEFAULT_UAA='{"url":"https://your-auth-url","clientid":"your-client-id","clientsecret":"your-client-secret","identityzone":"your-identity-zone"}'

CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL=https://your-agent-memory-api-url-here
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL=https://your-agent-memory-api-url-here
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA='{"url":"https://your-auth-url","clientid":"your-client-id","clientsecret":"your-client-secret"}'
2 changes: 1 addition & 1 deletion docs/INTEGRATION_TESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ For Agent Memory integration tests, configure the following variables in `.env_i

```bash
# Agent Memory Configuration
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL=https://your-agent-memory-api-url
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL=https://your-agent-memory-api-url
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA='{"url":"https://your-auth-url","clientid":"your-client-id","clientsecret":"your-client-secret"}'
```

Expand Down
14 changes: 7 additions & 7 deletions src/sap_cloud_sdk/agent_memory/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@

Mount path convention::

/etc/secrets/appfnd/hana-agent-memory/default/url
/etc/secrets/appfnd/hana-agent-memory/default/application_url
/etc/secrets/appfnd/hana-agent-memory/default/uaa

``url`` is the Agent Memory service base URL (plain string).
``application_url`` is the Agent Memory service base URL (plain string).
``uaa`` is a JSON string with OAuth2 credentials containing at minimum:
``clientid``, ``clientsecret``, and ``url`` (UAA base URL).

Env fallback convention::

CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL
CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA
"""

Expand Down Expand Up @@ -81,14 +81,14 @@ class BindingData:
All fields must be plain ``str`` to satisfy the resolver contract.
"""

url: str = ""
application_url: str = ""
uaa: str = ""

def validate(self) -> None:
"""Raise ``AgentMemoryConfigError`` if any required field is empty."""
if not self.url:
if not self.application_url:
raise AgentMemoryConfigError(
"Agent Memory binding is missing required field: url"
"Agent Memory binding is missing required field: application_url"
)
if not self.uaa:
raise AgentMemoryConfigError(
Expand All @@ -104,7 +104,7 @@ def extract_config(self) -> AgentMemoryConfig:

try:
return AgentMemoryConfig(
base_url=self.url,
base_url=self.application_url,
token_url=uaa_data["url"].rstrip("/") + "/oauth/token",
client_id=uaa_data["clientid"],
client_secret=uaa_data["clientsecret"],
Expand Down
6 changes: 3 additions & 3 deletions src/sap_cloud_sdk/agent_memory/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ environment variables or service binding.
### Service Binding

- **Mount path**: `$SERVICE_BINDING_ROOT/hana-agent-memory/default/` (defaults to `/etc/secrets/appfnd/hana-agent-memory/default/`)
- **Required keys**: `url` (Agent Memory service URL), `uaa` (JSON string with XSUAA credentials)
- **Required keys**: `application_url` (Agent Memory service URL), `uaa` (JSON string with XSUAA credentials)
- **Env var fallback**: `CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_{FIELD}` (uppercased)

> **Note:** `SERVICE_BINDING_ROOT` defaults to `/etc/secrets/appfnd` when not set. See the [Secret Resolver guide](../core/secret_resolver/user-guide.md) for details.
Expand All @@ -784,14 +784,14 @@ environment variables or service binding.

```
$SERVICE_BINDING_ROOT/hana-agent-memory/default/
├── url
├── application_url
└── uaa
```

#### Environment Variables

```bash
export CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL="https://agent-memory.example.com"
export CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL="https://agent-memory.example.com"
export CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA='{"clientid":"...","clientsecret":"...","url":"https://..."}'
```

Expand Down
2 changes: 1 addition & 1 deletion tests/agent_memory/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_uses_provided_config(self):
def test_reads_env_when_no_config_provided(self, monkeypatch):
"""Factory falls back to environment variables when no config given."""
import json
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL", "http://memory.example.com")
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL", "http://memory.example.com")
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA", json.dumps({
"url": "http://auth.example.com",
"clientid": "client-id",
Expand Down
34 changes: 17 additions & 17 deletions tests/agent_memory/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,43 +64,43 @@ def test_valid_config_with_all_fields_does_not_raise(self):


class TestBindingData:
def test_validate_raises_when_url_missing(self):
with pytest.raises(AgentMemoryConfigError, match="url"):
BindingData(url="", uaa=_VALID_UAA).validate()
def test_validate_raises_when_application_url_missing(self):
with pytest.raises(AgentMemoryConfigError, match="application_url"):
BindingData(application_url="", uaa=_VALID_UAA).validate()

def test_validate_raises_when_uaa_missing(self):
with pytest.raises(AgentMemoryConfigError, match="uaa"):
BindingData(url="https://memory.example.com", uaa="").validate()
BindingData(application_url="https://memory.example.com", uaa="").validate()

def test_validate_passes_when_all_fields_set(self):
BindingData(url="https://memory.example.com", uaa=_VALID_UAA).validate()
BindingData(application_url="https://memory.example.com", uaa=_VALID_UAA).validate()

def test_extract_config_maps_url(self):
config = BindingData(url="https://memory.example.com", uaa=_VALID_UAA).extract_config()
config = BindingData(application_url="https://memory.example.com", uaa=_VALID_UAA).extract_config()
assert config.base_url == "https://memory.example.com"

def test_extract_config_derives_token_url(self):
config = BindingData(url="https://memory.example.com", uaa=_VALID_UAA).extract_config()
config = BindingData(application_url="https://memory.example.com", uaa=_VALID_UAA).extract_config()
assert config.token_url == "https://auth.example.com/oauth/token"

def test_extract_config_strips_trailing_slash_from_uaa_url(self):
uaa = json.dumps({"url": "https://auth.example.com/", "clientid": "c", "clientsecret": "s"})
config = BindingData(url="https://memory.example.com", uaa=uaa).extract_config()
config = BindingData(application_url="https://memory.example.com", uaa=uaa).extract_config()
assert config.token_url == "https://auth.example.com/oauth/token"

def test_extract_config_maps_client_credentials(self):
config = BindingData(url="https://memory.example.com", uaa=_VALID_UAA).extract_config()
config = BindingData(application_url="https://memory.example.com", uaa=_VALID_UAA).extract_config()
assert config.client_id == "my-client"
assert config.client_secret == "my-secret"

def test_extract_config_raises_on_invalid_json(self):
with pytest.raises(AgentMemoryConfigError, match="Failed to parse uaa JSON"):
BindingData(url="https://memory.example.com", uaa="not-json").extract_config()
BindingData(application_url="https://memory.example.com", uaa="not-json").extract_config()

def test_extract_config_raises_on_missing_json_key(self):
uaa = json.dumps({"url": "https://auth.example.com"}) # missing clientid/clientsecret
with pytest.raises(AgentMemoryConfigError, match="Missing required field in uaa JSON"):
BindingData(url="https://memory.example.com", uaa=uaa).extract_config()
BindingData(application_url="https://memory.example.com", uaa=uaa).extract_config()

def test_extract_config_ignores_extra_uaa_fields(self):
uaa = json.dumps({
Expand All @@ -114,23 +114,23 @@ def test_extract_config_ignores_extra_uaa_fields(self):
"xsappname": "my-app",
"zoneid": "1acb547d-6df6-40a6-abb6-e41dd7d079d1",
})
config = BindingData(url="https://memory.example.com", uaa=uaa).extract_config()
config = BindingData(application_url="https://memory.example.com", uaa=uaa).extract_config()
assert config.base_url == "https://memory.example.com"
assert config.token_url == "https://auth.example.com/oauth/token"
assert config.client_id == "my-client"
assert config.client_secret == "my-secret"

def test_extract_config_raises_on_empty_uaa_object(self):
with pytest.raises(AgentMemoryConfigError, match="Missing required field in uaa JSON"):
BindingData(url="https://memory.example.com", uaa="{}").extract_config()
BindingData(application_url="https://memory.example.com", uaa="{}").extract_config()


# ── _load_config_from_env ─────────────────────────────────────────────────────


def _fill_binding(**kwargs) -> None:
target = kwargs["target"]
target.url = "https://memory.example.com"
target.application_url = "https://memory.example.com"
target.uaa = _VALID_UAA


Expand All @@ -156,7 +156,7 @@ def test_calls_resolver_with_correct_arguments(self):
assert kwargs["instance"] == "default"

def test_falls_back_to_env_vars(self, monkeypatch):
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL", "https://memory.example.com")
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL", "https://memory.example.com")
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA", _VALID_UAA)

# Let the real resolver run — mount will fail, env vars will succeed
Expand All @@ -173,15 +173,15 @@ def test_raises_config_error_when_resolver_fails(self):

def test_raises_config_error_when_binding_incomplete(self):
def partial_fill(**kwargs):
kwargs["target"].url = "https://memory.example.com"
kwargs["target"].application_url = "https://memory.example.com"
# uaa remains empty → validate() raises

with patch(_RESOLVER, side_effect=partial_fill):
with pytest.raises(AgentMemoryConfigError, match="uaa"):
_load_config_from_env()

def test_raises_config_error_when_uaa_json_invalid(self, monkeypatch):
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_URL", "https://memory.example.com")
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_APPLICATION_URL", "https://memory.example.com")
monkeypatch.setenv("CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_UAA", "not-valid-json")

with patch("os.stat", side_effect=FileNotFoundError("no mount")):
Expand Down
Loading