Spaces:
Running
Running
| """ | |
| Unit tests for the CLI of SlideDeck AI. | |
| """ | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| from unittest.mock import patch, MagicMock | |
| import pytest | |
| # Apply BertTokenizer patch before importing anything that might use it | |
| from .test_utils import patch_bert_tokenizer | |
| with patch('transformers.BertTokenizer', patch_bert_tokenizer()): | |
| from slidedeckai.cli import ( | |
| group_models_by_provider, | |
| format_models_as_bullets, | |
| CustomArgumentParser, | |
| CustomHelpFormatter, | |
| format_models_list, | |
| format_model_help, | |
| main | |
| ) | |
| from slidedeckai.global_config import GlobalConfig | |
| def test_group_models_by_provider(): | |
| # Test with sample model names | |
| test_models = [ | |
| '[az]azure/open-ai', | |
| '[gg]gemini-2.0-flash', | |
| '[gg]gemini-2.0-flash-lite', | |
| '[to]deepseek-ai/DeepSeek-V3', | |
| ] | |
| result = group_models_by_provider(test_models) | |
| assert 'an' not in result | |
| assert 'az' in result | |
| assert len(result['gg']) == 2 | |
| # Test with empty list | |
| assert group_models_by_provider([]) == {} | |
| # Test with invalid format | |
| assert len(group_models_by_provider(['invalid-model'])) == 0 | |
| def test_format_models_as_bullets(): | |
| test_models = [ | |
| '[az]azure/open-ai', | |
| '[gg]gemini-2.0-flash', | |
| '[gg]gemini-2.0-flash-lite', | |
| '[to]deepseek-ai/DeepSeek-V3', | |
| ] | |
| result = format_models_as_bullets(test_models) | |
| assert 'anthropic:' not in result | |
| assert 'deepseek' in result | |
| assert '• [gg]gemini-2.0-flash-lite' in result | |
| # Test with empty list | |
| assert format_models_as_bullets([]) == '' | |
| # Test with single model | |
| single_result = format_models_as_bullets(['[az]model1']) | |
| assert '\naz:' in single_result | |
| assert '• [az]model1' in single_result | |
| def test_custom_help_formatter_comprehensive(): | |
| formatter = CustomHelpFormatter('prog') | |
| # Test _format_action_invocation for model argument | |
| action = argparse.Action( | |
| option_strings=['--model'], | |
| dest='model', | |
| nargs=None, | |
| choices=GlobalConfig.VALID_MODELS.keys() | |
| ) | |
| result = formatter._format_action_invocation(action) | |
| assert result == '--model MODEL' | |
| # Test non-model argument | |
| other_action = argparse.Action( | |
| option_strings=['--topic'], | |
| dest='topic', | |
| nargs=None | |
| ) | |
| other_result = formatter._format_action_invocation(other_action) | |
| assert 'MODEL' not in other_result | |
| # Test _split_lines for model choices | |
| text = 'Model choices:\n[az]model1\n[gg]model2' | |
| result = formatter._split_lines(text, 80) | |
| assert 'Available models:' in result | |
| assert '------------------------' in result | |
| assert any('az:' in line for line in result) | |
| # Test _split_lines for 'choose from' format | |
| choose_text = "choose from '[az]model1', '[gg]model2'" | |
| choose_result = formatter._split_lines(choose_text, 80) | |
| assert 'Available models:' in choose_result | |
| assert any('az:' in line for line in choose_result) | |
| # Test _split_lines for regular text | |
| regular_text = 'This is a regular text' | |
| regular_result = formatter._split_lines(regular_text, 80) | |
| assert regular_text in regular_result | |
| def test_custom_argument_parser_error_handling(): | |
| parser = CustomArgumentParser() | |
| parser.add_argument('--model', choices=['[az]model1', '[gg]model2']) | |
| # Test invalid model error | |
| with pytest.raises(SystemExit) as exc_info: | |
| with patch('sys.stderr'): # Suppress stderr output | |
| parser.parse_args(['--model', 'invalid-model']) | |
| assert exc_info.value.code == 2 | |
| # Test non-model argument error | |
| parser.add_argument('--topic', required=True) | |
| with pytest.raises(SystemExit): | |
| with patch('sys.stderr'): # Suppress stderr output | |
| parser.parse_args(['--model', '[az]model1']) # Missing required --topic | |
| # Test with no arguments | |
| with pytest.raises(SystemExit): | |
| with patch('sys.stderr'): | |
| parser.parse_args([]) | |
| def test_format_models_list(): | |
| result = format_models_list() | |
| assert 'Supported SlideDeck AI models:' in result | |
| # Verify that at least one model from each provider is present | |
| for provider_code in ['az', 'gg']: # Add more providers as needed | |
| assert any(f'[{provider_code}]' in line for line in result.split('\n')) | |
| # Verify structure | |
| lines = result.split('\n') | |
| assert len(lines) > 2 # Should have header and at least one model | |
| assert lines[0] == 'Supported SlideDeck AI models:' | |
| def test_format_model_help(): | |
| result = format_model_help() | |
| # Should have provider sections | |
| assert any('az:' in line for line in result.split('\n')) | |
| # Should contain actual model names | |
| assert any('[az]' in line for line in result.split('\n')) | |
| # Verify it uses the same format as format_models_as_bullets | |
| assert result == format_models_as_bullets(list(GlobalConfig.VALID_MODELS.keys())) | |
| def test_main_no_args(): | |
| # Test behavior when no arguments are provided | |
| with patch.object(sys, 'argv', ['slidedeckai']): | |
| with patch('argparse.ArgumentParser.print_help') as mock_print_help: | |
| main() | |
| mock_print_help.assert_called_once() | |
| # Test with empty args list by providing minimal argv | |
| with patch.object(sys, 'argv', ['script.py']): | |
| with patch('argparse.ArgumentParser.print_help') as mock_print_help: | |
| main() | |
| mock_print_help.assert_called_once() | |
| def test_main_list_models(): | |
| # Test --list-models flag | |
| with patch.object(sys, 'argv', ['script.py', '--list-models']): | |
| with patch('builtins.print') as mock_print: | |
| main() | |
| mock_print.assert_called_once() | |
| output = mock_print.call_args[0][0] | |
| assert 'Supported SlideDeck AI models:' in output | |
| def test_main_generate_command(mock_move, mock_slidedeckai): | |
| # Mock the SlideDeckAI instance | |
| mock_instance = MagicMock() | |
| mock_instance.generate.return_value = Path('test_presentation.pptx') | |
| mock_slidedeckai.return_value = mock_instance | |
| # Test generate command | |
| test_args = [ | |
| 'script.py', | |
| 'generate', | |
| '--model', next(iter(GlobalConfig.VALID_MODELS.keys())), | |
| '--topic', 'Test Topic' | |
| ] | |
| with patch.object(sys, 'argv', test_args): | |
| main() | |
| # Verify SlideDeckAI was called with correct parameters | |
| mock_slidedeckai.assert_called_once() | |
| mock_instance.generate.assert_called_once() | |
| mock_move.assert_not_called() # No output path specified, no move needed | |
| def test_main_generate_with_all_options(mock_move, mock_slidedeckai): | |
| # Mock the SlideDeckAI instance | |
| mock_instance = MagicMock() | |
| output_path = Path('test_presentation.pptx') | |
| mock_instance.generate.return_value = output_path | |
| mock_slidedeckai.return_value = mock_instance | |
| test_args = [ | |
| 'script.py', | |
| 'generate', | |
| '--model', next(iter(GlobalConfig.VALID_MODELS.keys())), | |
| '--topic', 'Test Topic', | |
| '--api-key', 'test-key', | |
| '--template-id', '1', | |
| '--output-path', 'output.pptx' | |
| ] | |
| with patch.object(sys, 'argv', test_args): | |
| main() | |
| # Verify SlideDeckAI was called with correct parameters | |
| mock_slidedeckai.assert_called_once_with( | |
| model=next(iter(GlobalConfig.VALID_MODELS.keys())), | |
| topic='Test Topic', | |
| api_key='test-key', | |
| template_idx=1 | |
| ) | |
| mock_instance.generate.assert_called_once_with() | |
| # Verify file was moved to specified output path | |
| mock_move.assert_called_once_with(str(output_path), 'output.pptx') | |
| def test_main_generate_missing_required_args(mock_slidedeckai): | |
| # Test generate command without required arguments | |
| test_args = ['script.py', 'generate'] | |
| with pytest.raises(SystemExit): | |
| with patch.object(sys, 'argv', test_args): | |
| with patch('sys.stderr'): # Suppress stderr output | |
| main() | |
| # Verify SlideDeckAI was not called | |
| mock_slidedeckai.assert_not_called() | |
| # Test with only --model | |
| test_args = ['script.py', 'generate', '--model', next(iter(GlobalConfig.VALID_MODELS.keys()))] | |
| with pytest.raises(SystemExit): | |
| with patch.object(sys, 'argv', test_args): | |
| with patch('sys.stderr'): | |
| main() | |
| # Test with only --topic | |
| test_args = ['script.py', 'generate', '--topic', 'Test Topic'] | |
| with pytest.raises(SystemExit): | |
| with patch.object(sys, 'argv', test_args): | |
| with patch('sys.stderr'): | |
| main() | |
| def test_main_generate_invalid_template_id(mock_slidedeckai): | |
| # Mock the SlideDeckAI instance | |
| mock_instance = MagicMock() | |
| mock_slidedeckai.return_value = mock_instance | |
| mock_instance.generate.return_value = Path('test_presentation.pptx') | |
| # Test generate command with invalid template_id | |
| test_args = [ | |
| 'script.py', | |
| 'generate', | |
| '--model', next(iter(GlobalConfig.VALID_MODELS.keys())), | |
| '--topic', 'Test Topic', | |
| '--template-id', '-1' # Invalid template ID | |
| ] | |
| with patch.object(sys, 'argv', test_args): | |
| main() # Should still work, as validation is handled by SlideDeckAI | |
| # Verify SlideDeckAI was called with the invalid template_id | |
| mock_slidedeckai.assert_called_once_with( | |
| model=next(iter(GlobalConfig.VALID_MODELS.keys())), | |
| topic='Test Topic', | |
| api_key=None, | |
| template_idx=-1 | |
| ) | |
| mock_instance.generate.assert_called_once_with() | |