Skip to content

Commit

Permalink
completed unit tests for fetch_weather_api()
Browse files Browse the repository at this point in the history
  • Loading branch information
Morgan-Sell committed Oct 26, 2024
1 parent 5e589a7 commit 87f774c
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 70 deletions.
26 changes: 15 additions & 11 deletions src/api.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
import requests


import json

from src.config import VISUAL_CROSSING_API_URL
import requests

from src.config import VISUAL_CROSSING_API_URL

# Location is the address, partial address or latitude,longitude location for which to retrieve weather data. You can also use US ZIP Codes


def fetch_weather_api(endpoint: str, location: str, unit: str, api_key: str) -> dict:

base_url = f"{VISUAL_CROSSING_API_URL}/{endpoint}"

params = {
"location": location,
"aggregateHours": 24,
"unitGroup": unit,
"contentType": "json",
"key": api_key
"key": api_key,
}

try:
response = requests.get(base_url, params)
response.raise_for_status() # raises an error for 4xx/5xx responses
response.raise_for_status() # raises an error for 4xx/5xx responses
except requests.exceptions.ConnectionError:
raise ConnectionError("Failed to connect to the API. Check your network connection.")
raise ConnectionError(
"Failed to connect to the API. Check your network connection."
)
except requests.exceptions.Timeout:
raise TimeoutError("The request timed out. Try again later.")
except requests.exceptions.HTTPError as http_err:
if response.status_code == 400:
raise ValueError("Invalid parameters were provided to the API.") from http_err
raise ValueError(
"Invalid parameters were provided to the API."
) from http_err
elif response.status_code == 401:
raise PermissionError("Invalid API key.") from http_err
else:
raise RuntimeError(f"HTTP error occured: {response.status_code}") from http_err
raise RuntimeError(
f"HTTP error occured: {response.status_code}"
) from http_err
except requests.exceptions.RequestException as e:
raise RuntimeError("An unexpected error occurred with the request.") from e

return response.json()
5 changes: 1 addition & 4 deletions src/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@



VISUAL_CROSSING_API_URL = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/weatherdata/"
VISUAL_CROSSING_API_URL = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/weatherdata/"
11 changes: 2 additions & 9 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@

import os
from pprint import pprint

from dotenv import load_dotenv

from src.api import fetch_weather_api
from src.config import VISUAL_CROSSING_API_URL



load_dotenv()

api_key = os.getenv("VISUAL_CROSSING_API_KEY")
Expand All @@ -18,10 +16,5 @@ def main():
pprint(fetch_weather_api("forecast", "Santa Monica", "us", api_key))







if __name__ == "__main__":
main()
main()
81 changes: 47 additions & 34 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,64 @@
import pytest




