tests.test_prompt   A
last analyzed

Complexity

Total Complexity 41

Size/Duplication

Total Lines 454
Duplicated Lines 11.45 %

Importance

Changes 0
Metric Value
wmc 41
eloc 309
dl 52
loc 454
rs 9.1199
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A TestReadUserChoice.test_should_not_invoke_read_user_variable() 0 18 1
A TestReadUserChoice.test_should_invoke_read_user_choice() 0 24 1
A TestReadUserChoice.test_should_render_choices() 0 35 1
A TestPrompt.test_prompt_for_config_unicode() 0 9 2
A TestPrompt.test_prompt_for_config_simple() 0 9 2
A TestPromptChoiceForConfig.context() 0 5 1
B TestPrompt.test_should_render_deep_dict() 0 48 1
A TestPrompt.test_prompt_for_config_deep_dict() 0 42 2
A TestPrompt.test_prompt_for_config_empty_dict() 0 9 2
A TestPrompt.test_should_render_dict() 0 15 1
A TestPrompt.test_prompt_for_config_dict() 0 12 2
A TestPromptChoiceForConfig.test_should_return_first_option_if_no_input() 0 15 1
A TestPrompt.test_dont_prompt_for_private_context_var() 0 10 2
A TestPromptChoiceForConfig.choices() 0 3 1
A TestPrompt.test_unicode_prompt_for_default_config_unicode() 0 9 2
A TestPrompt.test_unicode_prompt_for_config_unicode() 0 9 2
A TestPromptChoiceForConfig.test_should_read_userchoice() 0 15 1
A TestPrompt.test_unicode_prompt_for_templated_config() 0 20 2

6 Functions

Rating   Name   Duplication   Size   Complexity  
A patch_readline_on_win() 0 4 3
A test_convert_to_str() 0 25 3
A test_undefined_variable_in_cookiecutter_dict_with_dict_key() 13 13 2
A test_undefined_variable_in_cookiecutter_dict_with_key_value() 13 13 2
A test_undefined_variable_in_cookiecutter_dict() 13 13 2
A test_undefined_variable_in_cookiecutter_dict_with_choices() 13 13 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like tests.test_prompt often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
3
"""Tests for `cookiecutter.prompt` module."""
4
5
import platform
6
from collections import OrderedDict
7
8
import pytest
9
import six
10
11
from cookiecutter import prompt, exceptions, environment
12
13
14
@pytest.mark.parametrize('raw_var, rendered_var', [
15
    (1, '1'),
16
    (True, 'True'),
17
    ('foo', 'foo'),
18
    ('{{cookiecutter.project}}', 'foobar'),
19
    (None, None),
20
])
21
def test_convert_to_str(mocker, raw_var, rendered_var):
22
    env = environment.StrictEnvironment()
23
    from_string = mocker.patch(
24
        'cookiecutter.prompt.StrictEnvironment.from_string',
25
        wraps=env.from_string
26
    )
27
    context = {'project': 'foobar'}
28
29
    result = prompt.render_variable(env, raw_var, context)
30
    assert result == rendered_var
31
32
    # Make sure that non None non str variables are converted beforehand
33
    if raw_var is not None:
34
        if not isinstance(raw_var, six.string_types):
35
            raw_var = str(raw_var)
36
        from_string.assert_called_once_with(raw_var)
37
    else:
38
        assert not from_string.called
39
40
41
@pytest.fixture(autouse=True)
42
def patch_readline_on_win(monkeypatch):
43
    if 'windows' in platform.platform().lower():
44
        monkeypatch.setattr('sys.stdin.readline', lambda: '\n')
45
46
47
class TestPrompt(object):
48
    """Class to unite user prompt related tests."""
49
50
    def test_prompt_for_config_simple(self, monkeypatch):
51
        monkeypatch.setattr(
52
            'cookiecutter.prompt.read_user_variable',
53
            lambda var, default: u'Audrey Roy'
54
        )
55
        context = {'cookiecutter': {'full_name': 'Your Name'}}
56
57
        cookiecutter_dict = prompt.prompt_for_config(context)
58
        assert cookiecutter_dict == {'full_name': u'Audrey Roy'}
59
60
    def test_prompt_for_config_unicode(self, monkeypatch):
61
        monkeypatch.setattr(
62
            'cookiecutter.prompt.read_user_variable',
63
            lambda var, default: u'Pizzä ïs Gööd'
64
        )
65
        context = {'cookiecutter': {'full_name': 'Your Name'}}
