Recovered tests

This commit is contained in:
2025-12-06 23:55:21 +01:00
parent 9ca31e45e0
commit 5b71233fb0
11 changed files with 3420 additions and 555 deletions

View File

@@ -0,0 +1,396 @@
"""Edge case tests for PromptBuilder."""
import pytest
import json
from agent.prompts import PromptBuilder
from agent.registry import make_tools
from infrastructure.persistence import get_memory
class TestPromptBuilderEdgeCases:
"""Edge case tests for PromptBuilder."""
def test_prompt_with_empty_memory(self, memory):
"""Should build prompt with completely empty memory."""
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "AVAILABLE TOOLS" in prompt
assert "CURRENT CONFIGURATION" in prompt
def test_prompt_with_unicode_config(self, memory):
"""Should handle unicode in config."""
memory.ltm.set_config("folder_日本語", "/path/to/日本語")
memory.ltm.set_config("emoji_folder", "/path/🎬")
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "日本語" in prompt
assert "🎬" in prompt
def test_prompt_with_very_long_config_value(self, memory):
"""Should handle very long config values."""
long_path = "/very/long/path/" + "x" * 1000
memory.ltm.set_config("download_folder", long_path)
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# Should include the path (possibly truncated)
assert "very/long/path" in prompt
def test_prompt_with_special_chars_in_config(self, memory):
"""Should escape special characters in config."""
memory.ltm.set_config("path", '/path/with "quotes" and \\backslash')
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# Should be valid (not crash)
assert "CURRENT CONFIGURATION" in prompt
def test_prompt_with_many_search_results(self, memory):
"""Should limit displayed search results."""
results = [{"name": f"Torrent {i}", "seeders": i} for i in range(50)]
memory.episodic.store_search_results("test query", results)
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# Should show limited results
assert "LAST SEARCH" in prompt
# Should indicate there are more
assert "more" in prompt.lower() or "..." in prompt
def test_prompt_with_search_results_missing_fields(self, memory):
"""Should handle search results with missing fields."""
results = [
{"name": "Complete"},
{}, # Empty
{"seeders": 100}, # Missing name
]
memory.episodic.store_search_results("test", results)
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# Should not crash
assert "LAST SEARCH" in prompt
def test_prompt_with_many_active_downloads(self, memory):
"""Should limit displayed active downloads."""
for i in range(20):
memory.episodic.add_active_download({
"task_id": str(i),
"name": f"Download {i}",
"progress": i * 5,
})
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "ACTIVE DOWNLOADS" in prompt
# Should show limited number
assert "Download 0" in prompt
def test_prompt_with_many_errors(self, memory):
"""Should show only last error."""
for i in range(10):
memory.episodic.add_error(f"action_{i}", f"Error {i}")
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "LAST ERROR" in prompt
# Should show the most recent error
# (depends on max_errors setting)
def test_prompt_with_pending_question_many_options(self, memory):
"""Should handle pending question with many options."""
options = [{"index": i, "label": f"Option {i}"} for i in range(20)]
memory.episodic.set_pending_question("Choose one:", options, {})
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "PENDING QUESTION" in prompt
assert "Choose one:" in prompt
def test_prompt_with_complex_workflow(self, memory):
"""Should handle complex workflow state."""
memory.stm.start_workflow("download", {
"title": "Test Movie",
"year": 2024,
"quality": "1080p",
"nested": {"deep": {"value": "test"}},
})
memory.stm.update_workflow_stage("searching_torrents")
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "CURRENT WORKFLOW" in prompt
assert "download" in prompt
assert "searching_torrents" in prompt
def test_prompt_with_many_entities(self, memory):
"""Should handle many extracted entities."""
for i in range(50):
memory.stm.set_entity(f"entity_{i}", f"value_{i}")
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "EXTRACTED ENTITIES" in prompt
def test_prompt_with_null_values_in_entities(self, memory):
"""Should handle null values in entities."""
memory.stm.set_entity("null_value", None)
memory.stm.set_entity("empty_string", "")
memory.stm.set_entity("zero", 0)
memory.stm.set_entity("false", False)
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# Should not crash
assert "EXTRACTED ENTITIES" in prompt
def test_prompt_with_unread_events(self, memory):
"""Should include unread events."""
memory.episodic.add_background_event("download_complete", {"name": "Movie.mkv"})
memory.episodic.add_background_event("new_files", {"count": 5})
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
assert "UNREAD EVENTS" in prompt
def test_prompt_with_all_sections(self, memory):
"""Should include all sections when all data present."""
# Config
memory.ltm.set_config("download_folder", "/downloads")
# Search results
memory.episodic.store_search_results("test", [{"name": "Result"}])
# Active downloads
memory.episodic.add_active_download({"task_id": "1", "name": "Download"})
# Errors
memory.episodic.add_error("action", "error")
# Pending question
memory.episodic.set_pending_question("Question?", [], {})
# Workflow
memory.stm.start_workflow("download", {"title": "Test"})
# Topic
memory.stm.set_topic("searching")
# Entities
memory.stm.set_entity("key", "value")
# Events
memory.episodic.add_background_event("event", {})
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# All sections should be present
assert "CURRENT CONFIGURATION" in prompt
assert "LAST SEARCH" in prompt
assert "ACTIVE DOWNLOADS" in prompt
assert "LAST ERROR" in prompt
assert "PENDING QUESTION" in prompt
assert "CURRENT WORKFLOW" in prompt
assert "CURRENT TOPIC" in prompt
assert "EXTRACTED ENTITIES" in prompt
assert "UNREAD EVENTS" in prompt
def test_prompt_json_serializable(self, memory):
"""Should produce JSON-serializable content."""
memory.ltm.set_config("key", {"nested": [1, 2, 3]})
memory.stm.set_entity("complex", {"a": {"b": {"c": "d"}}})
tools = make_tools()
builder = PromptBuilder(tools)
prompt = builder.build_system_prompt()
# The prompt itself is a string, but embedded JSON should be valid
assert isinstance(prompt, str)
class TestFormatToolsDescriptionEdgeCases:
"""Edge case tests for _format_tools_description."""
def test_format_with_no_tools(self, memory):
"""Should handle empty tools dict."""
builder = PromptBuilder({})
desc = builder._format_tools_description()
assert desc == ""
def test_format_with_complex_parameters(self, memory):
"""Should format complex parameter schemas."""
from agent.registry import Tool
tools = {
"complex_tool": Tool(
name="complex_tool",
description="A complex tool",
func=lambda: {},
parameters={
"type": "object",
"properties": {
"nested": {
"type": "object",
"properties": {
"deep": {"type": "string"},
},
},
"array": {
"type": "array",
"items": {"type": "integer"},
},
},
"required": ["nested"],
},
),
}
builder = PromptBuilder(tools)
desc = builder._format_tools_description()
assert "complex_tool" in desc
assert "nested" in desc
class TestFormatEpisodicContextEdgeCases:
"""Edge case tests for _format_episodic_context."""
def test_format_with_empty_search_query(self, memory):
"""Should handle empty search query."""
memory.episodic.store_search_results("", [{"name": "Result"}])
tools = make_tools()
builder = PromptBuilder(tools)
context = builder._format_episodic_context()
assert "LAST SEARCH" in context
def test_format_with_search_results_none_names(self, memory):
"""Should handle results with None names."""
memory.episodic.store_search_results("test", [
{"name": None},
{"title": None},
{},
])
tools = make_tools()
builder = PromptBuilder(tools)
context = builder._format_episodic_context()
# Should not crash
assert "LAST SEARCH" in context
def test_format_with_download_missing_progress(self, memory):
"""Should handle download without progress."""
memory.episodic.add_active_download({"task_id": "1", "name": "Test"})
tools = make_tools()
builder = PromptBuilder(tools)
context = builder._format_episodic_context()
assert "ACTIVE DOWNLOADS" in context
assert "0%" in context # Default progress
class TestFormatStmContextEdgeCases:
"""Edge case tests for _format_stm_context."""
def test_format_with_workflow_missing_target(self, memory):
"""Should handle workflow with missing target."""
memory.stm.current_workflow = {
"type": "download",
"stage": "started",
}
tools = make_tools()
builder = PromptBuilder(tools)
context = builder._format_stm_context()
assert "CURRENT WORKFLOW" in context
def test_format_with_workflow_none_target(self, memory):
"""Should handle workflow with None target."""
memory.stm.start_workflow("download", None)
tools = make_tools()
builder = PromptBuilder(tools)
try:
context = builder._format_stm_context()
assert "CURRENT WORKFLOW" in context or True
except (AttributeError, TypeError):
# Expected if None target causes issues
pass
def test_format_with_empty_topic(self, memory):
"""Should handle empty topic."""
memory.stm.set_topic("")
tools = make_tools()
builder = PromptBuilder(tools)
context = builder._format_stm_context()
# Empty topic might not be shown
assert isinstance(context, str)
def test_format_with_entities_containing_json(self, memory):
"""Should handle entities containing JSON strings."""
memory.stm.set_entity("json_string", '{"key": "value"}')
tools = make_tools()
builder = PromptBuilder(tools)
context = builder._format_stm_context()
assert "EXTRACTED ENTITIES" in context