Skip to content
Open
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 pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sap-cloud-sdk"
version = "0.53.0"
version = "0.54.0"
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
5 changes: 5 additions & 0 deletions src/sap_cloud_sdk/extensibility/_ums_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
id
title
extensionVersion
isActive
solutionId
jouleStudioGsid
capabilityImplementations {
Expand Down Expand Up @@ -372,6 +373,10 @@ def _transform_ums_response(

for edge in edges:
node = edge.get("node", {})
# Only include extensions that are explicitly active.
# isActive absent/None means the extension is not active.
if node.get("isActive") is not True:
continue
nodes.append(node)
title = node.get("title", "")
if title:
Expand Down
149 changes: 147 additions & 2 deletions tests/extensibility/unit/_ums_test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"id": "ext-instance-1",
"title": "ServiceNow Extension",
"extensionVersion": "2.1.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -96,6 +97,7 @@
"id": "ext-instance-1",
"title": "ServiceNow Extension",
"extensionVersion": "1.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand All @@ -122,6 +124,7 @@
"id": "ext-instance-2",
"title": "Jira Extension",
"extensionVersion": "3.2.1",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -182,6 +185,7 @@
"id": "ext-1",
"title": "Minimal Extension",
"extensionVersion": "0.1.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -217,6 +221,7 @@
"id": "ext-1",
"title": "Empty Instruction Extension",
"extensionVersion": "1.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand All @@ -233,15 +238,15 @@
}
}

