# test_weather.py # # author: deng # date: 20251129 from unittest.mock import Mock, patch import pytest import requests class TestWeatherFetcher: """Test cases for WeatherFetcher class.""" @pytest.fixture def weather_fetcher(self): """Create a WeatherFetcher instance.""" from hiking_assistant.weather import WeatherFetcher return WeatherFetcher() @pytest.fixture def mock_weather_response(self): """Mock weather API response data.""" return { 'current': { 'relative_humidity_2m': 75, 'apparent_temperature': 18.5, 'precipitation_probability': 30, 'precipitation': 0.5, 'wind_speed_10m': 15.2, 'wind_direction_10m': 180, }, 'daily': { 'temperature_2m_max': [22.0, 23.5, 21.0], 'temperature_2m_min': [15.0, 16.0, 14.5], 'apparent_temperature_max': [20.0, 21.5, 19.0], 'apparent_temperature_min': [13.0, 14.0, 12.5], 'relative_humidity_2m_mean': [70, 75, 68], 'precipitation_sum': [0.0, 2.5, 0.0], 'precipitation_probability_max': [20, 60, 15], 'wind_speed_10m_max': [25.0, 35.0, 20.0], 'wind_direction_10m_dominant': [180, 90, 270], 'sunrise': ['2024-01-01T06:00:00', '2024-01-02T06:01:00', '2024-01-03T06:02:00'], 'sunset': ['2024-01-01T18:00:00', '2024-01-02T18:01:00', '2024-01-03T18:02:00'], 'uv_index_max': [5.0, 7.0, 3.0], 'time': ['2024-01-01', '2024-01-02', '2024-01-03'], }, } def test_init_default_values(self): """Test WeatherFetcher initialization with defaults.""" from hiking_assistant.weather import WeatherFetcher fetcher = WeatherFetcher() assert fetcher.api_url == 'https://api.open-meteo.com/v1/forecast' assert fetcher.request_timeout == 8 assert fetcher.forecast_days == 7 def test_init_custom_values(self): """Test WeatherFetcher initialization with custom values.""" from hiking_assistant.weather import WeatherFetcher fetcher = WeatherFetcher(api_url='https://custom.api.com', request_timeout=10, forecast_days=3) assert fetcher.api_url == 'https://custom.api.com' assert fetcher.request_timeout == 10 assert fetcher.forecast_days == 3 def test_convert_wind_degrees_to_flow_direction_north(self, weather_fetcher): """Test wind direction conversion for north.""" result = weather_fetcher.convert_wind_degrees_to_flow_direction(0) assert result in ['⬇️', '⬆️'] # 0 and 360 degrees def test_convert_wind_degrees_to_flow_direction_east(self, weather_fetcher): """Test wind direction conversion for east.""" result = weather_fetcher.convert_wind_degrees_to_flow_direction(90) assert result == '⬅️' # 90 degrees = east wind blowing west def test_convert_wind_degrees_to_flow_direction_south(self, weather_fetcher): """Test wind direction conversion for south.""" result = weather_fetcher.convert_wind_degrees_to_flow_direction(180) assert result == '⬆️' def test_convert_wind_degrees_to_flow_direction_west(self, weather_fetcher): """Test wind direction conversion for west.""" result = weather_fetcher.convert_wind_degrees_to_flow_direction(270) assert result == '➡️' # 270 degrees = west wind blowing east def test_convert_wind_degrees_to_flow_direction_none(self, weather_fetcher): """Test wind direction conversion with None input.""" result = weather_fetcher.convert_wind_degrees_to_flow_direction(None) assert result == 'N/A' def test_get_wind_speed_indicator_safe(self, weather_fetcher): """Test wind speed indicator for safe level.""" result = weather_fetcher.get_wind_speed_indicator(15) assert result == '🟢' def test_get_wind_speed_indicator_caution(self, weather_fetcher): """Test wind speed indicator for caution level.""" result = weather_fetcher.get_wind_speed_indicator(30) assert result == '🟡' def test_get_wind_speed_indicator_alert(self, weather_fetcher): """Test wind speed indicator for alert level.""" result = weather_fetcher.get_wind_speed_indicator(50) assert result == '🟠' def test_get_wind_speed_indicator_dangerous(self, weather_fetcher): """Test wind speed indicator for dangerous level.""" result = weather_fetcher.get_wind_speed_indicator(65) assert result == '🔴' def test_get_wind_speed_indicator_none(self, weather_fetcher): """Test wind speed indicator with None input.""" result = weather_fetcher.get_wind_speed_indicator(None) assert result == '' def test_get_uv_index_indicator_low(self, weather_fetcher): """Test UV index indicator for low level.""" result = weather_fetcher.get_uv_index_indicator(2) assert result == '🟢' def test_get_uv_index_indicator_moderate(self, weather_fetcher): """Test UV index indicator for moderate level.""" result = weather_fetcher.get_uv_index_indicator(4) assert result == '🟡' def test_get_uv_index_indicator_high(self, weather_fetcher): """Test UV index indicator for high level.""" result = weather_fetcher.get_uv_index_indicator(6) assert result == '🟠' def test_get_uv_index_indicator_very_high(self, weather_fetcher): """Test UV index indicator for very high level.""" result = weather_fetcher.get_uv_index_indicator(9) assert result == '🔴' def test_get_uv_index_indicator_extreme(self, weather_fetcher): """Test UV index indicator for extreme level.""" result = weather_fetcher.get_uv_index_indicator(11) assert result == '🟣' def test_get_uv_index_indicator_none(self, weather_fetcher): """Test UV index indicator with None input.""" result = weather_fetcher.get_uv_index_indicator(None) assert result == '' @patch('hiking_assistant.weather.requests.get') def test_get_weather_success(self, mock_get, weather_fetcher, mock_weather_response): """Test successful weather data retrieval.""" mock_response = Mock() mock_response.json.return_value = mock_weather_response mock_get.return_value = mock_response result = weather_fetcher.get_weather(25.0, 121.0) assert result is not None assert 'current_humidity' in result assert 'daily_temp_max' in result assert result['current_humidity'] == 75 assert len(result['daily_temp_max']) == 3 @patch('hiking_assistant.weather.requests.get') def test_get_weather_request_exception(self, mock_get, weather_fetcher): """Test weather retrieval with request exception.""" mock_get.side_effect = requests.exceptions.RequestException('Connection error') result = weather_fetcher.get_weather(25.0, 121.0) assert result is None @patch('hiking_assistant.weather.requests.get') def test_get_weather_json_parsing_error(self, mock_get, weather_fetcher): """Test weather retrieval with JSON parsing error.""" mock_response = Mock() mock_response.json.side_effect = ValueError('Invalid JSON') mock_get.return_value = mock_response result = weather_fetcher.get_weather(25.0, 121.0) assert result is None @patch('hiking_assistant.weather.requests.get') def test_get_weather_api_parameters(self, mock_get, weather_fetcher, mock_weather_response): """Test that correct API parameters are sent.""" mock_response = Mock() mock_response.json.return_value = mock_weather_response mock_get.return_value = mock_response weather_fetcher.get_weather(25.123, 121.456) # Verify the call was made with correct parameters mock_get.assert_called_once() call_args = mock_get.call_args params = call_args[1]['params'] assert params['latitude'] == 25.123 assert params['longitude'] == 121.456 assert params['timezone'] == 'auto' assert params['forecast_days'] == 7 @patch('hiking_assistant.weather.requests.get') def test_get_weather_timeout_parameter(self, mock_get, weather_fetcher, mock_weather_response): """Test that timeout parameter is used.""" mock_response = Mock() mock_response.json.return_value = mock_weather_response mock_get.return_value = mock_response weather_fetcher.get_weather(25.0, 121.0) # Verify timeout was passed call_args = mock_get.call_args assert call_args[1]['timeout'] == 8 class TestWeatherFetcherEdgeCases: """Test edge cases for WeatherFetcher.""" def test_forecast_days_minimum_is_one(self): """Test that forecast_days is at least 1.""" from hiking_assistant.weather import WeatherFetcher fetcher = WeatherFetcher(forecast_days=0) assert fetcher.forecast_days == 1 fetcher = WeatherFetcher(forecast_days=-5) assert fetcher.forecast_days == 1 def test_wind_direction_boundary_values(self): """Test wind direction conversion at boundary values.""" from hiking_assistant.weather import WeatherFetcher fetcher = WeatherFetcher() # Test 360 degrees (same as 0) result_0 = fetcher.convert_wind_degrees_to_flow_direction(0) result_360 = fetcher.convert_wind_degrees_to_flow_direction(360) assert result_0 == result_360 def test_wind_speed_boundary_values(self): """Test wind speed indicators at exact boundary values.""" from hiking_assistant.weather import WeatherFetcher fetcher = WeatherFetcher() assert fetcher.get_wind_speed_indicator(20) == '🟡' # Exactly 20 assert fetcher.get_wind_speed_indicator(40) == '🟠' # Exactly 40 assert fetcher.get_wind_speed_indicator(60) == '🔴' # Exactly 60 def test_uv_index_boundary_values(self): """Test UV index indicators at exact boundary values.""" from hiking_assistant.weather import WeatherFetcher fetcher = WeatherFetcher() assert fetcher.get_uv_index_indicator(2) == '🟢' # Exactly 2 assert fetcher.get_uv_index_indicator(5) == '🟡' # Exactly 5 assert fetcher.get_uv_index_indicator(7) == '🟠' # Exactly 7 assert fetcher.get_uv_index_indicator(10) == '🔴' # Exactly 10