"""Tests for main application module."""
import os
import pytest
from unittest.mock import patch, Mock, MagicMock
import uvicorn
from src.main import create_app, main
from src.server import MCPServer
class TestCreateApp:
"""Test cases for create_app function."""
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_no_api_keys(self, mock_get_settings, mock_validate_api_keys):
"""Test app creation when no API keys are available."""
# Mock settings
mock_settings = Mock()
mock_get_settings.return_value = mock_settings
# Mock API key validation - no keys available
mock_validate_api_keys.return_value = {
"weather": False,
"news": False,
"stock": False,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {}
mock_server_class.return_value = mock_server
app = create_app()
# Verify server was created with settings
mock_server_class.assert_called_once_with(mock_settings)
# Verify no tools were registered (no API keys available)
assert mock_server.register_tool.call_count == 0
assert app == mock_server
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_all_api_keys_available(self, mock_get_settings, mock_validate_api_keys):
"""Test app creation when all API keys are available."""
mock_settings = Mock()
mock_get_settings.return_value = mock_settings
# Mock API key validation - all keys available
mock_validate_api_keys.return_value = {
"weather": True,
"news": True,
"stock": True,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {}
mock_server_class.return_value = mock_server
app = create_app()
# Verify server was created
mock_server_class.assert_called_once_with(mock_settings)
# Verify all tools were registered (4 total: 1 weather, 1 news, 2 stock)
assert mock_server.register_tool.call_count == 4
# Verify specific tools were registered by checking call arguments
tool_names = [call[1]["name"] for call in mock_server.register_tool.call_args_list]
expected_tools = {"get_weather", "get_news", "get_stock_price", "search_stocks"}
assert expected_tools.issubset(set(tool_names))
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_partial_api_keys(self, mock_get_settings, mock_validate_api_keys):
"""Test app creation when only some API keys are available."""
mock_settings = Mock()
mock_get_settings.return_value = mock_settings
# Mock API key validation - only weather and stock available
mock_validate_api_keys.return_value = {
"weather": True,
"news": False, # Missing news API key
"stock": True,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {}
mock_server_class.return_value = mock_server
app = create_app()
# Should register weather + 2 stock tools = 3 total
assert mock_server.register_tool.call_count == 3
# Verify specific tools were registered
tool_names = [call[1]["name"] for call in mock_server.register_tool.call_args_list]
assert "get_weather" in tool_names
assert "get_stock_price" in tool_names
assert "search_stocks" in tool_names
assert "get_news" not in tool_names # Should not be registered
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_weather_tool_registration(self, mock_get_settings, mock_validate_api_keys):
"""Test weather tool registration details."""
mock_settings = Mock()
mock_get_settings.return_value = mock_settings
mock_validate_api_keys.return_value = {
"weather": True,
"news": False,
"stock": False,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {}
mock_server_class.return_value = mock_server
create_app()
# Verify weather tool was registered with correct parameters
mock_server.register_tool.assert_called_once()
call_args = mock_server.register_tool.call_args
assert call_args[1]["name"] == "get_weather"
assert "weather information" in call_args[1]["description"].lower()
assert "input_schema" in call_args[1]
assert "handler" in call_args[1]
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_news_tool_registration(self, mock_get_settings, mock_validate_api_keys):
"""Test news tool registration details."""
mock_settings = Mock()
mock_get_settings.return_value = mock_settings
mock_validate_api_keys.return_value = {
"weather": False,
"news": True,
"stock": False,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {}
mock_server_class.return_value = mock_server
create_app()
# Verify news tool was registered
mock_server.register_tool.assert_called_once()
call_args = mock_server.register_tool.call_args
assert call_args[1]["name"] == "get_news"
assert "news" in call_args[1]["description"].lower()
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_stock_tools_registration(self, mock_get_settings, mock_validate_api_keys):
"""Test stock tools registration details."""
mock_settings = Mock()
mock_get_settings.return_value = mock_settings
mock_validate_api_keys.return_value = {
"weather": False,
"news": False,
"stock": True,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {}
mock_server_class.return_value = mock_server
create_app()
# Should register 2 stock tools
assert mock_server.register_tool.call_count == 2
# Get all tool names
tool_names = [call[1]["name"] for call in mock_server.register_tool.call_args_list]
assert "get_stock_price" in tool_names
assert "search_stocks" in tool_names
@patch("src.main.validate_api_keys")
@patch("src.main.get_settings")
def test_create_app_debug_logging(self, mock_get_settings, mock_validate_api_keys):
"""Test that debug information is logged correctly."""
mock_settings = Mock()
mock_settings.debug = True
mock_get_settings.return_value = mock_settings
mock_validate_api_keys.return_value = {
"weather": True,
"news": False,
"stock": False,
}
with patch("src.main.MCPServer") as mock_server_class:
mock_server = Mock(spec=MCPServer)
mock_server._tools = {"get_weather": Mock()}
mock_server_class.return_value = mock_server
with patch("src.main.logger") as mock_logger:
create_app()
# Verify initialization log was called
mock_logger.info.assert_called()
# Check that the final log call includes tool information
final_log_call = mock_logger.info.call_args_list[-1]
log_message = final_log_call[0][0]
assert "MCP Server initialized" in log_message
class TestMain:
"""Test cases for main function."""
@patch("src.main.uvicorn.run")
@patch("src.main.create_app")
@patch("src.main.get_settings")
def test_main_function_calls(self, mock_get_settings, mock_create_app, mock_uvicorn_run):
"""Test that main function calls all necessary components."""
# Mock settings
mock_settings = Mock()
mock_settings.host = "localhost"
mock_settings.port = 8000
mock_settings.debug = False
mock_settings.log_level = "INFO"
mock_get_settings.return_value = mock_settings
# Mock MCP server
mock_server = Mock()
mock_app = Mock()
mock_server.get_app.return_value = mock_app
mock_create_app.return_value = mock_server
main()
# Verify create_app was called
mock_create_app.assert_called_once()
# Verify uvicorn.run was called with correct parameters
mock_uvicorn_run.assert_called_once_with(
mock_app,
host="localhost",
port=8000,
log_level="info",
reload=False,
)
@patch("src.main.uvicorn.run")
@patch("src.main.create_app")
@patch("src.main.get_settings")
def test_main_with_debug_settings(self, mock_get_settings, mock_create_app, mock_uvicorn_run):
"""Test main function with debug settings enabled."""
mock_settings = Mock()
mock_settings.host = "0.0.0.0"
mock_settings.port = 9000
mock_settings.debug = True
mock_settings.log_level = "DEBUG"
mock_get_settings.return_value = mock_settings
mock_server = Mock()
mock_app = Mock()
mock_server.get_app.return_value = mock_app
mock_create_app.return_value = mock_server
main()
# Verify uvicorn.run was called with debug settings
mock_uvicorn_run.assert_called_once_with(
mock_app,
host="0.0.0.0",
port=9000,
log_level="debug",
reload=True, # Should be True when debug=True
)
@patch("src.main.uvicorn.run")
@patch("src.main.create_app")
@patch("src.main.get_settings")
@patch("logging.basicConfig")
def test_main_logging_configuration(self, mock_logging_config, mock_get_settings, mock_create_app, mock_uvicorn_run):
"""Test that logging is configured correctly."""
mock_settings = Mock()
mock_settings.host = "localhost"
mock_settings.port = 8000
mock_settings.debug = False
mock_settings.log_level = "WARNING"
mock_get_settings.return_value = mock_settings
mock_server = Mock()
mock_app = Mock()
mock_server.get_app.return_value = mock_app
mock_create_app.return_value = mock_server
with patch("logging.WARNING", 30): # Mock logging level constant
main()
# Verify logging was configured with correct level
mock_logging_config.assert_called_once()
call_args = mock_logging_config.call_args
# The level should be the result of getattr(logging, "WARNING")
assert "level" in call_args[1]
@patch("src.main.uvicorn.run")
@patch("src.main.create_app")
@patch("src.main.get_settings")
def test_main_server_startup_logging(self, mock_get_settings, mock_create_app, mock_uvicorn_run):
"""Test that server startup is logged correctly."""
mock_settings = Mock()
mock_settings.host = "localhost"
mock_settings.port = 8000
mock_settings.debug = False
mock_settings.log_level = "INFO"
mock_get_settings.return_value = mock_settings
mock_server = Mock()
mock_app = Mock()
mock_server.get_app.return_value = mock_app
mock_create_app.return_value = mock_server
with patch("src.main.logger") as mock_logger:
main()
# Verify startup logging was called
mock_logger.info.assert_called()
# Check the startup log message
startup_log_call = None
for call in mock_logger.info.call_args_list:
if "Starting API Aggregator MCP Server" in call[0][0]:
startup_log_call = call
break
assert startup_log_call is not None
class TestMainIntegration:
"""Integration test cases for main module."""
@patch("src.main.uvicorn.run")
@patch("src.main.validate_api_keys")
def test_full_app_creation_flow(self, mock_validate_api_keys, mock_uvicorn_run):
"""Test the full app creation flow with real components."""
# Mock API keys being available
mock_validate_api_keys.return_value = {
"weather": True,
"news": True,
"stock": True,
}
# Mock settings with environment variables
with patch.dict(os.environ, {
"MCP_SERVER_HOST": "test-host",
"MCP_SERVER_PORT": "9999",
"MCP_SERVER_DEBUG": "true",
"OPENWEATHER_API_KEY": "test-weather",
"NEWS_API_KEY": "test-news",
"ALPHA_VANTAGE_API_KEY": "test-stock",
}, clear=True):
# This should work end-to-end without mocking internal components
main()
# Verify uvicorn was called
mock_uvicorn_run.assert_called_once()
# Verify the call arguments include our test settings
call_args = mock_uvicorn_run.call_args
assert call_args[1]["host"] == "test-host"
assert call_args[1]["port"] == 9999
assert call_args[1]["reload"] is True # debug=true
def test_app_creation_without_mocking_settings(self):
"""Test app creation using real settings (but mocked API validation)."""
with patch("src.main.validate_api_keys") as mock_validate:
mock_validate.return_value = {
"weather": False,
"news": False,
"stock": False,
}
# This should create a real MCPServer instance
app = create_app()
# Verify it's a real MCPServer
assert isinstance(app, MCPServer)
assert hasattr(app, '_tools')
assert hasattr(app, '_handlers')
assert hasattr(app, 'app') # FastAPI app
# Should have no tools registered due to no API keys
assert len(app._tools) == 0
assert len(app._handlers) == 0