Skip to main content
Glama
test_io_refactor.py12.1 kB
"""Integration tests for IOContext and QuestionPrompter refactor.""" from __future__ import annotations import sys from io import StringIO from unittest.mock import MagicMock, patch import pytest from promptheus.io_context import IOContext from promptheus.question_prompter import ( QuestionPrompter, RichPrompter, StdioPrompter, create_prompter, ) class TestIOContext: """Test IOContext creation and properties.""" @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=True) def test_create_fully_interactive(self, mock_stdout_tty, mock_stdin_tty): """Test IOContext creation when both stdin and stdout are TTYs.""" io = IOContext.create() assert io.stdin_is_tty is True assert io.stdout_is_tty is True assert io.quiet_output is False assert io.is_fully_interactive is True assert io.is_semi_interactive is False assert io.is_non_interactive is False assert io.notify is not None assert io.console_out is not None assert io.console_err is not None @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=False) def test_create_semi_interactive(self, mock_stdout_tty, mock_stdin_tty): """Test IOContext creation when stdin is TTY but stdout is not (piped).""" io = IOContext.create() assert io.stdin_is_tty is True assert io.stdout_is_tty is False assert io.quiet_output is True # Auto-quiet when stdout piped assert io.is_fully_interactive is False assert io.is_semi_interactive is True assert io.is_non_interactive is False @patch('sys.stdin.isatty', return_value=False) @patch('sys.stdout.isatty', return_value=True) def test_create_non_interactive(self, mock_stdout_tty, mock_stdin_tty): """Test IOContext creation when stdin is not a TTY (piped input).""" io = IOContext.create() assert io.stdin_is_tty is False assert io.stdout_is_tty is True assert io.quiet_output is False # stdout is TTY, so not quiet assert io.is_fully_interactive is False assert io.is_semi_interactive is False assert io.is_non_interactive is True @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=True) def test_create_with_quiet_flag(self, mock_stdout_tty, mock_stdin_tty): """Test IOContext creation with explicit quiet mode flag (internal use).""" io = IOContext.create(quiet_output_flag=True) assert io.stdin_is_tty is True assert io.stdout_is_tty is True assert io.quiet_output is True # Forced quiet via flag assert io.is_fully_interactive is True @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=True) def test_create_with_plain_mode(self, mock_stdout_tty, mock_stdin_tty): """Test IOContext creation with plain mode flag.""" io = IOContext.create(plain_mode=True) assert io.plain_mode is True def test_notify_always_present(self): """Test that notify is always a callable, never None.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() # Notify should always be callable assert callable(io.notify) # Should not raise an exception io.notify("[red]Error: test[/red]") class TestQuestionPrompter: """Test QuestionPrompter interface and implementations.""" @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=True) def test_create_rich_prompter_for_fully_interactive(self, mock_stdout_tty, mock_stdin_tty): """Test that RichPrompter is created for fully interactive sessions.""" io = IOContext.create() prompter = create_prompter(io) assert isinstance(prompter, RichPrompter) @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=False) def test_create_stdio_prompter_for_semi_interactive(self, mock_stdout_tty, mock_stdin_tty): """Test that StdioPrompter is created when stdout is piped.""" io = IOContext.create() prompter = create_prompter(io) assert isinstance(prompter, StdioPrompter) @patch('sys.stdin.isatty', return_value=False) @patch('sys.stdout.isatty', return_value=True) def test_create_stdio_prompter_for_non_interactive(self, mock_stdout_tty, mock_stdin_tty): """Test that StdioPrompter is created when stdin is not interactive.""" io = IOContext.create() prompter = create_prompter(io) assert isinstance(prompter, StdioPrompter) def test_rich_prompter_confirmation(self): """Test RichPrompter confirmation prompt.""" prompter = RichPrompter() with patch('questionary.confirm') as mock_confirm: mock_confirm.return_value.ask.return_value = True result = prompter.prompt_confirmation("Test question?", default=True) assert result is True mock_confirm.assert_called_once_with("Test question?", default=True) def test_rich_prompter_radio(self): """Test RichPrompter radio selection.""" prompter = RichPrompter() choices = ["Option 1", "Option 2", "Option 3"] with patch('questionary.select') as mock_select: mock_select.return_value.ask.return_value = "Option 2" result = prompter.prompt_radio("Choose one:", choices) assert result == "Option 2" mock_select.assert_called_once_with("Choose one:", choices=choices) def test_rich_prompter_checkbox(self): """Test RichPrompter checkbox selection.""" prompter = RichPrompter() choices = ["Option 1", "Option 2", "Option 3"] with patch('questionary.checkbox') as mock_checkbox: mock_checkbox.return_value.ask.return_value = ["Option 1", "Option 3"] result = prompter.prompt_checkbox("Choose multiple:", choices) assert result == ["Option 1", "Option 3"] mock_checkbox.assert_called_once_with("Choose multiple:", choices=choices) def test_rich_prompter_text(self): """Test RichPrompter text input.""" prompter = RichPrompter() with patch('questionary.text') as mock_text: mock_text.return_value.ask.return_value = "User input" result = prompter.prompt_text("Enter text:", default="default") assert result == "User input" mock_text.assert_called_once_with("Enter text:", default="default") def test_stdio_prompter_confirmation_yes(self): """Test StdioPrompter confirmation with 'yes' response.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) with patch('builtins.input', return_value='y'): result = prompter.prompt_confirmation("Proceed?", default=True) assert result is True def test_stdio_prompter_confirmation_no(self): """Test StdioPrompter confirmation with 'no' response.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) with patch('builtins.input', return_value='n'): result = prompter.prompt_confirmation("Proceed?", default=True) assert result is False def test_stdio_prompter_confirmation_default(self): """Test StdioPrompter confirmation with empty response (uses default).""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) with patch('builtins.input', return_value=''): result = prompter.prompt_confirmation("Proceed?", default=True) assert result is True def test_stdio_prompter_radio(self): """Test StdioPrompter radio selection.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) choices = ["Option 1", "Option 2", "Option 3"] with patch('builtins.input', return_value='2'): result = prompter.prompt_radio("Choose:", choices) assert result == "Option 2" def test_stdio_prompter_checkbox(self): """Test StdioPrompter checkbox selection.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) choices = ["Option 1", "Option 2", "Option 3"] with patch('builtins.input', return_value='1,3'): result = prompter.prompt_checkbox("Choose multiple:", choices) assert result == ["Option 1", "Option 3"] def test_stdio_prompter_text(self): """Test StdioPrompter text input.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) with patch('builtins.input', return_value='User input'): result = prompter.prompt_text("Enter text:", default="default") assert result == "User input" def test_stdio_prompter_eoferror(self): """Test StdioPrompter raises EOFError when stdin is not interactive.""" with patch('sys.stdin.isatty', return_value=True), \ patch('sys.stdout.isatty', return_value=False): io = IOContext.create() prompter = StdioPrompter(io.notify) with patch('builtins.input', side_effect=EOFError): with pytest.raises(EOFError): prompter.prompt_confirmation("Proceed?") class TestIntegration: """Integration tests combining IOContext and QuestionPrompter.""" @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=True) def test_fully_interactive_workflow(self, mock_stdout_tty, mock_stdin_tty): """Test complete workflow in fully interactive mode.""" io = IOContext.create() prompter = create_prompter(io) # Should use RichPrompter assert isinstance(prompter, RichPrompter) assert io.is_fully_interactive is True assert io.quiet_output is False @patch('sys.stdin.isatty', return_value=True) @patch('sys.stdout.isatty', return_value=False) def test_semi_interactive_workflow(self, mock_stdout_tty, mock_stdin_tty): """Test complete workflow when stdout is piped.""" io = IOContext.create() prompter = create_prompter(io) # Should use StdioPrompter for semi-interactive assert isinstance(prompter, StdioPrompter) assert io.is_semi_interactive is True assert io.quiet_output is True # Auto-quiet @patch('sys.stdin.isatty', return_value=False) @patch('sys.stdout.isatty', return_value=True) def test_non_interactive_workflow(self, mock_stdout_tty, mock_stdin_tty): """Test workflow when stdin is piped.""" io = IOContext.create() # Should use StdioPrompter prompter = create_prompter(io) assert isinstance(prompter, StdioPrompter) assert io.is_non_interactive is True # Stdin prompts should raise EOFError in this mode with patch('builtins.input', side_effect=EOFError): with pytest.raises(EOFError): prompter.prompt_text("Enter something:")

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abhichandra21/Promptheus'

If you have feedback or need assistance with the MCP directory API, please join our Discord server