Spaces:
Running
Running
| """ | |
| Test suite for session prompt adoption workflow. | |
| This module tests the session prompt adoption workflow including: | |
| - Promoting session overrides to permanent files | |
| - Validation before adoption | |
| - Backup and rollback capabilities | |
| - Error handling for adoption process | |
| **Feature: prompt-optimization, Task 11.5: Add session prompt adoption workflow** | |
| **Validates: Requirements 9.5** | |
| """ | |
| import pytest | |
| import sys | |
| import tempfile | |
| import os | |
| from pathlib import Path | |
| from unittest.mock import patch, mock_open, MagicMock | |
| from datetime import datetime | |
| # Add src to path for imports | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) | |
| from config.prompt_management.prompt_controller import PromptController | |
| class TestSessionPromptAdoption: | |
| """Test session prompt adoption workflow functionality.""" | |
| def setup_method(self): | |
| """Set up test environment.""" | |
| self.controller = PromptController() | |
| # Clear any existing state | |
| self.controller._prompt_cache.clear() | |
| self.controller._session_overrides.clear() | |
| def test_promote_session_to_file_success(self): | |
| """Test successful promotion of session override to file.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'test_promotion_session' | |
| override_content = """ | |
| <system_role> | |
| Updated spiritual monitor prompt for testing promotion workflow. | |
| This content should be promoted to the permanent file. | |
| </system_role> | |
| <output_format> | |
| Respond with JSON: {"state": "green|yellow|red", "confidence": 0.0-1.0} | |
| </output_format> | |
| """ | |
| # Set session override | |
| success = self.controller.set_session_override(agent_type, override_content, session_id) | |
| assert success, "Session override should be set successfully" | |
| # Mock file operations to avoid actual file changes | |
| with patch('pathlib.Path.exists', return_value=True), \ | |
| patch('pathlib.Path.rename') as mock_rename, \ | |
| patch('builtins.open', mock_open()) as mock_file: | |
| # Promote session to file | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should succeed | |
| assert result, "Promotion should succeed" | |
| # Verify backup was created | |
| mock_rename.assert_called_once() | |
| # Verify file was written | |
| mock_file.assert_called_once() | |
| handle = mock_file() | |
| handle.write.assert_called_once_with(override_content) | |
| def test_promote_session_to_file_with_backup(self): | |
| """Test that promotion creates proper backup of existing file.""" | |
| agent_type = 'triage_question' | |
| session_id = 'backup_test_session' | |
| override_content = "New triage question prompt content" | |
| # Set session override | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| # Mock file operations - test that backup logic is invoked | |
| with patch('pathlib.Path.exists', return_value=True), \ | |
| patch('pathlib.Path.rename') as mock_rename, \ | |
| patch('pathlib.Path.with_suffix') as mock_with_suffix, \ | |
| patch('builtins.open', mock_open()): | |
| # Setup mock backup path | |
| mock_backup_path = MagicMock() | |
| mock_with_suffix.return_value = mock_backup_path | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should succeed | |
| assert result, "Promotion with backup should succeed" | |
| # Verify backup path was created with timestamp | |
| assert mock_with_suffix.called, "Backup path should be created" | |
| if mock_with_suffix.called: | |
| backup_call_args = str(mock_with_suffix.call_args[0][0]) | |
| assert '.backup.' in backup_call_args | |
| assert datetime.now().strftime('%Y%m%d') in backup_call_args | |
| def test_promote_session_to_file_no_override(self): | |
| """Test promotion when no session override exists.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'no_override_session' | |
| # Try to promote without setting override | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should fail gracefully | |
| assert not result, "Promotion should fail when no override exists" | |
| def test_promote_session_to_file_clears_override(self): | |
| """Test that promotion clears the session override.""" | |
| agent_type = 'triage_evaluator' | |
| session_id = 'clear_override_session' | |
| override_content = "Content to be promoted and cleared" | |
| # Set session override | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| # Verify override exists | |
| assert self.controller._has_session_override(agent_type, session_id) | |
| # Mock file operations | |
| with patch('pathlib.Path.exists', return_value=False), \ | |
| patch('builtins.open', mock_open()): | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should succeed | |
| assert result, "Promotion should succeed" | |
| # Verify override was cleared | |
| assert not self.controller._has_session_override(agent_type, session_id) | |
| def test_promote_session_to_file_clears_cache(self): | |
| """Test that promotion clears the prompt cache.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'cache_clear_session' | |
| override_content = "Content for cache clearing test" | |
| # Set session override and load config to populate cache | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| config = self.controller.get_prompt(agent_type, session_id=session_id) | |
| # Verify cache is populated | |
| cache_key = f"{agent_type}_{session_id}" | |
| assert cache_key in self.controller._prompt_cache | |
| # Mock file operations | |
| with patch('pathlib.Path.exists', return_value=False), \ | |
| patch('builtins.open', mock_open()): | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should succeed | |
| assert result, "Promotion should succeed" | |
| # Verify cache was cleared | |
| assert len(self.controller._prompt_cache) == 0 | |
| def test_promote_session_to_file_error_handling(self): | |
| """Test error handling during promotion process.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'error_test_session' | |
| override_content = "Content for error testing" | |
| # Set session override | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| # Mock file operation that raises exception | |
| with patch('pathlib.Path.exists', return_value=True), \ | |
| patch('pathlib.Path.rename', side_effect=OSError("Permission denied")): | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should fail gracefully | |
| assert not result, "Promotion should fail gracefully on file errors" | |
| # Session override should still exist (not cleared on failure) | |
| assert self.controller._has_session_override(agent_type, session_id) | |
| def test_promote_session_validation_before_adoption(self): | |
| """Test validation of session content before promotion.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'validation_test_session' | |
| # Test with empty content (should still work but might be flagged) | |
| empty_content = "" | |
| self.controller.set_session_override(agent_type, empty_content, session_id) | |
| with patch('pathlib.Path.exists', return_value=False), \ | |
| patch('builtins.open', mock_open()): | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should still succeed (validation is informational, not blocking) | |
| assert result, "Promotion should succeed even with empty content" | |
| def test_promote_session_with_placeholders(self): | |
| """Test promotion of session content containing placeholders.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'placeholder_test_session' | |
| override_content = """ | |
| <system_role> | |
| You are a spiritual distress classifier. | |
| Use these indicators: | |
| {{SHARED_INDICATORS}} | |
| Apply these rules: | |
| {{SHARED_RULES}} | |
| </system_role> | |
| """ | |
| # Set session override with placeholders | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| # Mock file operations | |
| with patch('pathlib.Path.exists', return_value=False), \ | |
| patch('builtins.open', mock_open()) as mock_file: | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should succeed | |
| assert result, "Promotion should succeed with placeholder content" | |
| # Verify original content (with placeholders) was written to file | |
| handle = mock_file() | |
| written_content = handle.write.call_args[0][0] | |
| assert "{{SHARED_INDICATORS}}" in written_content | |
| assert "{{SHARED_RULES}}" in written_content | |
| def test_multiple_session_promotions(self): | |
| """Test promoting multiple different sessions.""" | |
| sessions_data = [ | |
| ('spiritual_monitor', 'session_1', 'Content for session 1'), | |
| ('triage_question', 'session_2', 'Content for session 2'), | |
| ('triage_evaluator', 'session_3', 'Content for session 3') | |
| ] | |
| # Set all session overrides | |
| for agent_type, session_id, content in sessions_data: | |
| success = self.controller.set_session_override(agent_type, content, session_id) | |
| assert success, f"Should set override for {agent_type} session {session_id}" | |
| # Promote all sessions | |
| with patch('pathlib.Path.exists', return_value=False), \ | |
| patch('builtins.open', mock_open()): | |
| for agent_type, session_id, content in sessions_data: | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| assert result, f"Should promote {agent_type} session {session_id}" | |
| # Verify override was cleared | |
| assert not self.controller._has_session_override(agent_type, session_id) | |
| def test_promote_session_rollback_capability(self): | |
| """Test that backup files enable rollback capability.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'rollback_test_session' | |
| override_content = "New content that might need rollback" | |
| # Set session override | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| # Mock file operations to test backup creation | |
| with patch('pathlib.Path.exists', return_value=True), \ | |
| patch('pathlib.Path.rename') as mock_rename, \ | |
| patch('pathlib.Path.with_suffix') as mock_with_suffix, \ | |
| patch('builtins.open', mock_open()): | |
| # Setup mock backup path | |
| mock_backup_path = MagicMock() | |
| mock_with_suffix.return_value = mock_backup_path | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| # Should succeed | |
| assert result, "Promotion should succeed" | |
| # Verify backup operations were called | |
| assert mock_with_suffix.called, "Backup path should be created" | |
| assert mock_rename.called, "File should be renamed for backup" | |
| # Verify backup path has correct format | |
| if mock_with_suffix.called: | |
| backup_call_args = str(mock_with_suffix.call_args[0][0]) | |
| assert '.backup.' in backup_call_args, "Backup should have .backup. in filename" | |
| class TestSessionPromptAdoptionIntegration: | |
| """Test integration aspects of session prompt adoption.""" | |
| def setup_method(self): | |
| """Set up test environment.""" | |
| self.controller = PromptController() | |
| self.controller._prompt_cache.clear() | |
| self.controller._session_overrides.clear() | |
| def test_adoption_workflow_end_to_end(self): | |
| """Test complete adoption workflow from session to file.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'end_to_end_session' | |
| # Step 1: Get original prompt | |
| original_config = self.controller.get_prompt(agent_type) | |
| original_content = original_config.base_prompt | |
| # Step 2: Set session override | |
| override_content = "Modified prompt content for end-to-end test" | |
| success = self.controller.set_session_override(agent_type, override_content, session_id) | |
| assert success | |
| # Step 3: Verify session override is active | |
| session_config = self.controller.get_prompt(agent_type, session_id=session_id) | |
| assert override_content in session_config.base_prompt | |
| # Step 4: Promote to file | |
| with patch('pathlib.Path.exists', return_value=True), \ | |
| patch('pathlib.Path.rename'), \ | |
| patch('builtins.open', mock_open()): | |
| promotion_result = self.controller.promote_session_to_file(agent_type, session_id) | |
| assert promotion_result | |
| # Step 5: Verify session override was cleared | |
| assert not self.controller._has_session_override(agent_type, session_id) | |
| # Step 6: Verify cache was cleared | |
| assert len(self.controller._prompt_cache) == 0 | |
| def test_adoption_preserves_shared_components(self): | |
| """Test that adoption preserves shared component integration.""" | |
| agent_type = 'spiritual_monitor' | |
| session_id = 'shared_components_session' | |
| # Create override with shared component placeholders | |
| override_content = """ | |
| <system_role> | |
| Enhanced spiritual monitor with shared components: | |
| {{SHARED_INDICATORS}} | |
| {{SHARED_RULES}} | |
| </system_role> | |
| """ | |
| # Set session override | |
| self.controller.set_session_override(agent_type, override_content, session_id) | |
| # Get session config to verify placeholder replacement works | |
| session_config = self.controller.get_prompt(agent_type, session_id=session_id) | |
| assert '{{SHARED_INDICATORS}}' not in session_config.base_prompt | |
| assert '{{SHARED_RULES}}' not in session_config.base_prompt | |
| # Promote to file (should preserve original placeholders) | |
| with patch('pathlib.Path.exists', return_value=False), \ | |
| patch('builtins.open', mock_open()) as mock_file: | |
| result = self.controller.promote_session_to_file(agent_type, session_id) | |
| assert result | |
| # Verify original content with placeholders was written | |
| written_content = mock_file().write.call_args[0][0] | |
| assert '{{SHARED_INDICATORS}}' in written_content | |
| assert '{{SHARED_RULES}}' in written_content | |
| if __name__ == '__main__': | |
| pytest.main([__file__, '-v']) |