""" Tests for orchestration module. Tests the multi-expert coordination infrastructure including delegation parsing, think updates, and result handling. """ import pytest from unittest.mock import AsyncMock, patch from src.agents.orchestration import ( OrchestrationContext, parse_delegation_from_steward_note, execute_delegation, orchestrate_with_think_updates, extract_delegation_context, ) from src.agents.delegation import DelegationTask, DelegationResult @pytest.mark.unit class TestParseDelegation: """Tests for parsing delegation from Steward's note.""" def test_parse_librarian_create(self): """Test parsing librarian create delegation.""" note = """DELEGATE: librarian to create a wiki page about CI/CD pipelines REASON: User wants to document CI/CD concepts COMPLEXITY: moderate CONTEXT: none""" task = parse_delegation_from_steward_note(note) assert task is not None assert task.expert_name == "librarian" assert "create a wiki page about CI/CD pipelines" in task.task def test_parse_librarian_search(self): """Test parsing librarian search delegation.""" note = """DELEGATE: librarian to search for information about Docker networking REASON: User needs Docker documentation COMPLEXITY: simple""" task = parse_delegation_from_steward_note(note) assert task is not None assert task.expert_name == "librarian" assert "search for information about Docker networking" in task.task def test_parse_no_delegation(self): """Test parsing when no delegation needed.""" note = """DELEGATE: none (conversational response only) REASON: Simple greeting requires no tools COMPLEXITY: simple""" task = parse_delegation_from_steward_note(note) assert task is None def test_parse_tatlock_core(self): """Test parsing tatlock_core delegation.""" note = """DELEGATE: tatlock_core to calculate the result REASON: Math calculation needed COMPLEXITY: simple""" task = parse_delegation_from_steward_note(note) assert task is not None assert task.expert_name == "tatlock_core" assert "calculate the result" in task.task def test_parse_case_insensitive(self): """Test parsing is case insensitive.""" note = """delegate: LIBRARIAN to search docs reason: Research query""" task = parse_delegation_from_steward_note(note) assert task is not None assert task.expert_name == "librarian" def test_parse_missing_delegate(self): """Test parsing when DELEGATE line is missing.""" note = """REASON: This has no delegation COMPLEXITY: simple""" task = parse_delegation_from_steward_note(note) assert task is None @pytest.mark.unit class TestExtractDelegationContext: """Tests for extracting context from Steward's note.""" def test_extract_all_fields(self): """Test extracting all context fields.""" note = """DELEGATE: librarian to create wiki page REASON: User wants documentation COMPLEXITY: moderate CONTEXT: Related to previous discussion about DevOps""" context = extract_delegation_context(note) assert context["reason"] == "User wants documentation" assert context["complexity"] == "moderate" assert "Related to previous discussion" in context["context"] def test_extract_partial_fields(self): """Test extracting when some fields missing.""" note = """DELEGATE: librarian to search REASON: Research query COMPLEXITY: simple""" context = extract_delegation_context(note) assert context["reason"] == "Research query" assert context["complexity"] == "simple" assert context["context"] == "" def test_extract_empty_note(self): """Test extracting from empty note.""" context = extract_delegation_context("") assert context["reason"] == "" assert context["complexity"] == "" assert context["context"] == "" @pytest.mark.unit class TestExecuteDelegation: """Tests for executing delegation tasks.""" @pytest.mark.asyncio async def test_execute_librarian_delegation(self): """Test executing delegation to librarian.""" task = DelegationTask( expert_name="librarian", task="search for Docker docs", context="User learning Docker", ) mock_result = DelegationResult( expert_name="librarian", task="search for Docker docs", success=True, output="Found Docker documentation...", ) with patch( "src.agents.orchestration.delegate_to_librarian", new_callable=AsyncMock, return_value=mock_result, ) as mock_delegate: result = await execute_delegation(task) mock_delegate.assert_called_once_with( task="search for Docker docs", context="User learning Docker", ) assert result.success is True assert "Docker" in result.output @pytest.mark.asyncio async def test_execute_unknown_expert(self): """Test executing delegation to unknown expert.""" task = DelegationTask( expert_name="unknown_expert", task="do something", ) result = await execute_delegation(task) assert result.success is False assert "Unknown expert" in result.error @pytest.mark.unit class TestOrchestrateWithThinkUpdates: """Tests for orchestration with think updates.""" @pytest.mark.asyncio async def test_orchestrate_emits_think_before_delegation(self): """Test that think update is emitted before delegation.""" mock_result = DelegationResult( expert_name="librarian", task="search docs", success=True, output="Found results", ) with patch( "src.agents.orchestration.delegate_to_librarian", new_callable=AsyncMock, return_value=mock_result, ): updates = [] async for update in orchestrate_with_think_updates( user_message="Search for Docker info", steward_note="DELEGATE: librarian to search for Docker info", ): updates.append(update) # First update should be think tag about consulting assert any("" in u and "Consulting" in u for u in updates) @pytest.mark.asyncio async def test_orchestrate_emits_think_after_delegation(self): """Test that think update is emitted after delegation.""" mock_result = DelegationResult( expert_name="librarian", task="search docs", success=True, output="Found results", ) with patch( "src.agents.orchestration.delegate_to_librarian", new_callable=AsyncMock, return_value=mock_result, ): updates = [] async for update in orchestrate_with_think_updates( user_message="Search for Docker info", steward_note="DELEGATE: librarian to search for Docker info", ): updates.append(update) # Should have think tag about completion assert any("" in u and "completed" in u for u in updates) @pytest.mark.asyncio async def test_orchestrate_yields_expert_output(self): """Test that expert output is yielded.""" mock_result = DelegationResult( expert_name="librarian", task="search docs", success=True, output="Found Docker documentation with networking details", ) with patch( "src.agents.orchestration.delegate_to_librarian", new_callable=AsyncMock, return_value=mock_result, ): updates = [] async for update in orchestrate_with_think_updates( user_message="Search for Docker info", steward_note="DELEGATE: librarian to search for Docker info", ): updates.append(update) # Should include expert output all_output = "".join(updates) assert "Docker documentation" in all_output @pytest.mark.asyncio async def test_orchestrate_handles_delegation_failure(self): """Test that delegation failure emits warning think update.""" mock_result = DelegationResult( expert_name="librarian", task="search docs", success=False, output="", error="Connection timeout", ) with patch( "src.agents.orchestration.delegate_to_librarian", new_callable=AsyncMock, return_value=mock_result, ): updates = [] async for update in orchestrate_with_think_updates( user_message="Search for info", steward_note="DELEGATE: librarian to search", ): updates.append(update) # Should have warning think update all_output = "".join(updates) assert "⚠️" in all_output or "issue" in all_output.lower() @pytest.mark.asyncio async def test_orchestrate_no_delegation_returns_empty(self): """Test that no delegation yields nothing.""" updates = [] async for update in orchestrate_with_think_updates( user_message="Hello", steward_note="DELEGATE: none (conversational)", ): updates.append(update) assert len(updates) == 0 @pytest.mark.asyncio async def test_orchestrate_with_preparsed_task(self): """Test orchestration with pre-parsed delegation task.""" task = DelegationTask( expert_name="librarian", task="create wiki page", ) mock_result = DelegationResult( expert_name="librarian", task="create wiki page", success=True, output="Wiki page created", ) with patch( "src.agents.orchestration.delegate_to_librarian", new_callable=AsyncMock, return_value=mock_result, ): updates = [] async for update in orchestrate_with_think_updates( user_message="Create wiki page", steward_note="", # Empty note since task is pre-parsed delegation_task=task, ): updates.append(update) assert len(updates) > 0 all_output = "".join(updates) assert "Wiki page created" in all_output @pytest.mark.unit class TestOrchestrationContext: """Tests for OrchestrationContext dataclass.""" def test_context_creation(self): """Test creating orchestration context.""" ctx = OrchestrationContext( user_message="Test message", steward_note="Test note", conversation_id="conv_123", ) assert ctx.user_message == "Test message" assert ctx.steward_note == "Test note" assert ctx.conversation_id == "conv_123" def test_context_defaults(self): """Test orchestration context default values.""" ctx = OrchestrationContext( user_message="Test", steward_note="Note", ) assert ctx.conversation_id is None