UMS_RESPONSE_DIFFERENT_CAPABILITY = {
"data": {
UMS_RESPONSE_DIFFERENT_CAPABILITY = { "data": {
"EXTHUB__ExtCapImplementationInstances": {
"edges": [
{
"node": {
"id": "ext-1",
"title": "Other Extension",
"extensionVersion": "2.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "onboarding",
Expand Down Expand Up @@ -286,6 +291,146 @@
}
}

# A single extension explicitly deactivated (isActive: False).
UMS_RESPONSE_DEACTIVATED = {
"data": {
"EXTHUB__ExtCapImplementationInstances": {
"edges": [
{
"node": {
"id": "ext-deactivated-1",
"title": "Deactivated Extension",
"extensionVersion": "1.0.0",
"isActive": False,
"capabilityImplementations": [
{
"capabilityId": "default",
"instruction": {"text": "Should not appear."},
"tools": {
"additions": [
{
"type": "MCP",
"mcpConfig": {
"globalTenantId": "tenant-x",
"ordId": "sap.mcp:apiResource:deactivated:v1",
"toolNames": ["hidden_tool"],
},
}
]
},
"hooks": [],
}
],
}
}
],
"pageInfo": {"hasNextPage": False, "cursor": None},
}
}
}

# One active extension and one deactivated extension in the same response.
UMS_RESPONSE_MIXED_ACTIVE = {
"data": {
"EXTHUB__ExtCapImplementationInstances": {
"edges": [
{
"node": {
"id": "ext-active-1",
"title": "Active Extension",
"extensionVersion": "2.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
"instruction": {"text": "Active instruction."},
"tools": {
"additions": [
{
"type": "MCP",
"mcpConfig": {
"globalTenantId": "tenant-a",
"ordId": "sap.mcp:apiResource:active:v1",
"toolNames": ["active_tool"],
},
}
]
},
"hooks": [],
}
],
}
},
{
"node": {
"id": "ext-inactive-1",
"title": "Inactive Extension",
"extensionVersion": "1.0.0",
"isActive": False,
"capabilityImplementations": [
{
"capabilityId": "default",
"instruction": {"text": "Should not appear."},
"tools": {
"additions": [
{
"type": "MCP",
"mcpConfig": {
"globalTenantId": "tenant-b",
"ordId": "sap.mcp:apiResource:inactive:v1",
"toolNames": ["inactive_tool"],
},
}
]
},
"hooks": [],
}
],
}
},
],
"pageInfo": {"hasNextPage": False, "cursor": None},
}
}
}

# Extension with no isActive field — deployed before activate/deactivate existed.
UMS_RESPONSE_LEGACY_NO_IS_ACTIVE = {
"data": {
"EXTHUB__ExtCapImplementationInstances": {
"edges": [
{
"node": {
"id": "ext-legacy-1",
"title": "Legacy Extension",
"extensionVersion": "1.0.0",
"capabilityImplementations": [
{
"capabilityId": "default",
"instruction": {"text": "Legacy instruction."},
"tools": {
"additions": [
{
"type": "MCP",
"mcpConfig": {
"globalTenantId": "tenant-leg",
"ordId": "sap.mcp:apiResource:legacy:v1",
"toolNames": ["legacy_tool"],
},
}
]
},
"hooks": [],
}
],
}
}
],
"pageInfo": {"hasNextPage": False, "cursor": None},
}
}
}


# ---------------------------------------------------------------------------
# Helper functions
Expand Down
5 changes: 5 additions & 0 deletions tests/extensibility/unit/test_ums_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def test_multiple_pages_accumulates_edges(self):
"id": "ext-1",
"title": "Extension A",
"extensionVersion": "1.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -110,6 +111,7 @@ def test_multiple_pages_accumulates_edges(self):
"id": "ext-2",
"title": "Extension B",
"extensionVersion": "2.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -179,6 +181,7 @@ def test_cursor_sent_on_subsequent_pages(self):
"id": "ext-1",
"title": "Ext",
"extensionVersion": "1.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -269,6 +272,7 @@ def test_error_on_second_page_raises(self):
"id": "ext-1",
"title": "Ext",
"extensionVersion": "1.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -311,6 +315,7 @@ def test_missing_page_info_stops_pagination(self):
"id": "ext-1",
"title": "Ext",
"extensionVersion": "1.0.0",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down
33 changes: 33 additions & 0 deletions tests/extensibility/unit/test_ums_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
UMS_RESPONSE_NO_INSTRUCTION,
UMS_RESPONSE_EMPTY_INSTRUCTION,
UMS_RESPONSE_DIFFERENT_CAPABILITY,
UMS_RESPONSE_DEACTIVATED,
UMS_RESPONSE_MIXED_ACTIVE,
UMS_RESPONSE_LEGACY_NO_IS_ACTIVE,
)


Expand Down Expand Up @@ -339,6 +342,7 @@ def test_hooks_with_unknown_type_skipped(self):
"node": {
"id": "ext-1",
"title": "Test",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -381,6 +385,7 @@ def test_node_without_title(self):
{
"node": {
"id": "ext-1",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -408,6 +413,7 @@ def test_hooks_null_in_response(self):
"node": {
"id": "ext-1",
"title": "Null Hooks Extension",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand Down Expand Up @@ -446,6 +452,7 @@ def test_tools_key_missing(self):
"node": {
"id": "ext-1",
"title": "No tools",
"isActive": True,
"capabilityImplementations": [
{
"capabilityId": "default",
Expand All @@ -459,3 +466,29 @@ def test_tools_key_missing(self):
}
result = _transform_ums_response(data, "default")
assert result.mcp_servers == []

def test_deactivated_extension_excluded(self):
"""isActive: False node must be filtered out entirely."""
result = _transform_ums_response(UMS_RESPONSE_DEACTIVATED["data"], "default")
assert result.extension_names == []
assert result.mcp_servers == []
assert result.instruction is None
assert result.hooks == []

def test_mixed_active_inactive(self):
"""Active node included; deactivated node excluded from the same response."""
result = _transform_ums_response(UMS_RESPONSE_MIXED_ACTIVE["data"], "default")
assert result.extension_names == ["Active Extension"]
assert len(result.mcp_servers) == 1
assert result.mcp_servers[0].ord_id == "sap.mcp:apiResource:active:v1"
assert result.instruction == "Active instruction."

def test_legacy_extension_without_is_active_excluded(self):
"""isActive absent (null) means not active — extension is excluded."""
result = _transform_ums_response(
UMS_RESPONSE_LEGACY_NO_IS_ACTIVE["data"], "default"
)
assert result.extension_names == []
assert result.mcp_servers == []
assert result.instruction is None
assert result.hooks == []
Loading