Skip to content

Commit 1f21abb

Browse files
committed
Update unit tests to align with OData filter changes
- Add comprehensive OData filter tests for pia_search tool - Add filter parameter tests for pia_search_facets tool - Update mock data to use correct field names from remote implementation: - SourceDocumentDataSource instead of data_source - RecStatus, RecPriorityFlag, IsIntegrityRelated field names - Add tests for complex boolean logic filters - Add tests for empty filters and all parameter combinations - Verify filter parameters are correctly passed to remote API - Clean up unused imports - All 14 tests passing with improved coverage Test scenarios covered: - Basic OData filters: SourceDocumentDataSource eq 'GAO' - Complex filters: boolean logic with AND/OR/NOT operators - Facets with filters: filtered facet discovery - Empty filters: handle empty/null filter values - Parameter validation: ensure all parameters passed correctly - Error handling: API errors, HTTP errors, missing API keys
1 parent e6a228e commit 1f21abb

File tree

1 file changed

+264
-6
lines changed

1 file changed

+264
-6
lines changed

tests/test_tools.py

Lines changed: 264 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tests for tools module."""
22

33
import pytest
4-
from unittest.mock import AsyncMock, patch, PropertyMock, Mock
4+
from unittest.mock import AsyncMock, patch, Mock
55
import httpx
66
from pia_mcp_server.tools.search_tools import (
77
handle_pia_search,
@@ -52,6 +52,96 @@ async def test_pia_search_success():
5252
assert "Test Document" in result[0].text
5353

5454

55+
@pytest.mark.asyncio
56+
async def test_pia_search_with_odata_filter():
57+
"""Test PIA search with OData filter parameter."""
58+
mock_response = {
59+
"jsonrpc": "2.0",
60+
"id": 1,
61+
"result": {
62+
"documents": [
63+
{
64+
"title": "GAO Fraud Report",
65+
"id": "gao_123",
66+
"summary": "GAO fraud investigation",
67+
}
68+
],
69+
"total": 1,
70+
},
71+
}
72+
73+
with patch.object(Settings, "_get_api_key_from_args", return_value="test_key"):
74+
with patch("httpx.AsyncClient") as mock_client:
75+
mock_response_obj = Mock()
76+
mock_response_obj.json.return_value = mock_response
77+
mock_response_obj.raise_for_status.return_value = None
78+
79+
mock_client_instance = AsyncMock()
80+
mock_client_instance.post.return_value = mock_response_obj
81+
mock_client.return_value.__aenter__.return_value = mock_client_instance
82+
83+
# Test with actual field names from the remote implementation
84+
result = await handle_pia_search(
85+
{"query": "fraud", "filter": "SourceDocumentDataSource eq 'GAO'"}
86+
)
87+
88+
# Verify the request was made with the filter
89+
mock_client_instance.post.assert_called_once()
90+
call_args = mock_client_instance.post.call_args
91+
request_data = call_args[1]["json"]
92+
assert (
93+
request_data["params"]["arguments"]["filter"]
94+
== "SourceDocumentDataSource eq 'GAO'"
95+
)
96+
97+
assert len(result) == 1
98+
assert "GAO Fraud Report" in result[0].text
99+
100+
101+
@pytest.mark.asyncio
102+
async def test_pia_search_with_complex_odata_filter():
103+
"""Test PIA search with complex OData filter."""
104+
mock_response = {
105+
"jsonrpc": "2.0",
106+
"id": 1,
107+
"result": {
108+
"documents": [
109+
{
110+
"title": "High Priority GAO Report",
111+
"id": "gao_456",
112+
"summary": "High priority integrity violation",
113+
}
114+
],
115+
"total": 1,
116+
},
117+
}
118+
119+
with patch.object(Settings, "_get_api_key_from_args", return_value="test_key"):
120+
with patch("httpx.AsyncClient") as mock_client:
121+
mock_response_obj = Mock()
122+
mock_response_obj.json.return_value = mock_response
123+
mock_response_obj.raise_for_status.return_value = None
124+
125+
mock_client_instance = AsyncMock()
126+
mock_client_instance.post.return_value = mock_response_obj
127+
mock_client.return_value.__aenter__.return_value = mock_client_instance
128+
129+
# Test complex boolean logic filter
130+
complex_filter = "(SourceDocumentDataSource eq 'GAO' or SourceDocumentDataSource eq 'OIG') and RecPriorityFlag in ('High', 'Critical')"
131+
result = await handle_pia_search(
132+
{"query": "integrity violations", "filter": complex_filter}
133+
)
134+
135+
# Verify the complex filter was passed correctly
136+
mock_client_instance.post.assert_called_once()
137+
call_args = mock_client_instance.post.call_args
138+
request_data = call_args[1]["json"]
139+
assert request_data["params"]["arguments"]["filter"] == complex_filter
140+
141+
assert len(result) == 1
142+
assert "High Priority GAO Report" in result[0].text
143+
144+
55145
@pytest.mark.asyncio
56146
async def test_pia_search_api_error():
57147
"""Test PIA search with API error."""
@@ -118,9 +208,10 @@ async def test_pia_search_facets_success():
118208
"id": 1,
119209
"result": {
120210
"facets": {
121-
"data_source": ["OIG", "GAO", "CMS"],
122-
"document_type": ["audit_report", "investigation", "guidance"],
123-
"agency": ["HHS", "DOD", "VA"],
211+
"SourceDocumentDataSource": ["OIG", "GAO", "CMS"],
212+
"RecStatus": ["Open", "Closed", "In Progress"],
213+
"RecPriorityFlag": ["High", "Medium", "Low", "Critical"],
214+
"IsIntegrityRelated": ["Yes", "No"],
124215
}
125216
},
126217
}
@@ -138,9 +229,56 @@ async def test_pia_search_facets_success():
138229
result = await handle_pia_search_facets({"query": "healthcare"})
139230

140231
assert len(result) == 1
141-
assert "data_source" in result[0].text
232+
assert "SourceDocumentDataSource" in result[0].text
142233
assert "OIG" in result[0].text
143-
assert "document_type" in result[0].text
234+
assert "RecStatus" in result[0].text
235+
236+
237+
@pytest.mark.asyncio
238+
async def test_pia_search_facets_with_filter():
239+
"""Test PIA search facets with OData filter parameter."""
240+
mock_response = {
241+
"jsonrpc": "2.0",
242+
"id": 1,
243+
"result": {
244+
"facets": {
245+
"SourceDocumentDataSource": ["GAO"],
246+
"RecStatus": ["Open", "In Progress"],
247+
"RecPriorityFlag": ["High", "Critical"],
248+
}
249+
},
250+
}
251+
252+
with patch.object(Settings, "_get_api_key_from_args", return_value="test_key"):
253+
with patch("httpx.AsyncClient") as mock_client:
254+
mock_response_obj = Mock()
255+
mock_response_obj.json.return_value = mock_response
256+
mock_response_obj.raise_for_status.return_value = None
257+
258+
mock_client_instance = AsyncMock()
259+
mock_client_instance.post.return_value = mock_response_obj
260+
mock_client.return_value.__aenter__.return_value = mock_client_instance
261+
262+
# Test facets with filter parameter
263+
result = await handle_pia_search_facets(
264+
{
265+
"query": "fraud",
266+
"filter": "SourceDocumentDataSource eq 'GAO' and RecStatus ne 'Closed'",
267+
}
268+
)
269+
270+
# Verify the filter was passed correctly
271+
mock_client_instance.post.assert_called_once()
272+
call_args = mock_client_instance.post.call_args
273+
request_data = call_args[1]["json"]
274+
assert (
275+
request_data["params"]["arguments"]["filter"]
276+
== "SourceDocumentDataSource eq 'GAO' and RecStatus ne 'Closed'"
277+
)
278+
279+
assert len(result) == 1
280+
assert "SourceDocumentDataSource" in result[0].text
281+
assert "GAO" in result[0].text
144282

145283

146284
@pytest.mark.asyncio
@@ -189,3 +327,123 @@ async def test_pia_search_facets_http_error():
189327

190328
assert len(result) == 1
191329
assert "HTTP Error 403" in result[0].text
330+
331+
332+
@pytest.mark.asyncio
333+
async def test_pia_search_empty_filter():
334+
"""Test PIA search with empty filter parameter."""
335+
mock_response = {
336+
"jsonrpc": "2.0",
337+
"id": 1,
338+
"result": {
339+
"documents": [
340+
{"title": "Test Document", "id": "123", "summary": "Test summary"}
341+
],
342+
"total": 1,
343+
},
344+
}
345+
346+
with patch.object(Settings, "_get_api_key_from_args", return_value="test_key"):
347+
with patch("httpx.AsyncClient") as mock_client:
348+
mock_response_obj = Mock()
349+
mock_response_obj.json.return_value = mock_response
350+
mock_response_obj.raise_for_status.return_value = None
351+
352+
mock_client_instance = AsyncMock()
353+
mock_client_instance.post.return_value = mock_response_obj
354+
mock_client.return_value.__aenter__.return_value = mock_client_instance
355+
356+
# Test with empty filter (should work normally)
357+
result = await handle_pia_search({"query": "test query", "filter": ""})
358+
359+
assert len(result) == 1
360+
assert "Test Document" in result[0].text
361+
362+
363+
@pytest.mark.asyncio
364+
async def test_pia_search_with_all_parameters():
365+
"""Test PIA search with all parameters including filter."""
366+
mock_response = {
367+
"jsonrpc": "2.0",
368+
"id": 1,
369+
"result": {
370+
"documents": [
371+
{"title": "Complete Test", "id": "456", "summary": "Full test"}
372+
],
373+
"total": 1,
374+
},
375+
}
376+
377+
with patch.object(Settings, "_get_api_key_from_args", return_value="test_key"):
378+
with patch("httpx.AsyncClient") as mock_client:
379+
mock_response_obj = Mock()
380+
mock_response_obj.json.return_value = mock_response
381+
mock_response_obj.raise_for_status.return_value = None
382+
383+
mock_client_instance = AsyncMock()
384+
mock_client_instance.post.return_value = mock_response_obj
385+
mock_client.return_value.__aenter__.return_value = mock_client_instance
386+
387+
# Test with all parameters
388+
result = await handle_pia_search(
389+
{
390+
"query": "comprehensive test",
391+
"filter": "SourceDocumentDataSource eq 'GAO' and IsIntegrityRelated eq 'Yes'",
392+
"page": 2,
393+
"page_size": 5,
394+
"search_mode": "titles",
395+
"limit": 10,
396+
"include_facets": True,
397+
}
398+
)
399+
400+
# Verify all parameters were passed correctly
401+
mock_client_instance.post.assert_called_once()
402+
call_args = mock_client_instance.post.call_args
403+
request_data = call_args[1]["json"]
404+
arguments = request_data["params"]["arguments"]
405+
406+
assert arguments["query"] == "comprehensive test"
407+
assert (
408+
arguments["filter"]
409+
== "SourceDocumentDataSource eq 'GAO' and IsIntegrityRelated eq 'Yes'"
410+
)
411+
assert arguments["page"] == 2
412+
assert arguments["page_size"] == 5
413+
assert arguments["search_mode"] == "titles"
414+
assert arguments["limit"] == 10
415+
assert arguments["include_facets"] is True
416+
417+
assert len(result) == 1
418+
assert "Complete Test" in result[0].text
419+
420+
421+
@pytest.mark.asyncio
422+
async def test_pia_search_facets_empty_filter():
423+
"""Test PIA search facets with empty filter parameter."""
424+
mock_response = {
425+
"jsonrpc": "2.0",
426+
"id": 1,
427+
"result": {
428+
"facets": {
429+
"SourceDocumentDataSource": ["OIG", "GAO", "CMS"],
430+
"RecStatus": ["Open", "Closed"],
431+
}
432+
},
433+
}
434+
435+
with patch.object(Settings, "_get_api_key_from_args", return_value="test_key"):
436+
with patch("httpx.AsyncClient") as mock_client:
437+
mock_response_obj = Mock()
438+
mock_response_obj.json.return_value = mock_response
439+
mock_response_obj.raise_for_status.return_value = None
440+
441+
mock_client_instance = AsyncMock()
442+
mock_client_instance.post.return_value = mock_response_obj
443+
mock_client.return_value.__aenter__.return_value = mock_client_instance
444+
445+
# Test facets with empty filter
446+
result = await handle_pia_search_facets({"query": "test", "filter": ""})
447+
448+
assert len(result) == 1
449+
assert "SourceDocumentDataSource" in result[0].text

0 commit comments

Comments
 (0)