""" Unit tests for CLI commands. """ import pytest from pathlib import Path from unittest.mock import Mock, patch, MagicMock from click.testing import CliRunner from src.cli.main import cli from src.cli.commands import process, batch, words, config, server class TestCLIMain: """Test main CLI entry point.""" def test_cli_help(self): """Test CLI help command.""" runner = CliRunner() result = runner.invoke(cli, ['--help']) assert result.exit_code == 0 assert 'Clean Tracks - Audio Censorship System' in result.output assert 'process' in result.output assert 'batch' in result.output assert 'words' in result.output assert 'config' in result.output assert 'server' in result.output def test_cli_version(self): """Test CLI version command.""" runner = CliRunner() result = runner.invoke(cli, ['--version']) assert result.exit_code == 0 assert 'Clean Tracks CLI version' in result.output def test_cli_verbose_mode(self): """Test CLI verbose mode.""" runner = CliRunner() result = runner.invoke(cli, ['--verbose', '--help']) assert result.exit_code == 0 assert 'Verbose mode enabled' in result.output class TestProcessCommand: """Test process command.""" @patch('src.cli.commands.process.AudioProcessor') def test_process_single_file(self, mock_processor, temp_dir, sample_audio_file): """Test processing a single audio file.""" # Setup mock mock_instance = Mock() mock_instance.process_file.return_value = Mock( words_detected=5, words_censored=5, audio_duration=30.0, detected_words=[] ) mock_processor.return_value = mock_instance # Run command runner = CliRunner() output_file = temp_dir / 'output.mp3' result = runner.invoke(cli, [ 'process', str(sample_audio_file), '--output', str(output_file), '--method', 'beep', '--model', 'base' ]) assert result.exit_code == 0 assert 'Processing complete' in result.output mock_instance.process_file.assert_called_once() @patch('src.cli.commands.process.AudioProcessor') def test_process_dry_run(self, mock_processor, sample_audio_file): """Test dry run mode.""" mock_instance = Mock() mock_instance.analyze_file.return_value = Mock( words_detected=3, words_censored=0, audio_duration=20.0, detected_words=[] ) mock_processor.return_value = mock_instance runner = CliRunner() result = runner.invoke(cli, [ 'process', str(sample_audio_file), '--output', 'dummy.mp3', '--dry-run' ]) assert result.exit_code == 0 assert 'No explicit content detected' in result.output or 'Detected' in result.output mock_instance.analyze_file.assert_called_once() def test_process_invalid_file(self): """Test processing with invalid file.""" runner = CliRunner() result = runner.invoke(cli, [ 'process', 'nonexistent.mp3', '--output', 'output.mp3' ]) assert result.exit_code != 0 assert 'File not found' in result.output class TestBatchCommand: """Test batch command.""" @patch('src.cli.commands.batch.BatchProcessor') def test_batch_process_files(self, mock_batch_processor, temp_dir): """Test batch processing multiple files.""" # Create test files file1 = temp_dir / 'test1.mp3' file2 = temp_dir / 'test2.mp3' file1.write_bytes(b'ID3') file2.write_bytes(b'ID3') # Setup mock mock_instance = Mock() mock_instance.process_single.return_value = Mock( words_detected=2, words_censored=2, audio_duration=15.0 ) mock_batch_processor.return_value = mock_instance runner = CliRunner() output_dir = temp_dir / 'output' result = runner.invoke(cli, [ 'batch', str(temp_dir / '*.mp3'), '--output-dir', str(output_dir), '--parallel', '2' ]) assert result.exit_code == 0 assert 'Batch processing complete' in result.output def test_batch_no_files_found(self, temp_dir): """Test batch with no matching files.""" runner = CliRunner() result = runner.invoke(cli, [ 'batch', str(temp_dir / '*.wav'), '--output-dir', str(temp_dir / 'output') ]) assert result.exit_code == 0 assert 'No audio files found' in result.output class TestWordsCommand: """Test words command group.""" @patch('src.cli.commands.words.WordListManager') def test_words_add(self, mock_manager): """Test adding a word.""" mock_instance = Mock() mock_manager.return_value = mock_instance runner = CliRunner() result = runner.invoke(cli, [ 'words', 'add', 'testword', '--severity', 'high', '--category', 'profanity' ]) assert result.exit_code == 0 assert 'Added "testword"' in result.output mock_instance.add_word.assert_called_once() @patch('src.cli.commands.words.WordListManager') def test_words_remove(self, mock_manager): """Test removing a word.""" mock_instance = Mock() mock_instance.word_exists.return_value = True mock_instance.remove_word.return_value = 1 mock_manager.return_value = mock_instance runner = CliRunner() result = runner.invoke(cli, [ 'words', 'remove', 'testword', '--confirm' ]) assert result.exit_code == 0 assert 'Removed "testword"' in result.output @patch('src.cli.commands.words.WordListManager') def test_words_list(self, mock_manager): """Test listing words.""" mock_instance = Mock() mock_instance.get_words.return_value = [ {'word': 'word1', 'severity': 'high', 'category': 'profanity'}, {'word': 'word2', 'severity': 'medium', 'category': 'slang'} ] mock_manager.return_value = mock_instance runner = CliRunner() result = runner.invoke(cli, ['words', 'list']) assert result.exit_code == 0 assert 'Word List' in result.output assert 'word1' in result.output assert 'word2' in result.output @patch('src.cli.commands.words.WordListManager') def test_words_import(self, mock_manager, temp_dir): """Test importing words from file.""" # Create test CSV file csv_file = temp_dir / 'words.csv' csv_file.write_text('word,severity,category\ntestword,high,profanity\n') mock_instance = Mock() mock_instance.word_exists.return_value = False mock_manager.return_value = mock_instance runner = CliRunner() result = runner.invoke(cli, [ 'words', 'import', str(csv_file) ]) assert result.exit_code == 0 assert 'Imported' in result.output @patch('src.cli.commands.words.WordListManager') def test_words_export(self, mock_manager, temp_dir): """Test exporting words to file.""" mock_instance = Mock() mock_instance.get_words.return_value = [ {'word': 'word1', 'severity': 'high', 'category': 'profanity'} ] mock_manager.return_value = mock_instance runner = CliRunner() export_file = temp_dir / 'export.csv' result = runner.invoke(cli, [ 'words', 'export', str(export_file) ]) assert result.exit_code == 0 assert 'Exported' in result.output assert export_file.exists() class TestConfigCommand: """Test config command group.""" def test_config_get(self): """Test getting config value.""" runner = CliRunner() with runner.isolated_filesystem(): # Create config file Path('.clean-tracks/config.yaml').mkdir(parents=True, exist_ok=True) Path('.clean-tracks/config.yaml').write_text('whisper:\n model: base\n') result = runner.invoke(cli, [ 'config', 'get', 'whisper.model' ]) assert result.exit_code == 0 assert 'base' in result.output def test_config_set(self): """Test setting config value.""" runner = CliRunner() with runner.isolated_filesystem(): result = runner.invoke(cli, [ 'config', 'set', 'whisper.model', 'large' ]) assert result.exit_code == 0 assert 'Set whisper.model = large' in result.output def test_config_list(self): """Test listing all config.""" runner = CliRunner() with runner.isolated_filesystem(): result = runner.invoke(cli, ['config', 'list']) assert result.exit_code == 0 assert 'Current Configuration' in result.output def test_config_reset(self): """Test resetting config.""" runner = CliRunner() with runner.isolated_filesystem(): result = runner.invoke(cli, [ 'config', 'reset', '--confirm' ]) assert result.exit_code == 0 assert 'Configuration reset to defaults' in result.output class TestServerCommand: """Test server command.""" @patch('src.cli.commands.server.create_app') def test_server_start(self, mock_create_app): """Test starting the server.""" # Mock Flask app mock_app = MagicMock() mock_socketio = MagicMock() mock_create_app.return_value = (mock_app, mock_socketio) runner = CliRunner() result = runner.invoke(cli, [ 'server', '--port', '5000', '--host', '127.0.0.1' ], catch_exceptions=False, input='\n') # Simulate Ctrl+C # The server command runs indefinitely, so we can't test exit code # Just verify it starts without errors mock_create_app.assert_called_once() def test_server_invalid_port(self): """Test server with invalid port.""" runner = CliRunner() result = runner.invoke(cli, [ 'server', '--port', '99999' ]) assert result.exit_code != 0 assert 'Invalid port' in result.output if __name__ == '__main__': pytest.main([__file__, '-v'])