Passed
Push — master ( 93a3aa...623e4b )
by
unknown
01:08
created

TestPrompt.test_should_render_private_variables_with_two_underscores()   A

Complexity

Conditions 1

Size

Total Lines 34
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 34
rs 9.328
c 0
b 0
f 0
cc 1
nop 1
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.fixture(autouse=True)
15
def patch_readline_on_win(monkeypatch):
16
    """Fixture. Overwrite windows end of line to linux standard."""
17
    if 'windows' in platform.platform().lower():
18
        monkeypatch.setattr('sys.stdin.readline', lambda: '\n')
19
20
21
class TestRenderVariable:
22
    """Class to unite simple and complex tests for render_variable function."""
23
24
    @pytest.mark.parametrize(
25
        'raw_var, rendered_var',
26
        [
27
            (1, '1'),
28
            (True, 'True'),
29
            ('foo', 'foo'),
30
            ('{{cookiecutter.project}}', 'foobar'),
31
            (None, None),
32
        ],
33
    )
34
    def test_convert_to_str(self, mocker, raw_var, rendered_var):
35
        """Verify simple items correctly rendered to strings."""
36
        env = environment.StrictEnvironment()
37
        from_string = mocker.patch(
38
            'cookiecutter.prompt.StrictEnvironment.from_string', wraps=env.from_string
39
        )
40
        context = {'project': 'foobar'}
41
42
        result = prompt.render_variable(env, raw_var, context)
43
        assert result == rendered_var
44
45
        # Make sure that non None non str variables are converted beforehand
46
        if raw_var is not None:
47
            if not isinstance(raw_var, six.string_types):
48
                raw_var = str(raw_var)
49
            from_string.assert_called_once_with(raw_var)
50
        else:
51
            assert not from_string.called
52
53
    @pytest.mark.parametrize(
54
        'raw_var, rendered_var',
55
        [
56
            ({1: True, 'foo': False}, {'1': 'True', 'foo': 'False'}),
57
            (
58
                {'{{cookiecutter.project}}': ['foo', 1], 'bar': False},
59
                {'foobar': ['foo', '1'], 'bar': 'False'},
60
            ),
61
            (['foo', '{{cookiecutter.project}}', None], ['foo', 'foobar', None]),
62
        ],
63
    )
64
    def test_convert_to_str_complex_variables(self, raw_var, rendered_var):
65
        """Verify tree items correctly rendered."""
66
        env = environment.StrictEnvironment()
67
        context = {'project': 'foobar'}
68
69
        result = prompt.render_variable(env, raw_var, context)
70
        assert result == rendered_var
71
72
73
class TestPrompt(object):
74
    """Class to unite user prompt related tests."""
75
76
    @pytest.mark.parametrize(
77
        'context',
78
        [
79
            {'cookiecutter': {'full_name': 'Your Name'}},
80
            {'cookiecutter': {'full_name': u'Řekni či napiš své jméno'}},
81
        ],
82
        ids=['ASCII default prompt/input', 'Unicode default prompt/input'],
83
    )
84
    def test_prompt_for_config(self, monkeypatch, context):
85
        """Verify `prompt_for_config` call `read_user_variable` on text request."""
86
        monkeypatch.setattr(
87
            'cookiecutter.prompt.read_user_variable', lambda var, default: default,
88
        )
89
90
        cookiecutter_dict = prompt.prompt_for_config(context)
91
        assert cookiecutter_dict == context['cookiecutter']
92
93
    def test_prompt_for_config_dict(self, monkeypatch):
94
        """Verify `prompt_for_config` call `read_user_variable` on dict request."""
95
        monkeypatch.setattr(
96
            'cookiecutter.prompt.read_user_dict',
97
            lambda var, default: {"key": "value", "integer": 37},
98
        )
99
        context = {'cookiecutter': {'details': {}}}
100
101
        cookiecutter_dict = prompt.prompt_for_config(context)
102
        assert cookiecutter_dict == {'details': {'key': u'value', 'integer': 37}}
103
104
    def test_should_render_dict(self):
105
        """Verify template inside dictionary variable rendered."""
106
        context = {
107
            'cookiecutter': {
108
                'project_name': 'Slartibartfast',
109
                'details': {
110
                    '{{cookiecutter.project_name}}': '{{cookiecutter.project_name}}'
111
                },
112
            }
113
        }
114
115
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
116
        assert cookiecutter_dict == {
117
            'project_name': 'Slartibartfast',
118
            'details': {'Slartibartfast': u'Slartibartfast'},
119
        }
120
121
    def test_should_render_deep_dict(self):
122
        """Verify nested structures like dict in dict, rendered correctly."""
