From e55477afcdfcc673e818eef2a269024e9b12ea5b Mon Sep 17 00:00:00 2001 From: Omar G Ghafour Date: Tue, 27 May 2025 17:23:41 -0400 Subject: [PATCH 1/5] Create weather-app.py --- weather-app.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 weather-app.py diff --git a/weather-app.py b/weather-app.py new file mode 100644 index 0000000..ed32d15 --- /dev/null +++ b/weather-app.py @@ -0,0 +1,30 @@ +import requests + +def get_temperature(city_name, api_key): + base_url = "https://api.openweathermap.org/data/2.5/weather" + params = { + 'q': f"{city_name},US", + 'appid': api_key, + 'units': 'imperial' # Fahrenheit + } + response = requests.get(base_url, params=params) + + if response.status_code == 200: + data = response.json() + temp = data['main']['temp'] + return temp + else: + return None + +def main(): + api_key = "YOUR_API_KEY_HERE" # Replace this with your OpenWeatherMap API key + city = input("Enter a major U.S. city: ").strip() + + temp = get_temperature(city, api_key) + if temp is not None: + print(f"The current temperature in {city.title()} is {temp:.1f}°F.") + else: + print("Sorry, couldn't find weather data for that city.") + +if __name__ == "__main__": + main() From b5287dbc358684c1346536ba00838de2b7b96a8f Mon Sep 17 00:00:00 2001 From: Omar G Ghafour Date: Tue, 27 May 2025 17:29:16 -0400 Subject: [PATCH 2/5] Update weather-app.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- weather-app.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/weather-app.py b/weather-app.py index ed32d15..16a9e6f 100644 --- a/weather-app.py +++ b/weather-app.py @@ -17,9 +17,17 @@ def get_temperature(city_name, api_key): return None def main(): - api_key = "YOUR_API_KEY_HERE" # Replace this with your OpenWeatherMap API key + import os + api_key = os.getenv('OPENWEATHER_API_KEY') + if not api_key: + print("Error: Please set the OPENWEATHER_API_KEY environment variable") + return + city = input("Enter a major U.S. city: ").strip() - + if not city: + print("Error: City name cannot be empty") + return + temp = get_temperature(city, api_key) if temp is not None: print(f"The current temperature in {city.title()} is {temp:.1f}°F.") From b27abc1ff4d02f3e75cf8ead1866f21a21f3b101 Mon Sep 17 00:00:00 2001 From: Omar G Ghafour Date: Tue, 27 May 2025 17:36:33 -0400 Subject: [PATCH 3/5] Update weather-app.py --- weather-app.py | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/weather-app.py b/weather-app.py index 16a9e6f..5f77bdb 100644 --- a/weather-app.py +++ b/weather-app.py @@ -1,20 +1,51 @@ import requests def get_temperature(city_name, api_key): + """ + Fetches the current temperature in Fahrenheit for a given U.S. city + using the OpenWeatherMap API. + + Parameters: + city_name (str): The name of the U.S. city. + api_key (str): Your OpenWeatherMap API key. + + Returns: + float: Current temperature in Fahrenheit if successful. + None: If an error occurs or the data cannot be retrieved. + """ + if not api_key or not isinstance(api_key, str): + print("Error: Invalid or missing API key.") + return None + base_url = "https://api.openweathermap.org/data/2.5/weather" params = { 'q': f"{city_name},US", 'appid': api_key, - 'units': 'imperial' # Fahrenheit + 'units': 'imperial' } - response = requests.get(base_url, params=params) - - if response.status_code == 200: + + try: + response = requests.get(base_url, params=params, timeout=10) + response.raise_for_status() data = response.json() - temp = data['main']['temp'] - return temp - else: - return None + + if 'main' in data and 'temp' in data['main']: + return data['main']['temp'] + else: + print("Error: Unexpected response structure.") + return None + except requests.exceptions.HTTPError as http_err: + print(f"HTTP error occurred: {http_err} - {response.text}") + except requests.exceptions.ConnectionError: + print("Error: Network connection error.") + except requests.exceptions.Timeout: + print("Error: The request timed out.") + except requests.exceptions.RequestException as err: + print(f"Error: An unexpected error occurred: {err}") + except ValueError: + print("Error: Failed to parse JSON response.") + + return None def main(): import os From 0fc090c163e77a4ca0fe70d7435305d9f2919dce Mon Sep 17 00:00:00 2001 From: Omar G Ghafour Date: Tue, 27 May 2025 17:39:19 -0400 Subject: [PATCH 4/5] Update weather-app.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- weather-app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weather-app.py b/weather-app.py index 5f77bdb..794e51f 100644 --- a/weather-app.py +++ b/weather-app.py @@ -35,7 +35,7 @@ def get_temperature(city_name, api_key): print("Error: Unexpected response structure.") return None except requests.exceptions.HTTPError as http_err: - print(f"HTTP error occurred: {http_err} - {response.text}") + print(f"HTTP error occurred: {http_err}") except requests.exceptions.ConnectionError: print("Error: Network connection error.") except requests.exceptions.Timeout: From 707ef30c2dbf8e882eeb1430c672f4c035371322 Mon Sep 17 00:00:00 2001 From: Omar G Ghafour Date: Wed, 5 Nov 2025 19:24:47 -0500 Subject: [PATCH 5/5] Implement unit tests for weather app functions Added unit tests for get_temperature and main functions. --- weather-app.py | 241 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/weather-app.py b/weather-app.py index 794e51f..48c71b5 100644 --- a/weather-app.py +++ b/weather-app.py @@ -67,3 +67,244 @@ def main(): if __name__ == "__main__": main() + + +import unittest +from unittest.mock import patch, Mock, MagicMock +import requests +from weather-app import get_temperature, main + + +class TestGetTemperature(unittest.TestCase): + """Test cases for the get_temperature function.""" + + @patch('weather-app.requests.get') + def test_successful_temperature_fetch(self, mock_get): + """Test successful temperature retrieval.""" + # Mock successful API response + mock_response = Mock() + mock_response.json.return_value = { + 'main': {'temp': 72.5} + } + mock_response.raise_for_status = Mock() + mock_get.return_value = mock_response + + result = get_temperature("New York", "valid_api_key") + + self.assertEqual(result, 72.5) + mock_get.assert_called_once_with( + "https://api.openweathermap.org/data/2.5/weather", + params={'q': 'New York,US', 'appid': 'valid_api_key', 'units': 'imperial'}, + timeout=10 + ) + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_invalid_api_key_none(self, mock_print, mock_get): + """Test with None API key.""" + result = get_temperature("Chicago", None) + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Invalid or missing API key.") + mock_get.assert_not_called() + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_invalid_api_key_empty_string(self, mock_print, mock_get): + """Test with empty string API key.""" + result = get_temperature("Chicago", "") + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Invalid or missing API key.") + mock_get.assert_not_called() + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_invalid_api_key_non_string(self, mock_print, mock_get): + """Test with non-string API key.""" + result = get_temperature("Chicago", 12345) + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Invalid or missing API key.") + mock_get.assert_not_called() + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_http_error_401_unauthorized(self, mock_print, mock_get): + """Test HTTP 401 Unauthorized error.""" + mock_response = Mock() + mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("401 Unauthorized") + mock_get.return_value = mock_response + + result = get_temperature("Boston", "invalid_key") + + self.assertIsNone(result) + mock_print.assert_called_once() + self.assertIn("HTTP error occurred", str(mock_print.call_args)) + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_http_error_404_not_found(self, mock_print, mock_get): + """Test HTTP 404 Not Found error.""" + mock_response = Mock() + mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError("404 Not Found") + mock_get.return_value = mock_response + + result = get_temperature("InvalidCity", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once() + self.assertIn("HTTP error occurred", str(mock_print.call_args)) + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_connection_error(self, mock_print, mock_get): + """Test network connection error.""" + mock_get.side_effect = requests.exceptions.ConnectionError("Network unreachable") + + result = get_temperature("Seattle", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Network connection error.") + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_timeout_error(self, mock_print, mock_get): + """Test request timeout.""" + mock_get.side_effect = requests.exceptions.Timeout("Request timed out") + + result = get_temperature("Miami", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: The request timed out.") + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_generic_request_exception(self, mock_print, mock_get): + """Test generic request exception.""" + mock_get.side_effect = requests.exceptions.RequestException("Unknown error") + + result = get_temperature("Phoenix", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once() + self.assertIn("An unexpected error occurred", str(mock_print.call_args)) + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_json_parsing_error(self, mock_print, mock_get): + """Test JSON parsing error.""" + mock_response = Mock() + mock_response.raise_for_status = Mock() + mock_response.json.side_effect = ValueError("Invalid JSON") + mock_get.return_value = mock_response + + result = get_temperature("Denver", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Failed to parse JSON response.") + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_unexpected_response_structure_missing_main(self, mock_print, mock_get): + """Test response with missing 'main' key.""" + mock_response = Mock() + mock_response.json.return_value = {'weather': [{'description': 'clear sky'}]} + mock_response.raise_for_status = Mock() + mock_get.return_value = mock_response + + result = get_temperature("Atlanta", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Unexpected response structure.") + + @patch('weather-app.requests.get') + @patch('builtins.print') + def test_unexpected_response_structure_missing_temp(self, mock_print, mock_get): + """Test response with missing 'temp' key.""" + mock_response = Mock() + mock_response.json.return_value = {'main': {'humidity': 65}} + mock_response.raise_for_status = Mock() + mock_get.return_value = mock_response + + result = get_temperature("Portland", "valid_key") + + self.assertIsNone(result) + mock_print.assert_called_once_with("Error: Unexpected response structure.") + + +class TestMain(unittest.TestCase): + """Test cases for the main function.""" + + @patch('weather-app.get_temperature') + @patch('builtins.input') + @patch('weather-app.os.getenv') + @patch('builtins.print') + def test_successful_flow(self, mock_print, mock_getenv, mock_input, mock_get_temp): + """Test successful execution with valid inputs.""" + mock_getenv.return_value = "test_api_key" + mock_input.return_value = " San Francisco " + mock_get_temp.return_value = 68.3 + + main() + + mock_getenv.assert_called_once_with('OPENWEATHER_API_KEY') + mock_input.assert_called_once_with("Enter a major U.S. city: ") + mock_get_temp.assert_called_once_with("San Francisco", "test_api_key") + mock_print.assert_called_once_with("The current temperature in San Francisco is 68.3°F.") + + @patch('weather-app.os.getenv') + @patch('builtins.print') + def test_missing_api_key_env_var(self, mock_print, mock_getenv): + """Test when OPENWEATHER_API_KEY environment variable is not set.""" + mock_getenv.return_value = None + + main() + + mock_getenv.assert_called_once_with('OPENWEATHER_API_KEY') + mock_print.assert_called_once_with("Error: Please set the OPENWEATHER_API_KEY environment variable") + + @patch('builtins.input') + @patch('weather-app.os.getenv') + @patch('builtins.print') + def test_empty_city_name(self, mock_print, mock_getenv, mock_input): + """Test when user enters empty city name.""" + mock_getenv.return_value = "test_api_key" + mock_input.return_value = " " + + main() + + mock_print.assert_called_once_with("Error: City name cannot be empty") + + @patch('weather-app.get_temperature') + @patch('builtins.input') + @patch('weather-app.os.getenv') + @patch('builtins.print') + def test_get_temperature_returns_none(self, mock_print, mock_getenv, mock_input, mock_get_temp): + """Test when get_temperature fails and returns None.""" + mock_getenv.return_value = "test_api_key" + mock_input.return_value = "InvalidCity" + mock_get_temp.return_value = None + + main() + + mock_get_temp.assert_called_once_with("InvalidCity", "test_api_key") + mock_print.assert_called_once_with("Sorry, couldn't find weather data for that city.") + + @patch('weather-app.get_temperature') + @patch('builtins.input') + @patch('weather-app.os.getenv') + @patch('builtins.print') + def test_temperature_formatting(self, mock_print, mock_getenv, mock_input, mock_get_temp): + """Test temperature output formatting with various values.""" + mock_getenv.return_value = "test_api_key" + mock_input.return_value = "los angeles" + mock_get_temp.return_value = 75.678 + + main() + + mock_print.assert_called_once_with("The current temperature in Los Angeles is 75.7°F.") + + +if __name__ == '__main__': + unittest.main()