Skip to content

Commit a268529

Browse files
committed
some test files didn't get checked in
checked in 4 test files that didn't get checked in previously
1 parent dcec2e1 commit a268529

File tree

4 files changed

+1579
-0
lines changed

4 files changed

+1579
-0
lines changed

tests/test_coverage.py

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
"""
2+
Code coverage tests for Alfresco MCP Server.
3+
These tests ensure maximum code coverage by testing edge cases and error paths.
4+
"""
5+
import pytest
6+
import os
7+
import tempfile
8+
from unittest.mock import patch, Mock, AsyncMock
9+
from fastmcp import Client
10+
from alfresco_mcp_server.fastmcp_server import mcp
11+
12+
13+
@pytest.mark.unit
14+
class TestCodeCoverage:
15+
"""Tests designed to maximize code coverage."""
16+
17+
@pytest.mark.asyncio
18+
async def test_ensure_connection_success(self, fastmcp_client):
19+
"""Test ensure_connection function success path."""
20+
with patch('alfresco_mcp_server.fastmcp_server.alfresco_factory', None), \
21+
patch('alfresco_mcp_server.fastmcp_server.auth_util', None), \
22+
patch('alfresco_mcp_server.config.AlfrescoConfig') as mock_config_class, \
23+
patch('python_alfresco_api.client_factory.ClientFactory') as mock_factory_class, \
24+
patch('python_alfresco_api.auth_util.AuthUtil') as mock_auth_class:
25+
26+
# Setup mocks
27+
mock_config = Mock()
28+
mock_config.alfresco_url = "http://localhost:8080"
29+
mock_config.username = "admin"
30+
mock_config.password = "admin"
31+
mock_config.verify_ssl = False
32+
mock_config.timeout = 30
33+
mock_config_class.return_value = mock_config
34+
35+
mock_factory = Mock()
36+
mock_factory_class.return_value = mock_factory
37+
38+
mock_auth = AsyncMock()
39+
mock_auth_class.return_value = mock_auth
40+
41+
# Import and call ensure_connection
42+
from alfresco_mcp_server.fastmcp_server import ensure_connection
43+
44+
# The function should complete without error when mocks are set up
45+
try:
46+
await ensure_connection()
47+
# If we get here, the function completed successfully
48+
assert True
49+
except Exception as e:
50+
# Should not raise an exception with proper mocks
51+
pytest.fail(f"ensure_connection raised unexpected exception: {e}")
52+
53+
@pytest.mark.asyncio
54+
async def test_ensure_connection_already_initialized(self, fastmcp_client):
55+
"""Test ensure_connection when already initialized."""
56+
with patch('alfresco_mcp_server.fastmcp_server.alfresco_factory', Mock()), \
57+
patch('alfresco_mcp_server.fastmcp_server.auth_util', Mock()):
58+
59+
from alfresco_mcp_server.fastmcp_server import ensure_connection
60+
61+
# Should return early since factory and auth_util are set
62+
await ensure_connection()
63+
# No exception should be raised
64+
65+
@pytest.mark.asyncio
66+
async def test_search_models_import_error(self, fastmcp_client):
67+
"""Test search functionality when search models can't be imported."""
68+
with patch('alfresco_mcp_server.fastmcp_server.ensure_connection'), \
69+
patch('alfresco_mcp_server.fastmcp_server.alfresco_factory') as mock_factory, \
70+
patch('alfresco_mcp_server.fastmcp_server.search_models', None):
71+
72+
search_client = AsyncMock()
73+
mock_factory.create_search_client.return_value = search_client
74+
75+
# This should handle the case where search_models is None
76+
result = await fastmcp_client.call_tool("search_content", {
77+
"query": "test",
78+
"max_results": 10
79+
})
80+
81+
assert len(result) == 1
82+
# Should handle gracefully
83+
84+
@pytest.mark.asyncio
85+
async def test_all_error_paths(self, fastmcp_client):
86+
"""Test error paths in all tools to maximize coverage."""
87+
88+
# Test each tool with connection failure
89+
tools_to_test = [
90+
("search_content", {"query": "test", "max_results": 10}),
91+
("upload_document", {"filename": "test.txt", "content_base64": "dGVzdA=="}),
92+
("download_document", {"node_id": "test-123"}),
93+
("checkout_document", {"node_id": "test-123"}),
94+
("checkin_document", {"node_id": "test-123"}),
95+
("delete_node", {"node_id": "test-123"}),
96+
("get_node_properties", {"node_id": "test-123"}),
97+
("update_node_properties", {"node_id": "test-123", "name": "new_name"}),
98+
("create_folder", {"folder_name": "Test Folder"})
99+
]
100+
101+
for tool_name, args in tools_to_test:
102+
with patch('alfresco_mcp_server.fastmcp_server.ensure_connection',
103+
side_effect=Exception("Connection error")):
104+
105+
result = await fastmcp_client.call_tool(tool_name, args)
106+
assert len(result) == 1
107+
assert any(keyword in result[0].text for keyword in [
108+
"failed", "Error", "error"
109+
])
110+
111+
@pytest.mark.asyncio
112+
async def test_base64_edge_cases(self, fastmcp_client):
113+
"""Test base64 handling edge cases."""
114+
# Test various invalid base64 strings
115+
invalid_base64_cases = [
116+
"not_base64_at_all",
117+
"invalid base64 with spaces",
118+
"YWJjZA===", # Too many padding characters
119+
"", # Empty string
120+
"x", # Too short
121+
]
122+
123+
for invalid_b64 in invalid_base64_cases:
124+
result = await fastmcp_client.call_tool("upload_document", {
125+
"filename": "test.txt",
126+
"content_base64": invalid_b64
127+
})
128+
129+
# Should handle gracefully
130+
assert len(result) == 1
131+
132+
@pytest.mark.asyncio
133+
async def test_search_edge_cases(self, fastmcp_client):
134+
"""Test search with various edge case inputs."""
135+
edge_cases = [
136+
{"query": " ", "max_results": 10}, # Whitespace only
137+
{"query": "test", "max_results": 1}, # Minimum results
138+
{"query": "test", "max_results": 100}, # Maximum results
139+
{"query": "a" * 1000, "max_results": 10}, # Very long query
140+
]
141+
142+
for case in edge_cases:
143+
result = await fastmcp_client.call_tool("search_content", case)
144+
assert len(result) == 1
145+
# Should not crash
146+
147+
148+
@pytest.mark.unit
149+
class TestResourcesCoverage:
150+
"""Tests for resource functionality coverage."""
151+
152+
@pytest.mark.asyncio
153+
async def test_all_resource_sections(self, fastmcp_client):
154+
"""Test all supported resource sections."""
155+
sections = ["info", "health", "stats", "config"]
156+
157+
for section in sections:
158+
uri = f"alfresco://repository/{section}"
159+
result = await fastmcp_client.read_resource(uri)
160+
161+
assert len(result) > 0
162+
# Should return valid JSON
163+
import json
164+
try:
165+
data = json.loads(result[0].text)
166+
assert isinstance(data, dict)
167+
except json.JSONDecodeError:
168+
pytest.fail(f"Resource {section} did not return valid JSON")
169+
170+
@pytest.mark.asyncio
171+
async def test_resource_error_cases(self, fastmcp_client):
172+
"""Test resource error handling."""
173+
# Test unknown section
174+
with patch('alfresco_mcp_server.fastmcp_server.alfresco_factory') as mock_factory, \
175+
patch('alfresco_mcp_server.fastmcp_server.auth_util') as mock_auth:
176+
177+
mock_auth.is_authenticated.return_value = False
178+
179+
result = await fastmcp_client.read_resource("alfresco://repository/unknown")
180+
assert len(result) > 0
181+
# Should handle unknown section gracefully
182+
183+
184+
@pytest.mark.unit
185+
class TestPromptsCoverage:
186+
"""Tests for prompt functionality coverage."""
187+
188+
@pytest.mark.asyncio
189+
async def test_prompt_all_analysis_types(self, fastmcp_client):
190+
"""Test prompt with all analysis types."""
191+
analysis_types = ["summary", "detailed", "trends", "compliance"]
192+
193+
for analysis_type in analysis_types:
194+
result = await fastmcp_client.get_prompt("search_and_analyze", {
195+
"query": "test documents",
196+
"analysis_type": analysis_type
197+
})
198+
199+
assert len(result.messages) > 0
200+
prompt_text = result.messages[0].content.text
201+
assert "test documents" in prompt_text
202+
assert analysis_type in prompt_text.lower()
203+
204+
@pytest.mark.asyncio
205+
async def test_prompt_edge_cases(self, fastmcp_client):
206+
"""Test prompt with edge case inputs."""
207+
edge_cases = [
208+
{"query": "", "analysis_type": "summary"}, # Empty query
209+
{"query": "test", "analysis_type": ""}, # Empty analysis type
210+
{"query": "a" * 500, "analysis_type": "summary"}, # Very long query
211+
]
212+
213+
for case in edge_cases:
214+
result = await fastmcp_client.get_prompt("search_and_analyze", case)
215+
assert len(result.messages) > 0
216+
# Should handle gracefully
217+
218+
219+
@pytest.mark.unit
220+
class TestConfigurationCoverage:
221+
"""Tests for configuration and environment handling."""
222+
223+
def test_config_environment_variables(self):
224+
"""Test configuration with various environment variable combinations."""
225+
with patch.dict(os.environ, {
226+
'ALFRESCO_URL': 'http://test:8080',
227+
'ALFRESCO_USERNAME': 'testuser',
228+
'ALFRESCO_PASSWORD': 'testpass',
229+
'ALFRESCO_VERIFY_SSL': 'true',
230+
'ALFRESCO_TIMEOUT': '60'
231+
}):
232+
from alfresco_mcp_server.config import AlfrescoConfig
233+
config = AlfrescoConfig()
234+
235+
assert config.alfresco_url == 'http://test:8080'
236+
assert config.username == 'testuser'
237+
assert config.password == 'testpass'
238+
assert config.verify_ssl == True
239+
assert config.timeout == 60
240+
241+
def test_config_defaults(self):
242+
"""Test configuration with default values."""
243+
# Clear environment variables by removing them completely
244+
env_vars = ['ALFRESCO_URL', 'ALFRESCO_USERNAME', 'ALFRESCO_PASSWORD',
245+
'ALFRESCO_VERIFY_SSL', 'ALFRESCO_TIMEOUT']
246+
247+
# Remove variables instead of setting to empty string
248+
with patch.dict(os.environ, {}, clear=True):
249+
for var in env_vars:
250+
os.environ.pop(var, None)
251+
252+
from alfresco_mcp_server.config import AlfrescoConfig
253+
config = AlfrescoConfig()
254+
255+
# Should use defaults
256+
assert config.alfresco_url == 'http://localhost:8080'
257+
assert config.username == 'admin'
258+
assert config.password == 'admin'
259+
assert config.verify_ssl == False
260+
assert config.timeout == 30
261+
262+
263+
@pytest.mark.unit
264+
class TestMainEntryPoint:
265+
"""Tests for main entry points and CLI handling."""
266+
267+
def test_main_function_coverage(self):
268+
"""Test main function argument parsing."""
269+
from alfresco_mcp_server.fastmcp_server import main
270+
271+
# Test with different argument combinations
272+
test_args = [
273+
[],
274+
['--transport', 'stdio'],
275+
['--transport', 'http', '--port', '8001'],
276+
['--log-level', 'DEBUG'],
277+
]
278+
279+
for args in test_args:
280+
with patch('sys.argv', ['fastmcp_server.py'] + args), \
281+
patch('alfresco_mcp_server.fastmcp_server.mcp.run') as mock_run:
282+
283+
try:
284+
main()
285+
mock_run.assert_called_once()
286+
except SystemExit:
287+
# Expected for help or invalid args
288+
pass
289+
290+
291+
@pytest.mark.unit
292+
class TestAsyncContextManagers:
293+
"""Tests for async context managers and cleanup."""
294+
295+
@pytest.mark.asyncio
296+
async def test_client_context_manager(self):
297+
"""Test FastMCP client context manager."""
298+
# Test that client properly connects and disconnects
299+
async with Client(mcp) as client:
300+
assert client.is_connected()
301+
302+
# Test basic functionality
303+
tools = await client.list_tools()
304+
assert len(tools) >= 9
305+
306+
# Client should be disconnected after context
307+
# Note: FastMCP handles this internally
308+
309+
310+
@pytest.mark.unit
311+
class TestExceptionHandling:
312+
"""Tests for comprehensive exception handling."""
313+
314+
@pytest.mark.asyncio
315+
async def test_network_timeout_simulation(self, fastmcp_client):
316+
"""Test handling of network timeouts."""
317+
import asyncio
318+
with patch('alfresco_mcp_server.fastmcp_server.ensure_connection',
319+
side_effect=asyncio.TimeoutError("Network timeout")):
320+
321+
result = await fastmcp_client.call_tool("search_content", {
322+
"query": "test",
323+
"max_results": 10
324+
})
325+
326+
assert len(result) == 1
327+
assert "failed" in result[0].text.lower()
328+
329+
@pytest.mark.asyncio
330+
async def test_authentication_failure_simulation(self, fastmcp_client):
331+
"""Test handling of authentication failures."""
332+
with patch('alfresco_mcp_server.fastmcp_server.ensure_connection',
333+
side_effect=Exception("Authentication failed")):
334+
335+
result = await fastmcp_client.call_tool("get_node_properties", {
336+
"node_id": "test-123"
337+
})
338+
339+
assert len(result) == 1
340+
assert "failed" in result[0].text.lower()
341+
342+
@pytest.mark.asyncio
343+
async def test_malformed_response_handling(self, fastmcp_client):
344+
"""Test handling of malformed Alfresco responses."""
345+
with patch('alfresco_mcp_server.fastmcp_server.ensure_connection'), \
346+
patch('alfresco_mcp_server.fastmcp_server.alfresco_factory') as mock_factory:
347+
348+
# Setup mock to return malformed data
349+
search_client = AsyncMock()
350+
search_client.search.return_value = "not a proper response object"
351+
mock_factory.create_search_client.return_value = search_client
352+
353+
result = await fastmcp_client.call_tool("search_content", {
354+
"query": "test",
355+
"max_results": 10
356+
})
357+
358+
assert len(result) == 1
359+
# Should handle gracefully without crashing

0 commit comments

Comments
 (0)