|
9 | 9 | import sys
|
10 | 10 | import unittest
|
11 | 11 | from typing import Any, Dict, List, Optional
|
12 |
| -from unittest.mock import MagicMock, patch |
| 12 | +from unittest.mock import MagicMock |
13 | 13 |
|
14 | 14 | from amazon.opentelemetry.distro.instrumentation.mcp import version
|
15 | 15 | from amazon.opentelemetry.distro.instrumentation.mcp.constants import MCPEnvironmentVariables
|
@@ -885,147 +885,61 @@ def model_validate(cls, data):
|
885 | 885 | return cls(params=FakeParams(name=data["params"].get("name")))
|
886 | 886 |
|
887 | 887 |
|
888 |
| -class TestMCPTypesCoverage(unittest.TestCase): |
889 |
| - """Test isinstance checks in _generate_mcp_attributes and _get_mcp_operation""" |
| 888 | +class TestGetMCPOperation(unittest.TestCase): |
| 889 | + """Test _get_mcp_operation function with patched types""" |
890 | 890 |
|
891 |
| - def setUp(self) -> None: |
| 891 | + def setUp(self): |
892 | 892 | self.instrumentor = MCPInstrumentor()
|
893 |
| - self.mock_span = MagicMock() |
894 | 893 |
|
895 |
| - def test_generate_mcp_attributes_list_tools(self) -> None: |
896 |
| - """Test _generate_mcp_attributes with ListToolsRequest""" |
897 |
| - from amazon.opentelemetry.distro.instrumentation.mcp.semconv import MCPAttributes |
| 894 | + @unittest.mock.patch("mcp.types") |
| 895 | + def test_get_mcp_operation_coverage(self, mock_types): |
| 896 | + """Test _get_mcp_operation with all request types""" |
898 | 897 |
|
899 |
| - class MockListToolsRequest: |
| 898 | + # Create actual classes for isinstance checks |
| 899 | + class FakeListToolsRequest: |
900 | 900 | pass
|
901 | 901 |
|
902 |
| - with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types: |
903 |
| - mock_types.ListToolsRequest = MockListToolsRequest |
904 |
| - mock_types.CallToolRequest = type("CallToolRequest", (), {}) |
905 |
| - mock_types.InitializeRequest = type("InitializeRequest", (), {}) |
906 |
| - |
907 |
| - request = MockListToolsRequest() |
908 |
| - self.instrumentor._generate_mcp_attributes(self.mock_span, request, is_client=True) |
909 |
| - self.mock_span.set_attribute.assert_any_call(MCPAttributes.MCP_LIST_TOOLS, True) |
910 |
| - |
911 |
| - def test_generate_mcp_attributes_call_tool(self) -> None: |
912 |
| - """Test _generate_mcp_attributes with CallToolRequest""" |
913 |
| - from amazon.opentelemetry.distro.instrumentation.mcp.semconv import MCPAttributes |
914 |
| - |
915 |
| - class MockCallToolRequest: |
| 902 | + class FakeCallToolRequest: |
916 | 903 | def __init__(self):
|
917 | 904 | self.params = type("Params", (), {"name": "test_tool"})()
|
918 | 905 |
|
919 |
| - with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types: |
920 |
| - mock_types.ListToolsRequest = type("ListToolsRequest", (), {}) |
921 |
| - mock_types.CallToolRequest = MockCallToolRequest |
922 |
| - mock_types.InitializeRequest = type("InitializeRequest", (), {}) |
923 |
| - |
924 |
| - request = MockCallToolRequest() |
925 |
| - self.instrumentor._generate_mcp_attributes(self.mock_span, request, is_client=True) |
926 |
| - self.mock_span.set_attribute.assert_any_call(MCPAttributes.MCP_CALL_TOOL, True) |
| 906 | + # Set up mock types |
| 907 | + mock_types.ListToolsRequest = FakeListToolsRequest |
| 908 | + mock_types.CallToolRequest = FakeCallToolRequest |
927 | 909 |
|
928 |
| - def test_generate_mcp_attributes_initialize(self) -> None: |
929 |
| - """Test _generate_mcp_attributes with InitializeRequest""" |
930 |
| - from amazon.opentelemetry.distro.instrumentation.mcp.semconv import MCPAttributes |
| 910 | + # Test ListToolsRequest path |
| 911 | + result1 = self.instrumentor._get_mcp_operation(FakeListToolsRequest()) |
| 912 | + self.assertEqual(result1, "tools/list") |
931 | 913 |
|
932 |
| - class MockInitializeRequest: |
933 |
| - pass |
934 |
| - |
935 |
| - with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types: |
936 |
| - mock_types.ListToolsRequest = type("ListToolsRequest", (), {}) |
937 |
| - mock_types.CallToolRequest = type("CallToolRequest", (), {}) |
938 |
| - mock_types.InitializeRequest = MockInitializeRequest |
| 914 | + # Test CallToolRequest path |
| 915 | + result2 = self.instrumentor._get_mcp_operation(FakeCallToolRequest()) |
| 916 | + self.assertEqual(result2, "tools/test_tool") |
939 | 917 |
|
940 |
| - request = MockInitializeRequest() |
941 |
| - self.instrumentor._generate_mcp_attributes(self.mock_span, request, is_client=True) |
942 |
| - self.mock_span.set_attribute.assert_any_call(MCPAttributes.MCP_INITIALIZE, True) |
| 918 | + # Test unknown request type |
| 919 | + result3 = self.instrumentor._get_mcp_operation(object()) |
| 920 | + self.assertEqual(result3, "unknown") |
943 | 921 |
|
944 |
| - def test_get_mcp_operation_list_tools(self) -> None: |
945 |
| - """Test _get_mcp_operation with ListToolsRequest""" |
| 922 | + @unittest.mock.patch("mcp.types") |
| 923 | + def test_generate_mcp_attributes_coverage(self, mock_types): |
| 924 | + """Test _generate_mcp_attributes with all request types""" |
946 | 925 |
|
947 |
| - class MockListToolsRequest: |
| 926 | + class FakeListToolsRequest: |
948 | 927 | pass
|
949 | 928 |
|
950 |
| - with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types: |
951 |
| - mock_types.ListToolsRequest = MockListToolsRequest |
952 |
| - mock_types.CallToolRequest = type("CallToolRequest", (), {}) |
953 |
| - |
954 |
| - request = MockListToolsRequest() |
955 |
| - result = self.instrumentor._get_mcp_operation(request) |
956 |
| - self.assertEqual(result, "tools/list") |
957 |
| - |
958 |
| - def test_get_mcp_operation_call_tool(self) -> None: |
959 |
| - """Test _get_mcp_operation with CallToolRequest""" |
960 |
| - |
961 |
| - class MockCallToolRequest: |
| 929 | + class FakeCallToolRequest: |
962 | 930 | def __init__(self):
|
963 |
| - self.params = type("Params", (), {"name": "my_tool"})() |
964 |
| - |
965 |
| - with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types: |
966 |
| - mock_types.ListToolsRequest = type("ListToolsRequest", (), {}) |
967 |
| - mock_types.CallToolRequest = MockCallToolRequest |
968 |
| - |
969 |
| - request = MockCallToolRequest() |
970 |
| - result = self.instrumentor._get_mcp_operation(request) |
971 |
| - self.assertEqual(result, "tools/my_tool") |
972 |
| - |
973 |
| - def test_get_mcp_operation_unknown(self) -> None: |
974 |
| - """Test _get_mcp_operation with unknown request type""" |
975 |
| - with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types: |
976 |
| - mock_types.ListToolsRequest = type("ListToolsRequest", (), {}) |
977 |
| - mock_types.CallToolRequest = type("CallToolRequest", (), {}) |
| 931 | + self.params = type("Params", (), {"name": "test_tool"})() |
978 | 932 |
|
979 |
| - unknown_request = object() |
980 |
| - result = self.instrumentor._get_mcp_operation(unknown_request) |
981 |
| - self.assertEqual(result, "unknown") |
| 933 | + class FakeInitializeRequest: |
| 934 | + pass |
982 | 935 |
|
| 936 | + mock_types.ListToolsRequest = FakeListToolsRequest |
| 937 | + mock_types.CallToolRequest = FakeCallToolRequest |
| 938 | + mock_types.InitializeRequest = FakeInitializeRequest |
983 | 939 |
|
984 |
| -class TestAdditionalCoverage(unittest.TestCase): |
985 |
| - def setUp(self): |
986 |
| - self.instrumentor = MCPInstrumentor() |
987 |
| - mock_tracer = MagicMock() |
988 | 940 | mock_span = MagicMock()
|
989 |
| - mock_span_context = MagicMock(trace_id=123, span_id=456) |
990 |
| - mock_span.get_span_context.return_value = mock_span_context |
991 |
| - mock_tracer.start_as_current_span.return_value.__enter__.return_value = mock_span |
992 |
| - self.instrumentor.tracer = mock_tracer |
993 |
| - |
994 |
| - def test_wrap_send_request_kwargs_only_with_extra_args(self): |
995 |
| - """Covers case where kwargs['request'] is set and args has extra elements.""" |
996 |
| - |
997 |
| - async def dummy_wrapped(*args, **kwargs): |
998 |
| - return {"ok": True} |
999 |
| - |
1000 |
| - req = FakeRequest(params=FakeParams("tool1")) |
1001 |
| - with patch.object(self.instrumentor, "_generate_mcp_attributes"): |
1002 |
| - result = asyncio.run(self.instrumentor._wrap_send_request(dummy_wrapped, None, (), {"request": req})) |
1003 |
| - self.assertTrue(result["ok"]) |
1004 |
| - |
1005 |
| - def test_wrap_handle_request_invalid_traceparent_path(self): |
1006 |
| - """Covers case where traceparent exists but is invalid -> no span created.""" |
1007 |
| - bad_meta = type("Meta", (), {"traceparent": "invalid-format"}) |
1008 |
| - req = FakeRequest(params=FakeParams(name="tool1", meta=bad_meta)) |
1009 |
| - |
1010 |
| - async def dummy_wrapped(*args, **kwargs): |
1011 |
| - return {"ok": True} |
1012 |
| - |
1013 |
| - result = asyncio.run(self.instrumentor._wrap_handle_request(dummy_wrapped, None, (None, req), {})) |
1014 |
| - self.assertTrue(result["ok"]) |
1015 |
| - self.instrumentor.tracer.start_as_current_span.assert_not_called() |
1016 |
| - |
1017 |
| - def test_add_client_attributes_missing_name_env_var(self): |
1018 |
| - """Covers _add_client_attributes with env var set but no name.""" |
1019 |
| - span = MagicMock() |
1020 |
| - req = FakeRequest(params=FakeParams(name=None)) |
1021 |
| - with patch.dict("os.environ", {"MCP_INSTRUMENTATION_SERVER_NAME": "env_server"}): |
1022 |
| - self.instrumentor._add_client_attributes(span, "op", req) |
1023 |
| - span.set_attribute.assert_any_call("rpc.service", "env_server") |
1024 |
| - |
1025 |
| - def test_add_server_attributes_name_missing(self): |
1026 |
| - """Covers _add_server_attributes when .params exists but .name is None.""" |
1027 |
| - span = MagicMock() |
1028 |
| - req = FakeRequest(params=FakeParams(name=None)) |
1029 |
| - self.instrumentor._add_server_attributes(span, "op", req) |
1030 |
| - # The method should still be called but with None value, so we just verify it was called |
1031 |
| - span.set_attribute.assert_called_once() |
| 941 | + self.instrumentor._generate_mcp_attributes(mock_span, FakeListToolsRequest(), True) |
| 942 | + self.instrumentor._generate_mcp_attributes(mock_span, FakeCallToolRequest(), True) |
| 943 | + self.instrumentor._generate_mcp_attributes(mock_span, FakeInitializeRequest(), True) |
| 944 | + mock_span.set_attribute.assert_any_call("mcp.list_tools", True) |
| 945 | + mock_span.set_attribute.assert_any_call("mcp.call_tool", True) |
0 commit comments