Merge pull request #440 from Br1an67/feat/issue-52-add-tests
test: add unit tests for parsing functions, logger, and utility
This commit is contained in:
@@ -1,20 +1,37 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Add project root to Python path
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Add project root to Python path
|
||||||
|
|
||||||
|
# Mock heavy dependencies
|
||||||
|
for mod_name in [
|
||||||
|
'torch', 'transformers', 'kokoro', 'adaptive_classifier', 'text2emotion',
|
||||||
|
'ollama', 'openai', 'together', 'IPython', 'IPython.display',
|
||||||
|
'playsound3', 'soundfile', 'pyaudio', 'librosa',
|
||||||
|
'pypdf', 'langid', 'pypinyin', 'fake_useragent',
|
||||||
|
'chromedriver_autoinstaller', 'num2words', 'sentencepiece', 'sacremoses',
|
||||||
|
'scipy', 'numpy', 'selenium_stealth', 'undetected_chromedriver',
|
||||||
|
'markdownify',
|
||||||
|
]:
|
||||||
|
if mod_name not in sys.modules:
|
||||||
|
sys.modules[mod_name] = MagicMock()
|
||||||
|
|
||||||
|
os.environ.setdefault('WORK_DIR', '/tmp')
|
||||||
|
|
||||||
from sources.agents.browser_agent import BrowserAgent
|
from sources.agents.browser_agent import BrowserAgent
|
||||||
|
|
||||||
class TestBrowserAgentParsing(unittest.TestCase):
|
class TestBrowserAgentParsing(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Initialize a basic BrowserAgent instance for testing
|
self.agent = BrowserAgent.__new__(BrowserAgent)
|
||||||
self.agent = BrowserAgent(
|
self.agent.notes = []
|
||||||
name="TestAgent",
|
self.agent.navigable_links = []
|
||||||
prompt_path="../prompts/base/browser_agent.txt",
|
self.agent.search_history = []
|
||||||
provider=None
|
self.agent.current_page = ""
|
||||||
)
|
self.agent.logger = MagicMock()
|
||||||
|
|
||||||
def test_extract_links(self):
|
def test_extract_links(self):
|
||||||
# Test various link formats
|
|
||||||
test_text = """
|
test_text = """
|
||||||
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
|
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
|
||||||
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
|
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
|
||||||
@@ -28,8 +45,17 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
|||||||
result = self.agent.extract_links(test_text)
|
result = self.agent.extract_links(test_text)
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_extract_links_no_links(self):
|
||||||
|
"""Test that text without links returns empty list."""
|
||||||
|
result = self.agent.extract_links("No links here at all.")
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
def test_extract_links_single_link(self):
|
||||||
|
"""Test extraction of a single link."""
|
||||||
|
result = self.agent.extract_links("Visit https://example.com for details")
|
||||||
|
self.assertEqual(result, ["https://example.com"])
|
||||||
|
|
||||||
def test_extract_form(self):
|
def test_extract_form(self):
|
||||||
# Test form extraction
|
|
||||||
test_text = """
|
test_text = """
|
||||||
Fill this: [username](john) and [password](secret123)
|
Fill this: [username](john) and [password](secret123)
|
||||||
Not a form: [random]text
|
Not a form: [random]text
|
||||||
@@ -38,8 +64,18 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
|||||||
result = self.agent.extract_form(test_text)
|
result = self.agent.extract_form(test_text)
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_extract_form_empty(self):
|
||||||
|
"""Test form extraction with no form inputs."""
|
||||||
|
result = self.agent.extract_form("Just regular text here.")
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
def test_extract_form_checkbox(self):
|
||||||
|
"""Test form extraction with checkbox values."""
|
||||||
|
text = "[agree](checked) and [newsletter](unchecked)"
|
||||||
|
result = self.agent.extract_form(text)
|
||||||
|
self.assertEqual(len(result), 2)
|
||||||
|
|
||||||
def test_clean_links(self):
|
def test_clean_links(self):
|
||||||
# Test link cleaning
|
|
||||||
test_links = [
|
test_links = [
|
||||||
"https://example.com.",
|
"https://example.com.",
|
||||||
"www.test.com,",
|
"www.test.com,",
|
||||||
@@ -55,8 +91,13 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
|||||||
result = self.agent.clean_links(test_links)
|
result = self.agent.clean_links(test_links)
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_clean_links_with_slash(self):
|
||||||
|
"""Test that trailing slash is stripped since it's not alphanumeric."""
|
||||||
|
links = ["https://example.com/path/"]
|
||||||
|
result = self.agent.clean_links(links)
|
||||||
|
self.assertEqual(result, ["https://example.com/path"])
|
||||||
|
|
||||||
def test_parse_answer(self):
|
def test_parse_answer(self):
|
||||||
# Test parsing answer with notes and links
|
|
||||||
test_text = """
|
test_text = """
|
||||||
Here's some info
|
Here's some info
|
||||||
Note: This is important. We are doing test it's very cool.
|
Note: This is important. We are doing test it's very cool.
|
||||||
@@ -66,5 +107,102 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
|||||||
self.agent.parse_answer(test_text)
|
self.agent.parse_answer(test_text)
|
||||||
self.assertEqual(self.agent.notes[0], "Note: This is important. We are doing test it's very cool.")
|
self.assertEqual(self.agent.notes[0], "Note: This is important. We are doing test it's very cool.")
|
||||||
|
|
||||||
|
def test_parse_answer_extracts_links(self):
|
||||||
|
"""Test that parse_answer returns extracted links."""
|
||||||
|
text = "Navigate to https://example.com and https://test.org"
|
||||||
|
links = self.agent.parse_answer(text)
|
||||||
|
self.assertIn("https://example.com", links)
|
||||||
|
self.assertIn("https://test.org", links)
|
||||||
|
|
||||||
|
def test_parse_answer_no_notes(self):
|
||||||
|
"""Test parse_answer with no notes section."""
|
||||||
|
text = "Go to https://example.com"
|
||||||
|
self.agent.parse_answer(text)
|
||||||
|
# Notes should have an empty entry
|
||||||
|
self.assertEqual(len(self.agent.notes), 1)
|
||||||
|
|
||||||
|
def test_select_link_unvisited(self):
|
||||||
|
"""Test selecting first unvisited link."""
|
||||||
|
self.agent.search_history = ["https://visited.com"]
|
||||||
|
self.agent.current_page = "https://current.com"
|
||||||
|
links = ["https://visited.com", "https://current.com", "https://new.com"]
|
||||||
|
result = self.agent.select_link(links)
|
||||||
|
self.assertEqual(result, "https://new.com")
|
||||||
|
|
||||||
|
def test_select_link_all_visited(self):
|
||||||
|
"""Test that None is returned when all links are visited."""
|
||||||
|
self.agent.search_history = ["https://a.com", "https://b.com"]
|
||||||
|
self.agent.current_page = ""
|
||||||
|
links = ["https://a.com", "https://b.com"]
|
||||||
|
result = self.agent.select_link(links)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_select_link_empty(self):
|
||||||
|
"""Test with empty links list."""
|
||||||
|
result = self.agent.select_link([])
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_jsonify_search_results(self):
|
||||||
|
"""Test parsing search result text into structured data."""
|
||||||
|
text = """Title: Result One
|
||||||
|
Snippet: First result snippet
|
||||||
|
Link: https://one.com
|
||||||
|
|
||||||
|
Title: Result Two
|
||||||
|
Snippet: Second result snippet
|
||||||
|
Link: https://two.com"""
|
||||||
|
results = self.agent.jsonify_search_results(text)
|
||||||
|
self.assertEqual(len(results), 2)
|
||||||
|
self.assertEqual(results[0]["title"], "Result One")
|
||||||
|
self.assertEqual(results[0]["link"], "https://one.com")
|
||||||
|
self.assertEqual(results[1]["snippet"], "Second result snippet")
|
||||||
|
|
||||||
|
def test_jsonify_search_results_empty(self):
|
||||||
|
"""Test with empty search results."""
|
||||||
|
results = self.agent.jsonify_search_results("")
|
||||||
|
self.assertEqual(results, [])
|
||||||
|
|
||||||
|
def test_jsonify_search_results_partial(self):
|
||||||
|
"""Test with partial result (only title and link)."""
|
||||||
|
text = """Title: Partial Result
|
||||||
|
Link: https://partial.com"""
|
||||||
|
results = self.agent.jsonify_search_results(text)
|
||||||
|
self.assertEqual(len(results), 1)
|
||||||
|
self.assertEqual(results[0]["title"], "Partial Result")
|
||||||
|
self.assertNotIn("snippet", results[0])
|
||||||
|
|
||||||
|
def test_stringify_search_results(self):
|
||||||
|
"""Test converting structured results back to string."""
|
||||||
|
results = [
|
||||||
|
{"link": "https://one.com", "snippet": "First snippet"},
|
||||||
|
{"link": "https://two.com", "snippet": "Second snippet"}
|
||||||
|
]
|
||||||
|
output = self.agent.stringify_search_results(results)
|
||||||
|
self.assertIn("https://one.com", output)
|
||||||
|
self.assertIn("First snippet", output)
|
||||||
|
self.assertIn("https://two.com", output)
|
||||||
|
|
||||||
|
def test_select_unvisited(self):
|
||||||
|
"""Test filtering visited results."""
|
||||||
|
self.agent.search_history = ["https://visited.com"]
|
||||||
|
results = [
|
||||||
|
{"link": "https://visited.com", "title": "Old"},
|
||||||
|
{"link": "https://new.com", "title": "New"}
|
||||||
|
]
|
||||||
|
unvisited = self.agent.select_unvisited(results)
|
||||||
|
self.assertEqual(len(unvisited), 1)
|
||||||
|
self.assertEqual(unvisited[0]["link"], "https://new.com")
|
||||||
|
|
||||||
|
def test_select_unvisited_all_new(self):
|
||||||
|
"""Test when no results are visited."""
|
||||||
|
self.agent.search_history = []
|
||||||
|
results = [
|
||||||
|
{"link": "https://a.com", "title": "A"},
|
||||||
|
{"link": "https://b.com", "title": "B"}
|
||||||
|
]
|
||||||
|
unvisited = self.agent.select_unvisited(results)
|
||||||
|
self.assertEqual(len(unvisited), 2)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import logging
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
from sources.logger import Logger
|
||||||
|
|
||||||
|
|
||||||
|
class TestLogger(unittest.TestCase):
|
||||||
|
"""Test suite for the Logger class."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.logger = Logger("test_logger.log")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if os.path.exists('.logs'):
|
||||||
|
for handler in self.logger.logger.handlers[:]:
|
||||||
|
handler.close()
|
||||||
|
self.logger.logger.removeHandler(handler)
|
||||||
|
log_path = os.path.join('.logs', 'test_logger.log')
|
||||||
|
if os.path.exists(log_path):
|
||||||
|
os.remove(log_path)
|
||||||
|
|
||||||
|
def test_initialization(self):
|
||||||
|
"""Test logger initializes correctly."""
|
||||||
|
self.assertTrue(self.logger.enabled)
|
||||||
|
self.assertIsNotNone(self.logger.logger)
|
||||||
|
self.assertTrue(os.path.exists('.logs'))
|
||||||
|
|
||||||
|
def test_log_creates_file(self):
|
||||||
|
"""Test that logging creates a log file."""
|
||||||
|
self.logger.info("test message")
|
||||||
|
self.assertTrue(os.path.exists(self.logger.log_path))
|
||||||
|
|
||||||
|
def test_log_writes_message(self):
|
||||||
|
"""Test that log messages are written to file."""
|
||||||
|
self.logger.info("hello world")
|
||||||
|
with open(self.logger.log_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
self.assertIn("hello world", content)
|
||||||
|
|
||||||
|
def test_log_deduplication(self):
|
||||||
|
"""Test that consecutive identical messages are not duplicated."""
|
||||||
|
self.logger.info("duplicate message")
|
||||||
|
self.logger.info("duplicate message")
|
||||||
|
with open(self.logger.log_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
self.assertEqual(content.count("duplicate message"), 1)
|
||||||
|
|
||||||
|
def test_log_different_messages(self):
|
||||||
|
"""Test that different messages are all written."""
|
||||||
|
self.logger.info("message one")
|
||||||
|
self.logger.info("message two")
|
||||||
|
with open(self.logger.log_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
self.assertIn("message one", content)
|
||||||
|
self.assertIn("message two", content)
|
||||||
|
|
||||||
|
def test_error_level(self):
|
||||||
|
"""Test error level logging."""
|
||||||
|
self.logger.error("error occurred")
|
||||||
|
with open(self.logger.log_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
self.assertIn("ERROR", content)
|
||||||
|
self.assertIn("error occurred", content)
|
||||||
|
|
||||||
|
def test_warning_level(self):
|
||||||
|
"""Test warning level logging."""
|
||||||
|
self.logger.warning("warning issued")
|
||||||
|
with open(self.logger.log_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
self.assertIn("WARNING", content)
|
||||||
|
self.assertIn("warning issued", content)
|
||||||
|
|
||||||
|
def test_create_folder(self):
|
||||||
|
"""Test folder creation."""
|
||||||
|
test_path = ".test_log_folder"
|
||||||
|
result = self.logger.create_folder(test_path)
|
||||||
|
self.assertTrue(result)
|
||||||
|
self.assertTrue(os.path.exists(test_path))
|
||||||
|
os.rmdir(test_path)
|
||||||
|
|
||||||
|
def test_create_folder_already_exists(self):
|
||||||
|
"""Test folder creation when folder already exists."""
|
||||||
|
result = self.logger.create_folder('.logs')
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import unittest
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
from sources.utility import get_color_map
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtility(unittest.TestCase):
|
||||||
|
"""Test suite for utility module functions."""
|
||||||
|
|
||||||
|
def test_get_color_map_returns_dict(self):
|
||||||
|
"""Test that get_color_map returns a dictionary."""
|
||||||
|
color_map = get_color_map()
|
||||||
|
self.assertIsInstance(color_map, dict)
|
||||||
|
|
||||||
|
def test_get_color_map_has_required_keys(self):
|
||||||
|
"""Test that color map contains all required color keys."""
|
||||||
|
color_map = get_color_map()
|
||||||
|
required_keys = ["success", "failure", "status", "code", "warning", "output", "info"]
|
||||||
|
for key in required_keys:
|
||||||
|
self.assertIn(key, color_map, f"Missing key: {key}")
|
||||||
|
|
||||||
|
def test_get_color_map_values_are_strings(self):
|
||||||
|
"""Test that all color values are strings."""
|
||||||
|
color_map = get_color_map()
|
||||||
|
for key, value in color_map.items():
|
||||||
|
self.assertIsInstance(value, str, f"Value for '{key}' should be a string")
|
||||||
|
|
||||||
|
def test_success_is_green(self):
|
||||||
|
"""Test that success maps to green."""
|
||||||
|
color_map = get_color_map()
|
||||||
|
self.assertEqual(color_map["success"], "green")
|
||||||
|
|
||||||
|
def test_failure_is_red(self):
|
||||||
|
"""Test that failure maps to red."""
|
||||||
|
color_map = get_color_map()
|
||||||
|
self.assertEqual(color_map["failure"], "red")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user