66
67
        cookiecutter_dict = prompt.prompt_for_config(context)
68
        assert cookiecutter_dict == {'full_name': u'Pizzä ïs Gööd'}
69
70
    def test_prompt_for_config_empty_dict(self, monkeypatch):
71
        monkeypatch.setattr(
72
            'cookiecutter.prompt.read_user_dict',
73
            lambda var, default: {}
74
        )
75
        context = {'cookiecutter': {'details': {}}}
76
77
        cookiecutter_dict = prompt.prompt_for_config(context)
78
        assert cookiecutter_dict == {'details': {}}
79
80
    def test_prompt_for_config_dict(self, monkeypatch):
81
        monkeypatch.setattr(
82
            'cookiecutter.prompt.read_user_dict',
83
            lambda var, default: {"key": "value", "integer": 37}
84
        )
85
        context = {'cookiecutter': {'details': {}}}
86
87
        cookiecutter_dict = prompt.prompt_for_config(context)
88
        assert cookiecutter_dict == {
89
            'details': {
90
                'key': u'value',
91
                'integer': 37
92
            }
93
        }
94
95
    def test_prompt_for_config_deep_dict(self, monkeypatch):
96
        monkeypatch.setattr(
97
            'cookiecutter.prompt.read_user_dict',
98
            lambda var, default: {
99
                "key": "value",
100
                "integer_key": 37,
101
                "dict_key": {
102
                    "deep_key": "deep_value",
103
                    "deep_integer": 42,
104
                    "deep_list": [
105
                        "deep value 1",
106
                        "deep value 2",
107
                        "deep value 3",
108
                    ]
109
                },
110
                "list_key": [
111
                    "value 1",
112
                    "value 2",
113
                    "value 3",
114
                ]
115
            }
116
        )
117
        context = {'cookiecutter': {'details': {}}}
118
119
        cookiecutter_dict = prompt.prompt_for_config(context)
120
        assert cookiecutter_dict == {
121
            'details': {
122
                "key": "value",
123
                "integer_key": 37,
124
                "dict_key": {
125
                    "deep_key": "deep_value",
126
                    "deep_integer": 42,
127
                    "deep_list": [
128
                        "deep value 1",
129
                        "deep value 2",
130
                        "deep value 3",
131
                    ]
132
                },
133
                "list_key": [
134
                    "value 1",
135
                    "value 2",
136
                    "value 3",
137
                ]
138
            }
139
        }
140
141
    def test_should_render_dict(self):
142
        context = {
143
            'cookiecutter': {
144
                'project_name': 'Slartibartfast',
145
                'details': {
146
                    'other_name': '{{cookiecutter.project_name}}'
147
                }
148
            }
149
        }
150
151
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
152
        assert cookiecutter_dict == {
153
            'project_name': 'Slartibartfast',
154
            'details': {
155
                'other_name': u'Slartibartfast',
156
            }
157
        }
158
159
    def test_should_render_deep_dict(self):
160
        context = {
161
            'cookiecutter': {
162
                'project_name': "Slartibartfast",
163
                'details': {
164
                    "key": "value",
165
                    "integer_key": 37,
166
                    "other_name": '{{cookiecutter.project_name}}',
167
                    "dict_key": {
168
                        "deep_key": "deep_value",
169
                        "deep_integer": 42,
170
                        "deep_other_name": '{{cookiecutter.project_name}}',
171
                        "deep_list": [
172
                            "deep value 1",
173
                            "{{cookiecutter.project_name}}",
174
                            "deep value 3",
175
                        ]
176
                    },
177
                    "list_key": [
178
                        "value 1",
179
                        "{{cookiecutter.project_name}}",
180
                        "value 3",
181
                    ]
182
                }
183
            }
184
        }
185
186
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
187
        assert cookiecutter_dict == {
188
            'project_name': "Slartibartfast",
189
            'details': {
190
                "key": "value",
191
                "integer_key": "37",
192
                "other_name": "Slartibartfast",
193
                "dict_key": {
194
                    "deep_key": "deep_value",
195
                    "deep_integer": "42",
196
                    "deep_other_name": "Slartibartfast",
197
                    "deep_list": [
198
                        "deep value 1",
199
                        "Slartibartfast",
200
                        "deep value 3",
201
                    ]
202
                },
203
                "list_key": [
204
                    "value 1",
205
                    "Slartibartfast",
206
                    "value 3",
207
                ]
208
            }
209
        }
