From 4d8d33967e572caf28599799b487c249ffac200a Mon Sep 17 00:00:00 2001 From: David Bures <12524436+PiDiBi@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:27:25 -0700 Subject: [PATCH] adding tests --- .vscode/settings.json | 11 +++ locationdata.py | 182 ++++++++++++++++++++----------------- locationdata_test.py | 44 +++++++++ mesh-bot.service | 8 +- mesh-bot.py => mesh_bot.py | 0 pong-bot.py => pong_bot.py | 0 requirements.txt | 1 + 7 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 locationdata_test.py rename mesh-bot.py => mesh_bot.py (100%) mode change 100755 => 100644 rename pong-bot.py => pong_bot.py (100%) mode change 100755 => 100644 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bcbf671 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + ".", + "-p", + "*test.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true +} \ No newline at end of file diff --git a/locationdata.py b/locationdata.py index 7919c71..cde633b 100644 --- a/locationdata.py +++ b/locationdata.py @@ -1,6 +1,7 @@ # helper functions to use location data # K7MHI Kelly Keeton 2024 +import json from geopy.geocoders import Nominatim # pip install geopy import maidenhead as mh # pip install maidenhead import requests # pip install requests @@ -8,11 +9,14 @@ import bs4 as bs # pip install beautifulsoup4 URL_TIMEOUT = 10 # wait time for URL requests DAYS_OF_WEATHER = 4 # weather forecast days, the first two rows are today and tonight +# unified error messages to be able to test them from tests +NO_DATA_NOGPS = "no location data: does your device have GPS?" +ERROR_FETCHING_DATA = "error fetching data" def where_am_i(lat=0, lon=0): whereIam = "" if float(lat) == 0 and float(lon) == 0: - return "no location data: does your device have GPS?" + return NO_DATA_NOGPS # initialize Nominatim API geolocator = Nominatim(user_agent="mesh-bot") @@ -28,100 +32,108 @@ def where_am_i(lat=0, lon=0): def get_tide(lat=0, lon=0): station_id = "" if float(lat) == 0 and float(lon) == 0: - return "no location data: does your device have GPS?" + return NO_DATA_NOGPS station_lookup_url = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/tidepredstations.json?lat=" + str(lat) + "&lon=" + str(lon) + "&radius=50" - station_data = requests.get(station_lookup_url, timeout=URL_TIMEOUT) - if(station_data.ok): - station_json = station_data.json() - # get first station id in 50 mile radius - station_id = station_json['stationList'][0]['stationId'] - else: - return "error fetching station data" + try: + station_data = requests.get(station_lookup_url, timeout=URL_TIMEOUT) + if station_data.ok: + station_json = station_data.json() + else: + return ERROR_FETCHING_DATA + except (requests.exceptions.RequestException, json.JSONDecodeError): + return ERROR_FETCHING_DATA + + station_id = station_json['stationList'][0]['stationId'] station_url = "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=" + station_id - station_data = requests.get(station_url, timeout=URL_TIMEOUT) - if(station_data.ok): - # extract table class="table table-condensed" - soup = bs.BeautifulSoup(station_data.text, 'html.parser') - table = soup.find('table', class_='table table-condensed') - # extract rows - rows = table.find_all('tr') - # extract data from rows - tide_data = [] - for row in rows: - row_text = "" - cols = row.find_all('td') - for col in cols: - row_text += col.text + " " - tide_data.append(row_text) - # format tide data into a string - tide_string = "" - for data in tide_data: - tide_string += data + "\n" - # trim off last newline - tide_string = tide_string[:-1] - return tide_string - - else: - return "error fetching tide data" + try: + station_data = requests.get(station_url, timeout=URL_TIMEOUT) + if not station_data.ok: + return ERROR_FETCHING_DATA + except (requests.exceptions.RequestException): + return ERROR_FETCHING_DATA + + # extract table class="table table-condensed" + soup = bs.BeautifulSoup(station_data.text, 'html.parser') + table = soup.find('table', class_='table table-condensed') + + # extract rows + rows = table.find_all('tr') + # extract data from rows + tide_data = [] + for row in rows: + row_text = "" + cols = row.find_all('td') + for col in cols: + row_text += col.text + " " + tide_data.append(row_text) + # format tide data into a string + tide_string = "" + for data in tide_data: + tide_string += data + "\n" + # trim off last newline + tide_string = tide_string[:-1] + return tide_string def get_weather(lat=0, lon=0): weather = "" if float(lat) == 0 and float(lon) == 0: - return "no location data: does your device have GPS?" + return NO_DATA_NOGPS weather_url = "https://forecast.weather.gov/MapClick.php?FcstType=text&lat=" + str(lat) + "&lon=" + str(lon) - weather_data = requests.get(weather_url, timeout=URL_TIMEOUT) - if(weather_data.ok): - soup = bs.BeautifulSoup(weather_data.text, 'html.parser') - table = soup.find('div', id="detailed-forecast-body") - - if table is None: - return "no weather data found on NOAA for your location" - else: - # get rows - rows = table.find_all('div', class_="row") - - # extract data from rows - for row in rows: - # shrink the text - line = row.text.replace("Monday", "Mon ") \ - .replace("Tuesday", "Tue ") \ - .replace("Wednesday", "Wed ") \ - .replace("Thursday", "Thu ") \ - .replace("Friday", "Fri ") \ - .replace("Saturday", "Sat ") \ - .replace("Today", "Today ") \ - .replace("Tonight", "Tonight ") \ - .replace("Tomorrow", "Tomorrow ") \ - .replace("This Afternoon", "Afternoon ") \ - .replace("northwest", "NW") \ - .replace("northeast", "NE") \ - .replace("southwest", "SW") \ - .replace("southeast", "SE") \ - .replace("north", "N") \ - .replace("south", "S") \ - .replace("east", "E") \ - .replace("west", "W") \ - .replace("Northwest", "NW") \ - .replace("Northeast", "NE") \ - .replace("Southwest", "SW") \ - .replace("Southeast", "SE") \ - .replace("North", "N") \ - .replace("South", "S") \ - .replace("East", "E") \ - .replace("West", "W") \ - .replace("precipitation", "precip") \ - .replace("showers", "shwrs") \ - .replace("thunderstorms", "t-storms") - # only grab a few days of weather - if len(weather.split("\n")) < DAYS_OF_WEATHER: - weather += line + "\n" - # trim off last newline - weather = weather[:-1] + try: + weather_data = requests.get(weather_url, timeout=TIMEOUT_DURATION) + if not weather_data.ok: + return ERROR_FETCHING_DATA + except (requests.exceptions.RequestException): + return ERROR_FETCHING_DATA - return weather + soup = bs.BeautifulSoup(weather_data.text, 'html.parser') + table = soup.find('div', id="detailed-forecast-body") + + if table is None: + return "no weather data found on NOAA for your location" else: - return "error fetching weather data" + # get rows + rows = table.find_all('div', class_="row") + # extract data from rows + for row in rows: + # shrink the text + line = row.text.replace("Monday", "Mon ") \ + .replace("Tuesday", "Tue ") \ + .replace("Wednesday", "Wed ") \ + .replace("Thursday", "Thu ") \ + .replace("Friday", "Fri ") \ + .replace("Saturday", "Sat ") \ + .replace("Today", "Today ") \ + .replace("Tonight", "Tonight ") \ + .replace("Tomorrow", "Tomorrow ") \ + .replace("This Afternoon", "Afternoon ") \ + .replace("northwest", "NW") \ + .replace("northeast", "NE") \ + .replace("southwest", "SW") \ + .replace("southeast", "SE") \ + .replace("north", "N") \ + .replace("south", "S") \ + .replace("east", "E") \ + .replace("west", "W") \ + .replace("Northwest", "NW") \ + .replace("Northeast", "NE") \ + .replace("Southwest", "SW") \ + .replace("Southeast", "SE") \ + .replace("North", "N") \ + .replace("South", "S") \ + .replace("East", "E") \ + .replace("West", "W") \ + .replace("precipitation", "precip") \ + .replace("showers", "shwrs") \ + .replace("thunderstorms", "t-storms") + # only grab a few days of weather + if len(weather.split("\n")) < DAYS_OF_WEATHER: + weather += line + "\n" + # trim off last newline + weather = weather[:-1] + + return weather diff --git a/locationdata_test.py b/locationdata_test.py new file mode 100644 index 0000000..6cfa9f7 --- /dev/null +++ b/locationdata_test.py @@ -0,0 +1,44 @@ +import unittest +from locationdata import * + +class TestGetWeather(unittest.TestCase): + + def test_get_weather_with_valid_coordinates(self): + # Test with valid coordinates + lat = "37.7749" + lon = "-122.4194" + weather = get_weather(lat, lon) + print(f"weather: {weather}") + self.assertNotEqual(weather, NO_DATA_NOGPS) + self.assertNotEqual(weather, ERROR_FETCHING_DATA) + + def test_get_weather_with_invalid_coordinates(self): + # Test with invalid coordinates + lat = 0 + lon = 0 + weather = get_weather(lat, lon) + print(f"weather: {weather}") + self.assertEqual(weather, NO_DATA_NOGPS) + + def test_where_am_i_with_valid_coordinates(self): + # Test with invalid coordinates + lat = "37.7749" + lon = "-122.4194" + location = where_am_i(lat, lon) + print(f"location: {location}") + self.assertEqual(location, "South Van Ness Avenue San Francisco California 94103 United States Grid: CM87ss") + self.assertNotEqual(location, NO_DATA_NOGPS) + self.assertNotEqual(location, ERROR_FETCHING_DATA) + + def test_get_tide_with_valid_coordinates(self): + # Test with valid coordinates + lat = "37.7749" + lon = "-122.4194" + tide = get_tide(lat, lon) + print(f"tide: {tide}") + self.assertNotEqual(tide, NO_DATA_NOGPS) + self.assertNotEqual(tide, ERROR_FETCHING_DATA) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/mesh-bot.service b/mesh-bot.service index 6586de6..1c3229f 100644 --- a/mesh-bot.service +++ b/mesh-bot.service @@ -1,16 +1,20 @@ +# /etc/systemd/system/meshbot.service +# sudo systemctl daemon-reload +# sudo systemctl start meshbot.service [Unit] Description=MESH-BOT +After=network.target [Service] -ExecStart=/usr/bin/python /usr/local/meshing-around/mesh-bot.py +ExecStart=/usr/bin/python /usr/local/meshing-around/mesh_bot.py # Disable Python's buffering of STDOUT and STDERR, so that output from the # service shows up immediately in systemd's logs Environment=PYTHONUNBUFFERED=1 Restart=on-failure -Type=notify +Type=notify #try simple if any problems [Install] WantedBy=default.target diff --git a/mesh-bot.py b/mesh_bot.py old mode 100755 new mode 100644 similarity index 100% rename from mesh-bot.py rename to mesh_bot.py diff --git a/pong-bot.py b/pong_bot.py old mode 100755 new mode 100644 similarity index 100% rename from pong-bot.py rename to pong_bot.py diff --git a/requirements.txt b/requirements.txt index 370a21d..52fff42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ geopy maidenhead beautifulsoup4 dadjokes +mock \ No newline at end of file