"""Edge case tests for PromptBuilder.""" from alfred.agent.prompts import PromptBuilder from alfred.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 alfred.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(memory) 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(memory) # 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(memory) 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(memory) 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(memory) 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(memory) # 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(memory) assert "EXTRACTED ENTITIES" in context