210
211
    def test_unicode_prompt_for_config_unicode(self, monkeypatch):
212
        monkeypatch.setattr(
213
            'cookiecutter.prompt.read_user_variable',
214
            lambda var, default: u'Pizzä ïs Gööd'
215
        )
216
        context = {'cookiecutter': {'full_name': u'Řekni či napiš své jméno'}}
217
218
        cookiecutter_dict = prompt.prompt_for_config(context)
219
        assert cookiecutter_dict == {'full_name': u'Pizzä ïs Gööd'}
220
221
    def test_unicode_prompt_for_default_config_unicode(self, monkeypatch):
222
        monkeypatch.setattr(
223
            'cookiecutter.prompt.read_user_variable',
224
            lambda var, default: default
225
        )
226
        context = {'cookiecutter': {'full_name': u'Řekni či napiš své jméno'}}
227
228
        cookiecutter_dict = prompt.prompt_for_config(context)
229
        assert cookiecutter_dict == {'full_name': u'Řekni či napiš své jméno'}
230
231
    def test_unicode_prompt_for_templated_config(self, monkeypatch):
232
        monkeypatch.setattr(
233
            'cookiecutter.prompt.read_user_variable',
234
            lambda var, default: default
235
        )
236
        context = {'cookiecutter': OrderedDict([
237
            (
238
                'project_name',
239
                u'A New Project'
240
            ), (
241
                'pkg_name',
242
                u'{{ cookiecutter.project_name|lower|replace(" ", "") }}'
243
            )
244
        ])}
245
246
        exp_cookiecutter_dict = {
247
            'project_name': u'A New Project', 'pkg_name': u'anewproject'
248
        }
249
        cookiecutter_dict = prompt.prompt_for_config(context)
250
        assert cookiecutter_dict == exp_cookiecutter_dict
251
252
    def test_dont_prompt_for_private_context_var(self, monkeypatch):
253
        monkeypatch.setattr(
254
            'cookiecutter.prompt.read_user_variable',
255
            lambda var, default: pytest.fail(
256
                'Should not try to read a response for private context var'
257
            )
258
        )
259
        context = {'cookiecutter': {'_copy_without_render': ['*.html']}}
260
        cookiecutter_dict = prompt.prompt_for_config(context)
261
        assert cookiecutter_dict == {'_copy_without_render': ['*.html']}
262
263
264
class TestReadUserChoice(object):
265
    """Class to unite choices prompt related tests."""
266
267
    def test_should_invoke_read_user_choice(self, mocker):
268
        prompt_choice = mocker.patch(
269
            'cookiecutter.prompt.prompt_choice_for_config',
270
            wraps=prompt.prompt_choice_for_config
271
        )
272
273
        read_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
274
        read_choice.return_value = 'all'
275
276
        read_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
277
278
        CHOICES = ['landscape', 'portrait', 'all']
279
        CONTEXT = {
280
            'cookiecutter': {
281
                'orientation': CHOICES
282
            }
283
        }
284
285
        cookiecutter_dict = prompt.prompt_for_config(CONTEXT)
286
287
        assert not read_variable.called
288
        assert prompt_choice.called
289
        read_choice.assert_called_once_with('orientation', CHOICES)
290
        assert cookiecutter_dict == {'orientation': 'all'}
291
292
    def test_should_not_invoke_read_user_variable(self, mocker):
293
        read_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
294
        read_variable.return_value = u'Audrey Roy'
295
296
        prompt_choice = mocker.patch(
297
            'cookiecutter.prompt.prompt_choice_for_config'
298
        )
299
300
        read_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
301
302
        CONTEXT = {'cookiecutter': {'full_name': 'Your Name'}}
303
304
        cookiecutter_dict = prompt.prompt_for_config(CONTEXT)
305
306
        assert not prompt_choice.called
307
        assert not read_choice.called
308
        read_variable.assert_called_once_with('full_name', 'Your Name')
309
        assert cookiecutter_dict == {'full_name': u'Audrey Roy'}
310
311
    def test_should_render_choices(self, mocker):
312
        read_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
313
        read_choice.return_value = u'anewproject'
314
315
        read_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
316
        read_variable.return_value = u'A New Project'
317
318
        RENDERED_CHOICES = [
319
            u'foo',
320
            u'anewproject',
321
            u'bar'
322
        ]
