| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- """
- Test suite using pytest's capsys for output capture with Agency.
- This approach uses pytest's built-in capsys fixture to capture output
- of the terminal.
- """
- from unittest.mock import patch
- import pytest
- from agents import ModelSettings
- from prompt_toolkit.document import Document
- from agency_swarm import Agency, Agent
- from agency_swarm.tools.send_message import Handoff
- from agency_swarm.ui.demos import terminal as terminal_module
- from agency_swarm.ui.demos.terminal import start_terminal
- class MockInputProvider:
- """Provides sequential input responses for testing."""
- def __init__(self, inputs: list[str]):
- self.inputs = inputs
- self.index = 0
- def __call__(self, prompt=""):
- """Mock input function that returns sequential inputs."""
- if self.index < len(self.inputs):
- result = self.inputs[self.index]
- self.index += 1
- # Echo input to simulate terminal behavior
- print(f"{prompt}{result}")
- return result
- return "/exit"
- async def async_call(self, prompt="", **kwargs):
- """Mock async prompt for prompt_toolkit."""
- return self(prompt)
- def _application_factory(provider: MockInputProvider):
- """Return a PromptToolkit Application stub that replays provider inputs."""
- class _ApplicationStub:
- def __init__(self, *args, **kwargs):
- self._provider = provider
- self._layout = kwargs.get("layout")
- def invalidate(self) -> None: # noqa: D401 - simple no-op
- """No-op invalidate used by dropdown integration."""
- async def run_async(self): # noqa: ANN201 - signature mirrors prompt_toolkit
- value = await self._provider.async_call()
- if value is None:
- value = "/exit"
- buffer = getattr(self._layout, "current_buffer", None)
- if buffer is not None:
- buffer.document = Document(value, len(value))
- return value
- return _ApplicationStub
- @pytest.fixture
- def agency():
- """Create an agency for testing."""
- test_agent = Agent(
- name="TestAgent",
- description="A test agent for terminal testing",
- instructions=(
- "You are a helpful test assistant. Keep responses very brief (max 10 words). "
- "Always respond with 'Test response: [user message]'. "
- "You can hand off to the Developer agent by using transfer_to_ tool."
- ),
- model_settings=ModelSettings(temperature=0),
- )
- developer_agent = Agent(
- name="Developer",
- description="A developer agent",
- instructions="You are a developer. Respond with 'Dev response: [message]'.",
- model_settings=ModelSettings(temperature=0),
- )
- # Agent with a bad formatted name
- security_expert_agent = Agent(
- name="SecUrity ExperT_Agent",
- description="A security expert agent",
- instructions="You are a security expert. Respond with 'Security expert response: [message]'.",
- model_settings=ModelSettings(temperature=0),
- )
- agency = Agency(
- test_agent,
- developer_agent,
- security_expert_agent,
- communication_flows=[
- (security_expert_agent < test_agent > developer_agent, Handoff),
- ],
- name="TestAgency",
- shared_instructions="This is a test agency. Keep all responses very brief.",
- )
- return agency
- class TestTerminalCapsys:
- """Test terminal demo using capsys for output capture."""
- def test_help_command_output(self, agency, capsys):
- """Test /help command output capture."""
- input_provider = MockInputProvider(["/help", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- assert "/help" in output
- assert "/new" in output
- assert "/compact" in output
- assert "/resume" in output
- assert "/status" in output
- assert "/exit" in output
- def test_status_command_output(self, agency, capsys):
- """Test /status command output capture."""
- input_provider = MockInputProvider(["/status", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- assert "Agency: TestAgency" in output
- assert "Entry Points: TestAgent" in output
- assert "Default Recipient: TestAgent" in output
- assert "cwd:" in output
- def test_new_command_functionality(self, agency, capsys):
- """Test /new command functionality."""
- input_provider = MockInputProvider(["/new", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- assert "Started a new chat session" in output
- def test_message_sending_and_response(self, agency, capsys):
- """Test sending message and receiving response from agent."""
- input_provider = MockInputProvider(["Hello world", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- assert "Hello world" in output
- assert "🤖 TestAgent → 👤 user" in output
- assert "Test response:" in output
- def test_agent_mention_parsing(self, agency, capsys):
- """Test @agent mention parsing with multi-agent setup."""
- input_provider = MockInputProvider(["@Developer help me", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- assert "🤖 Developer → 👤 user" in output
- assert "Dev response:" in output
- def test_empty_input_handling(self, agency, capsys):
- """Test handling of empty input."""
- input_provider = MockInputProvider(["", " ", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- assert "message cannot be empty" in captured.out
- def test_handoff_chat_transfer(self, agency, capsys):
- """Test handoff chat transfer."""
- input_provider = MockInputProvider(["Transfer to Developer", "Hi", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- # Verify that initial input is sent to main agent
- assert "🤖 TestAgent 🛠️ Executing Function"
- assert "Calling transfer_to_Developer tool with:" in output
- # Verify that after handoff, response is sent by developer
- assert "🤖 Developer → 👤 user" in output
- assert "Dev response:" in output
- # Verify that next input went to developer
- assert "🤖 Developer → 👤 user" in output.split("USER: Hi")[-1]
- assert "Dev response:" in output.split("USER: Hi")[-1]
- def test_slash_dropdown_populates_commands(self, agency):
- """Ensure slash input populates dropdown items."""
- input_provider = MockInputProvider(["/", "/exit"])
- captured_labels: list[list[str]] = []
- original_set_items = terminal_module.DropdownMenu.set_items
- def _capture(self, items): # noqa: ANN001
- captured_labels.append([item.label for item in items])
- original_set_items(self, items)
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- with patch.object(terminal_module.DropdownMenu, "set_items", _capture):
- start_terminal(agency, reload=False)
- assert captured_labels, "Expected dropdown items to be set"
- assert any(label.startswith("/") for label in captured_labels[0])
- class TestTerminalEdgeCases:
- """Test edge cases and error handling with agency."""
- def test_invalid_agent_mention_logs_error(self, agency, capsys, caplog):
- """Test that invalid @agent mentions are logged as errors."""
- input_provider = MockInputProvider(["@NonExistentAgent help", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- # Check that error was logged
- assert "Recipient agent NonExistentAgent not found" in caplog.text
- def test_very_long_message(self, agency, capsys):
- """Test handling of very long messages."""
- long_message = "This is a very long message. " * 50
- input_provider = MockInputProvider([long_message, "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- assert "🤖 TestAgent → 👤 user" in captured.out
- assert "Test response:" in captured.out
- def test_special_characters_in_message(self, agency, capsys):
- """Test handling of special characters in messages."""
- special_message = "Test with émojis 🎉 and symbols @#$%!"
- input_provider = MockInputProvider([special_message, "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- assert "🤖 TestAgent → 👤 user" in captured.out
- assert "Test response: Test with émojis 🎉 and symbols @#$%!" in captured.out
- def test_bad_formatted_agent_name(self, agency, capsys):
- """Test handling of bad formatted agent name."""
- input_provider = MockInputProvider(["@SecUrity ExperT_Agent hi", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- assert "🤖 SecUrity ExperT_Agent → 👤 user" in captured.out
- assert "Security expert response:" in captured.out
- def test_bad_formatted_agent_name_handoff(self, agency, capsys):
- """Test handling of bad formatted agent name."""
- # Use the exact tool name so this test validates UI handoff rendering, not
- # the live model's ability to normalize a mismatched tool name.
- input_provider = MockInputProvider(["Use the transfer_to_SecUrity_ExperT_Agent tool", "Hi", "/exit"])
- with patch("builtins.input", input_provider):
- with patch("agency_swarm.ui.demos.terminal.Application", new=_application_factory(input_provider)):
- start_terminal(agency, reload=False)
- captured = capsys.readouterr()
- output = captured.out
- # Handoffs will replace spaces with underscores
- assert "🤖 TestAgent 🛠️ Executing Function"
- assert "Calling transfer_to_SecUrity_ExperT_Agent tool with:" in output
- # Verify that after handoff, name is shown correctly
- assert "🤖 SecUrity ExperT_Agent → 👤 user" in output
- assert "Security expert response:" in output
- # Verify that next input went didn't cause any errors
- assert "🤖 SecUrity ExperT_Agent → 👤 user" in output.split("USER: Hi")[-1]
- assert "Security expert response:" in output.split("USER: Hi")[-1]
- if __name__ == "__main__":
- pytest.main([__file__, "-v"])
|