barunsaha commited on
Commit
8be9c06
·
1 Parent(s): 9119006

Add more unit tests to increase the coverage of the CLI module

Browse files
Files changed (1) hide show
  1. tests/unit/test_cli.py +219 -22
tests/unit/test_cli.py CHANGED
@@ -1,9 +1,10 @@
1
  """
2
  Unit tests for the cli module.
3
  """
4
- from unittest.mock import patch, MagicMock
5
  import sys
6
  from pathlib import Path
 
7
 
8
  import pytest
9
 
@@ -11,6 +12,9 @@ from slidedeckai.cli import (
11
  group_models_by_provider,
12
  format_models_as_bullets,
13
  CustomArgumentParser,
 
 
 
14
  main
15
  )
16
  from slidedeckai.global_config import GlobalConfig
@@ -31,6 +35,12 @@ def test_group_models_by_provider():
31
  assert 'az' in result
32
  assert len(result['gg']) == 2
33
 
 
 
 
 
 
 
34
 
35
  def test_format_models_as_bullets():
36
  test_models = [
@@ -47,51 +57,238 @@ def test_format_models_as_bullets():
47
  assert 'deepseek' in result
48
  assert '• [gg]gemini-2.0-flash-lite' in result
49
 
 
 
50
 
51
- def test_argument_parser_model_validation():
52
- parser = CustomArgumentParser()
53
- parser.add_argument(
54
- '--model',
 
 
 
 
 
 
 
 
 
 
55
  choices=GlobalConfig.VALID_MODELS.keys()
56
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- # Test valid model
59
- valid_model = next(iter(GlobalConfig.VALID_MODELS.keys()))
60
- args = parser.parse_args(['--model', valid_model])
61
- assert args.model == valid_model
 
62
 
63
- # Test invalid model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  with pytest.raises(SystemExit):
65
- parser.parse_args(['--model', 'invalid-model'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
 
68
  @patch('slidedeckai.cli.SlideDeckAI')
69
- def test_main_generate_command(mock_slidedeckai):
 
70
  # Mock the SlideDeckAI instance
71
  mock_instance = MagicMock()
72
- mock_instance.generate.return_value = Path("test_presentation.pptx")
73
  mock_slidedeckai.return_value = mock_instance
74
 
75
  # Test generate command
76
  test_args = [
 
77
  'generate',
78
  '--model', next(iter(GlobalConfig.VALID_MODELS.keys())),
79
  '--topic', 'Test Topic'
80
  ]
81
 
82
- with patch.object(sys, 'argv', ['slidedeckai'] + test_args):
83
  main()
84
 
85
  # Verify SlideDeckAI was called with correct parameters
86
  mock_slidedeckai.assert_called_once()
87
  mock_instance.generate.assert_called_once()
 
88
 
89
 
90
- def test_main_list_models():
91
- # Test --list-models flag
92
- with patch.object(sys, 'argv', ['slidedeckai', '--list-models']):
93
- with patch('builtins.print') as mock_print:
94
- main()
95
- mock_print.assert_called_once()
96
- output = mock_print.call_args[0][0]
97
- assert "Supported SlideDeck AI models:" in output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
  Unit tests for the cli module.
3
  """
4
+ import argparse
5
  import sys
6
  from pathlib import Path
7
+ from unittest.mock import patch, MagicMock
8
 
9
  import pytest
10
 
 
12
  group_models_by_provider,
13
  format_models_as_bullets,
14
  CustomArgumentParser,
15
+ CustomHelpFormatter,
16
+ format_models_list,
17
+ format_model_help,
18
  main
19
  )
20
  from slidedeckai.global_config import GlobalConfig
 
35
  assert 'az' in result
36
  assert len(result['gg']) == 2
37
 
38
+ # Test with empty list
39
+ assert group_models_by_provider([]) == {}
40
+
41
+ # Test with invalid format
42
+ assert len(group_models_by_provider(['invalid-model'])) == 0
43
+
44
 
45
  def test_format_models_as_bullets():
46
  test_models = [
 
57
  assert 'deepseek' in result
58
  assert '• [gg]gemini-2.0-flash-lite' in result
59
 
60
+ # Test with empty list
61
+ assert format_models_as_bullets([]) == ''
62
 
63
+ # Test with single model
64
+ single_result = format_models_as_bullets(['[az]model1'])
65
+ assert '\naz:' in single_result
66
+ assert '• [az]model1' in single_result
67
+
68
+
69
+ def test_custom_help_formatter_comprehensive():
70
+ formatter = CustomHelpFormatter('prog')
71
+
72
+ # Test _format_action_invocation for model argument
73
+ action = argparse.Action(
74
+ option_strings=['--model'],
75
+ dest='model',
76
+ nargs=None,
77
  choices=GlobalConfig.VALID_MODELS.keys()
78
  )
79
+ result = formatter._format_action_invocation(action)
80
+ assert result == '--model MODEL'
81
+
82
+ # Test non-model argument
83
+ other_action = argparse.Action(
84
+ option_strings=['--topic'],
85
+ dest='topic',
86
+ nargs=None
87
+ )
88
+ other_result = formatter._format_action_invocation(other_action)
89
+ assert 'MODEL' not in other_result
90
+
91
+ # Test _split_lines for model choices
92
+ text = 'Model choices:\n[az]model1\n[gg]model2'
93
+ result = formatter._split_lines(text, 80)
94
+ assert 'Available models:' in result
95
+ assert '------------------------' in result
96
+ assert any('az:' in line for line in result)
97
 
98
+ # Test _split_lines for 'choose from' format
99
+ choose_text = "choose from '[az]model1', '[gg]model2'"
100
+ choose_result = formatter._split_lines(choose_text, 80)
101
+ assert 'Available models:' in choose_result
102
+ assert any('az:' in line for line in choose_result)
103
 
104
+ # Test _split_lines for regular text
105
+ regular_text = 'This is a regular text'
106
+ regular_result = formatter._split_lines(regular_text, 80)
107
+ assert regular_text in regular_result
108
+
109
+
110
+ def test_custom_argument_parser_error_handling():
111
+ parser = CustomArgumentParser()
112
+ parser.add_argument('--model', choices=['[az]model1', '[gg]model2'])
113
+
114
+ # Test invalid model error
115
+ with pytest.raises(SystemExit) as exc_info:
116
+ with patch('sys.stderr'): # Suppress stderr output
117
+ parser.parse_args(['--model', 'invalid-model'])
118
+ assert exc_info.value.code == 2
119
+
120
+ # Test non-model argument error
121
+ parser.add_argument('--topic', required=True)
122
  with pytest.raises(SystemExit):
123
+ with patch('sys.stderr'): # Suppress stderr output
124
+ parser.parse_args(['--model', '[az]model1']) # Missing required --topic
125
+
126
+ # Test with no arguments
127
+ with pytest.raises(SystemExit):
128
+ with patch('sys.stderr'):
129
+ parser.parse_args([])
130
+
131
+
132
+ def test_format_models_list():
133
+ result = format_models_list()
134
+ assert 'Supported SlideDeck AI models:' in result
135
+ # Verify that at least one model from each provider is present
136
+ for provider_code in ['az', 'gg']: # Add more providers as needed
137
+ assert any(f'[{provider_code}]' in line for line in result.split('\n'))
138
+
139
+ # Verify structure
140
+ lines = result.split('\n')
141
+ assert len(lines) > 2 # Should have header and at least one model
142
+ assert lines[0] == 'Supported SlideDeck AI models:'
143
+
144
+
145
+ def test_format_model_help():
146
+ result = format_model_help()
147
+ # Should have provider sections
148
+ assert any('az:' in line for line in result.split('\n'))
149
+ # Should contain actual model names
150
+ assert any('[az]' in line for line in result.split('\n'))
151
+
152
+ # Verify it uses the same format as format_models_as_bullets
153
+ assert result == format_models_as_bullets(list(GlobalConfig.VALID_MODELS.keys()))
154
+
155
+
156
+ def test_main_no_args():
157
+ # Test behavior when no arguments are provided
158
+ with patch.object(sys, 'argv', ['slidedeckai']):
159
+ with patch('argparse.ArgumentParser.print_help') as mock_print_help:
160
+ main()
161
+ mock_print_help.assert_called_once()
162
+
163
+ # Test with empty args list by providing minimal argv
164
+ with patch.object(sys, 'argv', ['script.py']):
165
+ with patch('argparse.ArgumentParser.print_help') as mock_print_help:
166
+ main()
167
+ mock_print_help.assert_called_once()
168
+
169
+
170
+ def test_main_list_models():
171
+ # Test --list-models flag
172
+ with patch.object(sys, 'argv', ['script.py', '--list-models']):
173
+ with patch('builtins.print') as mock_print:
174
+ main()
175
+ mock_print.assert_called_once()
176
+ output = mock_print.call_args[0][0]
177
+ assert 'Supported SlideDeck AI models:' in output
178
 
179
 
180
  @patch('slidedeckai.cli.SlideDeckAI')
181
+ @patch('shutil.move')
182
+ def test_main_generate_command(mock_move, mock_slidedeckai):
183
  # Mock the SlideDeckAI instance
184
  mock_instance = MagicMock()
185
+ mock_instance.generate.return_value = Path('test_presentation.pptx')
186
  mock_slidedeckai.return_value = mock_instance
187
 
188
  # Test generate command
189
  test_args = [
190
+ 'script.py',
191
  'generate',
192
  '--model', next(iter(GlobalConfig.VALID_MODELS.keys())),
193
  '--topic', 'Test Topic'
194
  ]
195
 
196
+ with patch.object(sys, 'argv', test_args):
197
  main()
198
 
199
  # Verify SlideDeckAI was called with correct parameters
200
  mock_slidedeckai.assert_called_once()
201
  mock_instance.generate.assert_called_once()
202
+ mock_move.assert_not_called() # No output path specified, no move needed
203
 
204
 
205
+ @patch('slidedeckai.cli.SlideDeckAI')
206
+ @patch('shutil.move')
207
+ def test_main_generate_with_all_options(mock_move, mock_slidedeckai):
208
+ # Mock the SlideDeckAI instance
209
+ mock_instance = MagicMock()
210
+ output_path = Path('test_presentation.pptx')
211
+ mock_instance.generate.return_value = output_path
212
+ mock_slidedeckai.return_value = mock_instance
213
+
214
+ test_args = [
215
+ 'script.py',
216
+ 'generate',
217
+ '--model', next(iter(GlobalConfig.VALID_MODELS.keys())),
218
+ '--topic', 'Test Topic',
219
+ '--api-key', 'test-key',
220
+ '--template-id', '1',
221
+ '--output-path', 'output.pptx'
222
+ ]
223
+
224
+ with patch.object(sys, 'argv', test_args):
225
+ main()
226
+
227
+ # Verify SlideDeckAI was called with correct parameters
228
+ mock_slidedeckai.assert_called_once_with(
229
+ model=next(iter(GlobalConfig.VALID_MODELS.keys())),
230
+ topic='Test Topic',
231
+ api_key='test-key',
232
+ template_idx=1
233
+ )
234
+ mock_instance.generate.assert_called_once_with()
235
+
236
+ # Verify file was moved to specified output path
237
+ mock_move.assert_called_once_with(str(output_path), 'output.pptx')
238
+
239
+
240
+ @patch('slidedeckai.cli.SlideDeckAI')
241
+ def test_main_generate_missing_required_args(mock_slidedeckai):
242
+ # Test generate command without required arguments
243
+ test_args = ['script.py', 'generate']
244
+
245
+ with pytest.raises(SystemExit):
246
+ with patch.object(sys, 'argv', test_args):
247
+ with patch('sys.stderr'): # Suppress stderr output
248
+ main()
249
+
250
+ # Verify SlideDeckAI was not called
251
+ mock_slidedeckai.assert_not_called()
252
+
253
+ # Test with only --model
254
+ test_args = ['script.py', 'generate', '--model', next(iter(GlobalConfig.VALID_MODELS.keys()))]
255
+ with pytest.raises(SystemExit):
256
+ with patch.object(sys, 'argv', test_args):
257
+ with patch('sys.stderr'):
258
+ main()
259
+
260
+ # Test with only --topic
261
+ test_args = ['script.py', 'generate', '--topic', 'Test Topic']
262
+ with pytest.raises(SystemExit):
263
+ with patch.object(sys, 'argv', test_args):
264
+ with patch('sys.stderr'):
265
+ main()
266
+
267
+
268
+ @patch('slidedeckai.cli.SlideDeckAI')
269
+ def test_main_generate_invalid_template_id(mock_slidedeckai):
270
+ # Mock the SlideDeckAI instance
271
+ mock_instance = MagicMock()
272
+ mock_slidedeckai.return_value = mock_instance
273
+ mock_instance.generate.return_value = Path('test_presentation.pptx')
274
+
275
+ # Test generate command with invalid template_id
276
+ test_args = [
277
+ 'script.py',
278
+ 'generate',
279
+ '--model', next(iter(GlobalConfig.VALID_MODELS.keys())),
280
+ '--topic', 'Test Topic',
281
+ '--template-id', '-1' # Invalid template ID
282
+ ]
283
+
284
+ with patch.object(sys, 'argv', test_args):
285
+ main() # Should still work, as validation is handled by SlideDeckAI
286
+
287
+ # Verify SlideDeckAI was called with the invalid template_id
288
+ mock_slidedeckai.assert_called_once_with(
289
+ model=next(iter(GlobalConfig.VALID_MODELS.keys())),
290
+ topic='Test Topic',
291
+ api_key=None,
292
+ template_idx=-1
293
+ )
294
+ mock_instance.generate.assert_called_once_with()