| import unittest |
| from importlib.metadata import PackageNotFoundError |
| from unittest.mock import patch |
| |
| from gerrit_mcp_server.http_headers import ( |
| RequestObservabilityContext, |
| build_curl_header_args, |
| ) |
| |
| |
| def _make_obs(**kwargs): |
| defaults = dict( |
| tool_name="my_tool", |
| client_name="test-client", |
| client_version="1.2.3", |
| protocol_version="2025-11-25", |
| ) |
| defaults.update(kwargs) |
| return RequestObservabilityContext(**defaults) |
| |
| |
| class TestBuildCurlHeaderArgs(unittest.TestCase): |
| def test_no_obs_returns_static_ua_only(self): |
| result = build_curl_header_args(None) |
| self.assertEqual(result[0], "-A") |
| ua = result[1] |
| self.assertIn("gerrit-mcp-server/", ua) |
| self.assertIn("mcp-python-sdk/", ua) |
| # No X- headers |
| self.assertNotIn("-H", result) |
| |
| def test_full_obs_has_three_ua_tokens_and_all_x_headers(self): |
| result = build_curl_header_args(_make_obs()) |
| ua = result[1] |
| tokens = ua.split(" ") |
| self.assertEqual(len(tokens), 3) |
| self.assertTrue(tokens[0].startswith("gerrit-mcp-server/")) |
| self.assertTrue(tokens[1].startswith("mcp-python-sdk/")) |
| self.assertEqual(tokens[2], "test-client/1.2.3") |
| self.assertIn("X-MCP-Tool: my_tool", result) |
| self.assertIn("X-MCP-Client: test-client", result) |
| self.assertIn("X-MCP-Protocol-Version: 2025-11-25", result) |
| |
| def test_missing_client_name_omits_x_mcp_client_and_ua_token(self): |
| result = build_curl_header_args(_make_obs(client_name=None)) |
| ua = result[1] |
| tokens = ua.split(" ") |
| self.assertEqual(len(tokens), 2) |
| self.assertNotIn("X-MCP-Client: None", result) |
| header_values = [result[i + 1] for i, v in enumerate(result) if v == "-H"] |
| self.assertFalse(any("X-MCP-Client" in h for h in header_values)) |
| |
| def test_missing_client_version_uses_unknown_in_ua(self): |
| result = build_curl_header_args(_make_obs(client_version=None)) |
| ua = result[1] |
| self.assertIn("test-client/unknown", ua) |
| |
| def test_missing_protocol_version_omits_x_mcp_protocol_version(self): |
| result = build_curl_header_args(_make_obs(protocol_version=None)) |
| header_values = [result[i + 1] for i, v in enumerate(result) if v == "-H"] |
| self.assertFalse(any("X-MCP-Protocol-Version" in h for h in header_values)) |
| |
| def test_version_fallback_on_package_not_found(self): |
| with patch( |
| "gerrit_mcp_server.http_headers.version", |
| side_effect=PackageNotFoundError("pkg"), |
| ): |
| result = build_curl_header_args(None) |
| ua = result[1] |
| self.assertIn("gerrit-mcp-server/unknown", ua) |
| self.assertIn("mcp-python-sdk/unknown", ua) |
| |
| def test_client_name_with_crlf_becomes_unparseable(self): |
| result = build_curl_header_args( |
| _make_obs(client_name="evil\r\nAuthorization: Bearer x") |
| ) |
| ua = result[1] |
| self.assertIn("unparseable/", ua) |
| self.assertNotIn("\r", ua) |
| self.assertNotIn("\n", ua) |
| self.assertIn("X-MCP-Client: unparseable", result) |
| |
| def test_client_version_with_crlf_becomes_unparseable(self): |
| result = build_curl_header_args( |
| _make_obs(client_version="1.0\r\nX-Injected: yes") |
| ) |
| ua = result[1] |
| self.assertIn("test-client/unparseable", ua) |
| self.assertNotIn("\r", ua) |
| self.assertNotIn("\n", ua) |
| |
| def test_protocol_version_with_crlf_becomes_unparseable(self): |
| result = build_curl_header_args( |
| _make_obs(protocol_version="2025-11-25\r\nX-Injected: yes") |
| ) |
| header_values = [result[i + 1] for i, v in enumerate(result) if v == "-H"] |
| proto_header = next(h for h in header_values if "X-MCP-Protocol-Version" in h) |
| self.assertEqual(proto_header, "X-MCP-Protocol-Version: unparseable") |
| self.assertNotIn("\r", proto_header) |
| self.assertNotIn("\n", proto_header) |
| |
| def test_tool_name_with_control_char_becomes_unparseable(self): |
| result = build_curl_header_args(_make_obs(tool_name="tool\x00name")) |
| header_values = [result[i + 1] for i, v in enumerate(result) if v == "-H"] |
| tool_header = next(h for h in header_values if "X-MCP-Tool" in h) |
| self.assertEqual(tool_header, "X-MCP-Tool: unparseable") |
| |
| def test_clean_values_pass_through_unchanged(self): |
| result = build_curl_header_args(_make_obs()) |
| self.assertIn("X-MCP-Client: test-client", result) |
| self.assertIn("X-MCP-Protocol-Version: 2025-11-25", result) |
| self.assertIn("X-MCP-Tool: my_tool", result) |