clean-tracks/tests/unit/test_cli_commands.py

341 lines
11 KiB
Python

"""
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'])