341 lines
11 KiB
Python
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']) |