323
324
        CONTEXT = {'cookiecutter': OrderedDict([
325
            (
326
                'project_name',
327
                u'A New Project'
328
            ), (
329
                'pkg_name',
330
                [
331
                    u'foo',
332
                    u'{{ cookiecutter.project_name|lower|replace(" ", "") }}',
333
                    u'bar'
334
                ]
335
            )
336
        ])}
337
338
        EXP_COOKIECUTTER_DICT = {
339
            'project_name': u'A New Project', 'pkg_name': u'anewproject'
340
        }
341
        cookiecutter_dict = prompt.prompt_for_config(CONTEXT)
342
343
        read_variable.assert_called_once_with('project_name', u'A New Project')
344
        read_choice.assert_called_once_with('pkg_name', RENDERED_CHOICES)
345
        assert cookiecutter_dict == EXP_COOKIECUTTER_DICT
346
347
348
class TestPromptChoiceForConfig(object):
349
    """Class to unite choices prompt related tests with config test."""
350
351
    @pytest.fixture
352
    def choices(self):
353
        return ['landscape', 'portrait', 'all']
354
355
    @pytest.fixture
356
    def context(self, choices):
357
        return {
358
            'cookiecutter': {
359
                'orientation': choices
360
            }
361
        }
362
363
    def test_should_return_first_option_if_no_input(
364
            self, mocker, choices, context):
365
        read_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
366
367
        expected_choice = choices[0]
368
369
        actual_choice = prompt.prompt_choice_for_config(
370
            context,
371
            environment.StrictEnvironment(),
372
            'orientation',
373
            choices,
374
            True  # Suppress user input
375
        )
376
        assert not read_choice.called
377
        assert expected_choice == actual_choice
378
379
    def test_should_read_userchoice(self, mocker, choices, context):
380
        read_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
381
        read_choice.return_value = 'all'
382
383
        expected_choice = 'all'
384
385
        actual_choice = prompt.prompt_choice_for_config(
386
            context,
387
            environment.StrictEnvironment(),
388
            'orientation',
389
            choices,
390
            False  # Ask the user for input
391
        )
392
        read_choice.assert_called_once_with('orientation', choices)
393
        assert expected_choice == actual_choice
394
395
396 View Code Duplication
def test_undefined_variable_in_cookiecutter_dict():
0 ignored issues
show
Duplication introduced by Raphael Pierzina
This code seems to be duplicated in your project.
Loading history...
397
    context = {
398
        'cookiecutter': {
399
            'hello': 'world',
400
            'foo': '{{cookiecutter.nope}}'
401
        }
402
    }
403
    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
404
        prompt.prompt_for_config(context, no_input=True)
405
406
    error = err.value
407
    assert error.message == "Unable to render variable 'foo'"
408
    assert error.context == context
409
410
411 View Code Duplication
def test_undefined_variable_in_cookiecutter_dict_with_choices():
0 ignored issues
show
Duplication introduced by Raphael Pierzina
This code seems to be duplicated in your project.
Loading history...
412
    context = {
413
        'cookiecutter': {
414
            'hello': 'world',
415
            'foo': ['123', '{{cookiecutter.nope}}', '456']
416
        }
417
    }
418
    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
419
        prompt.prompt_for_config(context, no_input=True)
420
421
    error = err.value
422
    assert error.message == "Unable to render variable 'foo'"
423
    assert error.context == context
424
425
426 View Code Duplication
def test_undefined_variable_in_cookiecutter_dict_with_dict_key():
0 ignored issues
show
Duplication introduced by Russell Keith-Magee
This code seems to be duplicated in your project.
Loading history...
427
    context = {
428
        'cookiecutter': {
429
            'hello': 'world',
430
            'foo': {'{{cookiecutter.nope}}': 'value'}
431
        }
432
    }
433
    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
434
        prompt.prompt_for_config(context, no_input=True)
435
436
    error = err.value
437
    assert error.message == "Unable to render variable 'foo'"
438
    assert error.context == context
439
440
441 View Code Duplication
def test_undefined_variable_in_cookiecutter_dict_with_key_value():
0 ignored issues
show
Duplication introduced by Russell Keith-Magee
This code seems to be duplicated in your project.
Loading history...
442
    context = {
443
        'cookiecutter': {
444
            'hello': 'world',
445
            'foo': {'key': '{{cookiecutter.nope}}'}
446
        }
447
    }
448
    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
449
        prompt.prompt_for_config(context, no_input=True)
450
451
    error = err.value
452
    assert error.message == "Unable to render variable 'foo'"
453
    assert error.context == context
454