123
        context = {
124
            'cookiecutter': {
125
                'project_name': "Slartibartfast",
126
                'details': {
127
                    "key": "value",
128
                    "integer_key": 37,
129
                    "other_name": '{{cookiecutter.project_name}}',
130
                    "dict_key": {
131
                        "deep_key": "deep_value",
132
                        "deep_integer": 42,
133
                        "deep_other_name": '{{cookiecutter.project_name}}',
134
                        "deep_list": [
135
                            "deep value 1",
136
                            "{{cookiecutter.project_name}}",
137
                            "deep value 3",
138
                        ],
139
                    },
140
                    "list_key": [
141
                        "value 1",
142
                        "{{cookiecutter.project_name}}",
143
                        "value 3",
144
                    ],
145
                },
146
            }
147
        }
148
149
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
150
        assert cookiecutter_dict == {
151
            'project_name': "Slartibartfast",
152
            'details': {
153
                "key": "value",
154
                "integer_key": "37",
155
                "other_name": "Slartibartfast",
156
                "dict_key": {
157
                    "deep_key": "deep_value",
158
                    "deep_integer": "42",
159
                    "deep_other_name": "Slartibartfast",
160
                    "deep_list": ["deep value 1", "Slartibartfast", "deep value 3"],
161
                },
162
                "list_key": ["value 1", "Slartibartfast", "value 3"],
163
            },
164
        }
165
166
    def test_prompt_for_templated_config(self, monkeypatch):
167
        """Verify Jinja2 templating works in unicode prompts."""
168
        monkeypatch.setattr(
169
            'cookiecutter.prompt.read_user_variable', lambda var, default: default
170
        )
171
        context = {
172
            'cookiecutter': OrderedDict(
173
                [
174
                    ('project_name', u'A New Project'),
175
                    (
176
                        'pkg_name',
177
                        u'{{ cookiecutter.project_name|lower|replace(" ", "") }}',
178
                    ),
179
                ]
180
            )
181
        }
182
183
        exp_cookiecutter_dict = {
184
            'project_name': u'A New Project',
185
            'pkg_name': u'anewproject',
186
        }
187
        cookiecutter_dict = prompt.prompt_for_config(context)
188
        assert cookiecutter_dict == exp_cookiecutter_dict
189
190
    def test_dont_prompt_for_private_context_var(self, monkeypatch):
191
        """Verify `read_user_variable` not called for private context variables."""
192
        monkeypatch.setattr(
193
            'cookiecutter.prompt.read_user_variable',
194
            lambda var, default: pytest.fail(
195
                'Should not try to read a response for private context var'
196
            ),
197
        )
198
        context = {'cookiecutter': {'_copy_without_render': ['*.html']}}
199
        cookiecutter_dict = prompt.prompt_for_config(context)
200
        assert cookiecutter_dict == {'_copy_without_render': ['*.html']}
201
202
    def test_should_render_private_variables_with_two_underscores(self):
203
        """Test rendering of private variables with two underscores.
204
205
        There are three cases:
206
        1. Variables beginning with a single underscore are private and not rendered.
207
        2. Variables beginning with a double underscore are private and are rendered.
208
        3. Variables beginning with anything other than underscores are not private and
209
           are rendered.
210
        """
211
        context = {
212
            'cookiecutter': OrderedDict(
213
                [
214
                    ('foo', 'Hello world'),
215
                    ('bar', 123),
216
                    ('rendered_foo', u'{{ cookiecutter.foo|lower }}'),
217
                    ('rendered_bar', 123),
218
                    ('_hidden_foo', u'{{ cookiecutter.foo|lower }}'),
219
                    ('_hidden_bar', 123),
220
                    ('__rendered_hidden_foo', u'{{ cookiecutter.foo|lower }}'),
221
                    ('__rendered_hidden_bar', 123),
222
                ]
223
            )
224
        }
225
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
226
        assert cookiecutter_dict == OrderedDict(
227
            [
228
                ('foo', 'Hello world'),
229
                ('bar', '123'),
230
                ('rendered_foo', 'hello world'),
231
                ('rendered_bar', '123'),
232
                ('_hidden_foo', u'{{ cookiecutter.foo|lower }}'),
233
                ('_hidden_bar', 123),
234
                ('__rendered_hidden_foo', 'hello world'),
235
                ('__rendered_hidden_bar', '123'),
236
            ]
237
        )
238
239
    def test_should_not_render_private_variables(self):
240
        """Verify private(underscored) variables not rendered by `prompt_for_config`.
241
242
        Private variables designed to be raw, same as context input.
243
        """
244
        context = {
245
            'cookiecutter': {
246
                'project_name': 'Skip render',
247
                '_skip_jinja_template': '{{cookiecutter.project_name}}',
248
                '_skip_float': 123.25,
249
                '_skip_integer': 123,
250
                '_skip_boolean': True,
251
                '_skip_nested': True,
252
            }
253
        }
254
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
255
        assert cookiecutter_dict == context['cookiecutter']
256
257
258
class TestReadUserChoice(object):
259
    """Class to unite choices prompt related tests."""
260
261
    def test_should_invoke_read_user_choice(self, mocker):
262
        """Verify correct function called for select(list) variables."""
263
        prompt_choice = mocker.patch(
264
            'cookiecutter.prompt.prompt_choice_for_config',
265
            wraps=prompt.prompt_choice_for_config,
266
        )
