Completed
Pull Request — master (#1008)
by
unknown
30s
created

test_prompt_json_default()   B

Complexity

Conditions 3

Size

Total Lines 26

Duplication

Lines 26
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 26
loc 26
rs 8.8571
1
# -*- coding: utf-8 -*-
2
# flake8: noqa
3
"""
4
test_context
5
------------
6
7
Tests for `cookiecutter.context` module that handles prompts for v2 context.
8
"""
9
from __future__ import unicode_literals
10
11
import sys
12
import os.path
13
import time
14
import json
15
import logging
16
17
import pytest
18
19
from collections import OrderedDict
20
21
from cookiecutter import context
22
23
from cookiecutter.exceptions import (
24
    ContextDecodingException
25
)
26
27
import click
28
29
from uuid import UUID
30
31
logger = logging.getLogger(__name__)
32
33
34
def load_cookiecutter(cookiecutter_file):
35
36
    context = {}
37
    try:
38
        with open(cookiecutter_file) as file_handle:
39
            obj = json.load(file_handle, object_pairs_hook=OrderedDict)
40
    except ValueError as e:
41
        # JSON decoding error.  Let's throw a new exception that is more
42
        # friendly for the developer or user.
43
        full_fpath = os.path.abspath(cookiecutter_file)
44
        json_exc_message = str(e)
45
        our_exc_message = (
46
            'JSON decoding error while loading "{0}".  Decoding'
47
            ' error details: "{1}"'.format(full_fpath, json_exc_message))
48
        raise ContextDecodingException(our_exc_message)
49
50
    # Add the Python object to the context dictionary
51
    file_name = os.path.split(cookiecutter_file)[1]
52
    file_stem = file_name.split('.')[0]
53
    context[file_stem] = obj
54
55
    return context
56
57
58
def context_data_check():
59
    context_all_reqs = (
60
        {
61
            'cookiecutter_context': OrderedDict([
62
                ("name", "cookiecutter-pytest-plugin"),
63
                ("cookiecutter_version", "2.0.0"),
64
                ("variables", [])
65
            ])
66
        },
67
        True
68
    )
69
70
    context_missing_name = (
71
        {
72
            'cookiecutter_context': OrderedDict([
73
                ("cookiecutter_version", "2.0.0"),
74
                ("variables", [])
75
            ])
76
        },
77
        False
78
    )
79
80
    context_missing_cookiecutter_version = (
81
        {
82
            'cookiecutter_context': OrderedDict([
83
                ("name", "cookiecutter-pytest-plugin"),
84
                ("variables", [])
85
            ])
86
        },
87
        False
88
    )
89
90
    context_missing_variables = (
91
        {
92
            'cookiecutter_context': OrderedDict([
93
                ("name", "cookiecutter-pytest-plugin"),
94
                ("cookiecutter_version", "2.0.0"),
95
            ])
96
        },
97
        False
98
    )
99
100
    yield context_all_reqs
101
    yield context_missing_name
102
    yield context_missing_cookiecutter_version
103
    yield context_missing_variables
104
105
106
@pytest.mark.usefixtures('clean_system')
107
@pytest.mark.parametrize('input_params, expected_result', context_data_check())
108
def test_context_check(input_params, expected_result):
109
    """
110
    Test that a context with the required fields will be detected as a
111
    v2 context.
112
    """
113
    assert context.context_is_version_2(**input_params) == expected_result
114
115
116
@pytest.mark.usefixtures('clean_system')
117
def test_load_context_defaults():
118
119
    cc = load_cookiecutter('tests/test-context/cookiecutter.json')
120
121
    cc_cfg = context.load_context(cc['cookiecutter'], no_input=True)
122
123
    assert cc_cfg['full_name'] == 'Raphael Pierzina'
124
    assert cc_cfg['email'] == '[email protected]'
125
    assert cc_cfg['plugin_name'] == 'emoji'
126
    assert cc_cfg['module_name'] == 'emoji'
127
    assert cc_cfg['license'] == 'MIT'
128
    assert cc_cfg['docs'] == False
129
    assert 'docs_tool' not in cc_cfg.keys()
130
    assert cc_cfg['year'] == time.strftime('%Y')
131
    assert cc_cfg['incept_year'] == 2017
132
    assert cc_cfg['released'] == False
133
    assert cc_cfg['temperature'] == 77.3
134
    assert cc_cfg['Release-GUID'] == UUID('04f5eaa9ee7345469dccffc538b27194')
135
    assert cc_cfg['extensions'] == "['jinja2_time.TimeExtension']"
136
    assert cc_cfg['copy_with_out_render'] == "['*.html', '*not_rendered_dir', 'rendered_dir/not_rendered_file.ini']"
137
    assert cc_cfg['fixtures'] == OrderedDict([('foo',
138
                                               OrderedDict([('scope', 'session'),
139
                                                            ('autouse', True)])),
140
                                              ('bar',
141
                                               OrderedDict([('scope', 'function'),
142
                                                            ('autouse',
143
                                                             False)]))])
144
145
146 View Code Duplication
def test_prompt_string(mocker):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
147
148
    EXPECTED_VALUE = 'Input String'
149
150
    mock_prompt = mocker.patch(
151
        'cookiecutter.prompt.click.prompt',
152
        autospec=True,
153
        return_value=EXPECTED_VALUE,
154
    )
155
156
    m = mocker.Mock()
157
    m.side_effect = context.Variable
158
    v = m.side_effect(name='name', default='', prompt='Enter Name', hide_input=False)
159
160
    r = context.prompt_string(v, default='Alpha')
161
162
    assert mock_prompt.call_args == mocker.call(
163
        v.prompt,
164
        default='Alpha',
165
        hide_input=v.hide_input,
166
        type=click.STRING,
167
    )
168
169
    assert r == EXPECTED_VALUE
170
171
172
def test_prompt_bool(mocker):
173
174
    EXPECTED_VALUE = True
175
176
    mock_prompt = mocker.patch(
177
        'cookiecutter.prompt.click.prompt',
178
        autospec=True,
179
        return_value=EXPECTED_VALUE,
180
    )
181
182
    m = mocker.Mock()
183
    m.side_effect = context.Variable
184
    v = m.side_effect(name='flag', default=False, prompt='Enter a Flag', hide_input=False)
185
186
    r = context.prompt_boolean(v, default=False)
187
188
    assert mock_prompt.call_args == mocker.call(
189
        v.prompt,
190
        default=False,
191
        hide_input=v.hide_input,
192
        type=click.BOOL,
193
    )
194
195
    assert r           # EXPECTED_VALUE
196
197
198 View Code Duplication
def test_prompt_int(mocker):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
199
200
    EXPECTED_VALUE = 777
201
202
    mock_prompt = mocker.patch(
203
        'cookiecutter.prompt.click.prompt',
204
        autospec=True,
205
        return_value=EXPECTED_VALUE,
206
    )
207
208
    m = mocker.Mock()
209
    m.side_effect = context.Variable
210
    v = m.side_effect(name='port', default=1000, prompt='Enter Port', hide_input=False)
211
212
    r = context.prompt_int(v, default=1000)
213
214
    assert mock_prompt.call_args == mocker.call(
215
        v.prompt,
216
        default=1000,
217
        hide_input=v.hide_input,
218
        type=click.INT,
219
    )
220
221
    assert r == EXPECTED_VALUE
222
223
224 View Code Duplication
def test_prompt_float(mocker):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
225
226
    EXPECTED_VALUE = 3.14
227
228
    mock_prompt = mocker.patch(
229
        'cookiecutter.prompt.click.prompt',
230
        autospec=True,
231
        return_value=EXPECTED_VALUE,
232
    )
233
234
    m = mocker.Mock()
235
    m.side_effect = context.Variable
236
    v = m.side_effect(name='PI', default=3.0, prompt='Enter PI', hide_input=False)
237
238
    r = context.prompt_float(v, default=3.0)
239
240
    assert mock_prompt.call_args == mocker.call(
241
        v.prompt,
242
        default=3.0,
243
        hide_input=v.hide_input,
244
        type=click.FLOAT,
245
    )
246
247
    assert r == EXPECTED_VALUE
248
249
250 View Code Duplication
def test_prompt_uuid(mocker):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
251
252
    EXPECTED_VALUE = '931ef56c3e7b45eea0427bac386f0a98'
253
254
    mock_prompt = mocker.patch(
255
        'cookiecutter.prompt.click.prompt',
256
        autospec=True,
257
        return_value=EXPECTED_VALUE,
258
    )
259
260
    m = mocker.Mock()
261
    m.side_effect = context.Variable
262
    v = m.side_effect(name='uuid', default=None, prompt='Enter a UUID', hide_input=False)
263
264
    r = context.prompt_uuid(v, default=None)
265
266
    assert mock_prompt.call_args == mocker.call(
267
        v.prompt,
268
        default=None,
269
        hide_input=v.hide_input,
270
        type=click.UUID,
271
    )
272
273
    assert r == EXPECTED_VALUE
274
275
276
def test_prompt_json(monkeypatch, mocker):
277
278
    EXPECTED_VALUE = '{"port": 67888, "colors": ["red", "green", "blue"]}'
279
280
    mocker.patch(
281
        'click.termui.visible_prompt_func',
282
        autospec=True,
283
        return_value=EXPECTED_VALUE,
284
    )
285
    m = mocker.Mock()
286
    m.side_effect = context.Variable
287
    v = m.side_effect(name='json', default=None, prompt='Enter Config', hide_input=False)
288
289
    r = context.prompt_json(v, default=None)
290
291
    assert r == {"port": 67888, "colors": ["red", "green", "blue"]}
292
293
294 View Code Duplication
def test_prompt_json_bad_json_decode_click_asks_again(mocker, capsys):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
295
296
    EXPECTED_BAD_VALUE = '{"port": 67888, "colors": ["red", "green", "blue"}'
297
    EXPECTED_GOOD_VALUE = '{"port": 67888, "colors": ["red", "green", "blue"]}'
298
299
    mocker.patch(
300
        'click.termui.visible_prompt_func',
301
        autospec=True,
302
        side_effect=[EXPECTED_BAD_VALUE, EXPECTED_GOOD_VALUE]
303
    )
304
    m = mocker.Mock()
305
    m.side_effect = context.Variable
306
    v = m.side_effect(name='json', default=None, prompt='Enter Config', hide_input=False)
307
308
    r = context.prompt_json(v, default=None)
309
310
    out, err = capsys.readouterr()
311
    assert 'Error: Unable to decode to JSON.' in out
312
    assert r == {"port": 67888, "colors": ["red", "green", "blue"]}
313
314
315 View Code Duplication
def test_prompt_json_default(mocker):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
316
    EXPECTED_VALUE = 'default'
317
318
    cfg = '{"port": 67888, "colors": ["red", "green", "blue"]}'
319
320
    mock_prompt = mocker.patch(
321
        'cookiecutter.prompt.click.prompt',
322
        autospec=True,
323
        return_value=EXPECTED_VALUE,
324
    )
325
326
    m = mocker.Mock()
327
    m.side_effect = context.Variable
328
    v = m.side_effect(name='json', default=None, prompt='Enter Config', hide_input=False)
329
330
    r = context.prompt_json(v, default=cfg)
331
332
    assert mock_prompt.call_args == mocker.call(
333
        v.prompt,
334
        default='default',
335
        hide_input=v.hide_input,
336
        type=click.STRING,
337
        value_proc=mocker.ANY,
338
    )
339
340
    assert r == cfg
341
342
343
def test_prompt_yes_no_default_no(mocker):
344
345
    EXPECTED_VALUE = 'y'
346
347
    mock_prompt = mocker.patch(
348
        'cookiecutter.prompt.click.prompt',
349
        autospec=True,
350
        return_value=EXPECTED_VALUE,
351
    )
352
353
    m = mocker.Mock()
354
    m.side_effect = context.Variable
355
    v = m.side_effect(name='enable_docs', default='n', prompt='Enable docs', hide_input=False)
356
357
    r = context.prompt_yes_no(v, default=False)
358
359
    assert mock_prompt.call_args == mocker.call(
360
        v.prompt,
361
        default='n',
362
        hide_input=v.hide_input,
363
        type=click.BOOL,
364
    )
365
366
    assert r           # EXPECTED_VALUE
367
368
369
def test_prompt_yes_no_default_yes(mocker):
370
371
    EXPECTED_VALUE = 'y'
372
373
    mock_prompt = mocker.patch(
374
        'cookiecutter.prompt.click.prompt',
375
        autospec=True,
376
        return_value=EXPECTED_VALUE,
377
    )
378
379
    m = mocker.Mock()
380
    m.side_effect = context.Variable
381
    v = m.side_effect(name='enable_docs', default='y', prompt='Enable docs', hide_input=False)
382
383
    r = context.prompt_yes_no(v, default=True)
384
385
    assert mock_prompt.call_args == mocker.call(
386
        v.prompt,
387
        default='y',
388
        hide_input=v.hide_input,
389
        type=click.BOOL,
390
    )
391
392
    assert r           # EXPECTED_VALUE
393
394
395
def test_prompt_choice(mocker):
396
397
    LICENSES = ['ISC', 'MIT', 'BSD3']
398
399
    DEFAULT_LICENSE = 'ISC'
400
401
    EXPECTED_VALUE = '2'
402
    EXPECTED_LICENSE = 'MIT'
403
404
    mocker.patch(
405
        'cookiecutter.prompt.click.prompt',
406
        autospec=True,
407
        return_value=EXPECTED_VALUE,
408
    )
409
410
    m = mocker.Mock()
411
    m.side_effect = context.Variable
412
    v = m.side_effect(name='license', default=DEFAULT_LICENSE, choices=LICENSES,
413
                      prompt='Pick a License', hide_input=False)
414
415
    r = context.prompt_choice(v, default=DEFAULT_LICENSE)
416
417
    assert r == EXPECTED_LICENSE
418
419
420
def test_variable_invalid_type_exception():
421
422
    with pytest.raises(ValueError) as excinfo:
423
        context.Variable(name='badtype', default=None, type='color')
424
425
    assert 'Variable: badtype has an invalid type color' in str(excinfo.value)
426
427
428
def test_variable_invalid_default_choice():
429
430
    CHOICES = ['green', 'red', 'blue', 'yellow']
431
432
    with pytest.raises(ValueError) as excinfo:
433
        context.Variable(name='badchoice', default='purple', type='string',
434
                         choices=CHOICES)
435
436
    assert 'Variable: badchoice has an invalid default value purple for choices: {choices}'.format(choices=CHOICES) in str(excinfo.value)
437
438
439
def test_variable_invalid_validation_control_flag_is_logged_and_removed(caplog):
440
441
    with caplog.at_level(logging.INFO):
442
        v = context.Variable(
443
            'module_name',
444
            "{{cookiecutter.plugin_name|lower|replace('-','_')}}",
445
            prompt="Please enter a name for your base python module",
446
            type='string',
447
            validation='^[a-z_]+$',
448
            validation_flags=['ignorecase', 'forget', ],
449
            hide_input=True)
450
451
        for record in caplog.records:
452
            assert record.levelname == 'WARNING'
453
454
        assert "Variable: module_name - Ignoring unkown RegEx validation Control Flag named 'forget'" in caplog.text
455
456
        assert v.validation_flag_names == ['ignorecase']
457
458
459
def test_variable_validation_compile_exception():
460
461
    VAR_NAME = 'module_name'
462
    BAD_REGEX_STRING = '^[a-z_+$'   # Missing a closing square-bracket (])
463
464
    with pytest.raises(ValueError) as excinfo:
465
        context.Variable(
466
            VAR_NAME,
467
            "{{cookiecutter.plugin_name|lower|replace('-','_')}}",
468
            prompt="Please enter a name for your base python module",
469
            type='string',
470
            validation=BAD_REGEX_STRING,
471
            validation_flags=['ignorecase'],
472
            hide_input=True)
473
474
    assert "Variable: {var_name} - Validation Setup Error: Invalid RegEx '{value}' - does not compile - ".format(var_name=VAR_NAME, value=BAD_REGEX_STRING) in str(excinfo.value)
475
476
477
def test_variable_forces_no_prompt_for_private_variable_names():
478
    v = context.Variable(
479
        '_private_variable_name',
480
        "{{cookiecutter.plugin_name|lower|replace('-','_')}}",
481
        prompt="Please enter a name for your base python module",
482
        prompt_user=True,
483
        type='string',
484
        validation='^[a-z_]+$',
485
        validation_flags=['ignorecase'],
486
        hide_input=True)
487
488
    assert v.prompt_user == False
489
490
491
def test_variable_repr():
492
493
    v = context.Variable(
494
        'module_name',
495
        "{{cookiecutter.plugin_name|lower|replace('-','_')}}",
496
        prompt="Please enter a name for your base python module",
497
        type='string',
498
        validation='^[a-z_]+$',
499
        validation_flags=['ignorecase'],
500
        hide_input=True)
501
502
    assert repr(v) == "<Variable module_name>"
503
504
505
def test_variable_str():
506
507
    v = context.Variable(
508
        'module_name',
509
        "{{cookiecutter.plugin_name|lower|replace('-','_')}}",
510
        prompt="Please enter a name for your base python module",
511
        type='string',
512
        validation='^[a-z_]+$',
513
        validation_flags=['ignorecase'],
514
        hide_input=True)
515
516
    assert '<Variable module_name>:' in str(v)
517
    assert "name='module_name'" in str(v)
518
    assert "default='{{cookiecutter.plugin_name|lower|replace('-','_')}}'" in str(v)
519
    assert "description='None'" in str(v)
520
    assert "prompt='Please enter a name for your base python module'" in str(v)
521
    assert "hide_input='True'" in str(v)
522
    assert "var_type='string'" in str(v)
523
    assert "skip_if=''" in str(v)
524
    assert "prompt_user='True'" in str(v)
525
    assert "choices='[]'" in str(v)
526
    assert "validation='^[a-z_]+$'" in str(v)
527
    assert "validation_flag_names='['ignorecase']'" in str(v)
528
    assert "validation_flags='2'" in str(v)
529
530
    if sys.version_info >= (3, 4):
531
        assert "validate='re.compile('^[a-z_]+$', re.IGNORECASE)'" in str(v)
532
    else:
533
        assert "validate='<_sre.SRE_Pattern object at" in str(v)
534
535
536
def test_variable_option_raise_invalid_type_value_error():
537
538
    VAR_NAME = 'module_name'
539
    OPT_VALUE_OF_INCORRECT_TYPE = 12   # should be a string
540
541
    with pytest.raises(ValueError) as excinfo:
542
        context.Variable(
543
            VAR_NAME,
544
            "{{cookiecutter.plugin_name|lower|replace('-','_')}}",
545
            prompt="Please enter a name for your base python module",
546
            type='string',
547
            validation=OPT_VALUE_OF_INCORRECT_TYPE,
548
            validation_flags=['ignorecase'],
549
            hide_input=True)
550
551
    msg = "Variable: '{var_name}' Option: 'validation' requires a value of type str, but has a value of: {value}"
552
    assert msg.format(var_name=VAR_NAME, value=OPT_VALUE_OF_INCORRECT_TYPE) in str(excinfo.value)
553
554
555
def test_cookiecutter_template_repr():
556
    #  name, cookiecutter_version, variables, **info
557
558
    cct = context.CookiecutterTemplate('cookiecutter_template_repr_test',
559
                                       cookiecutter_version='2.0.0', variables=[])
560
561
    assert repr(cct) == "<CookiecutterTemplate cookiecutter_template_repr_test>"
562
563
564 View Code Duplication
def test_load_context_with_input_chioces(mocker):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
565
    cc = load_cookiecutter('tests/test-context/cookiecutter_choices.json')
566
567
    INPUT_1 = 'E.R. Uber'
568
    INPUT_2 = '[email protected]'
569
    INPUT_3 = '2'  # 'MIT'
570
    mocker.patch(
571
        'click.termui.visible_prompt_func',
572
        autospec=True,
573
        side_effect=[INPUT_1, INPUT_2, INPUT_3]
574
    )
575
576
    cc_cfg = context.load_context(cc['cookiecutter_choices'], no_input=False)
577
578
    assert cc_cfg['full_name'] == INPUT_1
579
    assert cc_cfg['email'] == INPUT_2
580
    assert cc_cfg['license'] == 'MIT'
581
582
583
def test_load_context_with_input_with_validation_success(mocker):
584
    cc = load_cookiecutter('tests/test-context/cookiecutter_val_success.json')
585
586
    INPUT_1 = 'Image Module Maker'
587
    INPUT_2 = ''
588
    mocker.patch(
589
        'click.termui.visible_prompt_func',
590
        autospec=True,
591
        side_effect=[INPUT_1, INPUT_2]
592
    )
593
594
    logger.debug(cc)
595
596
    cc_cfg = context.load_context(cc['cookiecutter_val_success'], no_input=False)
597
598
    assert cc_cfg['project_name'] == INPUT_1
599
    assert cc_cfg['module_name'] == 'image_module_maker'
600
601
602 View Code Duplication
def test_load_context_with_input_with_validation_failure(mocker, capsys):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
603
    cc = load_cookiecutter('tests/test-context/cookiecutter_val_failure.json')
604
605
    INPUT_1 = '6 Debug Shell'
606
    INPUT_2 = ''
607
    INPUT_3 = 'debug_shell'
608
    mocker.patch(
609
        'click.termui.visible_prompt_func',
610
        autospec=True,
611
        side_effect=[INPUT_1, INPUT_2, INPUT_3]
612
    )
613
614
    cc_cfg = context.load_context(cc['cookiecutter_val_failure'], no_input=False)
615
616
    out, err = capsys.readouterr()
617
618
    msg = "Input validation failure against regex: '^[a-z_]+$', try again!"
619
    assert msg in out
620
621
    assert cc_cfg['project_name'] == INPUT_1
622
    assert cc_cfg['module_name'] == INPUT_3
623