mirror of
https://github.com/JamesTheGiblet/BuddAI.git
synced 2026-01-08 21:58:40 +00:00
Add comprehensive unit tests for BuddAI functionality
- Introduced 16 additional coverage tests in `test_additional_coverage.py` to enhance overall test coverage. - Added 15 extended feature tests in `test_extended_features.py` to validate new functionalities. - Implemented 27 final coverage tests in `test_final_coverage.py` to achieve a total of 100 tests. - Created 2 fallback logic tests in `test_fallback_logic.py` to ensure proper fallback behavior based on confidence scores. - Each test suite covers various aspects of the BuddAI system, including command handling, database interactions, and hardware detection.
This commit is contained in:
parent
f9fd27d228
commit
27601aa2ba
34 changed files with 5022 additions and 2921 deletions
254
tests/test_additional_coverage.py
Normal file
254
tests/test_additional_coverage.py
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Additional Coverage Tests for BuddAI
|
||||
Adds 16 tests to improve overall system coverage.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import sqlite3
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, mock_open
|
||||
import importlib.util
|
||||
|
||||
# Dynamic import setup
|
||||
REPO_ROOT = Path(__file__).parent.parent
|
||||
if str(REPO_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
MODULE_PATH = REPO_ROOT / "buddai_executive.py"
|
||||
spec = importlib.util.spec_from_file_location("buddai_executive", MODULE_PATH)
|
||||
buddai_module = importlib.util.module_from_spec(spec)
|
||||
sys.modules["buddai_executive"] = buddai_module
|
||||
spec.loader.exec_module(buddai_module)
|
||||
BuddAI = buddai_module.BuddAI
|
||||
|
||||
class TestAdditionalCoverage(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Create temp DB
|
||||
self.db_fd, self.db_path = tempfile.mkstemp(suffix=".db")
|
||||
os.close(self.db_fd)
|
||||
self.db_path_obj = Path(self.db_path)
|
||||
|
||||
# Patch DB_PATH
|
||||
self.db_patcher = patch.object(buddai_module, 'DB_PATH', self.db_path_obj)
|
||||
self.db_patcher.start()
|
||||
|
||||
# Suppress prints
|
||||
self.print_patcher = patch("builtins.print")
|
||||
self.print_patcher.start()
|
||||
|
||||
# Initialize BuddAI
|
||||
self.buddai = BuddAI(server_mode=False)
|
||||
|
||||
# Create tables required for tests
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("CREATE TABLE IF NOT EXISTS repo_index (id INTEGER PRIMARY KEY, file_path TEXT, repo_name TEXT, function_name TEXT, content TEXT, last_modified TIMESTAMP, user_id TEXT)")
|
||||
cursor.execute("CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, role TEXT, content TEXT, timestamp TIMESTAMP)")
|
||||
cursor.execute("CREATE TABLE IF NOT EXISTS code_rules (rule_text TEXT, confidence FLOAT, pattern_find TEXT, pattern_replace TEXT, learned_from TEXT)")
|
||||
cursor.execute("CREATE TABLE IF NOT EXISTS style_preferences (id INTEGER PRIMARY KEY, user_id TEXT, category TEXT, preference TEXT, confidence FLOAT, extracted_at TIMESTAMP)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def tearDown(self):
|
||||
self.db_patcher.stop()
|
||||
self.print_patcher.stop()
|
||||
try:
|
||||
os.unlink(self.db_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Test 31: Welcome Message Formatting
|
||||
def test_welcome_message(self):
|
||||
"""Test welcome message includes rule count"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
mock_cursor.fetchone.return_value = [42]
|
||||
|
||||
with patch('builtins.print') as mock_print:
|
||||
self.buddai.display_welcome_message()
|
||||
# Check if any print call contained '42'
|
||||
found = any('42' in str(call) for call in mock_print.call_args_list)
|
||||
self.assertTrue(found)
|
||||
|
||||
# Test 32: Scan Style - No Index
|
||||
def test_scan_style_no_index(self):
|
||||
"""Test scan_style_signature when no code is indexed"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
mock_cursor.fetchall.return_value = []
|
||||
|
||||
with patch('builtins.print') as mock_print:
|
||||
self.buddai.scan_style_signature()
|
||||
mock_print.assert_any_call("❌ No code indexed. Run /index first.")
|
||||
|
||||
# Test 33: Scan Style - Execution
|
||||
def test_scan_style_execution(self):
|
||||
"""Test successful style scan and DB insertion"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
mock_cursor.fetchall.return_value = [("code sample",)]
|
||||
|
||||
with patch.object(self.buddai, 'call_model', return_value="Naming: camelCase"):
|
||||
self.buddai.scan_style_signature()
|
||||
|
||||
# Verify insertion
|
||||
insert_calls = [c for c in mock_cursor.execute.call_args_list if "INSERT INTO style_preferences" in c[0][0]]
|
||||
self.assertTrue(len(insert_calls) > 0)
|
||||
self.assertIn("camelCase", insert_calls[0][0][1])
|
||||
|
||||
# Test 34: Get Applicable Rules Filtering
|
||||
def test_get_applicable_rules(self):
|
||||
"""Test that only high-confidence rules are returned"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
# Return one high confidence, one low
|
||||
mock_cursor.fetchall.return_value = [("Rule 1", 0.8), ("Rule 2", 0.4)]
|
||||
|
||||
# The method itself filters in SQL usually, but let's verify the SQL query
|
||||
self.buddai.get_applicable_rules("msg")
|
||||
|
||||
call_args = mock_cursor.execute.call_args[0][0]
|
||||
self.assertIn("confidence > 0.6", call_args)
|
||||
|
||||
# Test 35: Teach Rule Persistence
|
||||
def test_teach_rule(self):
|
||||
"""Test explicit rule teaching persistence"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
|
||||
self.buddai.teach_rule("Always use const")
|
||||
|
||||
# Verify insert
|
||||
call_args = mock_cursor.execute.call_args
|
||||
self.assertIn("INSERT INTO code_rules", call_args[0][0])
|
||||
self.assertIn("user_taught", call_args[0][1])
|
||||
|
||||
# Test 36: Regenerate - Invalid ID
|
||||
def test_regenerate_invalid_id(self):
|
||||
"""Test regeneration with non-existent message ID"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
mock_cursor.fetchone.return_value = None
|
||||
|
||||
result = self.buddai.regenerate_response(999)
|
||||
self.assertEqual(result, "Error: Message not found.")
|
||||
|
||||
# Test 37: Regenerate - Success Flow
|
||||
def test_regenerate_success(self):
|
||||
"""Test successful regeneration flow"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
# First fetch: session_id, current_id
|
||||
# Second fetch: user prompt
|
||||
mock_cursor.fetchone.side_effect = [("sess1", 10), ("User Prompt",)]
|
||||
|
||||
with patch.object(self.buddai, 'chat', return_value="New Response") as mock_chat:
|
||||
result = self.buddai.regenerate_response(10, "Better comment")
|
||||
|
||||
self.assertEqual(result, "New Response")
|
||||
mock_chat.assert_called()
|
||||
self.assertIn("Feedback: Better comment", mock_chat.call_args[0][0])
|
||||
|
||||
# Test 38: Slash Command - Reload
|
||||
def test_slash_reload(self):
|
||||
"""Test /reload command refreshes registry"""
|
||||
with patch.object(buddai_module, 'load_registry', return_value={'new': 'skill'}):
|
||||
res = self.buddai.handle_slash_command("/reload")
|
||||
self.assertIn("Reloaded 1 skills", res)
|
||||
self.assertEqual(self.buddai.skills_registry, {'new': 'skill'})
|
||||
|
||||
# Test 39: Slash Command - Debug Empty
|
||||
def test_slash_debug_empty(self):
|
||||
"""Test /debug when no prompt has been sent"""
|
||||
self.buddai.last_prompt_debug = None
|
||||
res = self.buddai.handle_slash_command("/debug")
|
||||
self.assertIn("No prompt sent yet", res)
|
||||
|
||||
# Test 40: Slash Command - Validate No Context
|
||||
def test_slash_validate_no_context(self):
|
||||
"""Test /validate with no history"""
|
||||
self.buddai.context_messages = []
|
||||
res = self.buddai.handle_slash_command("/validate")
|
||||
self.assertIn("No recent code", res)
|
||||
|
||||
# Test 41: Slash Command - Validate No Code
|
||||
def test_slash_validate_no_code(self):
|
||||
"""Test /validate when last message has no code"""
|
||||
self.buddai.context_messages = [
|
||||
{"role": "user", "content": "hi"},
|
||||
{"role": "assistant", "content": "Hello there"}
|
||||
]
|
||||
res = self.buddai.handle_slash_command("/validate")
|
||||
self.assertIn("No code blocks found", res)
|
||||
|
||||
# Test 42: Import Session - Collision
|
||||
def test_import_session_collision(self):
|
||||
"""Test importing session with ID collision generates new ID"""
|
||||
data = {
|
||||
"session_id": "sess1",
|
||||
"messages": [{"role": "user", "content": "hi"}]
|
||||
}
|
||||
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
# First check: returns row (collision)
|
||||
mock_cursor.fetchone.return_value = [1]
|
||||
|
||||
new_id = self.buddai.import_session_from_json(data)
|
||||
|
||||
self.assertNotEqual(new_id, "sess1")
|
||||
self.assertTrue("sess1_imp_" in new_id)
|
||||
|
||||
# Test 43: Export Session to Markdown
|
||||
def test_export_markdown(self):
|
||||
"""Test markdown export content generation"""
|
||||
with patch('sqlite3.connect') as mock_conn:
|
||||
mock_cursor = MagicMock()
|
||||
mock_conn.return_value.cursor.return_value = mock_cursor
|
||||
mock_cursor.fetchall.return_value = [("user", "hi", "2025-01-01")]
|
||||
|
||||
with patch('builtins.open', mock_open()) as mock_file:
|
||||
with patch('buddai_executive.DATA_DIR', Path('/tmp')):
|
||||
res = self.buddai.export_session_to_markdown("sess1")
|
||||
|
||||
self.assertIn("exported to", res)
|
||||
handle = mock_file()
|
||||
handle.write.assert_any_call("# BuddAI Session: sess1\n\n")
|
||||
|
||||
# Test 44: Backup Delegation
|
||||
def test_backup_delegation(self):
|
||||
"""Test backup command delegates to storage manager"""
|
||||
with patch.object(self.buddai.storage, 'create_backup', return_value=(True, "path.db")):
|
||||
res = self.buddai.handle_slash_command("/backup")
|
||||
self.assertIn("backed up to: path.db", res)
|
||||
|
||||
# Test 45: Metrics Delegation
|
||||
def test_metrics_delegation(self):
|
||||
"""Test metrics command delegates to metrics component"""
|
||||
with patch.object(self.buddai.metrics, 'calculate_accuracy', return_value={'accuracy': 100, 'correction_rate': 0, 'improvement': '0'}):
|
||||
res = self.buddai.handle_slash_command("/metrics")
|
||||
self.assertIn("100.0%", res)
|
||||
|
||||
# Test 46: Hardware Detection Flow
|
||||
def test_hardware_detection_flow(self):
|
||||
"""Test chat flow updates hardware profile"""
|
||||
with patch.object(self.buddai.hardware_profile, 'detect_hardware', return_value="Arduino Uno"):
|
||||
with patch.object(self.buddai, '_route_request', return_value="Response"):
|
||||
self.buddai.chat("Use Arduino Uno")
|
||||
self.assertEqual(self.buddai.current_hardware, "Arduino Uno")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue