diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py index 47d62767..a7d6ffa0 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py @@ -907,7 +907,7 @@ class Vnic(BaseModel): "If the VNIC belongs to a VLAN as part of the Oracle Cloud VMware Solution " "(instead of belonging to a subnet), the value of the `nsgIds` attribute is ignored. " "Instead, the VNIC belongs to the NSGs that are associated with the VLAN itself. " - "See Vlan.", + "See Vlan." ), ) vlan_id: Optional[str] = Field( diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py index ffd2e032..303b91f3 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py @@ -49,7 +49,7 @@ def get_networking_client(): return oci.core.VirtualNetworkClient(config, signer=signer) -@mcp.tool +@mcp.tool(description="Lists the VCNs in the specified compartment.") def list_vcns(compartment_id: str) -> list[Vcn]: vcns: list[Vcn] = [] @@ -78,7 +78,7 @@ def list_vcns(compartment_id: str) -> list[Vcn]: raise -@mcp.tool +@mcp.tool(description="Gets the specified VCN's information.") def get_vcn(vcn_id: str) -> Vcn: try: client = get_networking_client() @@ -93,7 +93,7 @@ def get_vcn(vcn_id: str) -> Vcn: raise -@mcp.tool +@mcp.tool(description="Deletes the specified VCN.") def delete_vcn(vcn_id: str) -> Response: try: client = get_networking_client() @@ -107,7 +107,7 @@ def delete_vcn(vcn_id: str) -> Response: raise -@mcp.tool +@mcp.tool(description="Creates a new VCN.") def create_vcn(compartment_id: str, cidr_block: str, display_name: str) -> Vcn: try: client = get_networking_client() @@ -128,7 +128,9 @@ def create_vcn(compartment_id: str, cidr_block: str, display_name: str) -> Vcn: raise -@mcp.tool +@mcp.tool( + description="Lists the subnets in the specified compartment. Optionally filter by VCN." +) def list_subnets(compartment_id: str, vcn_id: str = None) -> list[Subnet]: subnets: list[Subnet] = [] @@ -159,7 +161,7 @@ def list_subnets(compartment_id: str, vcn_id: str = None) -> list[Subnet]: raise -@mcp.tool +@mcp.tool(description="Gets the specified subnet's information.") def get_subnet(subnet_id: str) -> Subnet: try: client = get_networking_client() @@ -174,7 +176,7 @@ def get_subnet(subnet_id: str) -> Subnet: raise -@mcp.tool +@mcp.tool(description="Creates a new subnet.") def create_subnet( vcn_id: str, compartment_id: str, cidr_block: str, display_name: str ) -> Subnet: @@ -253,10 +255,8 @@ def get_security_list(security_list_id: Annotated[str, "security list id"]): @mcp.tool( - description="Lists either the network security groups in the specified compartment," - "or those associated with the specified VLAN. You must specify either a vlanId or" - "a compartmentId, but not both. If you specify a vlanId, all other parameters are " - "ignored.", + description="Lists the network security groups in the specified compartment. " + "Optionally filter by vcn_id or vlan_id.", ) def list_network_security_groups( compartment_id: Annotated[str, "compartment ocid"], diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py index aa7ff47c..cf86126e 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py @@ -497,13 +497,9 @@ async def test_get_vnic(self, mock_get_client): mock_client.get_vnic.return_value = mock_get_response async with Client(mcp) as client: - # Expect ToolError due to schema validation issue in installed package - with pytest.raises(ToolError): - call_tool_result = await client.call_tool( - "get_vnic", {"vnic_id": "vnic1"} - ) - result = call_tool_result.structured_content - assert result["id"] == "vnic1" + call_tool_result = await client.call_tool("get_vnic", {"vnic_id": "vnic1"}) + result = call_tool_result.structured_content + assert result["id"] == "vnic1" class TestServer: diff --git a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py index f35e4afa..a2d169d6 100644 --- a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py +++ b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py @@ -119,8 +119,6 @@ def search_resources( try: client = get_search_client() - oci.identity.models.Compartment - response: oci.response.Response = None has_next_page = True next_page: str = None diff --git a/tests/e2e/features/mcphost.json b/tests/e2e/features/mcphost.json index 0b062999..314c65bb 100644 --- a/tests/e2e/features/mcphost.json +++ b/tests/e2e/features/mcphost.json @@ -1,13 +1,13 @@ { "mcpServers": { - "oracle-oci-api-mcp-server": { + "oracle-oci-cloud-guard-mcp-server": { "disabled": false, "timeout": 60, "type": "stdio", "command": "uv", "args": [ "run", - "oracle.oci-api-mcp-server" + "oracle.oci-cloud-guard-mcp-server" ], "env": { "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", @@ -20,14 +20,14 @@ "CURL_CA_BUNDLE": "" } }, - "oracle-oci-cloud-guard-mcp-server": { + "oracle-oci-compute-mcp-server": { "disabled": false, "timeout": 60, "type": "stdio", "command": "uv", "args": [ "run", - "oracle.oci-cloud-guard-mcp-server" + "oracle.oci-compute-mcp-server" ], "env": { "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", @@ -40,14 +40,14 @@ "CURL_CA_BUNDLE": "" } }, - "oracle-oci-compute-mcp-server": { + "oracle-oci-compute-instance-agent-mcp-server": { "disabled": false, "timeout": 60, "type": "stdio", "command": "uv", "args": [ "run", - "oracle.oci-compute-mcp-server" + "oracle.oci-compute-instance-agent-mcp-server" ], "env": { "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", @@ -142,6 +142,70 @@ "CURL_CA_BUNDLE": "" } }, + "oracle-oci-monitoring-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "oracle.oci-monitoring-mcp-server" + ], + "env": { + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, + "oracle-oci-networking-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "oracle.oci-networking-mcp-server" + ], + "env": { + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, + "oracle-oci-network-load-balancer-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "python", + "-c", + "import sys; sys.path.insert(0, 'mocks'); import sitecustomize; from oracle.oci_network_load_balancer_mcp_server.server import main; main()" + ], + "env": { + "PYTHONPATH": "../../../src/oci-network-load-balancer-mcp-server:mocks", + "PYTHONNOUSERSITE": "0", + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, "oracle-oci-object-storage-mcp-server": { "disabled": false, "timeout": 60, @@ -149,9 +213,81 @@ "command": "uv", "args": [ "run", - "oracle.oci-object-storage-mcp-server" + "python", + "-c", + "import sys; sys.path.insert(0, 'mocks'); import sitecustomize; from oracle.oci_object_storage_mcp_server.server import main; main()" + ], + "env": { + "PYTHONPATH": "../../../src/oci-object-storage-mcp-server:mocks", + "PYTHONNOUSERSITE": "0", + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, + "oracle-oci-registry-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "oracle.oci-registry-mcp-server" + ], + "env": { + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, + "oracle-oci-resource-search-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "python", + "-c", + "import sys; sys.path.insert(0, 'mocks'); import sitecustomize; from oracle.oci_resource_search_mcp_server.server import main; main()" + ], + "env": { + "PYTHONPATH": "../../../src/oci-resource-search-mcp-server:mocks", + "PYTHONNOUSERSITE": "0", + "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", + "HTTP_PROXY": "http://127.0.0.1:5000", + "HTTPS_PROXY": "http://127.0.0.1:5000", + "OCI_SDK_CERT_BUNDLE": "False", + "PYTHONHTTPSVERIFY": "0", + "OCI_SKIP_SSL_VERIFICATION": "True", + "REQUESTS_CA_BUNDLE": "", + "CURL_CA_BUNDLE": "" + } + }, + "oracle-oci-usage-mcp-server": { + "disabled": false, + "timeout": 60, + "type": "stdio", + "command": "uv", + "args": [ + "run", + "python", + "-c", + "import sys; sys.path.insert(0, 'mocks'); import sitecustomize; from oracle.oci_usage_mcp_server.server import main; main()" ], "env": { + "PYTHONPATH": "../../../src/oci-usage-mcp-server:mocks", + "PYTHONNOUSERSITE": "0", "OCI_CONFIG_FILE": "${env:OCI_CONFIG_FILE}", "HTTP_PROXY": "http://127.0.0.1:5000", "HTTPS_PROXY": "http://127.0.0.1:5000", diff --git a/tests/e2e/features/mocks/mock_oci_server.py b/tests/e2e/features/mocks/mock_oci_server.py index fe18820f..f4af6ed3 100644 --- a/tests/e2e/features/mocks/mock_oci_server.py +++ b/tests/e2e/features/mocks/mock_oci_server.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/proxy_shim.py b/tests/e2e/features/mocks/proxy_shim.py index 36de3143..2f8e21a3 100644 --- a/tests/e2e/features/mocks/proxy_shim.py +++ b/tests/e2e/features/mocks/proxy_shim.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/cloud_guard_data.py b/tests/e2e/features/mocks/services/cloud_guard_data.py index d8f4ef8d..3a7fbc32 100644 --- a/tests/e2e/features/mocks/services/cloud_guard_data.py +++ b/tests/e2e/features/mocks/services/cloud_guard_data.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/cloud_guard_routes.py b/tests/e2e/features/mocks/services/cloud_guard_routes.py index b585a998..15e8ddc3 100644 --- a/tests/e2e/features/mocks/services/cloud_guard_routes.py +++ b/tests/e2e/features/mocks/services/cloud_guard_routes.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/compute_data.py b/tests/e2e/features/mocks/services/compute_data.py index a860e546..7b2f35f0 100644 --- a/tests/e2e/features/mocks/services/compute_data.py +++ b/tests/e2e/features/mocks/services/compute_data.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/compute_instance_agent_data.py b/tests/e2e/features/mocks/services/compute_instance_agent_data.py new file mode 100644 index 00000000..850e5f50 --- /dev/null +++ b/tests/e2e/features/mocks/services/compute_instance_agent_data.py @@ -0,0 +1,43 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from datetime import datetime, timezone + +# CamelCase keys as required + +# Stored commands created via the mock +INSTANCE_AGENT_COMMANDS = [] + +# Pre-populated command executions (summaries). Tests can list these even +# before creating new ones. +INSTANCE_AGENT_EXECUTIONS = [ + { + "instanceAgentCommandId": "ocid1.instanceagentcommand.oc1..mock-cmd-1", + "instanceId": "ocid1.instance.oc1..mock-uuid-1", + "deliveryState": "VISIBLE", + "lifecycleState": "SUCCEEDED", + "timeCreated": "2026-01-13T10:00:00Z", + "timeUpdated": "2026-01-13T10:00:10Z", + "sequenceNumber": 100, + "displayName": "echo-hello", + "content": { + "outputType": "TEXT", + "exitCode": 0, + "message": "Execution successful", + "text": "hello", + "textSha256": "abc123", + }, + } +] + + +def now_rfc3339(): + return ( + datetime.now(timezone.utc) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") + ) diff --git a/tests/e2e/features/mocks/services/compute_instance_agent_routes.py b/tests/e2e/features/mocks/services/compute_instance_agent_routes.py new file mode 100644 index 00000000..cefe83f2 --- /dev/null +++ b/tests/e2e/features/mocks/services/compute_instance_agent_routes.py @@ -0,0 +1,78 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import uuid + +from _common import oci_res +from compute_instance_agent_data import ( + INSTANCE_AGENT_COMMANDS, + INSTANCE_AGENT_EXECUTIONS, + now_rfc3339, +) +from flask import Blueprint, request + +# OCI Compute Instance Agent API version base path (matches OCI SDK expectations via proxy shim) +compute_instance_agent_bp = Blueprint( + "compute_instance_agent", __name__, url_prefix="/20180530" +) + + +@compute_instance_agent_bp.route("/instanceAgentCommands", methods=["POST"]) +def create_instance_agent_command(): + data = request.json or {} + cmd_id = f"ocid1.instanceagentcommand.oc1..{uuid.uuid4()}" + + # Persist created command + record = { + "id": cmd_id, + "displayName": data.get("displayName", "agent-command-adhoc"), + "compartmentId": data.get("compartmentId"), + "executionTimeOutInSeconds": data.get("executionTimeOutInSeconds", 30), + "timeCreated": now_rfc3339(), + } + INSTANCE_AGENT_COMMANDS.append(record) + + return oci_res(record) + + +@compute_instance_agent_bp.route( + "/instanceAgentCommands//status", + methods=["GET"], +) +def get_instance_agent_command_execution(instance_agent_command_id): + # Synthesize a successful execution for the created command + exec_obj = { + "instanceAgentCommandId": instance_agent_command_id, + "instanceId": request.args.get("instanceId", "ocid1.instance.oc1..mock-uuid-1"), + "deliveryState": "VISIBLE", + "lifecycleState": "SUCCEEDED", + "timeCreated": now_rfc3339(), + "timeUpdated": now_rfc3339(), + "sequenceNumber": 1, + "displayName": "adhoc", + "content": { + "outputType": "TEXT", + "exitCode": 0, + "message": "Execution successful", + "text": "Hello from agent", + "textSha256": "deadbeef", + }, + } + return oci_res(exec_obj) + + +@compute_instance_agent_bp.route("/instanceAgentCommandExecutions", methods=["GET"]) +def list_instance_agent_command_executions(): + instance_id = request.args.get("instanceId") + limit = request.args.get("limit", type=int) + + items = INSTANCE_AGENT_EXECUTIONS + if instance_id: + items = [i for i in items if i.get("instanceId") == instance_id] + if limit is not None: + items = items[:limit] + + return oci_res(items) diff --git a/tests/e2e/features/mocks/services/compute_routes.py b/tests/e2e/features/mocks/services/compute_routes.py index 4745c5a2..231e128f 100644 --- a/tests/e2e/features/mocks/services/compute_routes.py +++ b/tests/e2e/features/mocks/services/compute_routes.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/faaas_data.py b/tests/e2e/features/mocks/services/faaas_data.py index 36e2823f..363993af 100644 --- a/tests/e2e/features/mocks/services/faaas_data.py +++ b/tests/e2e/features/mocks/services/faaas_data.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/faaas_routes.py b/tests/e2e/features/mocks/services/faaas_routes.py index de0a920c..62202407 100644 --- a/tests/e2e/features/mocks/services/faaas_routes.py +++ b/tests/e2e/features/mocks/services/faaas_routes.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/identity_data.py b/tests/e2e/features/mocks/services/identity_data.py index f5266710..e9efee1d 100644 --- a/tests/e2e/features/mocks/services/identity_data.py +++ b/tests/e2e/features/mocks/services/identity_data.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/identity_routes.py b/tests/e2e/features/mocks/services/identity_routes.py index 0ea4691a..c4ffbb47 100644 --- a/tests/e2e/features/mocks/services/identity_routes.py +++ b/tests/e2e/features/mocks/services/identity_routes.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/logging_data.py b/tests/e2e/features/mocks/services/logging_data.py index f69aed9c..cf923d5f 100644 --- a/tests/e2e/features/mocks/services/logging_data.py +++ b/tests/e2e/features/mocks/services/logging_data.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/logging_routes.py b/tests/e2e/features/mocks/services/logging_routes.py index d41ab598..93ee8a62 100644 --- a/tests/e2e/features/mocks/services/logging_routes.py +++ b/tests/e2e/features/mocks/services/logging_routes.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/migration_data.py b/tests/e2e/features/mocks/services/migration_data.py index 617246d6..b01524fd 100644 --- a/tests/e2e/features/mocks/services/migration_data.py +++ b/tests/e2e/features/mocks/services/migration_data.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/migration_routes.py b/tests/e2e/features/mocks/services/migration_routes.py index fb185b7b..0acbaf74 100644 --- a/tests/e2e/features/mocks/services/migration_routes.py +++ b/tests/e2e/features/mocks/services/migration_routes.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/mocks/services/monitoring_data.py b/tests/e2e/features/mocks/services/monitoring_data.py new file mode 100644 index 00000000..9b0dce1d --- /dev/null +++ b/tests/e2e/features/mocks/services/monitoring_data.py @@ -0,0 +1,49 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +# Alarms +ALARMS = [ + { + "id": "ocid1.alarm.oc1..mock-alarm-1", + "displayName": "High CPU Alarm", + "compartmentId": "ocid1.tenancy.oc1..mock", + "metricCompartmentId": "ocid1.tenancy.oc1..mock", + "namespace": "oci_computeagent", + "query": "CpuUtilization[1m].mean() > 80", + "severity": "CRITICAL", + "lifecycleState": "ACTIVE", + "isEnabled": True, + "timeCreated": "2026-01-15T10:00:00Z", + "timeUpdated": "2026-01-15T11:00:00Z", + } +] + +# Metric Definitions +METRICS = [ + { + "name": "CpuUtilization", + "namespace": "oci_computeagent", + "resourceGroup": "mock-resource-group", + "compartmentId": "ocid1.tenancy.oc1..mock", + "dimensions": {"resourceId": "ocid1.instance.oc1..mock-uuid-1"}, + } +] + +# Metric Data (Summarized) +METRIC_DATA = [ + { + "namespace": "oci_computeagent", + "resourceGroup": "mock-resource-group", + "compartmentId": "ocid1.tenancy.oc1..mock", + "name": "CpuUtilization", + "dimensions": {"resourceId": "ocid1.instance.oc1..mock-uuid-1"}, + "metadata": {"displayName": "CpuUtilization", "unit": "Percent"}, + "aggregatedDatapoints": [ + {"timestamp": "2026-01-15T12:00:00Z", "value": 45.5}, + {"timestamp": "2026-01-15T12:01:00Z", "value": 50.2}, + ], + } +] diff --git a/tests/e2e/features/mocks/services/monitoring_routes.py b/tests/e2e/features/mocks/services/monitoring_routes.py new file mode 100644 index 00000000..c5a0ba1e --- /dev/null +++ b/tests/e2e/features/mocks/services/monitoring_routes.py @@ -0,0 +1,38 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint, request +from monitoring_data import ALARMS, METRIC_DATA, METRICS + +monitoring_bp = Blueprint("monitoring", __name__, url_prefix="/20180401") + + +@monitoring_bp.route("/alarms", methods=["GET"]) +def list_alarms(): + compartment_id = request.args.get("compartmentId") + items = ALARMS + if compartment_id: + items = [a for a in items if a.get("compartmentId") == compartment_id] + + return oci_res(items) + + +@monitoring_bp.route("/metrics", methods=["GET"]) +def list_metrics(): + compartment_id = request.args.get("compartmentId") + items = METRICS + if compartment_id: + items = [m for m in items if m.get("compartmentId") == compartment_id] + + return oci_res(items) + + +@monitoring_bp.route("/metrics/actions/summarizeMetricsData", methods=["POST"]) +def summarize_metrics_data(): + # In a real scenario, we'd parse the body for query, start/end time, etc. + # For mock, we just return static data. + return oci_res(METRIC_DATA) diff --git a/tests/e2e/features/mocks/services/network_load_balancer_data.py b/tests/e2e/features/mocks/services/network_load_balancer_data.py new file mode 100644 index 00000000..6a8ea785 --- /dev/null +++ b/tests/e2e/features/mocks/services/network_load_balancer_data.py @@ -0,0 +1,83 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +# CamelCase mock data for Network Load Balancer service + +NETWORK_LOAD_BALANCERS = [ + { + "id": "ocid1.nlb.oc1..mock-nlb-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "displayName": "mock-nlb-1", + "lifecycleState": "ACTIVE", + "timeCreated": "2026-01-13T10:00:00Z", + "nlbIpVersion": "IPV4", + "ipAddresses": [ + { + "ipAddress": "203.0.113.10", + "isPublic": True, + "ipVersion": "IPV4", + "reservedIp": {"id": "ocid1.publicip.oc1..mockip1"}, + } + ], + "isPrivate": False, + "isPreserveSourceDestination": False, + "isSymmetricHashEnabled": False, + "subnetId": "ocid1.subnet.oc1..mock-subnet-1", + "networkSecurityGroupIds": ["ocid1.nsg.oc1..mocknsg1"], + "listeners": { + "listener-1": { + "name": "listener-1", + "defaultBackendSetName": "backendset-1", + "port": 80, + "protocol": "TCP", + "ipVersion": "IPV4", + "isPpv2Enabled": False, + "tcpIdleTimeout": 60, + "udpIdleTimeout": 30, + "l3IpIdleTimeout": 60, + } + }, + "backendSets": { + "backendset-1": { + "name": "backendset-1", + "policy": "FIVE_TUPLE", + "isPreserveSource": False, + "isFailOpen": False, + "isInstantFailoverEnabled": False, + "isInstantFailoverTcpResetEnabled": False, + "areOperationallyActiveBackendsPreferred": False, + "ipVersion": "IPV4", + "healthChecker": { + "protocol": "TCP", + "port": 80, + "retries": 3, + "timeoutInMillis": 3000, + "intervalInMillis": 10000, + }, + "backends": [ + { + "name": "backend-1", + "ipAddress": "10.0.0.10", + "port": 80, + "weight": 1, + "isDrain": False, + "isBackup": False, + "isOffline": False, + }, + { + "name": "backend-2", + "ipAddress": "10.0.0.11", + "port": 80, + "weight": 1, + "isDrain": False, + "isBackup": False, + "isOffline": False, + }, + ], + } + }, + } +] diff --git a/tests/e2e/features/mocks/services/network_load_balancer_routes.py b/tests/e2e/features/mocks/services/network_load_balancer_routes.py new file mode 100644 index 00000000..70d8cb9d --- /dev/null +++ b/tests/e2e/features/mocks/services/network_load_balancer_routes.py @@ -0,0 +1,141 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint, jsonify, request +from network_load_balancer_data import NETWORK_LOAD_BALANCERS + +# OCI NLB API version base path (per docs) +network_load_balancer_bp = Blueprint( + "network_load_balancer", __name__, url_prefix="/20200501" +) + + +def _apply_limit(items): + try: + limit = request.args.get("limit") + if limit is not None: + lim = int(limit) + if lim >= 0: + return items[:lim] + except Exception: + pass + return items + + +def _find_nlb(nlb_id): + return next((n for n in NETWORK_LOAD_BALANCERS if n.get("id") == nlb_id), None) + + +@network_load_balancer_bp.route("/networkLoadBalancers", methods=["GET"]) +def list_network_load_balancers(): + items = NETWORK_LOAD_BALANCERS + + # Optional filters + comp = request.args.get("compartmentId") + if comp: + items = [i for i in items if i.get("compartmentId") == comp] + + lifecycle_state = request.args.get("lifecycleState") + if lifecycle_state: + items = [i for i in items if i.get("lifecycleState") == lifecycle_state] + + items = _apply_limit(items) + + # Shape should be a collection with items + return oci_res({"items": items}) + + +@network_load_balancer_bp.route("/networkLoadBalancers/", methods=["GET"]) +def get_network_load_balancer(nlb_id): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(nlb) + + +@network_load_balancer_bp.route( + "/networkLoadBalancers//listeners", methods=["GET"] +) +def list_listeners(nlb_id): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + listeners_map = nlb.get("listeners", {}) or {} + items = list(listeners_map.values()) + items = _apply_limit(items) + return oci_res({"items": items}) + + +@network_load_balancer_bp.route( + "/networkLoadBalancers//listeners/", methods=["GET"] +) +def get_listener(nlb_id, listener_name): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + listener = (nlb.get("listeners", {}) or {}).get(listener_name) + if not listener: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(listener) + + +@network_load_balancer_bp.route( + "/networkLoadBalancers//backendSets", methods=["GET"] +) +def list_backend_sets(nlb_id): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + backend_sets_map = nlb.get("backendSets", {}) or {} + items = list(backend_sets_map.values()) + items = _apply_limit(items) + return oci_res({"items": items}) + + +@network_load_balancer_bp.route( + "/networkLoadBalancers//backendSets/", methods=["GET"] +) +def get_backend_set(nlb_id, backend_set_name): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + backend_set = (nlb.get("backendSets", {}) or {}).get(backend_set_name) + if not backend_set: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(backend_set) + + +@network_load_balancer_bp.route( + "/networkLoadBalancers//backendSets//backends", + methods=["GET"], +) +def list_backends(nlb_id, backend_set_name): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + backend_set = (nlb.get("backendSets", {}) or {}).get(backend_set_name) or {} + items = backend_set.get("backends", []) + items = _apply_limit(items) + return oci_res({"items": items}) + + +@network_load_balancer_bp.route( + "/networkLoadBalancers//backendSets//backends/", + methods=["GET"], +) +def get_backend(nlb_id, backend_set_name, backend_name): + nlb = _find_nlb(nlb_id) + if not nlb: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + backend_set = (nlb.get("backendSets", {}) or {}).get(backend_set_name) or {} + backend = next( + (b for b in backend_set.get("backends", []) if b.get("name") == backend_name), + None, + ) + if not backend: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(backend) diff --git a/tests/e2e/features/mocks/services/networking_data.py b/tests/e2e/features/mocks/services/networking_data.py index a54f4bbb..e703b033 100644 --- a/tests/e2e/features/mocks/services/networking_data.py +++ b/tests/e2e/features/mocks/services/networking_data.py @@ -1,13 +1,63 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ +# Using camelCase for keys to match OCI API responses + +VCNS = [ + { + "id": "ocid1.vcn.oc1..mock-vcn", + "displayName": "mock-vcn", + "compartmentId": "ocid1.tenancy.oc1..mock", + "cidrBlock": "10.0.0.0/16", + "lifecycleState": "AVAILABLE", + "dnsLabel": "mockvcn", + "vcnDomainName": "mockvcn.oraclevcn.com", + } +] + SUBNETS = [ { "id": "ocid1.subnet.oc1..mock-subnet", "displayName": "Default Subnet", "compartmentId": "ocid1.tenancy.oc1..mock", + "vcnId": "ocid1.vcn.oc1..mock-vcn", + "cidrBlock": "10.0.1.0/24", + "lifecycleState": "AVAILABLE", + "availabilityDomain": "aNMj:US-ASHBURN-AD-1", + "virtualRouterIp": "10.0.1.1", + "virtualRouterMac": "00:00:00:00:00:01", + } +] + +SECURITY_LISTS = [ + { + "id": "ocid1.securitylist.oc1..mock-sl", + "displayName": "Default Security List", + "compartmentId": "ocid1.tenancy.oc1..mock", + "vcnId": "ocid1.vcn.oc1..mock-vcn", + "lifecycleState": "AVAILABLE", + } +] + +NETWORK_SECURITY_GROUPS = [ + { + "id": "ocid1.networksecuritygroup.oc1..mock-nsg", + "displayName": "mock-nsg", + "compartmentId": "ocid1.tenancy.oc1..mock", + "vcnId": "ocid1.vcn.oc1..mock-vcn", + "lifecycleState": "AVAILABLE", + } +] + +VNICS = [ + { + "id": "ocid1.vnic.oc1..mock-vnic", + "displayName": "mock-vnic", + "compartmentId": "ocid1.tenancy.oc1..mock", + "subnetId": "ocid1.subnet.oc1..mock-subnet", + "lifecycleState": "AVAILABLE", } ] diff --git a/tests/e2e/features/mocks/services/networking_routes.py b/tests/e2e/features/mocks/services/networking_routes.py index bb24f056..dde7fb73 100644 --- a/tests/e2e/features/mocks/services/networking_routes.py +++ b/tests/e2e/features/mocks/services/networking_routes.py @@ -1,16 +1,98 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ from _common import oci_res -from flask import Blueprint -from networking_data import SUBNETS +from flask import Blueprint, jsonify, request +from networking_data import ( + NETWORK_SECURITY_GROUPS, + SECURITY_LISTS, + SUBNETS, + VCNS, + VNICS, +) networking_bp = Blueprint("networking", __name__, url_prefix="/20160918") +@networking_bp.route("/vcns", methods=["GET"]) +def list_vcns(): + compartment_id = request.args.get("compartmentId") + items = VCNS + if compartment_id: + items = [i for i in items if i.get("compartmentId") == compartment_id] + return oci_res(items) + + +@networking_bp.route("/vcns/", methods=["GET"]) +def get_vcn(vcn_id): + vcn = next((v for v in VCNS if v["id"] == vcn_id), None) + return oci_res(vcn) if vcn else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) + + @networking_bp.route("/subnets", methods=["GET"]) def list_subnets(): - return oci_res(SUBNETS) + compartment_id = request.args.get("compartmentId") + vcn_id = request.args.get("vcnId") + items = SUBNETS + if compartment_id: + items = [i for i in items if i.get("compartmentId") == compartment_id] + if vcn_id: + items = [i for i in items if i.get("vcnId") == vcn_id] + return oci_res(items) + + +@networking_bp.route("/subnets/", methods=["GET"]) +def get_subnet(subnet_id): + subnet = next((s for s in SUBNETS if s["id"] == subnet_id), None) + return ( + oci_res(subnet) + if subnet + else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) + ) + + +@networking_bp.route("/securityLists", methods=["GET"]) +def list_security_lists(): + compartment_id = request.args.get("compartmentId") + vcn_id = request.args.get("vcnId") + items = SECURITY_LISTS + if compartment_id: + items = [i for i in items if i.get("compartmentId") == compartment_id] + if vcn_id: + items = [i for i in items if i.get("vcnId") == vcn_id] + return oci_res(items) + + +@networking_bp.route("/securityLists/", methods=["GET"]) +def get_security_list(security_list_id): + sl = next((s for s in SECURITY_LISTS if s["id"] == security_list_id), None) + return oci_res(sl) if sl else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) + + +@networking_bp.route("/networkSecurityGroups", methods=["GET"]) +def list_network_security_groups(): + compartment_id = request.args.get("compartmentId") + vcn_id = request.args.get("vcnId") + items = NETWORK_SECURITY_GROUPS + if compartment_id: + items = [i for i in items if i.get("compartmentId") == compartment_id] + if vcn_id: + items = [i for i in items if i.get("vcnId") == vcn_id] + return oci_res(items) + + +@networking_bp.route("/networkSecurityGroups/", methods=["GET"]) +def get_network_security_group(nsg_id): + nsg = next((n for n in NETWORK_SECURITY_GROUPS if n["id"] == nsg_id), None) + return oci_res(nsg) if nsg else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) + + +@networking_bp.route("/vnics/", methods=["GET"]) +def get_vnic(vnic_id): + vnic = next((v for v in VNICS if v["id"] == vnic_id), None) + return ( + oci_res(vnic) if vnic else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) + ) diff --git a/tests/e2e/features/mocks/services/object_storage_data.py b/tests/e2e/features/mocks/services/object_storage_data.py new file mode 100644 index 00000000..aefb5853 --- /dev/null +++ b/tests/e2e/features/mocks/services/object_storage_data.py @@ -0,0 +1,160 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +# CamelCase keys to mimic OCI SDK JSON structures + +NAMESPACE = "mock-namespace" + +BUCKETS = [ + { + "namespace": NAMESPACE, + "name": "mcp-e2e-bucket", + "compartmentId": "ocid1.tenancy.oc1..mock", + "createdBy": "ocid1.user.oc1..mock", + "timeCreated": "2026-01-01T00:00:00Z", + "etag": "etag-001", + "freeformTags": {"env": "test"}, + "definedTags": {}, + }, + { + "namespace": NAMESPACE, + "name": "mcp-e2e-logs", + "compartmentId": "ocid1.tenancy.oc1..mock", + "createdBy": "ocid1.user.oc1..mock", + "timeCreated": "2026-01-02T00:00:00Z", + "etag": "etag-002", + "freeformTags": {}, + "definedTags": {"ops": {"team": "mcp"}}, + }, +] + +# Full bucket details keyed by bucket name +BUCKET_DETAILS = { + "mcp-e2e-bucket": { + "namespace": NAMESPACE, + "name": "mcp-e2e-bucket", + "id": "ocid1.bucket.oc1..mock-bucket-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "timeCreated": "2026-01-01T00:00:00Z", + "etag": "etag-001", + "publicAccessType": "NoPublicAccess", + "storageTier": "Standard", + "objectEventsEnabled": False, + "freeformTags": {"env": "test"}, + "definedTags": {}, + "kmsKeyId": None, + "objectLifecyclePolicyEtag": None, + "approximateCount": 2, + "approximateSize": 2048, + "replicationEnabled": False, + "isReadOnly": False, + "versioning": "Enabled", + "autoTiering": "InfrequentAccess", + }, + "mcp-e2e-logs": { + "namespace": NAMESPACE, + "name": "mcp-e2e-logs", + "id": "ocid1.bucket.oc1..mock-bucket-2", + "compartmentId": "ocid1.tenancy.oc1..mock", + "timeCreated": "2026-01-02T00:00:00Z", + "etag": "etag-002", + "publicAccessType": "NoPublicAccess", + "storageTier": "Standard", + "objectEventsEnabled": False, + "freeformTags": {}, + "definedTags": {"ops": {"team": "mcp"}}, + "kmsKeyId": None, + "objectLifecyclePolicyEtag": None, + "approximateCount": 1, + "approximateSize": 1024, + "replicationEnabled": False, + "isReadOnly": False, + "versioning": "Disabled", + "autoTiering": "Disabled", + }, +} + +# Objects per bucket +OBJECTS = { + "mcp-e2e-bucket": { + "objects": [ + { + "name": "file1.txt", + "size": 1234, + "md5": "d41d8cd98f00b204e9800998ecf8427e", + "timeCreated": "2026-01-03T12:00:00Z", + "etag": "o-etag-001", + "storageTier": "Standard", + "archivalState": None, + "timeModified": "2026-01-03T12:00:00Z", + }, + { + "name": "file2.log", + "size": 814, + "md5": "098f6bcd4621d373cade4e832627b4f6", + "timeCreated": "2026-01-03T13:00:00Z", + "etag": "o-etag-002", + "storageTier": "Standard", + "archivalState": None, + "timeModified": "2026-01-03T13:00:00Z", + }, + ], + "prefixes": ["dir1/"], + "nextStartWith": None, + }, + "mcp-e2e-logs": { + "objects": [ + { + "name": "app.log", + "size": 1024, + "md5": "098f6bcd4621d373cade4e832627b4f6", + "timeCreated": "2026-01-04T13:00:00Z", + "etag": "o-etag-010", + "storageTier": "Standard", + "archivalState": None, + "timeModified": "2026-01-04T13:00:00Z", + } + ], + "prefixes": [], + "nextStartWith": None, + }, +} + +OBJECT_VERSIONS = { + "mcp-e2e-bucket": { + "items": [ + { + "name": "file1.txt", + "size": 1234, + "md5": "d41d8cd98f00b204e9800998ecf8427e", + "timeCreated": "2026-01-03T12:00:00Z", + "timeModified": "2026-01-03T12:00:00Z", + "etag": "v-etag-001", + "storageTier": "Standard", + "archivalState": None, + "versionId": "version_1", + "isDeleteMarker": False, + }, + { + "name": "file1.txt", + "size": 1230, + "md5": "e41d8cd98f00b204e9800998ecf8427e", + "timeCreated": "2026-01-02T10:00:00Z", + "timeModified": "2026-01-02T10:00:00Z", + "etag": "v-etag-000", + "storageTier": "Standard", + "archivalState": None, + "versionId": "version_0", + "isDeleteMarker": False, + }, + ], + "prefixes": ["dir1/"], + }, + "mcp-e2e-logs": { + "items": [], + "prefixes": [], + }, +} diff --git a/tests/e2e/features/mocks/services/object_storage_routes.py b/tests/e2e/features/mocks/services/object_storage_routes.py new file mode 100644 index 00000000..d41f4e8e --- /dev/null +++ b/tests/e2e/features/mocks/services/object_storage_routes.py @@ -0,0 +1,139 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint, jsonify, request +from object_storage_data import ( + BUCKET_DETAILS, + BUCKETS, + NAMESPACE, + OBJECT_VERSIONS, + OBJECTS, +) + +# OCI Object Storage endpoints style +# Base prefix is /n to match real API shape +object_storage_bp = Blueprint("object_storage", __name__) + + +@object_storage_bp.route("/n", methods=["GET"]) +def get_namespace(): + # compartmentId is accepted but not used for the mock + return oci_res(NAMESPACE) + + +@object_storage_bp.route("/n//b", methods=["GET"]) +def list_buckets(namespace): + _limit = request.args.get("limit", type=int) + + items = BUCKETS if namespace == NAMESPACE else [] + if _limit is not None: + items = items[:_limit] + return oci_res(items) + + +@object_storage_bp.route("/n//b/", methods=["GET"]) +def get_bucket(namespace, bucket_name): + if namespace != NAMESPACE: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + details = BUCKET_DETAILS.get(bucket_name) + if not details: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + return oci_res(details) + + +@object_storage_bp.route("/n//b//o", methods=["GET"]) +def list_objects(namespace, bucket_name): + if namespace != NAMESPACE: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + data = OBJECTS.get( + bucket_name, {"objects": [], "prefixes": [], "nextStartWith": None} + ) + + prefix = request.args.get("prefix", default="") + objects = data.get("objects", []) + if prefix: + objects = [o for o in objects if o.get("name", "").startswith(prefix)] + + # Basic prefix derivation for the filtered set + prefixes = sorted( + {o["name"].split("/", 1)[0] + "/" for o in objects if "/" in o["name"]} + ) + + return oci_res( + { + "objects": objects, + "prefixes": prefixes, + "nextStartWith": None, + } + ) + + +@object_storage_bp.route( + "/n//b//objectversions", methods=["GET"] +) +def list_object_versions(namespace, bucket_name): + if namespace != NAMESPACE: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + data = OBJECT_VERSIONS.get(bucket_name, {"items": [], "prefixes": []}) + prefix = request.args.get("prefix", default="") + limit = request.args.get("limit", type=int) + items = data.get("items", []) + if prefix: + items = [o for o in items if o.get("name", "").startswith(prefix)] + + prefixes = sorted( + {i["name"].split("/", 1)[0] + "/" for i in items if "/" in i["name"]} + ) + + if limit is not None: + items = items[:limit] + + return oci_res( + { + "items": items, + "prefixes": prefixes, + } + ) + + +@object_storage_bp.route( + "/n//b//o/", + methods=["GET", "PUT", "HEAD", "DELETE"], +) +def get_or_put_object(namespace, bucket_name, object_name): + if namespace != NAMESPACE: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + if request.method in ["GET", "HEAD"]: + bucket = OBJECTS.get(bucket_name, {"objects": []}) + obj = next( + (o for o in bucket.get("objects", []) if o.get("name") == object_name), None + ) + if not obj: + return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 + + if request.method == "HEAD": + resp = jsonify({}) + resp.headers["Content-Length"] = obj.get("size", 0) + resp.headers["ETag"] = obj.get("etag", "mock-etag") + return resp + + # GET returns content + return f"Content of {object_name}", 200 + + if request.method == "DELETE": + bucket = OBJECTS.get(bucket_name) + if bucket: + bucket["objects"] = [ + o for o in bucket["objects"] if o.get("name") != object_name + ] + return "", 204 + + # PUT upload: accept and return simple ack + return oci_res({"message": "Object uploaded successfully"}) diff --git a/tests/e2e/features/mocks/services/registry_data.py b/tests/e2e/features/mocks/services/registry_data.py new file mode 100644 index 00000000..1304ef3d --- /dev/null +++ b/tests/e2e/features/mocks/services/registry_data.py @@ -0,0 +1,38 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +CONTAINER_REPOSITORIES = [ + { + "id": "ocid1.containerrepo.oc1..mock-repo-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "displayName": "mytenant/app1", + "imageCount": 3, + "isImmutable": False, + "isPublic": False, + "layerCount": 10, + "layersSizeInBytes": 12345678, + "lifecycleState": "AVAILABLE", + "namespace": "mytenant", + "timeCreated": "2026-01-10T10:00:00Z", + "timeLastPushed": "2026-01-13T11:00:00Z", + "freeformTags": {"env": "test"}, + }, + { + "id": "ocid1.containerrepo.oc1..mock-repo-2", + "compartmentId": "ocid1.tenancy.oc1..mock", + "displayName": "mytenant/app2", + "imageCount": 5, + "isImmutable": True, + "isPublic": True, + "layerCount": 20, + "layersSizeInBytes": 22334455, + "lifecycleState": "AVAILABLE", + "namespace": "mytenant", + "timeCreated": "2026-01-12T08:30:00Z", + "timeLastPushed": "2026-01-15T09:45:00Z", + "definedTags": {"Operations": {"CostCenter": "42"}}, + }, +] diff --git a/tests/e2e/features/mocks/services/registry_routes.py b/tests/e2e/features/mocks/services/registry_routes.py new file mode 100644 index 00000000..1c447702 --- /dev/null +++ b/tests/e2e/features/mocks/services/registry_routes.py @@ -0,0 +1,40 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint, jsonify, request +from registry_data import CONTAINER_REPOSITORIES + +# OCI Artifacts (Container Registry) API base path +registry_bp = Blueprint("registry", __name__, url_prefix="/20160918") + + +def _apply_limit(items): + try: + limit = request.args.get("limit") + if limit is not None: + lim = int(limit) + if lim >= 0: + return items[:lim] + except Exception: + pass + return items + + +@registry_bp.route("/container/repositories", methods=["GET"]) +def list_container_repositories(): + # Accept common filters (ignored in mock): compartmentId, namespace, displayName, page, limit + items = _apply_limit(CONTAINER_REPOSITORIES) + # Shape must match ContainerRepositoryCollection + return oci_res({"items": items}) + + +@registry_bp.route("/container/repositories/", methods=["GET"]) +def get_container_repository(repository_id): + repo = next((r for r in CONTAINER_REPOSITORIES if r["id"] == repository_id), None) + return ( + oci_res(repo) if repo else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) + ) diff --git a/tests/e2e/features/mocks/services/resource_search_data.py b/tests/e2e/features/mocks/services/resource_search_data.py new file mode 100644 index 00000000..78a4a1dd --- /dev/null +++ b/tests/e2e/features/mocks/services/resource_search_data.py @@ -0,0 +1,47 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +# Mock data for OCI Resource Search service (camelCase keys) + +RESOURCE_TYPES = [ + {"name": "instance"}, + {"name": "vcn"}, + {"name": "subnet"}, + {"name": "volume"}, + {"name": "bucket"}, +] + +RESOURCES = [ + { + "resourceType": "Instance", + "identifier": "ocid1.instance.oc1..mock-uuid-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "timeCreated": "2026-01-13T10:00:00Z", + "displayName": "Mock-Server-1", + "availabilityDomain": "aNMj:US-ASHBURN-AD-1", + "lifecycleState": "RUNNING", + "freeformTags": {"Department": "Finance"}, + "definedTags": {"Operations": {"CostCenter": "42"}}, + "systemTags": {"orcl-cloud": {"free-tier-retain": True}}, + "searchContext": {"highlights": {"displayName": ["

Mock

-Server-1"]}}, + "identityContext": None, + "additionalDetails": None, + }, + { + "resourceType": "Subnet", + "identifier": "ocid1.subnet.oc1..mock-subnet-1", + "compartmentId": "ocid1.tenancy.oc1..mock", + "timeCreated": "2026-01-13T10:00:00Z", + "displayName": "Mock-Subnet-1", + "lifecycleState": "AVAILABLE", + "freeformTags": {}, + "definedTags": {}, + "systemTags": {}, + "searchContext": None, + "identityContext": None, + "additionalDetails": None, + }, +] diff --git a/tests/e2e/features/mocks/services/resource_search_routes.py b/tests/e2e/features/mocks/services/resource_search_routes.py new file mode 100644 index 00000000..6fb19173 --- /dev/null +++ b/tests/e2e/features/mocks/services/resource_search_routes.py @@ -0,0 +1,95 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint, request +from resource_search_data import RESOURCE_TYPES, RESOURCES + +# OCI Resource Search API version base path +resource_search_bp = Blueprint("resource_search", __name__, url_prefix="/20180409") + + +def _apply_limit(items): + try: + limit = request.args.get("limit") + if limit is not None: + lim = int(limit) + if lim >= 0: + return items[:lim] + except Exception: + pass + return items + + +@resource_search_bp.route("/resources", methods=["POST"]) +def search_resources(): + body = request.get_json(silent=True) or {} + + # OCI SDK typically sends camelCase searchDetails + details = body.get("searchDetails") or body.get("search_details") or {} + + results = RESOURCES + + # Handle FreeText vs Structured queries + if (details.get("type") or details.get("Type")) in ("FreeText", "FREETEXT"): + text = details.get("text", "") or details.get("Text", "") + if text: + t = str(text).lower() + results = [ + r + for r in RESOURCES + if t in str(r.get("displayName", "")).lower() + or t in str(r.get("identifier", "")).lower() + or t in str(r.get("resourceType", "")).lower() + ] + else: + query = details.get("query", "") or details.get("Query", "") + q = str(query) + # Very simple/forgiving parsing to support the queries used by the tools + # compartmentId filter + if "compartmentId" in q: + # extract between compartmentId = '...' + try: + part = q.split("compartmentId", 1)[1] + comp_val = part.split("'", 2)[1] + results = [r for r in results if r.get("compartmentId") == comp_val] + except Exception: + pass + # displayName =~ '...' + if "displayName" in q and "=~" in q: + try: + disp_val = q.split("displayName", 1)[1].split("'", 2)[1] + dv = disp_val.lower() + results = [ + r for r in results if dv in str(r.get("displayName", "")).lower() + ] + except Exception: + pass + # query {resource_type} resources + if q.strip().lower().startswith("query ") and " resources" in q.lower(): + try: + after_query = q.strip()[len("query ") :] # noqa + resource_type = after_query.split(" ", 1)[0].strip().lower() + results = [ + r + for r in results + if str(r.get("resourceType", "")).lower() == resource_type + ] + except Exception: + pass + + # Apply limit via query param if present + results = _apply_limit(results) + + # Shape must match OCI ResourceSummaryCollection + return oci_res({"items": results}) + + +@resource_search_bp.route("/resourceTypes", methods=["GET"]) +def list_resource_types(): + items = _apply_limit(RESOURCE_TYPES) + # Shape: list of ResourceType objects + return oci_res(items) diff --git a/tests/e2e/features/mocks/services/usage_data.py b/tests/e2e/features/mocks/services/usage_data.py new file mode 100644 index 00000000..140a70a6 --- /dev/null +++ b/tests/e2e/features/mocks/services/usage_data.py @@ -0,0 +1,30 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +SUMMARIZED_USAGES = [ + { + "tenantId": "ocid1.tenancy.oc1..mock", + "timeUsageStarted": "2025-01-01T00:00:00Z", + "timeUsageEnded": "2025-01-02T00:00:00Z", + "computedAmount": 10.50, + "computedQuantity": 5, + "currency": "USD", + "unit": "GB-Hours", + "service": "Object Storage", + "region": "us-ashburn-1", + }, + { + "tenantId": "ocid1.tenancy.oc1..mock", + "timeUsageStarted": "2025-01-02T00:00:00Z", + "timeUsageEnded": "2025-01-03T00:00:00Z", + "computedAmount": 12.00, + "computedQuantity": 6, + "currency": "USD", + "unit": "GB-Hours", + "service": "Object Storage", + "region": "us-ashburn-1", + }, +] diff --git a/tests/e2e/features/mocks/services/usage_routes.py b/tests/e2e/features/mocks/services/usage_routes.py new file mode 100644 index 00000000..ca1469ba --- /dev/null +++ b/tests/e2e/features/mocks/services/usage_routes.py @@ -0,0 +1,16 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from _common import oci_res +from flask import Blueprint +from usage_data import SUMMARIZED_USAGES + +usage_bp = Blueprint("usage", __name__, url_prefix="/20200107") + + +@usage_bp.route("/usage", methods=["POST"]) +def get_summarized_usage(): + return oci_res({"items": SUMMARIZED_USAGES}) diff --git a/tests/e2e/features/mocks/sitecustomize.py b/tests/e2e/features/mocks/sitecustomize.py index ffab7860..6f1d2391 100644 --- a/tests/e2e/features/mocks/sitecustomize.py +++ b/tests/e2e/features/mocks/sitecustomize.py @@ -1,26 +1,96 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + import os import sys -import oci.identity +import oci # Use this file to perform patches for individual servers that ignore HTTP/HTTPS proxy settings # E.g. the OCI identity service has one global endpoint that ignores proxies. # You can patch the identity client's init function to shove the mocked endpoint in there. -original_init = oci.identity.IdentityClient.__init__ +# Identity +original_identity_init = oci.identity.IdentityClient.__init__ -def patched_init(self, config, **kwargs): +def patched_identity_init(self, config, **kwargs): # Change http to https so the Shim triggers the SSL logic kwargs["service_endpoint"] = "https://iaas.us-mock-1.oraclecloud.com" kwargs["verify"] = False - return original_init(self, config, **kwargs) - + return original_identity_init(self, config, **kwargs) -oci.identity.IdentityClient.__init__ = patched_init -# IMPORTANT: Use stderr for logging +oci.identity.IdentityClient.__init__ = patched_identity_init sys.stderr.write("!!! IDENTITY PATCH INJECTED VIA SITECUSTOMIZE !!!\n") + +# Network load balancer search +original_network_load_balancer_init = ( + oci.network_load_balancer.NetworkLoadBalancerClient.__init__ +) + + +def patched_network_load_balancer_init(self, config, **kwargs): + # Change http to https so the Shim triggers the SSL logic + kwargs["service_endpoint"] = "https://iaas.us-mock-1.oraclecloud.com" + kwargs["verify"] = False + return original_network_load_balancer_init(self, config, **kwargs) + + +oci.network_load_balancer.NetworkLoadBalancerClient.__init__ = ( + patched_network_load_balancer_init +) +sys.stderr.write("!!! NLB PATCH INJECTED VIA SITECUSTOMIZE !!!\n") + + +# Object storage +original_object_storage_init = oci.object_storage.ObjectStorageClient.__init__ + + +def patched_object_storage_init(self, config, **kwargs): + # Change http to https so the Shim triggers the SSL logic + kwargs["service_endpoint"] = "https://iaas.us-mock-1.oraclecloud.com" + kwargs["verify"] = False + return original_object_storage_init(self, config, **kwargs) + + +oci.object_storage.ObjectStorageClient.__init__ = patched_object_storage_init +sys.stderr.write("!!! OBJECT STORAGE PATCH INJECTED VIA SITECUSTOMIZE !!!\n") + + +# Usage +original_usage_init = oci.usage_api.UsageapiClient.__init__ + + +def patched_usage_init(self, config, **kwargs): + # Change http to https so the Shim triggers the SSL logic + kwargs["service_endpoint"] = "https://iaas.us-mock-1.oraclecloud.com" + kwargs["verify"] = False + return original_usage_init(self, config, **kwargs) + + +oci.usage_api.UsageapiClient.__init__ = patched_usage_init +sys.stderr.write("!!! USAGE PATCH INJECTED VIA SITECUSTOMIZE !!!\n") + + +# Resource search +original_resource_search_init = oci.resource_search.ResourceSearchClient.__init__ + + +def patched_resource_search_init(self, config, **kwargs): + # Change http to https so the Shim triggers the SSL logic + kwargs["service_endpoint"] = "https://iaas.us-mock-1.oraclecloud.com" + kwargs["verify"] = False + return original_resource_search_init(self, config, **kwargs) + + +oci.resource_search.ResourceSearchClient.__init__ = patched_resource_search_init +sys.stderr.write("!!! RESOURCE SEARCH PATCH INJECTED VIA SITECUSTOMIZE !!!\n") + + config_env = os.environ.get("OCI_CONFIG_FILE", "NOT SET (Using default ~/.oci/config)") -sys.stderr.write(f"!!! DEBUG: Identity Server using config: {config_env} !!!\n") +sys.stderr.write(f"!!! DEBUG: MCP server using config: {config_env} !!!\n") diff --git a/tests/e2e/features/oci-api-mcp-server.feature b/tests/e2e/features/oci-api-mcp-server.feature deleted file mode 100644 index b7a05cab..00000000 --- a/tests/e2e/features/oci-api-mcp-server.feature +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. -# Licensed under the Universal Permissive License v1.0 as shown at -# https://oss.oracle.com/licenses/upl. - - -Feature: OCI API MCP Server - Scenario: List the tools available in the agent - Given the MCP server is running with OCI tools - And the ollama model with the tools is properly working - When I send a request with the prompt "What tools do you have" - Then the response should contain a list of tools available - - Scenario: List the instances in the root compartment - Given the MCP server is running with OCI tools - And the ollama model with the tools is properly working - When I send a request with the prompt "list my instances and limit to 2" - Then the response should contain a list of all instances - - Scenario: Generate Terraform for Stable Diffusion GPU infrastructure - Given the MCP server is running with OCI tools - And the ollama model with the tools is properly working - When I send a request with the prompt "Create TF for me to create and configure all resources needed to run stable diffusion in a webUI on an OCI GPU. I want to make sure i'm using OCI Gen AI services as much as possible" - Then the response should contain terraform configuration - And the response should mention GPU instances - And the response should mention OCI Gen AI services \ No newline at end of file diff --git a/tests/e2e/features/oci-cloud-guard-mcp-server.feature b/tests/e2e/features/oci-cloud-guard-mcp-server.feature index 71a27ef3..18313f72 100644 --- a/tests/e2e/features/oci-cloud-guard-mcp-server.feature +++ b/tests/e2e/features/oci-cloud-guard-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. diff --git a/tests/e2e/features/oci-compute-instance-agent-mcp-server.feature b/tests/e2e/features/oci-compute-instance-agent-mcp-server.feature new file mode 100644 index 00000000..69bb07a7 --- /dev/null +++ b/tests/e2e/features/oci-compute-instance-agent-mcp-server.feature @@ -0,0 +1,16 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +Feature: OCI Compute Instance Agent MCP Server + Scenario: List instance agent command executions for an instance + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my instances, then list the instance agent command executions on the first instance in the list and limit to 2" + Then the response should contain a list of instance agent command executions + + Scenario: Run an instance agent command and get execution details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my instances, then run an instance agent command 'echo Hello from agent' on the first instance in the list" + Then the response should contain instance agent command execution details diff --git a/tests/e2e/features/oci-compute-mcp-server.feature b/tests/e2e/features/oci-compute-mcp-server.feature index 3b489493..1b9e223d 100644 --- a/tests/e2e/features/oci-compute-mcp-server.feature +++ b/tests/e2e/features/oci-compute-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2025, 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. diff --git a/tests/e2e/features/oci-faaas-mcp-server.feature b/tests/e2e/features/oci-faaas-mcp-server.feature index 6484c667..143e4fa4 100644 --- a/tests/e2e/features/oci-faaas-mcp-server.feature +++ b/tests/e2e/features/oci-faaas-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. diff --git a/tests/e2e/features/oci-identity-mcp-server.feature b/tests/e2e/features/oci-identity-mcp-server.feature index 90afbda2..20e2ad5b 100644 --- a/tests/e2e/features/oci-identity-mcp-server.feature +++ b/tests/e2e/features/oci-identity-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. diff --git a/tests/e2e/features/oci-logging-mcp-server.feature b/tests/e2e/features/oci-logging-mcp-server.feature index 866beab4..ea61cebc 100644 --- a/tests/e2e/features/oci-logging-mcp-server.feature +++ b/tests/e2e/features/oci-logging-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. diff --git a/tests/e2e/features/oci-migration-mcp-server.feature b/tests/e2e/features/oci-migration-mcp-server.feature index ca6e1058..1ee90071 100644 --- a/tests/e2e/features/oci-migration-mcp-server.feature +++ b/tests/e2e/features/oci-migration-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. diff --git a/tests/e2e/features/oci-monitoring-mcp-server.feature b/tests/e2e/features/oci-monitoring-mcp-server.feature new file mode 100644 index 00000000..2d5483c8 --- /dev/null +++ b/tests/e2e/features/oci-monitoring-mcp-server.feature @@ -0,0 +1,28 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +Feature: OCI Monitoring MCP Server + Scenario: List the monitoring tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What monitoring mcp tools do you have" + Then the response should contain a list of monitoring tools available + + Scenario: List alarms + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list the alarms in my compartment" + Then the response should contain a list of alarms + + Scenario: List metric definitions + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list the metric definitions for namespace oci_computeagent" + Then the response should contain a list of metric definitions + + Scenario: Get metrics data + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "get the CpuUtilization metrics data for the last hour" + Then the response should contain aggregated metric data \ No newline at end of file diff --git a/tests/e2e/features/oci-network-load-balancer-mcp-server.feature b/tests/e2e/features/oci-network-load-balancer-mcp-server.feature new file mode 100644 index 00000000..cdee08b4 --- /dev/null +++ b/tests/e2e/features/oci-network-load-balancer-mcp-server.feature @@ -0,0 +1,52 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +Feature: OCI Network Load Balancer MCP Server + Scenario: List the network load balancer tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What network load balancer mcp tools do you have" + Then the response should contain a list of network load balancer tools available + + Scenario: List the network load balancers + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers" + Then the response should contain a list of network load balancers + + Scenario: Get network load balancer details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers, then get the details of the first network load balancer in the list" + Then the response should contain the details of a network load balancer + + Scenario: List the listeners on the first network load balancer + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers and list the listeners on the first network load balancer in the list" + Then the response should contain a list of listeners + + Scenario: Get listener details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers and list the listeners on the first network load balancer in the list, then fetch the details of the first listener in that list" + Then the response should contain the details of a listener + + Scenario: List the backend sets on the first network load balancer + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers and list the backend sets on the first network load balancer in the list" + Then the response should contain a list of backend sets + + Scenario: Get backend set details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers and list the backend sets on the first network load balancer in the list, then fetch the details of the first backend set in that list" + Then the response should contain the details of a backend set + + Scenario: List the backends in the first backend set + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network load balancers and list the backend sets on the first network load balancer in the list, then list the backends in the first backend set" + Then the response should contain a list of backends diff --git a/tests/e2e/features/oci-networking-mcp-server.feature b/tests/e2e/features/oci-networking-mcp-server.feature new file mode 100644 index 00000000..3f02ddca --- /dev/null +++ b/tests/e2e/features/oci-networking-mcp-server.feature @@ -0,0 +1,64 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + +Feature: OCI Networking MCP Server + Scenario: List the networking tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What networking mcp tools do you have" + Then the response should contain a list of networking tools available + + Scenario: List the virtual networks (VCNs) + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my vcns" + Then the response should contain a list of vcns + + Scenario: Get virtual network (VCN) details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my vcns, then get the details of the first vcn in the list" + Then the response should contain the details of a vcn + + Scenario: List the subnets + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my subnets" + Then the response should contain a list of subnets + + Scenario: Get subnet details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my subnets, then get the details of the first subnet in the list" + Then the response should contain the details of a subnet + + Scenario: List the security lists + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my security lists" + Then the response should contain a list of security lists + + Scenario: Get security list details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my security lists, then get the details of the first security list in the list" + Then the response should contain the details of a security list + + Scenario: List the network security groups + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network security groups" + Then the response should contain a list of network security groups + + Scenario: Get network security group details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my network security groups, then get the details of the first network security group in the list" + Then the response should contain the details of a network security group + + Scenario: Get vnic details by id + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "get the vnic details for ocid ocid1.vnic.oc1..mock-vnic" + Then the response should contain the details of a vnic diff --git a/tests/e2e/features/oci-object-storage-mcp-server.feature b/tests/e2e/features/oci-object-storage-mcp-server.feature index 9fbd8636..62f54b8f 100644 --- a/tests/e2e/features/oci-object-storage-mcp-server.feature +++ b/tests/e2e/features/oci-object-storage-mcp-server.feature @@ -1,4 +1,4 @@ -# Copyright (c) 2025, Oracle and/or its affiliates. +# Copyright (c) 2026, Oracle and/or its affiliates. # Licensed under the Universal Permissive License v1.0 as shown at # https://oss.oracle.com/licenses/upl. @@ -16,3 +16,27 @@ Feature: OCI Object Storage MCP Server When I send a request with the prompt "list my buckets" Then the response should contain a list of buckets available + Scenario: Get bucket details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my buckets, then get the details of the first bucket in the list" + Then the response should contain the details of a bucket + + Scenario: List objects in a bucket + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list the objects in the bucket mcp-e2e-bucket" + Then the response should contain a list of objects + + Scenario: List object versions in a bucket + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list the object versions in the bucket mcp-e2e-bucket" + Then the response should contain a list of object versions + + # Commented out because of a bug in get_object + # Scenario: Get a specific object from a bucket + # Given the MCP server is running with OCI tools + # And the ollama model with the tools is properly working + # When I send a request with the prompt "get the object file1.txt from the bucket mcp-e2e-bucket" + # Then the response should contain the details of an object diff --git a/tests/e2e/features/oci-registry-mcp-server.feature b/tests/e2e/features/oci-registry-mcp-server.feature new file mode 100644 index 00000000..1356d0b0 --- /dev/null +++ b/tests/e2e/features/oci-registry-mcp-server.feature @@ -0,0 +1,23 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + + +Feature: OCI Registry MCP Server + Scenario: List the registry tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What registry mcp tools do you have" + Then the response should contain a list of registry tools available + + Scenario: List container repositories + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my container repositories and limit to 5" + Then the response should contain a list of container repositories + + Scenario: Get container repository details + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list my container repositories and limit to 5, then get the details of the first repository in the list" + Then the response should contain the details of a container repository diff --git a/tests/e2e/features/oci-resource-search-mcp-server.feature b/tests/e2e/features/oci-resource-search-mcp-server.feature new file mode 100644 index 00000000..d2a35a79 --- /dev/null +++ b/tests/e2e/features/oci-resource-search-mcp-server.feature @@ -0,0 +1,41 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + + +Feature: OCI Resource Search MCP Server + Scenario: List the resource search tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What resource search mcp tools do you have" + Then the response should contain a list of resource search tools available + + Scenario: List supported resource types + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list the resource search resource types and limit to 5" + Then the response should contain a list of resource types + + Scenario: List all resources in a compartment + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "list all resources for tenant ocid1.tenancy.oc1..mock in compartment ocid1.tenancy.oc1..mock and limit to 10" + Then the response should contain a list of resources + + Scenario: Search resources by display name + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "search for resources in compartment ocid1.tenancy.oc1..mock for tenant ocid1.tenancy.oc1..mock with display name Mock-Server-1" + Then the response should contain resources matching the display name search + + Scenario: Free-form search for resources + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "free text search for resources for tenant ocid1.tenancy.oc1..mock containing the text mock" + Then the response should contain resources matching the free-text search + + Scenario: Search resources by type + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "search for resources by type subnet for tenant ocid1.tenancy.oc1..mock in compartment ocid1.tenancy.oc1..mock" + Then the response should contain resources of the specified type diff --git a/tests/e2e/features/oci-usage-mcp-server.feature b/tests/e2e/features/oci-usage-mcp-server.feature new file mode 100644 index 00000000..0f383c2d --- /dev/null +++ b/tests/e2e/features/oci-usage-mcp-server.feature @@ -0,0 +1,17 @@ +# Copyright (c) 2026, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v1.0 as shown at +# https://oss.oracle.com/licenses/upl. + + +Feature: OCI Usage MCP Server + Scenario: List the usage tools available in the agent + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "What usage mcp tools do you have" + Then the response should contain a list of usage tools available + + Scenario: Get summarized usage + Given the MCP server is running with OCI tools + And the ollama model with the tools is properly working + When I send a request with the prompt "Get the summarized usage for tenant ocid1.tenancy.oc1..mock from 2025-01-01T00:00:00Z to 2025-01-03T00:00:00Z" + Then the response should contain a list of usage summaries \ No newline at end of file diff --git a/tests/e2e/features/steps/oci-api-mcp-server-steps.py b/tests/e2e/features/steps/oci-api-mcp-server-steps.py deleted file mode 100644 index 672aac7b..00000000 --- a/tests/e2e/features/steps/oci-api-mcp-server-steps.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Copyright (c) 2025, Oracle and/or its affiliates. -Licensed under the Universal Permissive License v1.0 as shown at -https://oss.oracle.com/licenses/upl. -""" - -from behave import then - - -@then("the response should contain terraform configuration") -def step_impl_terraform_configuration(context): - response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." - content = response_json["message"]["content"].lower() - assert any( - keyword in content - for keyword in ["terraform", "resource", "provider", ".tf", "infrastructure"] - ), "Terraform configuration could not be generated." - - -@then("the response should mention GPU instances") -def step_impl_gpu_instances(context): - response_json = context.response.json() - content = response_json["message"]["content"].lower() - assert any( - keyword in content - for keyword in ["gpu", "bm.gpu", "vm.gpu", "a10", "v100", "graphics"] - ), "GPU instances were not mentioned in the response." - - -@then("the response should mention OCI Gen AI services") -def step_impl_oci_gen_ai(context): - response_json = context.response.json() - content = response_json["message"]["content"].lower() - assert any( - keyword in content - for keyword in [ - "gen ai", - "generative ai", - "ai service", - "llama", - "cohere", - "model", - ] - ), "OCI Gen AI services were not mentioned in the response." diff --git a/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py b/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py index 9774cb61..b22247cc 100644 --- a/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/steps/oci-compute-instance-agent-mcp-server-steps.py b/tests/e2e/features/steps/oci-compute-instance-agent-mcp-server-steps.py new file mode 100644 index 00000000..281fc1b8 --- /dev/null +++ b/tests/e2e/features/steps/oci-compute-instance-agent-mcp-server-steps.py @@ -0,0 +1,31 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of instance agent command executions") +def step_impl_list_agent_command_executions(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "ocid1.instanceagentcommand" in content + or "instance agent command" in content + or "command executions" in content + ), "List of instance agent command executions not found." + + +@then("the response should contain instance agent command execution details") +def step_impl_get_agent_command_execution_details(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "hello from agent" in content + or "exit code" in content + or "succeeded" in content + ), "Instance agent command execution details not found." diff --git a/tests/e2e/features/steps/oci-compute-mcp-server-steps.py b/tests/e2e/features/steps/oci-compute-mcp-server-steps.py index f7c4dee1..5af3237e 100644 --- a/tests/e2e/features/steps/oci-compute-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-compute-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2025, 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py b/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py index cc7d14c2..5a2656a8 100644 --- a/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/steps/oci-identity-mcp-server-steps.py b/tests/e2e/features/steps/oci-identity-mcp-server-steps.py index ed312261..1bd0817b 100644 --- a/tests/e2e/features/steps/oci-identity-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-identity-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/steps/oci-logging-mcp-server-steps.py b/tests/e2e/features/steps/oci-logging-mcp-server-steps.py index 94e7e242..2a8ce769 100644 --- a/tests/e2e/features/steps/oci-logging-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-logging-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/steps/oci-migration-mcp-server-steps.py b/tests/e2e/features/steps/oci-migration-mcp-server-steps.py index 3b6f97c6..f056a7c6 100644 --- a/tests/e2e/features/steps/oci-migration-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-migration-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ diff --git a/tests/e2e/features/steps/oci-monitoring-mcp-server-steps.py b/tests/e2e/features/steps/oci-monitoring-mcp-server-steps.py new file mode 100644 index 00000000..746a0bd3 --- /dev/null +++ b/tests/e2e/features/steps/oci-monitoring-mcp-server-steps.py @@ -0,0 +1,56 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of monitoring tools available") +def step_impl_monitoring_tools_available(context): + response_json = context.response.json() + assert ( + "content" in response_json["message"] + ), "Response does not contain a content key." + content = response_json["message"]["content"].lower() + assert any( + tool in content + for tool in [ + "list_alarms", + "list_metric_definitions", + "get_metrics_data", + ] + ), "Monitoring tools could not be queried." + + +@then("the response should contain a list of alarms") +def step_impl_list_alarms(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "ocid1.alarm" in content or "high cpu alarm" in content + ), "List of alarms not found." + + +@then("the response should contain a list of metric definitions") +def step_impl_list_metric_definitions(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "cpuutilization" in content or "oci_computeagent" in content + ), "List of metric definitions not found." + + +@then("the response should contain aggregated metric data") +def step_impl_get_metrics_data(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "cpuutilization" in content + or "aggregateddatapoints" in content + or "45.5" in content + ), "Aggregated metric data not found." diff --git a/tests/e2e/features/steps/oci-network-load-balancer-mcp-server-steps.py b/tests/e2e/features/steps/oci-network-load-balancer-mcp-server-steps.py new file mode 100644 index 00000000..58cdcb7b --- /dev/null +++ b/tests/e2e/features/steps/oci-network-load-balancer-mcp-server-steps.py @@ -0,0 +1,107 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of network load balancer tools available") +def step_impl_nlb_tools_available(context): + response_json = context.response.json() + assert ( + "content" in response_json["message"] + ), "Response does not contain a content key." + content = response_json["message"]["content"].lower() + expected_tools = [ + "list_network_load_balancers", + "get_network_load_balancer", + "list_network_load_balancer_listeners", + "get_network_load_balancer_listener", + "list_network_load_balancer_backend_sets", + "get_network_load_balancer_backend_set", + "list_network_load_balancer_backends", + "get_network_load_balancer_backend", + ] + assert any( + tool in content for tool in expected_tools + ), "Network Load Balancer tools could not be queried." + + +@then("the response should contain a list of network load balancers") +def step_impl_list_nlbs(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.nlb" in result["message"]["content"] + ), "List of network load balancers not found." + + +@then("the response should contain the details of a network load balancer") +def step_impl_get_nlb(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.nlb" in result["message"]["content"] + ), "Network load balancer details not found." + + +@then("the response should contain a list of listeners") +def step_impl_list_listeners(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + x in content for x in ["listener-1", "port", "protocol"] + ), "List of listeners not found." + + +@then("the response should contain the details of a listener") +def step_impl_get_listener(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + x in content for x in ["listener-1", "defaultbackendsetname", "port"] + ), "Listener details not found." + + +@then("the response should contain a list of backend sets") +def step_impl_list_backend_sets(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + x in content for x in ["backendset-1", "policy", "backends"] + ), "List of backend sets not found." + + +@then("the response should contain the details of a backend set") +def step_impl_get_backend_set(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + x in content for x in ["backendset-1", "healthchecker", "policy"] + ), "Backend set details not found." + + +@then("the response should contain a list of backends") +def step_impl_list_backends(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + x in content for x in ["backend-1", "10.0.0.10", "port"] + ), "List of backends not found." + + +@then("the response should contain the details of a backend") +def step_impl_get_backend(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + x in content for x in ["backend-1", "10.0.0.10", "weight"] + ), "Backend details not found." diff --git a/tests/e2e/features/steps/oci-networking-mcp-server-steps.py b/tests/e2e/features/steps/oci-networking-mcp-server-steps.py new file mode 100644 index 00000000..e15f20cb --- /dev/null +++ b/tests/e2e/features/steps/oci-networking-mcp-server-steps.py @@ -0,0 +1,97 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of networking tools available") +def step_impl_networking_tools_available(context): + response_json = context.response.json() + assert ( + "content" in response_json["message"] + ), "Response does not contain a content key." + content = response_json["message"]["content"].lower() + assert any( + tool in content + for tool in [ + "list_vcns", + "get_vcn", + "list_subnets", + "get_subnet", + "list_security_lists", + ] + ), "Networking tools could not be queried." + + +@then("the response should contain a list of vcns") +def step_impl_list_vcns(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert "ocid1.vcn" in result["message"]["content"], "List of VCNs not found." + + +@then("the response should contain the details of a vcn") +def step_impl_get_vcn(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert "ocid1.vcn" in result["message"]["content"], "VCN details not found." + + +@then("the response should contain a list of subnets") +def step_impl_list_subnets(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert "ocid1.subnet" in result["message"]["content"], "List of subnets not found." + + +@then("the response should contain the details of a subnet") +def step_impl_get_subnet(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert "ocid1.subnet" in result["message"]["content"], "Subnet details not found." + + +@then("the response should contain a list of security lists") +def step_impl_list_security_lists(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.securitylist" in result["message"]["content"] + ), "List of security lists not found." + + +@then("the response should contain the details of a security list") +def step_impl_get_security_list(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.securitylist" in result["message"]["content"] + ), "Security list details not found." + + +@then("the response should contain a list of network security groups") +def step_impl_list_nsgs(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.networksecuritygroup" in result["message"]["content"] + ), "List of network security groups not found." + + +@then("the response should contain the details of a network security group") +def step_impl_get_nsg(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.networksecuritygroup" in result["message"]["content"] + ), "Network security group details not found." + + +@then("the response should contain the details of a vnic") +def step_impl_get_vnic(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert "ocid1.vnic" in result["message"]["content"], "Vnic details not found." diff --git a/tests/e2e/features/steps/oci-object-storage-mcp-server-steps.py b/tests/e2e/features/steps/oci-object-storage-mcp-server-steps.py index 790172a2..2b2de929 100644 --- a/tests/e2e/features/steps/oci-object-storage-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-object-storage-mcp-server-steps.py @@ -1,5 +1,5 @@ """ -Copyright (c) 2025, Oracle and/or its affiliates. +Copyright (c) 2026, Oracle and/or its affiliates. Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl. """ @@ -10,14 +10,56 @@ @then("the response should contain a the tenancy namespace") def step_impl_namespace(context): result = context.response.json() - print("buckets", result) assert "content" in result["message"], "Response does not contain a content key." - # assert "bucket" in result["message"]["content"] + assert any( + key in result["message"]["content"].lower() + for key in ["namespace", "mock-namespace"] + ), "Namespace not found in response." @then("the response should contain a list of buckets available") def step_impl_list_buckets(context): result = context.response.json() - print("buckets", result) assert "content" in result["message"], "Response does not contain a content key." - # assert "bucket" in result["message"]["content"] + content = result["message"]["content"].lower() + assert any( + name in content for name in ["mcp-e2e-bucket", "mcp-e2e-logs"] + ), "Buckets not listed." + + +@then("the response should contain the details of a bucket") +def step_impl_bucket_details(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "ocid1.bucket" in content or "approximate" in content + ), "Bucket details not found." + + +@then("the response should contain a list of objects") +def step_impl_list_objects(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert any( + obj in content for obj in ["file1.txt", "app.log", "file2.log"] + ), "Objects not listed." + + +@then("the response should contain a list of object versions") +def step_impl_list_object_versions(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert ( + "version_" in content or "object versions" in content + ), "Object versions not listed." + + +@then("the response should contain the details of an object") +def step_impl_object_details(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert "file1.txt" in content or "size" in content, "Object details not found." diff --git a/tests/e2e/features/steps/oci-registry-mcp-server-steps.py b/tests/e2e/features/steps/oci-registry-mcp-server-steps.py new file mode 100644 index 00000000..760f50b9 --- /dev/null +++ b/tests/e2e/features/steps/oci-registry-mcp-server-steps.py @@ -0,0 +1,41 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of registry tools available") +def step_impl_registry_tools_available(context): + response_json = context.response.json() + assert ( + "content" in response_json["message"] + ), "Response does not contain a content key." + content = response_json["message"]["content"].lower() + assert any( + tool in content + for tool in [ + "list_container_repositories", + "get_container_repository", + ] + ), "Registry tools could not be queried." + + +@then("the response should contain a list of container repositories") +def step_impl_list_container_repositories(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.containerrepo" in result["message"]["content"] + ), "List of container repositories not found." + + +@then("the response should contain the details of a container repository") +def step_impl_get_container_repository(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + assert ( + "ocid1.containerrepo" in result["message"]["content"] + ), "Container repository details not found." diff --git a/tests/e2e/features/steps/oci-resource-search-mcp-server-steps.py b/tests/e2e/features/steps/oci-resource-search-mcp-server-steps.py new file mode 100644 index 00000000..47128b47 --- /dev/null +++ b/tests/e2e/features/steps/oci-resource-search-mcp-server-steps.py @@ -0,0 +1,78 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of resource search tools available") +def step_impl_resource_search_tools_available(context): + response_json = context.response.json() + assert ( + "content" in response_json["message"] + ), "Response does not contain a content key." + content = response_json["message"]["content"].lower() + expected_tools = [ + "list_all_resources", + "search_resources", + "search_resources_free_form", + "search_resources_by_type", + "list_resource_types", + ] + assert any( + tool in content for tool in expected_tools + ), "Resource Search tools could not be queried." + + +@then("the response should contain a list of resource types") +def step_impl_list_resource_types(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + # Check for mock resource type names + assert any( + x in content for x in ["instance", "vcn", "subnet", "volume", "bucket"] + ), "List of resource types not found." + + +@then("the response should contain a list of resources") +def step_impl_list_resources(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"] + # We expect at least one OCID or display name from our mock + assert ( + "ocid1.instance" in content or "mock-server-1" in content.lower() + ), "List of resources not found." + + +@then("the response should contain resources matching the display name search") +def step_impl_search_by_display_name(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + assert "mock-server-1" in content, "Display name search results not found." + + +@then("the response should contain resources matching the free-text search") +def step_impl_search_free_text(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + # Free text search for "mock" should return at least one resource + assert any( + x in content for x in ["mock-server-1", "ocid1.instance", "subnet"] + ), "Free-text search results not found." + + +@then("the response should contain resources of the specified type") +def step_impl_search_by_type(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"].lower() + # Searching for type subnet should include a subnet ocid or name + assert ( + "ocid1.subnet" in content or "mock-subnet-1" in content + ), "Typed resource search results not found." diff --git a/tests/e2e/features/steps/oci-usage-mcp-server-steps.py b/tests/e2e/features/steps/oci-usage-mcp-server-steps.py new file mode 100644 index 00000000..b751529e --- /dev/null +++ b/tests/e2e/features/steps/oci-usage-mcp-server-steps.py @@ -0,0 +1,33 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from behave import then + + +@then("the response should contain a list of usage tools available") +def step_impl_usage_tools_available(context): + response_json = context.response.json() + assert ( + "content" in response_json["message"] + ), "Response does not contain a content key." + content = response_json["message"]["content"].lower() + assert any( + tool in content + for tool in [ + "get_summarized_usage", + ] + ), "Usage tools could not be queried." + + +@then("the response should contain a list of usage summaries") +def step_impl_get_summarized_usage(context): + result = context.response.json() + assert "content" in result["message"], "Response does not contain a content key." + content = result["message"]["content"] + # We check for values present in the mock data + assert ( + "10.5" in content or "12.0" in content or "Object Storage" in content + ), "Usage summaries not found."