import unittest
from unittest.mock import MagicMock, patch
import json
import sys
import os
# Mock missing dependencies BEFORE importing app
sys.modules["torch"] = MagicMock()
sys.modules["transformers"] = MagicMock()
sys.modules["gradio"] = MagicMock()
sys.modules["numpy"] = MagicMock()
sys.modules["scipy"] = MagicMock()
sys.modules["scipy.io"] = MagicMock()
sys.modules["scipy.io.wavfile"] = MagicMock()
sys.modules["accelerate"] = MagicMock()
# Add parent directory to path to import app
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import build_agent_prompt, chat_agent_stream
class TestNameExtraction(unittest.TestCase):
def setUp(self):
self.mock_history = []
self.mock_vs_state = MagicMock()
self.mock_mh_state = MagicMock()
def test_prompt_includes_name_argument_definition(self):
"""Verify that the system prompt includes the 'name' argument in tool definition."""
prompt = build_agent_prompt("Hello", [], [], language="English")
# Check for the specific tool definition we added
expected_part = '"name": "str (Optional. Use ONLY if the user explicitly stated their name, otherwise omit)"'
self.assertIn(expected_part, prompt)
@patch('app.get_llm')
@patch('app.TextIteratorStreamer')
@patch('app.retrieve_relevant_chunks')
@patch('app.detect_language')
@patch('app.get_oracle_data')
def test_oracle_call_with_name(self, mock_get_oracle_data, mock_detect, mock_retrieve, mock_streamer_cls, mock_get_llm):
"""Test that the agent calls get_oracle_data with the extracted name."""
# Setup mocks
mock_detect.return_value = "English"
mock_retrieve.return_value = []
mock_model = MagicMock()
mock_processor = MagicMock()
mock_get_llm.return_value = (mock_model, mock_processor)
# Mock streamer to yield tool call
tool_call_json = json.dumps({
"name": "oracle_consultation",
"arguments": {
"topic": "Future",
"name": "Julian"
}
})
tool_call_text = f"{tool_call_json}"
# The loop iterates over the streamer
mock_inst = mock_streamer_cls.return_value
mock_inst.__iter__.side_effect = [
iter([tool_call_text]), # First turn yields tool call
iter(["Done"]), # Second turn (post tool) yields done
iter([])
]
# Mock oracle return
mock_get_oracle_data.return_value = {"wisdom_nodes": []}
# Run generator
# We need to list() it to exhaust the generator and force execution
list(chat_agent_stream("Consult oracle for Julian", [], self.mock_vs_state, self.mock_mh_state))
# Verify get_oracle_data was called with correct name
mock_get_oracle_data.assert_called()
call_args = mock_get_oracle_data.call_args
self.assertEqual(call_args.kwargs.get('name'), "Julian")
self.assertEqual(call_args.kwargs.get('topic'), "Future")
@patch('app.get_llm')
@patch('app.TextIteratorStreamer')
@patch('app.retrieve_relevant_chunks')
@patch('app.detect_language')
@patch('app.get_oracle_data')
def test_oracle_call_without_name_defaults_to_seeker(self, mock_get_oracle_data, mock_detect, mock_retrieve, mock_streamer_cls, mock_get_llm):
"""Test that the agent defaults to 'Seeker' if no name is provided."""
# Setup mocks
mock_detect.return_value = "English"
mock_retrieve.return_value = []
mock_get_llm.return_value = (MagicMock(), MagicMock())
# Mock streamer to yield tool call WITHOUT name
tool_call_json = json.dumps({
"name": "oracle_consultation",
"arguments": {
"topic": "Destiny"
}
})
tool_call_text = f"{tool_call_json}"
mock_inst = mock_streamer_cls.return_value
mock_inst.__iter__.side_effect = [
iter([tool_call_text]),
iter(["Done"]),
iter([])
]
mock_get_oracle_data.return_value = {"wisdom_nodes": []}
list(chat_agent_stream("Consult oracle", [], self.mock_vs_state, self.mock_mh_state))
mock_get_oracle_data.assert_called()
call_args = mock_get_oracle_data.call_args
self.assertEqual(call_args.kwargs.get('name'), "Seeker")
self.assertEqual(call_args.kwargs.get('topic'), "Destiny")
if __name__ == '__main__':
unittest.main()