267
268
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
269
        read_user_choice.return_value = 'all'
270
271
        read_user_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
272
273
        choices = ['landscape', 'portrait', 'all']
274
        context = {'cookiecutter': {'orientation': choices}}
275
276
        cookiecutter_dict = prompt.prompt_for_config(context)
277
278
        assert not read_user_variable.called
279
        assert prompt_choice.called
280
        read_user_choice.assert_called_once_with('orientation', choices)
281
        assert cookiecutter_dict == {'orientation': 'all'}
282
283
    def test_should_invoke_read_user_variable(self, mocker):
284
        """Verify correct function called for string input variables."""
285
        read_user_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
286
        read_user_variable.return_value = u'Audrey Roy'
287
288
        prompt_choice = mocker.patch('cookiecutter.prompt.prompt_choice_for_config')
289
290
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
291
292
        context = {'cookiecutter': {'full_name': 'Your Name'}}
293
294
        cookiecutter_dict = prompt.prompt_for_config(context)
295
296
        assert not prompt_choice.called
297
        assert not read_user_choice.called
298
        read_user_variable.assert_called_once_with('full_name', 'Your Name')
299
        assert cookiecutter_dict == {'full_name': u'Audrey Roy'}
300
301
    def test_should_render_choices(self, mocker):
302
        """Verify Jinja2 templating engine works inside choices variables."""
303
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
304
        read_user_choice.return_value = u'anewproject'
305
306
        read_user_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
307
        read_user_variable.return_value = u'A New Project'
308
309
        rendered_choices = [u'foo', u'anewproject', u'bar']
310
311
        context = {
312
            'cookiecutter': OrderedDict(
313
                [
314
                    ('project_name', u'A New Project'),
315
                    (
316
                        'pkg_name',
317
                        [
318
                            u'foo',
319
                            u'{{ cookiecutter.project_name|lower|replace(" ", "") }}',
320
                            u'bar',
321
                        ],
322
                    ),
323
                ]
324
            )
325
        }
326
327
        expected = {
328
            'project_name': u'A New Project',
329
            'pkg_name': u'anewproject',
330
        }
331
        cookiecutter_dict = prompt.prompt_for_config(context)
332
333
        read_user_variable.assert_called_once_with('project_name', u'A New Project')
334
        read_user_choice.assert_called_once_with('pkg_name', rendered_choices)
335
        assert cookiecutter_dict == expected
336
337
338
class TestPromptChoiceForConfig(object):
339
    """Class to unite choices prompt related tests with config test."""
340
341
    @pytest.fixture
342
    def choices(self):
343
        """Fixture. Just populate choices variable."""
344
        return ['landscape', 'portrait', 'all']
345
346
    @pytest.fixture
347
    def context(self, choices):
348
        """Fixture. Just populate context variable."""
349
        return {'cookiecutter': {'orientation': choices}}
350
351
    def test_should_return_first_option_if_no_input(self, mocker, choices, context):
352
        """Verify prompt_choice_for_config return first list option on no_input=True."""
353
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
354
355
        expected_choice = choices[0]
356
357
        actual_choice = prompt.prompt_choice_for_config(
358
            cookiecutter_dict=context,
359
            env=environment.StrictEnvironment(),
360
            key='orientation',
361
            options=choices,
362
            no_input=True,  # Suppress user input
363
        )
364
365
        assert not read_user_choice.called
366
        assert expected_choice == actual_choice
367
368
    def test_should_read_user_choice(self, mocker, choices, context):
369
        """Verify prompt_choice_for_config return user selection on no_input=False."""
370
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
371
        read_user_choice.return_value = 'all'
372
373
        expected_choice = 'all'
374
375
        actual_choice = prompt.prompt_choice_for_config(
376
            cookiecutter_dict=context,
377
            env=environment.StrictEnvironment(),
378
            key='orientation',
379
            options=choices,
380
            no_input=False,  # Ask the user for input
381
        )
382
        read_user_choice.assert_called_once_with('orientation', choices)
383
        assert expected_choice == actual_choice
384
385
386
@pytest.mark.parametrize(
387
    'context',
388
    (
389
        {'cookiecutter': {'foo': '{{cookiecutter.nope}}'}},
390
        {'cookiecutter': {'foo': ['123', '{{cookiecutter.nope}}', '456']}},
391
        {'cookiecutter': {'foo': {'{{cookiecutter.nope}}': 'value'}}},
392
        {'cookiecutter': {'foo': {'key': '{{cookiecutter.nope}}'}}},
393
    ),
394
    ids=[
395
        'Undefined variable in cookiecutter dict',
396
        'Undefined variable in cookiecutter dict with choices',
397
        'Undefined variable in cookiecutter dict with dict_key',
398
        'Undefined variable in cookiecutter dict with key_value',
399
    ],
400
)
401
def test_undefined_variable(context):
402
    """Verify `prompt.prompt_for_config` raises correct error."""
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