Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add thesaurus module #1125

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
'fact',
'hello',
'help',
'horoscope',
'joke',
'lyrics',
'movie',
Expand Down
50 changes: 50 additions & 0 deletions modules/src/horoscope.py
Original file line number Diff line number Diff line change
@@ -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
43 changes: 43 additions & 0 deletions modules/src/thesaurus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import requests
import config
from templates.text import TextTemplate

API_URL = "https://words.bighugelabs.com/api/2"

def process(input_text, entities):
output = {}
try:
# Extract the word from the entities
word = entities.get("word", [{}])[0].get("value", "")
if not word.strip():
raise ValueError("Input cannot be empty. Please provide a valid word for thesaurus lookup.")

# Make the API request
response = requests.get(f"{API_URL}/{config.BHT_THESAURUS_API_KEY}/{word}/json")
if response.status_code != 200:
raise Exception(f"API request failed with status code {response.status_code}")

# Parse the JSON response
try:
data = response.json()
except ValueError:
raise ValueError("Malformed API response. Please try again later.")

# Extract synonyms from the response
synonyms = []
for key in ["noun", "verb", "adjective", "adverb"]:
if key in data and "syn" in data[key]:
synonyms.extend(data[key]["syn"])

if synonyms:
output_text = f"Synonyms for '{word}':\n" + ", ".join(synonyms)
output["output"] = TextTemplate(output_text).get_message()
output["success"] = True
else:
output["error_msg"] = TextTemplate(f"Couldn't find synonyms for '{word}'.").get_message()
output["success"] = False
except Exception as e:
output["error_msg"] = TextTemplate(f"An error occurred: {str(e)}").get_message()
output["success"] = False

return output
144 changes: 144 additions & 0 deletions modules/tests/test_horoscope.py
Original file line number Diff line number Diff line change
@@ -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()

146 changes: 146 additions & 0 deletions modules/tests/test_thesaurus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import sys
import os
import unittest
import requests
# 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()