From 17830824a509554a0c82f1994f49fbca882d456d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cantonyj=E2=80=9D?= Date: Sat, 7 Dec 2024 22:27:29 -0500 Subject: [PATCH 1/2] Add new feature: horoscope --- modules/src/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/src/__init__.py b/modules/src/__init__.py index bee0ffa9..a9419015 100644 --- a/modules/src/__init__.py +++ b/modules/src/__init__.py @@ -10,6 +10,7 @@ 'fact', 'hello', 'help', + 'horoscope', 'joke', 'lyrics', 'movie', From 5c2e956b992b759374550c660eed690fe3d30f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cantonyj=E2=80=9D?= Date: Mon, 9 Dec 2024 05:19:46 -0500 Subject: [PATCH 2/2] Add horoscope module with tests --- modules/src/horoscope.py | 50 +++++++++++ modules/tests/test_horoscope.py | 144 ++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 modules/src/horoscope.py create mode 100644 modules/tests/test_horoscope.py diff --git a/modules/src/horoscope.py b/modules/src/horoscope.py new file mode 100644 index 00000000..dc959656 --- /dev/null +++ b/modules/src/horoscope.py @@ -0,0 +1,50 @@ +import requests +import requests_cache +from templates.text import TextTemplate + +AZTRO_API_URL = "https://aztro.sameerkumar.website/" + +def process(input, entities): + output = {} + try: + # Extract zodiac sign and day from entities + sign = entities.get('sign', [{}])[0].get('value', '').lower() + day = entities.get('day', [{}])[0].get('value', 'today').lower() + + # Validate zodiac sign + valid_signs = [ + "aries", "taurus", "gemini", "cancer", "leo", "virgo", + "libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces" + ] + if sign not in valid_signs: + raise ValueError("Invalid zodiac sign") + + # Validate day + valid_days = ["today", "yesterday", "tomorrow"] + if day not in valid_days: + raise ValueError("Invalid day") + + # Make API request + with requests_cache.enabled('horoscope_cache', backend='sqlite', expire_after=86400): + response = requests.post(AZTRO_API_URL, params={"sign": sign, "day": day}) + data = response.json() + + # Extract the horoscope from the response + horoscope = data.get('description', "I couldn't fetch the horoscope right now.") + + # Create the output message + output['input'] = input + output['output'] = TextTemplate(f"Horoscope for {sign.capitalize()} ({day.capitalize()}):\n{horoscope}").get_message() + output['success'] = True + + except Exception as e: + # Error handling and fallback messages + error_message = "I couldn't fetch your horoscope." + error_message += "\nPlease try again with something like:" + error_message += "\n - horoscope for leo" + error_message += "\n - today's cancer horoscope" + error_message += "\n - tomorrow's virgo horoscope" + output['error_msg'] = TextTemplate(error_message).get_message() + output['success'] = False + + return output diff --git a/modules/tests/test_horoscope.py b/modules/tests/test_horoscope.py new file mode 100644 index 00000000..767ca903 --- /dev/null +++ b/modules/tests/test_horoscope.py @@ -0,0 +1,144 @@ +import sys +import os +import unittest + +# Add the parent directory of src to sys.path +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from unittest.mock import patch # Import patch +from modules.src.horoscope import process +class TestHoroscope(unittest.TestCase): + + def test_valid_input(self): #1 + """Test case with valid 'sign' and 'day' inputs""" + input_text = "horoscope for aries" + entities = {"sign": [{"value": "aries"}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("output", result) + self.assertTrue(result["success"]) + + def test_missing_day(self): #2 + """Test case with 'sign' provided but missing 'day'""" + input_text = "horoscope for aries" + entities = {"sign": [{"value": "aries"}]} + result = process(input_text, entities) + self.assertIn("output", result) + self.assertTrue(result["success"]) + + def test_missing_sign(self):#3 + """Test case with 'day' provided but missing 'sign'""" + input_text = "horoscope for today" + entities = {"day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + + def test_invalid_sign(self):#4 + """Test case with an invalid zodiac sign""" + input_text = "horoscope for dragon" + entities = {"sign": [{"value": "dragon"}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + + def test_invalid_day(self):#5 + """Test case with an invalid day""" + input_text = "horoscope for aries on someday" + entities = {"sign": [{"value": "aries"}], "day": [{"value": "someday"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + + + def test_all_zodiac_signs(self):#7 + """Test with all valid zodiac signs""" + zodiac_signs = [ + "aries", "taurus", "gemini", "cancer", "leo", "virgo", + "libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces" + ] + for sign in zodiac_signs: + with self.subTest(sign=sign): + + input_text = f"horoscope for {sign}" + entities = {"sign": [{"value": sign}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("output", result) + self.assertTrue(result["success"]) + def test_all_days(self):#8 + """Test with all valid day inputs""" + days = ["yesterday", "today", "tomorrow"] + for day in days: + with self.subTest(day=day): + input_text = f"horoscope for aries on {day}" + entities = {"sign": [{"value": "aries"}], "day": [{"value": day}]} + result = process(input_text, entities) + self.assertIn("output", result) + self.assertTrue(result["success"]) + def test_fuzzy_inputs(self): # Include this inside the TestHoroscope class + """Test fuzzy inputs for zodiac signs""" + fuzzy_inputs = ["aeries", "taurrus", "jimini", "leo.", "cancer@", "sagitarius"] + for fuzzy_sign in fuzzy_inputs: + with self.subTest(fuzzy_sign=fuzzy_sign): + input_text = f"horoscope for {fuzzy_sign}" + entities = {"sign": [{"value": fuzzy_sign}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + + def test_empty_entities(self): # Include this inside the TestHoroscope class + """Test behavior with empty entities""" + input_text = "horoscope" + entities = {} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + + def test_completely_invalid_input(self): + """Test with a completely invalid input""" + input_text = "random gibberish" + entities = {} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + def test_edge_case_inputs(self): # fails + """Test edge cases for input text and entities""" + edge_cases = [ + {"input_text": "", "entities": {"sign": [{"value": "aries"}], "day": [{"value": "today"}]}}, + {"input_text": "123456", "entities": {"sign": [{"value": "123456"}], "day": [{"value": "today"}]}}, + {"input_text": "!@#$%^&*", "entities": {"sign": [{"value": "!@#$%^&*"}], "day": [{"value": "today"}]}}, + ] + for case in edge_cases: + with self.subTest(input_text=case["input_text"]): + result = process(case["input_text"], case["entities"]) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + @patch("modules.src.horoscope.requests.get") + def test_api_unavailability(self, mock_get): + """Test behavior when the API is unavailable""" + mock_get.side_effect = requests.exceptions.ConnectionError + input_text = "horoscope for aries today" + entities = {"sign": [{"value": "aries"}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + @patch("modules.src.horoscope.requests.get") + def test_api_timeout(self, mock_get): + """Test behavior when the API times out""" + mock_get.side_effect = requests.exceptions.Timeout + input_text = "horoscope for leo today" + entities = {"sign": [{"value": "leo"}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + @patch("modules.src.horoscope.requests.get") + def test_malformed_api_response(self, mock_get): + """Test behavior with a malformed API response""" + mock_get.return_value.json.return_value = {"unexpected": "data"} + input_text = "horoscope for cancer today" + entities = {"sign": [{"value": "cancer"}], "day": [{"value": "today"}]} + result = process(input_text, entities) + self.assertIn("error_msg", result) + self.assertFalse(result["success"]) + +if __name__ == "__main__": + unittest.main() +