Skip to main content
Glama

Weather MCP Server

Apache 2.0
23
  • Linux
  • Apple
test_time_tools.py19.6 kB
""" Unit tests for time-related tool handlers. """ import pytest import json from unittest.mock import Mock, patch from datetime import datetime, timezone from zoneinfo import ZoneInfo from mcp.types import TextContent, ErrorData from mcp import McpError from src.mcp_weather_server.tools.tools_time import ( GetCurrentDateTimeToolHandler, GetTimeZoneInfoToolHandler, ConvertTimeToolHandler ) class TestGetCurrentDateTimeToolHandler: """Test cases for GetCurrentDateTimeToolHandler.""" @pytest.fixture def handler(self): """Create a GetCurrentDateTimeToolHandler instance.""" return GetCurrentDateTimeToolHandler() def test_tool_description(self, handler): """Test the tool description is properly formatted.""" description = handler.get_tool_description() assert description.name == "get_current_datetime" assert "current time" in description.description.lower() assert description.inputSchema["type"] == "object" assert "timezone_name" in description.inputSchema["properties"] assert description.inputSchema["required"] == ["timezone_name"] @pytest.mark.asyncio async def test_run_tool_success(self, handler): """Test successful tool execution.""" fixed_time = datetime(2024, 1, 1, 15, 30, 45, tzinfo=ZoneInfo("America/New_York")) with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.return_value = ZoneInfo("America/New_York") with patch('src.mcp_weather_server.tools.tools_time.datetime') as mock_datetime: mock_datetime.now.return_value = fixed_time args = {"timezone_name": "America/New_York"} result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) response_data = json.loads(result[0].text) assert response_data["timezone"] == "America/New_York" assert "2024-01-01T15:30:45" in response_data["datetime"] @pytest.mark.asyncio async def test_run_tool_missing_timezone(self, handler): """Test tool execution with missing timezone argument.""" args = {} result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Missing required arguments: timezone_name" in result[0].text @pytest.mark.asyncio async def test_run_tool_invalid_timezone(self, handler): """Test tool execution with invalid timezone.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.side_effect = McpError(ErrorData(code=-1, message="Invalid timezone: Invalid/Timezone")) args = {"timezone_name": "Invalid/Timezone"} result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Error getting current time: Invalid timezone" in result[0].text @pytest.mark.asyncio async def test_run_tool_utc_timezone(self, handler): """Test tool execution with UTC timezone.""" fixed_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.return_value = ZoneInfo("UTC") with patch('src.mcp_weather_server.tools.tools_time.datetime') as mock_datetime: mock_datetime.now.return_value = fixed_time args = {"timezone_name": "UTC"} result = await handler.run_tool(args) assert len(result) == 1 response_data = json.loads(result[0].text) assert response_data["timezone"] == "UTC" assert "2024-01-01T12:00:00" in response_data["datetime"] @pytest.mark.asyncio async def test_run_tool_real_timezone_no_mocks(self, handler): """Integration test with real timezones (no mocks) to catch tzdata issues.""" # This test specifically targets the Windows tzdata issue # by using real timezone functionality without mocks test_cases = [ "UTC", # Failed in original issue "America/New_York", # Original failing case ] for timezone_name in test_cases: args = {"timezone_name": timezone_name} result = await handler.run_tool(args) # Should not get an error about timezone not found assert len(result) == 1 assert isinstance(result[0], TextContent) # Parse the result and verify it's not an error result_text = result[0].text assert "Error getting current time" not in result_text assert "No time zone found with key" not in result_text assert "ModuleNotFoundError: No module named 'tzdata'" not in result_text # Should be valid JSON with timezone info try: response_data = json.loads(result_text) assert response_data["timezone"] == timezone_name assert "datetime" in response_data # Verify datetime is in ISO format assert "T" in response_data["datetime"] except json.JSONDecodeError: pytest.fail(f"Invalid JSON response for timezone {timezone_name}: {result_text}") @pytest.mark.asyncio async def test_run_tool_windows_specific_timezones(self, handler): """Test timezones that commonly fail on Windows without tzdata.""" # These timezones frequently cause issues on Windows systems # without proper tzdata installation windows_problematic_timezones = [ "America/New_York", "America/Los_Angeles", "Europe/London", "Asia/Tokyo", "UTC" ] for timezone_name in windows_problematic_timezones: args = {"timezone_name": timezone_name} result = await handler.run_tool(args) assert len(result) == 1 result_text = result[0].text # Should not contain tzdata error messages tzdata_error_indicators = [ "No module named 'tzdata'", "ZoneInfoNotFoundError", "No time zone found with key", "Invalid timezone", ] for error_indicator in tzdata_error_indicators: assert error_indicator not in result_text, \ f"Timezone {timezone_name} failed with tzdata error: {result_text}" class TestGetTimeZoneInfoToolHandler: """Test cases for GetTimeZoneInfoToolHandler.""" @pytest.fixture def handler(self): """Create a GetTimeZoneInfoToolHandler instance.""" return GetTimeZoneInfoToolHandler() def test_tool_description(self, handler): """Test the tool description is properly formatted.""" description = handler.get_tool_description() assert description.name == "get_timezone_info" assert "timezone" in description.description.lower() assert description.inputSchema["type"] == "object" assert "timezone_name" in description.inputSchema["properties"] assert description.inputSchema["required"] == ["timezone_name"] @pytest.mark.asyncio async def test_run_tool_success(self, handler): """Test successful tool execution.""" fixed_time = datetime(2024, 6, 15, 14, 30, 0, tzinfo=ZoneInfo("Europe/London")) fixed_utc_time = datetime(2024, 6, 15, 13, 0, 0) # UTC time without timezone with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.return_value = ZoneInfo("Europe/London") with patch('src.mcp_weather_server.tools.tools_time.datetime') as mock_datetime: mock_datetime.now.return_value = fixed_time mock_datetime.utcnow.return_value = fixed_utc_time args = {"timezone_name": "Europe/London"} result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) response_data = json.loads(result[0].text) assert response_data["timezone_name"] == "Europe/London" assert "current_local_time" in response_data assert "utc_offset_hours" in response_data @pytest.mark.asyncio async def test_run_tool_missing_timezone(self, handler): """Test tool execution with missing timezone argument.""" args = {} result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Missing required arguments: timezone_name" in result[0].text @pytest.mark.asyncio async def test_run_tool_invalid_timezone(self, handler): """Test tool execution with invalid timezone.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.side_effect = McpError(ErrorData(code=-1, message="Invalid timezone")) args = {"timezone_name": "Invalid/Timezone"} result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Error getting timezone info: Invalid timezone" in result[0].text class TestConvertTimeToolHandler: """Test cases for ConvertTimeToolHandler.""" @pytest.fixture def handler(self): """Create a ConvertTimeToolHandler instance.""" return ConvertTimeToolHandler() def test_tool_description(self, handler): """Test the tool description is properly formatted.""" description = handler.get_tool_description() assert description.name == "convert_time" assert "convert time" in description.description.lower() assert description.inputSchema["type"] == "object" # Check that required parameters are supported assert "datetime_str" in description.inputSchema["properties"] assert "from_timezone" in description.inputSchema["properties"] assert "to_timezone" in description.inputSchema["properties"] assert "to_timezone" in description.inputSchema["properties"] # Check that required fields include the timezone fields assert "from_timezone" in description.inputSchema["required"] assert "to_timezone" in description.inputSchema["required"] @pytest.mark.asyncio async def test_run_tool_success(self, handler): """Test successful time conversion.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: # Mock timezone creation mock_get_tz.side_effect = lambda tz: ZoneInfo(tz) with patch('dateutil.parser.parse') as mock_parse: # Mock parsing the datetime string source_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) mock_parse.return_value = source_time args = { "datetime_str": "2024-01-01T12:00:00", "from_timezone": "UTC", "to_timezone": "America/New_York" } result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) response_data = json.loads(result[0].text) assert response_data["original_timezone"] == "UTC" assert response_data["converted_timezone"] == "America/New_York" # Check that the datetime includes timezone info assert "2024-01-01T12:00:00" in response_data["original_datetime"] assert "converted_datetime" in response_data @pytest.mark.asyncio async def test_run_tool_missing_required_args(self, handler): """Test tool execution with missing required arguments.""" args = {"datetime": "2024-01-01T12:00:00"} # Missing timezones result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Missing required arguments" in result[0].text @pytest.mark.asyncio async def test_run_tool_invalid_datetime_format(self, handler): """Test tool execution with invalid datetime format.""" with patch('dateutil.parser.parse') as mock_parse: mock_parse.side_effect = ValueError("Invalid datetime format") args = { "datetime": "invalid-datetime", "from_timezone": "UTC", "to_timezone": "America/New_York" } result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Error converting time" in result[0].text @pytest.mark.asyncio async def test_run_tool_invalid_from_timezone(self, handler): """Test tool execution with invalid from_timezone.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: def side_effect(tz): if tz == "Invalid/Timezone": raise McpError(ErrorData(code=-1, message="Invalid timezone")) return ZoneInfo(tz) mock_get_tz.side_effect = side_effect args = { "datetime": "2024-01-01T12:00:00", "from_timezone": "Invalid/Timezone", "to_timezone": "UTC" } result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Error converting time" in result[0].text @pytest.mark.asyncio async def test_run_tool_invalid_to_timezone(self, handler): """Test tool execution with invalid to_timezone.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: def side_effect(tz): if tz == "Invalid/Timezone": raise McpError(ErrorData(code=-1, message="Invalid timezone")) return ZoneInfo(tz) mock_get_tz.side_effect = side_effect with patch('dateutil.parser.parse') as mock_parse: mock_parse.return_value = datetime(2024, 1, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) args = { "datetime": "2024-01-01T12:00:00", "from_timezone": "UTC", "to_timezone": "Invalid/Timezone" } result = await handler.run_tool(args) assert len(result) == 1 assert isinstance(result[0], TextContent) assert "Error converting time" in result[0].text @pytest.mark.asyncio async def test_run_tool_same_timezone_conversion(self, handler): """Test time conversion between same timezones.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.return_value = ZoneInfo("UTC") with patch('dateutil.parser.parse') as mock_parse: source_time = datetime(2024, 1, 1, 12, 0, 0, tzinfo=ZoneInfo("UTC")) mock_parse.return_value = source_time args = { "datetime_str": "2024-01-01T12:00:00", "from_timezone": "UTC", "to_timezone": "UTC" } result = await handler.run_tool(args) assert len(result) == 1 response_data = json.loads(result[0].text) # When converting to same timezone, time should remain the same assert "2024-01-01T12:00:00" in response_data["original_datetime"] assert response_data["original_timezone"] == "UTC" assert response_data["converted_timezone"] == "UTC" @pytest.mark.asyncio async def test_run_tool_across_date_line(self, handler): """Test time conversion across international date line.""" with patch('src.mcp_weather_server.utils.get_zoneinfo') as mock_get_tz: mock_get_tz.side_effect = lambda tz: ZoneInfo(tz) with patch('dateutil.parser.parse') as mock_parse: # Late evening in one timezone source_time = datetime(2024, 1, 1, 23, 0, 0, tzinfo=ZoneInfo("Pacific/Kiritimati")) mock_parse.return_value = source_time args = { "datetime_str": "2024-01-01T23:00:00", "from_timezone": "Pacific/Kiritimati", "to_timezone": "Pacific/Honolulu" } result = await handler.run_tool(args) assert len(result) == 1 response_data = json.loads(result[0].text) assert response_data["original_timezone"] == "Pacific/Kiritimati" assert response_data["converted_timezone"] == "Pacific/Honolulu" # Should handle date line crossing correctly assert "converted_datetime" in response_data @pytest.mark.asyncio async def test_run_tool_real_timezone_conversion_no_mocks(self, handler): """Integration test for timezone conversion without mocks to catch tzdata issues.""" # Test the exact conversion that was failing in Windows args = { "datetime_str": "2024-10-19T12:00:00", "from_timezone": "UTC", "to_timezone": "America/New_York" } result = await handler.run_tool(args) assert len(result) == 1 result_text = result[0].text # Should not contain tzdata-related errors assert "No module named 'tzdata'" not in result_text assert "ZoneInfoNotFoundError" not in result_text assert "No time zone found with key" not in result_text assert "Error converting time" not in result_text # Should be valid JSON with conversion data try: response_data = json.loads(result_text) assert response_data["original_timezone"] == "UTC" assert response_data["converted_timezone"] == "America/New_York" assert "original_datetime" in response_data assert "converted_datetime" in response_data assert "time_difference_hours" in response_data except json.JSONDecodeError: pytest.fail(f"Invalid JSON response: {result_text}") @pytest.mark.asyncio async def test_run_tool_now_keyword_real_timezone(self, handler): """Test 'now' keyword with real timezone (integration test).""" args = { "datetime_str": "now", "from_timezone": "America/New_York", "to_timezone": "UTC" } result = await handler.run_tool(args) assert len(result) == 1 result_text = result[0].text # Should not have tzdata errors assert "No module named 'tzdata'" not in result_text assert "ZoneInfoNotFoundError" not in result_text # Should be valid conversion result try: response_data = json.loads(result_text) assert response_data["original_timezone"] == "America/New_York" assert response_data["converted_timezone"] == "UTC" except json.JSONDecodeError: pytest.fail(f"Invalid JSON response for 'now' conversion: {result_text}")

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/isdaniel/mcp_weather_server'

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