Skip to main content
Glama
mfang0126

API Aggregator MCP Server

by mfang0126
test_weather.py15.8 kB
"""Tests for weather tool implementation.""" import pytest from unittest.mock import AsyncMock, patch, Mock import aiohttp from src.tools.weather import ( WeatherService, get_weather_handler, WEATHER_TOOL_SCHEMA, ) from src.utils.errors import APIError, ErrorCode class TestWeatherService: """Test cases for WeatherService class.""" def test_weather_service_initialization(self, mock_settings): """Test WeatherService initialization.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() assert service.settings == mock_settings assert service.base_url == "https://api.openweathermap.org/data/2.5" @pytest.mark.asyncio async def test_get_current_weather_success(self, mock_settings, sample_weather_api_response): """Test successful weather data retrieval.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() # Mock aiohttp session and response mock_response = AsyncMock() mock_response.status = 200 mock_response.json = AsyncMock(return_value=sample_weather_api_response) # Create a mock session that properly handles async context with patch("aiohttp.ClientSession") as mock_session_class: mock_session = AsyncMock() mock_session_class.return_value.__aenter__ = AsyncMock(return_value=mock_session) mock_session_class.return_value.__aexit__ = AsyncMock(return_value=None) # Mock the get method to return our mock response mock_session.get.return_value.__aenter__ = AsyncMock(return_value=mock_response) mock_session.get.return_value.__aexit__ = AsyncMock(return_value=None) result = await service.get_current_weather("San Francisco", "US", "metric") # Verify the request was made correctly mock_session.get.assert_called_once() call_args = mock_session.get.call_args assert call_args[0][0] == "https://api.openweathermap.org/data/2.5/weather" params = call_args[1]["params"] assert params["q"] == "San Francisco,US" assert params["appid"] == "test_weather_key" assert params["units"] == "metric" # Verify the normalized response assert result["location"]["city"] == "San Francisco" assert result["location"]["country"] == "US" assert result["temperature"]["current"] == 18.5 assert result["temperature"]["unit"] == "°C" assert result["weather"]["condition"] == "Clouds" assert result["source"] == "OpenWeatherMap" @pytest.mark.asyncio async def test_get_current_weather_no_api_key(self): """Test weather request without API key.""" mock_settings = Mock() mock_settings.openweather_api_key = None with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() with pytest.raises(APIError) as exc_info: await service.get_current_weather("San Francisco") error = exc_info.value assert error.code == ErrorCode.API_KEY_MISSING assert "OpenWeatherMap" in error.message @pytest.mark.asyncio async def test_get_current_weather_empty_city(self, mock_settings): """Test weather request with empty city name.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() with pytest.raises(APIError) as exc_info: await service.get_current_weather("") error = exc_info.value assert error.code == ErrorCode.INVALID_PARAMS assert "City name cannot be empty" in error.message @pytest.mark.asyncio async def test_get_current_weather_invalid_units(self, mock_settings): """Test weather request with invalid units.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() with pytest.raises(APIError) as exc_info: await service.get_current_weather("San Francisco", units="invalid") error = exc_info.value assert error.code == ErrorCode.INVALID_PARAMS assert "Units must be one of: metric, imperial, kelvin" in error.message @pytest.mark.asyncio async def test_get_current_weather_api_error_401(self, mock_settings): """Test weather request with invalid API key (401 error).""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() mock_response = AsyncMock() mock_response.status = 401 mock_response.text = AsyncMock(return_value="Unauthorized") with patch("aiohttp.ClientSession") as mock_session_class: mock_session = AsyncMock() mock_session_class.return_value.__aenter__ = AsyncMock(return_value=mock_session) mock_session_class.return_value.__aexit__ = AsyncMock(return_value=None) mock_session.get.return_value.__aenter__ = AsyncMock(return_value=mock_response) mock_session.get.return_value.__aexit__ = AsyncMock(return_value=None) with pytest.raises(APIError) as exc_info: await service.get_current_weather("San Francisco") error = exc_info.value assert error.code == ErrorCode.API_KEY_INVALID assert "Invalid OpenWeatherMap API key" in error.message @pytest.mark.asyncio async def test_get_current_weather_city_not_found(self, mock_settings): """Test weather request for non-existent city (404 error).""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() mock_response = AsyncMock() mock_response.status = 404 mock_response.text = AsyncMock(return_value="City not found") with patch("aiohttp.ClientSession") as mock_session_class: mock_session = AsyncMock() mock_session_class.return_value.__aenter__ = AsyncMock(return_value=mock_session) mock_session_class.return_value.__aexit__ = AsyncMock(return_value=None) mock_session.get.return_value.__aenter__ = AsyncMock(return_value=mock_response) mock_session.get.return_value.__aexit__ = AsyncMock(return_value=None) with pytest.raises(APIError) as exc_info: await service.get_current_weather("NonExistentCity") error = exc_info.value assert error.code == ErrorCode.INVALID_PARAMS assert "City 'NonExistentCity' not found" in error.message @pytest.mark.asyncio async def test_get_current_weather_network_error(self, mock_settings): """Test weather request with network error.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() mock_session = AsyncMock() mock_session.get.side_effect = aiohttp.ClientError("Network error") with patch("aiohttp.ClientSession", return_value=mock_session): with pytest.raises(APIError) as exc_info: await service.get_current_weather("San Francisco") error = exc_info.value assert error.code == ErrorCode.EXTERNAL_API_ERROR def test_normalize_weather_data_metric(self, sample_weather_api_response, mock_settings): """Test weather data normalization with metric units.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() result = service._normalize_weather_data(sample_weather_api_response, "metric") assert result["location"]["city"] == "San Francisco" assert result["location"]["country"] == "US" assert result["location"]["coordinates"]["latitude"] == 37.77 assert result["location"]["coordinates"]["longitude"] == -122.42 assert result["weather"]["condition"] == "Clouds" assert result["weather"]["description"] == "few clouds" assert result["temperature"]["current"] == 18.5 assert result["temperature"]["unit"] == "°C" assert result["temperature"]["feels_like"] == 17.2 assert result["humidity"] == "72%" assert result["pressure"] == "1013 hPa" assert result["wind"]["speed"] == "3.6 m/s" assert result["clouds"] == "20%" assert result["source"] == "OpenWeatherMap" def test_normalize_weather_data_imperial(self, sample_weather_api_response, mock_settings): """Test weather data normalization with imperial units.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() result = service._normalize_weather_data(sample_weather_api_response, "imperial") assert result["temperature"]["unit"] == "°F" assert result["wind"]["speed"] == "3.6 mph" def test_normalize_weather_data_kelvin(self, sample_weather_api_response, mock_settings): """Test weather data normalization with kelvin units.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() result = service._normalize_weather_data(sample_weather_api_response, "kelvin") assert result["temperature"]["unit"] == "K" assert result["wind"]["speed"] == "3.6 m/s" @pytest.mark.asyncio async def test_get_current_weather_whitespace_handling(self, mock_settings, sample_weather_api_response): """Test weather request with whitespace in city/country names.""" with patch("src.tools.weather.get_settings", return_value=mock_settings): service = WeatherService() mock_response = AsyncMock() mock_response.status = 200 mock_response.json = AsyncMock(return_value=sample_weather_api_response) with patch("aiohttp.ClientSession") as mock_session_class: mock_session = AsyncMock() mock_session_class.return_value.__aenter__ = AsyncMock(return_value=mock_session) mock_session_class.return_value.__aexit__ = AsyncMock(return_value=None) mock_session.get.return_value.__aenter__ = AsyncMock(return_value=mock_response) mock_session.get.return_value.__aexit__ = AsyncMock(return_value=None) await service.get_current_weather(" San Francisco ", " US ") # Verify whitespace was stripped call_args = mock_session.get.call_args params = call_args[1]["params"] assert params["q"] == "San Francisco,US" class TestWeatherHandler: """Test cases for get_weather_handler function.""" @pytest.mark.asyncio async def test_get_weather_handler_success(self, sample_weather_api_response): """Test successful weather handler execution.""" parameters = { "city": "San Francisco", "country": "US", "units": "metric" } # Mock the weather service with patch("src.tools.weather.weather_service") as mock_service: mock_service.get_current_weather = AsyncMock(return_value={"test": "data"}) result = await get_weather_handler(parameters) mock_service.get_current_weather.assert_called_once_with( city="San Francisco", country="US", units="metric" ) assert result == {"test": "data"} @pytest.mark.asyncio async def test_get_weather_handler_missing_city(self): """Test weather handler with missing city parameter.""" parameters = {"country": "US"} with pytest.raises(APIError) as exc_info: await get_weather_handler(parameters) error = exc_info.value assert error.code == ErrorCode.INVALID_PARAMS assert "City parameter is required" in error.message @pytest.mark.asyncio async def test_get_weather_handler_default_units(self): """Test weather handler with default units.""" parameters = {"city": "San Francisco"} with patch("src.tools.weather.weather_service") as mock_service: mock_service.get_current_weather = AsyncMock(return_value={"test": "data"}) await get_weather_handler(parameters) mock_service.get_current_weather.assert_called_once_with( city="San Francisco", country=None, units="metric" # Default value ) @pytest.mark.asyncio async def test_get_weather_handler_propagates_service_errors(self): """Test that handler propagates errors from weather service.""" parameters = {"city": "San Francisco"} with patch("src.tools.weather.weather_service") as mock_service: service_error = APIError("Service error", ErrorCode.EXTERNAL_API_ERROR) mock_service.get_current_weather = AsyncMock(side_effect=service_error) with pytest.raises(APIError) as exc_info: await get_weather_handler(parameters) assert exc_info.value == service_error class TestWeatherToolSchema: """Test cases for weather tool schema.""" def test_weather_tool_schema_structure(self): """Test that the weather tool schema has the correct structure.""" schema = WEATHER_TOOL_SCHEMA assert schema["type"] == "object" assert "properties" in schema properties = schema["properties"] # Check required city property assert "city" in properties city_prop = properties["city"] assert city_prop["type"] == "string" assert "description" in city_prop # Check optional country property assert "country" in properties country_prop = properties["country"] assert country_prop["type"] == "string" assert "description" in country_prop # Check optional units property assert "units" in properties units_prop = properties["units"] assert units_prop["type"] == "string" assert units_prop["default"] == "metric" assert set(units_prop["enum"]) == {"metric", "imperial", "kelvin"} def test_weather_tool_schema_required_fields(self): """Test that the weather tool schema specifies required fields correctly.""" schema = WEATHER_TOOL_SCHEMA assert "required" in schema assert schema["required"] == ["city"]

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mfang0126/api-aggregator-MCPServer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server