Skip to content

Commit 904b83a

Browse files
committed
feat(integration-tests): Add integration tests for MCP server
1 parent 6abc934 commit 904b83a

File tree

4 files changed

+1753
-0
lines changed

4 files changed

+1753
-0
lines changed

integration-tests/.pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ addopts =
66
--strict-config
77
--strict-markers
88
--verbose
9+
-m "not mcp"
910
env =
1011
D:CLP_BUILD_DIR=../build
1112
D:CLP_CORE_BINS_DIR=../build/core
@@ -18,3 +19,4 @@ markers =
1819
clp: mark tests that use the CLP storage engine
1920
clp_s: mark tests that use the CLP-S storage engine
2021
core: mark tests that test the CLP core binaries
22+
mcp: mark tests that test the MCP server

integration-tests/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ dev = [
2121
"ruff>=0.11.12",
2222
"pytest>=8.4.1",
2323
"pytest-env>=1.1.5",
24+
"pytest-asyncio>=1.2.0",
25+
"fastmcp>=2.12.4",
2426
]
2527

2628
[tool.mypy]
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""
2+
The integration tests for the CLP MCP server.
3+
4+
IMPORTANT: These tests are designed to be run **manually** against a deployed CLP package.
5+
We will migrate them to automated tests when the test environment includes docker compose.
6+
7+
### Environment Setup
8+
9+
Deploy the CLP package with MCP server. The test suites assume the MCP server is accessible
10+
at http://0.0.0.0:8000/mcp. If the MCP server is hosted at a different address, update the
11+
`MCP_SERVER_ENDPOINT` variable below accordingly.
12+
13+
### Log Dataset
14+
15+
The test dataset is taken from the `HDFS-11896` case study. To access the logs, visit the
16+
repository [y-scope/agent-dev](https://github.com/y-scope/agent-dev) and follow the instructions
17+
from `case-study/sync-logs-to-r2.sh`. The log file is located in `CLODS/HDFS/HDFS-11896/json`.
18+
Use the instructions below to ingest the logs into CLP package:
19+
```
20+
gzip -d CLODS/HDFS/HDFS-11896/json/hadoop-dn1__logs__hadoop.jsonl.gz
21+
sbin/compress.sh --timestamp-key ts CLODS/HDFS/HDFS-11896/json/hadoop-dn1__logs__hadoop.jsonl
22+
```
23+
24+
### Running Tests
25+
26+
The tests in this suite are skipped by default because they require a manually deployed CLP package
27+
with MCP server enabled. (The skipping is done by setting `-m "not mcp"` in `.pytest.ini`.) To run
28+
them, execute the following command in the terminal:
29+
```
30+
pytest -m mcp tests/test_mcp_server.py
31+
```
32+
"""
33+
34+
import pytest
35+
from fastmcp import Client
36+
37+
MCP_SERVER_ENDPOINT = "http://0.0.0.0:8000/mcp"
38+
39+
40+
@pytest.mark.mcp
41+
@pytest.mark.asyncio
42+
async def test_get_instructions() -> None:
43+
"""Tests the get_instructions tool."""
44+
async with Client(MCP_SERVER_ENDPOINT) as client:
45+
res = await client.call_tool("get_instructions")
46+
assert isinstance(res.data, str)
47+
48+
49+
@pytest.mark.mcp
50+
@pytest.mark.asyncio
51+
async def test_search_by_kql() -> None:
52+
"""Tests the search_by_kql tool."""
53+
async with Client(MCP_SERVER_ENDPOINT) as client:
54+
await client.call_tool("get_instructions")
55+
56+
query = "*filter*"
57+
result = await client.call_tool("search_by_kql", {"kql_query": query})
58+
59+
assert "items" in result.data
60+
assert isinstance(result.data["items"], list)
61+
assert "num_total_items" in result.data
62+
assert "num_total_pages" in result.data
63+
assert "num_items_per_page" in result.data
64+
assert "has_next" in result.data
65+
assert "has_previous" in result.data
66+
67+
68+
@pytest.mark.mcp
69+
@pytest.mark.asyncio
70+
async def test_get_nth_page() -> None:
71+
"""Tests the get_nth_page tool."""
72+
async with Client(MCP_SERVER_ENDPOINT) as client:
73+
await client.call_tool("get_instructions")
74+
75+
query = "*filter*"
76+
await client.call_tool("search_by_kql", {"kql_query": query})
77+
78+
result = await client.call_tool("get_nth_page", {"page_index": 1})
79+
assert "items" in result.data
80+
assert isinstance(result.data["items"], list)
81+
assert "num_total_items" in result.data
82+
assert "num_total_pages" in result.data
83+
assert "num_items_per_page" in result.data
84+
assert "has_next" in result.data
85+
assert "has_previous" in result.data
86+
87+
88+
@pytest.mark.mcp
89+
class TestSearchWithTimestamp:
90+
"""Tests the search_by_kql_with_timestamp_range tool."""
91+
92+
@pytest.mark.asyncio
93+
async def test_success_case(self) -> None:
94+
"""Tests the success case of the search_by_kql_with_timestamp_range tool."""
95+
async with Client(MCP_SERVER_ENDPOINT) as client:
96+
await client.call_tool("get_instructions")
97+
98+
query = "*filter*"
99+
result = await client.call_tool(
100+
"search_by_kql_with_timestamp_range",
101+
{
102+
"kql_query": query,
103+
"formatted_begin_timestamp": "2025-08-27T15:35:30.000Z",
104+
"formatted_end_timestamp": "2025-08-27T15:35:50.000Z",
105+
},
106+
)
107+
assert "items" in result.data
108+
assert isinstance(result.data["items"], list)
109+
assert "num_total_items" in result.data
110+
assert "num_total_pages" in result.data
111+
assert "num_items_per_page" in result.data
112+
assert "has_next" in result.data
113+
assert "has_previous" in result.data
114+
115+
@pytest.mark.asyncio
116+
async def test_invalid_timestamps(self) -> None:
117+
"""Tests the search_by_kql_with_timestamp_range tool with invalid timestamps."""
118+
async with Client(MCP_SERVER_ENDPOINT) as client:
119+
await client.call_tool("get_instructions")
120+
121+
query = "*filter*"
122+
result = await client.call_tool(
123+
"search_by_kql_with_timestamp_range",
124+
{
125+
"kql_query": query,
126+
"formatted_begin_timestamp": "2025-08-27T15:35:55.000Z",
127+
"formatted_end_timestamp": "2025-08-27T15:35:50.000Z",
128+
},
129+
)
130+
assert "Error" in result.data
131+
132+
@pytest.mark.asyncio
133+
async def test_query_failure(self) -> None:
134+
"""Tests the search_by_kql_with_timestamp_range tool with a query that is killed."""
135+
async with Client(MCP_SERVER_ENDPOINT) as client:
136+
await client.call_tool("get_instructions")
137+
138+
query = "*filter*"
139+
result = await client.call_tool(
140+
"search_by_kql_with_timestamp_range",
141+
{
142+
"kql_query": query,
143+
# This test (ab)uses a design in CLP: when the begin timestamp is set before
144+
# Unix epoch, the query will be killed. It works as of Oct. 22, 2025. The
145+
# future versions of CLP may change this behavior.
146+
"formatted_begin_timestamp": "1970-01-01T00:00:00.000Z",
147+
"formatted_end_timestamp": "1970-01-02T00:00:00.000Z",
148+
},
149+
)
150+
assert "Error" in result.data
151+
152+
153+
@pytest.mark.mcp
154+
@pytest.mark.asyncio
155+
async def test_log_link() -> None:
156+
"""Tests that the search results contain log links."""
157+
async with Client(MCP_SERVER_ENDPOINT) as client:
158+
await client.call_tool("get_instructions")
159+
160+
query = "*error*"
161+
result = await client.call_tool("search_by_kql", {"kql_query": query})
162+
163+
for item in result.data["items"]:
164+
assert "link" in item
165+

0 commit comments

Comments
 (0)