@pytest.fixture(scope="function")
def weather_api_data():
"""Fixture that returns simplified mock data for weather API response."""
return {
'columns': {
'address': {'id': 'address', 'name': 'Address', 'type': 1, 'unit': None},
'temp': {'id': 'temp', 'name': 'Temperature', 'type': 2, 'unit': 'degf'},
'humidity': {'id': 'humidity', 'name': 'Relative Humidity', 'type': 2, 'unit': None},
'conditions': {'id': 'conditions', 'name': 'Conditions', 'type': 1, 'unit': None},
'datetime': {'id': 'datetime', 'name': 'Date time', 'type': 3, 'unit': None}
"columns": {
"address": {"id": "address", "name": "Address", "type": 1, "unit": None},
"temp": {"id": "temp", "name": "Temperature", "type": 2, "unit": "degf"},
"humidity": {
"id": "humidity",
"name": "Relative Humidity",
"type": 2,
"unit": None,
},
"conditions": {
"id": "conditions",
"name": "Conditions",
"type": 1,
"unit": None,
},
"datetime": {
"id": "datetime",
"name": "Date time",
"type": 3,
"unit": None,
},
},
'locations': {
'Santa Monica': {
'address': 'Santa Monica, CA, United States',
'currentConditions': {
'temp': 61.8,
'humidity': 82.3,
'conditions': 'Clear',
'datetime': '2024-10-26T10:35:00-07:00',
"locations": {
"Santa Monica": {
"address": "Santa Monica, CA, United States",
"currentConditions": {
"temp": 61.8,
"humidity": 82.3,
"conditions": "Clear",
"datetime": "2024-10-26T10:35:00-07:00",
},
'values': [
"values": [
{
'temp': 64.2,
'humidity': 89.5,
'conditions': 'Overcast',
'datetimeStr': '2024-10-26T00:00:00-07:00'
"temp": 64.2,
"humidity": 89.5,
"conditions": "Overcast",
"datetimeStr": "2024-10-26T00:00:00-07:00",
},
{
'temp': 73.2,
'humidity': 67.3,
'conditions': 'Sunny',
'datetimeStr': '2024-10-27T00:00:00-07:00'
"temp": 73.2,
"humidity": 67.3,
"conditions": "Sunny",
"datetimeStr": "2024-10-27T00:00:00-07:00",
},
{
'temp': 69.5,
'humidity': 78.8,
'conditions': 'Windy',
'datetimeStr': '2024-10-28T00:00:00-07:00'
"temp": 69.5,
"humidity": 78.8,
"conditions": "Windy",
"datetimeStr": "2024-10-28T00:00:00-07:00",
},
]
],
}
},
'messages': None,
'queryCost': 1,
'remainingCost': 0
}
"messages": None,
"queryCost": 1,
"remainingCost": 0,
}
123 changes: 111 additions & 12 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import json
import urllib
from pprint import pprint
from unittest.mock import MagicMock, patch

import pytest
import requests
from src.api import fetch_weather_api
import urllib

from src.api import fetch_weather_api
from src.config import VISUAL_CROSSING_API_URL

import pytest

@patch("urllib.request.urlopen")
@patch("requests.get")
def test_fetch_weather_api_success(mock_urlopen, weather_api_data):
# Arrange: mock a successful response
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(weather_api_data).encode("utf-8")
mock_response.status_code = 200
mock_response.json.return_value = weather_api_data
mock_urlopen.return_value = mock_response

endpoint = "forecast"
Expand All @@ -24,17 +26,21 @@ def test_fetch_weather_api_success(mock_urlopen, weather_api_data):
# Act
data = fetch_weather_api(endpoint, location, unit, api_key)


# Assert
assert isinstance(data, dict)
assert data["location"] == location
assert len(data["location"]["values"]) == 3
assert location in data["locations"]
assert len(data["locations"][location]["values"]) == 3

assert data["locations"][location]["currentConditions"]["temp"] == 61.8
assert data["locations"][location]["values"][0]["conditions"] == "Overcast"


@patch("urllib.request.urlopen")
def test_fetch_weather_api_connection_error(mock_urlopen):
@patch("requests.get")
def test_fetch_weather_api_connection_error(mock_get):
# Arrange: mock a failed response
mock_urlopen.side_effect = ConnectionError("Failed to connect to the API. Check your network connection.")
mock_get.side_effect = ConnectionError(
"Failed to connect to the API. Check your network connection."
)

endpoint = "forecast"
location = "Buenos Aires"
Expand All @@ -43,4 +49,97 @@ def test_fetch_weather_api_connection_error(mock_urlopen):

# Action & Assert
with pytest.raises(ConnectionError):
fetch_weather_api(endpoint, location, unit, api_key)
fetch_weather_api(endpoint, location, unit, api_key)


@patch("requests.get")
def test_fetch_weather_api_timeout_error(mock_get):
# Arrange
mock_get.side_effect = TimeoutError("The request timed out. Try again later.")
endpoint = "forecast"
location = "Buenos Aires"
unit = "metric"
api_key = "secret"

# Action & Assert
with pytest.raises(TimeoutError):
fetch_weather_api(endpoint, location, unit, api_key)


@patch("requests.get")
def test_fetch_weather_api_400_error(mock_get):
# Arrange
mock_response = MagicMock()
mock_response.status_code = 400
mock_response.raise_for_status.side_effect = requests.HTTPError(
"400 Client Error: Bad Request for url"
)
mock_get.return_value = mock_response

endpoint = "forecast"
location = "Invalid Location"
unit = "metric"
api_key = "secret"

# Action & Assert
with pytest.raises(
ValueError, match="Invalid parameters were provided to the API."
):
fetch_weather_api(endpoint, location, unit, api_key)


@patch("requests.get")
def test_fetch_weather_api_401_error(mock_get):
# Arrange
mock_response = MagicMock()
mock_response.status_code = 401
mock_response.raise_for_status.side_effect = requests.HTTPError(
"401 Client Error: Invalid API key."
)
mock_get.return_value = mock_response

endpoint = "forecast"
location = "Capetown"
unit = "metric"
api_key = "invalid_key"

# Action & Assert
with pytest.raises(PermissionError, match="Invalid API key."):
fetch_weather_api(endpoint, location, unit, api_key)


@patch("requests.get")
def test_fetch_weather_api_runtime_error(mock_get):
# Arrange
mock_response = MagicMock()
mock_response.status_code = 403
mock_response.raise_for_status.side_effect = requests.HTTPError(
"403 Client Error: Forbidden for url"
)
mock_get.return_value = mock_response

endpoint = "forecast"
location = "Capetown"
unit = "metric"
api_key = "secret"

# Act & Assert
with pytest.raises(RuntimeError, match="HTTP error occured: 403"):
fetch_weather_api(endpoint, location, unit, api_key)


@patch("requests.get")
def test_fetch_weather_api_unexpected_request_error(mock_get):
# Arrange
mock_get.side_effect = requests.RequestException("A general request error occured.")

endpoint = "forecast"
location = "Capetown"
unit = "metric"
api_key = "secret"

# Act & Assert
with pytest.raises(
RuntimeError, match="An unexpected error occurred with the request."
):
fetch_weather_api(endpoint, location, unit, api_key)

0 comments on commit 87f774c

Please sign in to comment.