● Terminal Client Improvement Plan - Part 6: Testing & Migration Guide Overview Comprehensive testing strategy and step-by-step migration guide to implement all terminal client improvements safely with full test coverage. --- A. Comprehensive Testing Strategy 1. Create Test Directory Structure backend/tests/unit/terminal_client/ ├── __init__.py ├── test_commands.py # From Part 1 ├── test_arg_parser.py # From Part 2 ├── test_completions.py # From Part 3 ├── test_help_text.py # From Part 4 ├── test_player_cache.py # From Part 5 └── test_integration.py # New - full integration tests 2. Create Integration Tests backend/tests/unit/terminal_client/test_integration.py """ Integration tests for terminal client improvements. Tests the complete flow of all improvements working together. """ import pytest import asyncio from uuid import uuid4 from unittest.mock import AsyncMock, MagicMock, patch, call from terminal_client.commands import GameCommands from terminal_client.arg_parser import ( parse_new_game_args, parse_defensive_args, parse_offensive_args ) from terminal_client.completions import GameREPLCompletions from terminal_client.help_text import show_help from terminal_client.player_cache import player_cache class TestEndToEndWorkflow: """Test complete workflow using all improvements.""" @pytest.fixture def game_commands(self): """Create GameCommands with mocked dependencies.""" commands = GameCommands() commands.db_ops = AsyncMock() return commands @pytest.fixture def repl_completions(self): """Create GameREPLCompletions instance.""" return GameREPLCompletions() @pytest.mark.asyncio async def test_complete_game_workflow(self, game_commands): """ Test complete workflow: create game, make decisions, resolve. This tests that all components work together: - Shared commands - Argument parsing - Game engine integration """ game_id = uuid4() with patch('terminal_client.commands.state_manager') as mock_sm: with patch('terminal_client.commands.game_engine') as mock_ge: with patch('terminal_client.commands.uuid4', return_value=game_id): # Mock game state from app.models.game_models import GameState mock_state = GameState( game_id=game_id, league_id='sba', home_team_id=1, away_team_id=2, inning=1, half='top', status='active' ) mock_sm.create_game = AsyncMock(return_value=mock_state) mock_ge.start_game = AsyncMock(return_value=mock_state) mock_ge.submit_defensive_decision = AsyncMock(return_value=mock_state) mock_ge.submit_offensive_decision = AsyncMock(return_value=mock_state) # Step 1: Create game gid, success = await game_commands.create_new_game( league='sba', home_team=1, away_team=2 ) assert success is True assert gid == game_id mock_ge.start_game.assert_called_once_with(game_id) # Step 2: Submit defensive decision success = await game_commands.submit_defensive_decision( game_id=game_id, alignment='shifted_left', hold_runners=[1, 3] ) assert success is True mock_ge.submit_defensive_decision.assert_called_once() # Step 3: Submit offensive decision success = await game_commands.submit_offensive_decision( game_id=game_id, approach='power', steal_attempts=[2] ) assert success is True mock_ge.submit_offensive_decision.assert_called_once() def test_argument_parsing_with_completions(self, repl_completions): """ Test that parsed arguments work with tab completions. Ensures argument parser and completion system are compatible. """ # Parse arguments args = parse_defensive_args('--alignment shifted_left --hold 1,3') assert args['alignment'] == 'shifted_left' assert args['hold'] == [1, 3] # Verify completion suggests valid values completions = repl_completions.complete_defensive( 'shift', 'defensive --alignment shift', 10, 30 ) assert 'shifted_left' in completions def test_help_system_covers_all_commands(self): """ Test that help system has documentation for all commands. Ensures every command has proper help text. """ from terminal_client.help_text import HELP_DATA required_commands = [ 'new_game', 'defensive', 'offensive', 'resolve', 'quick_play', 'status', 'list_games', 'use_game' ] for cmd in required_commands: assert cmd in HELP_DATA, f"Missing help for {cmd}" help_data = HELP_DATA[cmd] assert 'summary' in help_data assert 'usage' in help_data assert 'examples' in help_data @pytest.mark.asyncio async def test_player_cache_integration(self): """ Test player cache integration with game workflow. Verifies caching works correctly during gameplay. """ game_id = uuid4() # Add players to cache player_cache.add_player({ 'card_id': 101, 'name': 'Mike Trout', 'position': 'CF', 'team_id': 1, 'league': 'sba' }, lineup_id=1) player_cache.add_player({ 'card_id': 201, 'name': 'Clayton Kershaw', 'position': 'P', 'team_id': 2, 'league': 'sba' }, lineup_id=10) # Verify retrieval batter = player_cache.get_by_lineup_id(1) assert batter is not None assert batter.name == 'Mike Trout' pitcher = player_cache.get_by_lineup_id(10) assert pitcher is not None assert pitcher.name == 'Clayton Kershaw' # Get stats stats = player_cache.get_stats() assert stats['card_cache_size'] >= 2 class TestErrorHandling: """Test error handling across all improvements.""" @pytest.fixture def game_commands(self): commands = GameCommands() commands.db_ops = AsyncMock() return commands @pytest.mark.asyncio async def test_command_handles_database_error(self, game_commands): """Test that commands handle database errors gracefully.""" game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: mock_ge.submit_defensive_decision = AsyncMock( side_effect=Exception("Database error") ) success = await game_commands.submit_defensive_decision( game_id=game_id ) assert success is False def test_argument_parser_handles_invalid_input(self): """Test that argument parser handles invalid input gracefully.""" from terminal_client.arg_parser import ( parse_defensive_args, ArgumentParseError ) # Invalid option with pytest.raises(ArgumentParseError, match="Unknown option"): parse_defensive_args('--invalid-option value') # Invalid type with pytest.raises(ArgumentParseError, match="expected int"): parse_defensive_args('--hold abc') def test_completion_handles_empty_state(self): """Test that completions work even with empty state.""" from terminal_client.completions import GameREPLCompletions repl = GameREPLCompletions() # Should not crash on empty game list with patch('terminal_client.completions.state_manager') as mock_sm: mock_sm.list_games.return_value = [] completions = repl.complete_use_game('', 'use_game ', 9, 9) assert completions == [] class TestPerformance: """Test performance of improvements.""" def test_player_cache_performance(self): """Test that player cache provides fast lookups.""" import time # Add 100 players for i in range(100): player_cache.add_player({ 'card_id': i, 'name': f'Player {i}', 'position': 'CF', 'team_id': 1, 'league': 'sba' }, lineup_id=i) # Time lookups start = time.time() for i in range(100): player = player_cache.get_by_lineup_id(i) assert player is not None end = time.time() # Should be very fast (< 10ms for 100 lookups) elapsed_ms = (end - start) * 1000 assert elapsed_ms < 10, f"Cache lookups too slow: {elapsed_ms}ms" def test_argument_parsing_performance(self): """Test that argument parsing is fast.""" import time from terminal_client.arg_parser import parse_defensive_args start = time.time() for _ in range(1000): args = parse_defensive_args( '--alignment shifted_left --infield double_play --hold 1,3' ) end = time.time() elapsed_ms = (end - start) * 1000 # Should parse 1000 times in < 100ms assert elapsed_ms < 100, f"Parsing too slow: {elapsed_ms}ms" class TestBackwardCompatibility: """Test that improvements don't break existing functionality.""" @pytest.mark.asyncio async def test_old_command_interface_still_works(self): """Test that old-style command usage still works.""" from terminal_client.commands import game_commands game_id = uuid4() with patch('terminal_client.commands.game_engine') as mock_ge: from app.models.game_models import GameState mock_state = GameState( game_id=game_id, league_id='sba', home_team_id=1, away_team_id=2 ) mock_ge.get_game_state = AsyncMock(return_value=mock_state) # Old-style status check should still work success = await game_commands.show_game_status(game_id) assert success is True mock_ge.get_game_state.assert_called_once_with(game_id) 3. End-to-End REPL Tests backend/tests/e2e/test_terminal_client.py """ End-to-end tests for terminal client REPL. These tests simulate actual user interaction with the REPL. """ import pytest import asyncio from io import StringIO from unittest.mock import patch, MagicMock from terminal_client.repl import GameREPL class TestREPLInteraction: """Test REPL user interaction flows.""" @pytest.fixture def repl(self): """Create REPL instance for testing.""" with patch('terminal_client.repl.state_manager'): with patch('terminal_client.repl.game_engine'): repl = GameREPL() return repl def test_help_command(self, repl): """Test that help command displays correctly.""" with patch('terminal_client.repl.HelpFormatter.show_command_list') as mock_help: repl.do_help('') mock_help.assert_called_once() def test_help_specific_command(self, repl): """Test help for specific command.""" with patch('terminal_client.repl.show_help') as mock_help: repl.do_help('new_game') mock_help.assert_called_once_with('new_game') def test_tab_completion_defensive(self, repl): """Test tab completion for defensive command.""" completions = repl.complete_defensive( '--align', 'defensive --align', 10, 17 ) assert '--alignment' in completions def test_cache_stats_command(self, repl): """Test cache stats command.""" with patch('terminal_client.repl.show_cache_stats') as mock_stats: repl.do_cache('stats') # Need to run the async function repl.loop.run_until_complete(repl._run_async(repl.do_cache('stats'))) class TestREPLWorkflow: """Test complete REPL workflows.""" def test_new_game_workflow(self): """ Test complete new game workflow: 1. new_game 2. defensive 3. offensive 4. resolve 5. status """ # This would be a full integration test # Implementation left as exercise pass --- B. Migration Guide Step-by-Step Implementation Order Phase 1: Preparation (Week 1 Day 1-2) 1. Create new files without breaking existing code # Create new modules touch backend/terminal_client/commands.py touch backend/terminal_client/arg_parser.py touch backend/terminal_client/completions.py touch backend/terminal_client/help_text.py touch backend/terminal_client/player_cache.py touch backend/terminal_client/enhanced_display.py # Create test directory mkdir -p backend/tests/unit/terminal_client touch backend/tests/unit/terminal_client/__init__.py 2. Implement shared commands module - Copy code from Part 1 into commands.py - Add all imports - Don't modify repl.py or main.py yet - Run: python -c "from terminal_client.commands import GameCommands; print('✓ Import successful')" 3. Run tests # Should still pass (no changes to existing code yet) pytest backend/tests/unit/terminal_client/ -v Phase 2: Argument Parser (Week 1 Day 3) 1. Implement argument parser - Copy code from Part 2 into arg_parser.py - Add unit tests from Part 2 - Test independently: pytest backend/tests/unit/terminal_client/test_arg_parser.py -v 2. Verify no regressions # Existing tests should still pass pytest backend/tests/ -v Phase 3: Update REPL (Week 1 Day 4-5) 1. Create backup cp backend/terminal_client/repl.py backend/terminal_client/repl.py.backup cp backend/terminal_client/main.py backend/terminal_client/main.py.backup 2. Update repl.py incrementally Update one command at a time: # Start with new_game def do_new_game(self, arg): # New implementation using shared commands pass # Test it python -m terminal_client ⚾ > new_game --league sba 3. Update all commands - Replace each do_* method with new implementation - Test each command individually - Verify existing functionality works 4. Run full test suite pytest backend/tests/unit/terminal_client/ -v Phase 4: Tab Completion (Week 2 Day 1-2) 1. Implement completions - Copy code from Part 3 into completions.py - Add tests 2. Update REPL class # Add mixin class GameREPL(GameREPLCompletions, cmd.Cmd): pass 3. Test completions interactively python -m terminal_client ⚾ > def ⚾ > defensive -- Phase 5: Help System (Week 2 Day 3) 1. Implement help text - Copy code from Part 4 into help_text.py - Verify all commands have help data 2. Update help methods in REPL - Replace do_help method - Add help_* methods 3. Test help system python -m terminal_client ⚾ > help ⚾ > help defensive Phase 6: Player Cache (Week 2 Day 4-5) [OPTIONAL - Requires Week 6 Models] 1. Implement cache (inactive by default) - Copy code from Part 5 - Set feature flags to False 2. Add tests - Test cache in isolation - Don't activate yet 3. Document activation process - Update CLAUDE.md with activation instructions Verification Checklist After each phase, verify: - All existing tests pass - New tests pass - REPL starts without errors - Can create a new game - Can submit decisions - Can resolve plays - Can see game status - No import errors - No runtime errors in logs Rollback Plan If something goes wrong: Quick Rollback: # Restore backups cp backend/terminal_client/repl.py.backup backend/terminal_client/repl.py cp backend/terminal_client/main.py.backup backend/terminal_client/main.py # Remove new modules rm backend/terminal_client/commands.py rm backend/terminal_client/arg_parser.py rm backend/terminal_client/completions.py rm backend/terminal_client/help_text.py # Verify system works python -m terminal_client ⚾ > new_game ⚾ > quit Incremental Rollback: If only one component is problematic: 1. Keep working components 2. Revert only the problematic module 3. Remove integration code for that module 4. Continue with working improvements --- C. Documentation Updates 1. Update backend/terminal_client/CLAUDE.md Add sections: ## Recent Improvements (2025-10-27) ### Shared Command Logic All command implementations now use `terminal_client/commands.py` for: - Reduced code duplication (-500 lines) - Consistent behavior between REPL and CLI - Easier testing and maintenance **Usage:** Commands automatically use shared logic ### Robust Argument Parsing Arguments now use `shlex` for proper parsing: - Handles quoted strings with spaces - Better error messages - Type validation **Example:** ```bash ⚾ > defensive --alignment "shifted left" # Now works! Tab Completion All commands support tab completion: - Command names: new → new_game - Options: defensive -- → shows all options - Values: --alignment → shows valid alignments Usage: Press TAB at any point for suggestions Enhanced Help System Detailed help with examples: - help - List all commands - help - Detailed help with examples Player Name Display (Future) Infrastructure ready for Week 6 player models: - Player cache system implemented - Enhanced display functions ready - Feature flags control activation Activation: Set ENABLE_PLAYER_NAMES = True when ready ### 2. Create Migration Document #### `backend/.claude/terminal_client_improvements.md` ```markdown # Terminal Client Improvements - Implementation Log ## Summary Six major improvements to terminal client: 1. Shared command logic (-500 lines duplication) 2. Robust argument parsing with shlex 3. Tab completion for all commands 4. Enhanced help system 5. Player cache infrastructure (inactive) 6. Comprehensive test suite ## Files Created - `terminal_client/commands.py` (450 lines) - `terminal_client/arg_parser.py` (250 lines) - `terminal_client/completions.py` (350 lines) - `terminal_client/help_text.py` (400 lines) - `terminal_client/player_cache.py` (300 lines) - `terminal_client/enhanced_display.py` (150 lines) Total: ~1900 lines of new, tested code ## Files Modified - `terminal_client/repl.py` (simplified, -200 lines) - `terminal_client/main.py` (simplified, -150 lines) Total: -350 lines of duplicated code ## Tests Added - `test_commands.py` (200 lines) - `test_arg_parser.py` (250 lines) - `test_completions.py` (200 lines) - `test_help_text.py` (150 lines) - `test_player_cache.py` (200 lines) - `test_integration.py` (300 lines) Total: ~1300 lines of test code ## Test Coverage - Unit tests: 80+ tests - Integration tests: 15+ tests - Coverage: ~95% of new code ## Performance Impact - Argument parsing: < 0.1ms per command - Tab completion: < 5ms per completion - Player cache: < 0.01ms per lookup - Help display: < 10ms **No measurable impact on REPL responsiveness** ## Migration Status - [x] Phase 1: Preparation - [x] Phase 2: Argument Parser - [x] Phase 3: Update REPL - [x] Phase 4: Tab Completion - [x] Phase 5: Help System - [ ] Phase 6: Player Cache (waiting on Week 6) ## Known Issues None ## Future Enhancements 1. Activate player cache when Week 6 models ready 2. Add command history persistence 3. Add command aliases (e.g., 'd' for defensive) 4. Add macro recording/playback for testing --- D. Final Verification Manual Testing Checklist Run through this checklist before considering migration complete: # 1. Start REPL python -m terminal_client # 2. Test help system ⚾ > help ⚾ > help new_game ⚾ > help defensive # 3. Test tab completion ⚾ > new # Should complete to new_game ⚾ > defensive -- # Should show all options ⚾ > defensive --alignment # Should show valid values # 4. Test argument parsing ⚾ > new_game --league pd --home-team 5 ⚾ > defensive --alignment shifted_left --hold 1,3 ⚾ > offensive --approach power --hit-run # 5. Test gameplay ⚾ > defensive ⚾ > offensive ⚾ > resolve ⚾ > status # 6. Test quick play ⚾ > quick_play 10 # 7. Test error handling ⚾ > defensive --invalid value # Should show clear error ⚾ > resolve # Should fail appropriately (no decisions) # 8. Test cache commands (if activated) ⚾ > cache stats ⚾ > cache clear # 9. Exit cleanly ⚾ > quit Automated Test Run # Run all tests pytest backend/tests/unit/terminal_client/ -v --cov=terminal_client --cov-report=html # Should see: # - 80+ passing tests # - ~95% coverage # - 0 failures --- E. Success Criteria Migration is successful when: - All 80+ tests pass - Test coverage ≥ 95% - REPL starts without errors - All commands work as before - Tab completion works - Help system displays correctly - Argument parsing handles edge cases - No performance degradation - Manual testing checklist passes - Documentation updated - No regressions in existing functionality --- Summary Total New Code: ~1900 lines Code Removed: ~350 lines (duplicates) Test Code: ~1300 lines Net Result: Better, more maintainable, well-tested terminal client Estimated Implementation Time: 2 weeks (1 developer) Benefits: - Reduced duplication - Better UX (tab completion, help) - Easier to maintain - Ready for future enhancements - Comprehensive test coverage Risks: Low (incremental migration, full rollback plan)