import unittest
from unittest.mock import MagicMock, patch
from app import chat_wrapper, chat_agent_stream, get_oracle_data
import json
class TestFinalSuite(unittest.TestCase):
@patch('app.get_llm')
@patch('app.TextIteratorStreamer')
@patch('app.retrieve_relevant_chunks')
@patch('app.detect_language')
def test_multi_message_bubbles(self, mock_detect, mock_rag, mock_streamer, mock_llm):
"""
Verify that multi-turn agent responses result in multiple distinct message bubbles in history.
"""
# Setup
mock_detect.return_value = "English"
mock_rag.return_value = []
mock_llm.return_value = (MagicMock(), MagicMock())
# Turn 1: "Thinking..." + Tool Call
# Turn 2: "Final Answer"
mock_inst = mock_streamer.return_value
mock_inst.__iter__.side_effect = [
iter(["Thinking", "...", ' {"name": "oracle_consultation", "arguments": {"topic": "life"}}']),
iter(["Final", " Answer"])
]
# Mock Oracle so it returns something valid
with patch('app.get_oracle_data', return_value={"wisdom_nodes": [], "els_revelation": "hidden"}):
# Run the WRAPPER (which manages bubbles)
# chat_wrapper yields (history, threads, ...)
# We want to see the FINAL history state.
gen = chat_wrapper("query", [], {}, "tid", None, None)
final_history = []
for h, _, _, _, _ in gen:
final_history = h
# Expectation:
# 1. User: "query"
# 2. Assistant: "Thinking..."
# 3. Assistant: "Final Answer"
# Total 3 messages.
print(f"\nFINAL HISTORY: {final_history}")
self.assertEqual(len(final_history), 3, "Should have 3 messages (User, Bubble1, Bubble2)")
self.assertEqual(final_history[1]["content"], "Thinking...")
self.assertEqual(final_history[2]["content"], "Final Answer")
@patch('app.get_llm')
@patch('app.TextIteratorStreamer')
@patch('app.retrieve_relevant_chunks')
@patch('app.detect_language')
@patch('app.get_oracle_data')
def test_oracle_filtering(self, mock_oracle, mock_detect, mock_rag, mock_streamer, mock_llm):
"""
Verify that ONLY wisdom_nodes are passed to the tool result string, masking 'els_revelation' etc.
"""
mock_detect.return_value = "English"
mock_rag.return_value = []
mock_llm.return_value = (MagicMock(), MagicMock())
# Mock Oracle returning sensitive data (BOS API / ELS)
mock_oracle.return_value = {
"wisdom_nodes": [{"source": "Psalm 1"}],
"els_revelation": "SECRET_DATA",
"bos_api": "HIDDEN"
}
# We need to capture what chat_agent_stream injects into messages
# We can inspect the logger OR inspect the messages list if we mock it?
# Actually, let's run the stream and spy on the 'messages' list built inside using a side_effect on generate?
# A simpler way: The generator yields 'tool_result' into the User Context message.
# But 'chat_agent_stream' function local var 'messages' is hard to access.
# However, we can use `mock_llm` call args! generate() is called with `input_ids`.
# Wait, build_agent_prompt is called, but that's initial.
# The tool result is injected in Turn 2 prompt.
# Turn 1 triggers tool. Turn 2 prompt contains the result.
mock_inst = mock_streamer.return_value
mock_inst.__iter__.side_effect = [
iter(['{"name": "oracle_consultation", "arguments": {}}']),
iter(["Done"]),
iter([]), iter([]) # Safety padding for extra turns
]
gen = chat_agent_stream("query", [], None, None)
list(gen) # Exhaust
# Now check the args passed to model.generate in Turn 2?
# Or easier: Check the LOGS if we could.
# Best: Mock 'json.dumps' inside app?
# Actually, let's verify what mock_oracle was called with,
# AND verify logic by importing the code?
# No, let's trust the logic if we can verify the messages list.
# We can patch 'app.messages' list? No it's local.
# Run agent
with patch('app.json.dumps', side_effect=json.dumps) as mock_json:
list(chat_agent_stream("query", [], None, None))
# Inspect the messages injected via Apply Chat Template
mock_proc = mock_llm.return_value[1]
calls = mock_proc.apply_chat_template.call_args_list
found_filtered = False
for call in calls:
msgs = call[0][0]
# Check the tool result injection message
for m in msgs:
if m["role"] == "user" and "" in m["content"][0]["text"]:
content = m["content"][0]["text"]
if "wisdom_nodes" in content and "els_revelation" not in content and "bos_api" not in content:
found_filtered = True
self.assertTrue(found_filtered, "Tool Result did not contain filtered data (or contained forbidden keys).")
@patch('app.get_llm')
@patch('app.TextIteratorStreamer')
@patch('app.retrieve_relevant_chunks')
@patch('app.detect_language')
def test_prompt_fluidity_instruction(self, mock_detect, mock_rag, mock_streamer, mock_llm):
"""
Verify that the injected prompt contains the 'connect smoothly' instruction.
"""
mock_detect.return_value = "English"
mock_rag.return_value = []
mock_llm.return_value = (MagicMock(), MagicMock())
# Turn 1 triggers tool. Turn 2 prompt injection.
mock_inst = mock_streamer.return_value
mock_inst.__iter__.side_effect = [
iter(['{"name": "oracle_consultation", "arguments": {}}']),
iter(["Done"]),
iter([]), iter([])
]
# We need to spy on 'messages' appended in app.py.
# Since we can't easily access the local variable, we can mock `model.generate`
# inside `chat_agent_stream` (it's called via thread, but `processor.apply_chat_template` is main thread).
# We'll spy on `processor.apply_chat_template`.
mock_proc = mock_llm.return_value[1]
# Run agent
with patch('app.get_oracle_data', return_value={"wisdom_nodes": []}):
list(chat_agent_stream("query", [], None, None))
# Check calls to apply_chat_template.
# The LAST call should contain the injected tool result + instruction.
calls = mock_proc.apply_chat_template.call_args_list
# Found relevant call?
found_instruction = False
for call in calls:
# call[0][0] is 'messages' list
msgs = call[0][0]
last_msg = msgs[-1]
if last_msg["role"] == "user" and "connect this smoothly" in last_msg["content"][0]["text"].lower():
found_instruction = True
break
self.assertTrue(found_instruction, "Did not find the 'connect smoothly' instruction in the prompt injection.")
if __name__ == "__main__":
unittest.main()