|
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