File size: 9,719 Bytes
9119006
91dea06
9119006
8be9c06
9119006
 
8be9c06
9119006
 
 
91dea06
 
 
 
 
 
 
 
 
 
 
 
 
 
9119006
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8be9c06
 
 
 
 
 
9119006
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8be9c06
 
9119006
8be9c06
 
 
 
 
 
 
 
 
 
 
 
 
 
9119006
 
8be9c06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9119006
8be9c06
 
 
 
 
9119006
8be9c06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9119006
8be9c06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9119006
 
 
8be9c06
 
9119006
 
8be9c06
9119006
 
 
 
8be9c06
9119006
 
 
 
 
8be9c06
9119006
 
 
 
 
8be9c06
9119006
 
8be9c06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
"""
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


@patch('slidedeckai.cli.SlideDeckAI')
@patch('shutil.move')
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


@patch('slidedeckai.cli.SlideDeckAI')
@patch('shutil.move')
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')


@patch('slidedeckai.cli.SlideDeckAI')
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()


@patch('slidedeckai.cli.SlideDeckAI')
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()