Updated folder structure (for Docker)
This commit is contained in:
@@ -1,400 +0,0 @@
|
||||
"""Edge case tests for PromptBuilder."""
|
||||
|
||||
from agent.prompts import PromptBuilder
|
||||
from agent.registry import make_tools
|
||||
|
||||
|
||||
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 recent errors."""
|
||||
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 "RECENT ERRORS" in prompt
|
||||
# Should show the most recent errors (up to 3)
|
||||
|
||||
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 "RECENT ERRORS" 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
|
||||
Reference in New Issue
Block a user