Passed
Push — master ( af4474...591779 )
by Andrey
04:46
created

test_debug_list_installed_templates()   A

Complexity

Conditions 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 15
rs 9.85
c 0
b 0
f 0
cc 2
nop 3
1
"""Collection of tests around cookiecutter's command-line interface."""
2
3
import json
4
import os
5
import sys
6
7
import pytest
8
from click.testing import CliRunner
9
10
from cookiecutter import utils
11
from cookiecutter.__main__ import main
12
from cookiecutter.main import cookiecutter
13
14
15
@pytest.fixture(scope='session')
16
def cli_runner():
17
    """Fixture that returns a helper function to run the cookiecutter cli."""
18
    runner = CliRunner()
19
20
    def cli_main(*cli_args, **cli_kwargs):
21
        """Run cookiecutter cli main with the given args."""
22
        return runner.invoke(main, cli_args, **cli_kwargs)
23
24
    return cli_main
25
26
27
@pytest.fixture
28
def remove_fake_project_dir(request):
29
    """Remove the fake project directory created during the tests."""
30
31
    def fin_remove_fake_project_dir():
32
        if os.path.isdir('fake-project'):
33
            utils.rmtree('fake-project')
34
35
    request.addfinalizer(fin_remove_fake_project_dir)
36
37
38
@pytest.fixture
39
def make_fake_project_dir(request):
40
    """Create a fake project to be overwritten in the according tests."""
41
    os.makedirs('fake-project')
42
43
44
@pytest.fixture(params=['-V', '--version'])
45
def version_cli_flag(request):
46
    """Pytest fixture return both version invocation options."""
47
    return request.param
48
49
50
def test_cli_version(cli_runner, version_cli_flag):
51
    """Verify correct version output by `cookiecutter` on cli invocation."""
52
    result = cli_runner(version_cli_flag)
53
    assert result.exit_code == 0
54
    assert result.output.startswith('Cookiecutter')
55
56
57
@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
58
def test_cli_error_on_existing_output_directory(cli_runner):
59
    """Test cli invocation without `overwrite-if-exists` fail if dir exist."""
60
    result = cli_runner('tests/fake-repo-pre/', '--no-input')
61
    assert result.exit_code != 0
62
    expected_error_msg = 'Error: "fake-project" directory already exists\n'
63
    assert result.output == expected_error_msg
64
65
66
@pytest.mark.usefixtures('remove_fake_project_dir')
67
def test_cli(cli_runner):
68
    """Test cli invocation work without flags if directory not exist."""
69
    result = cli_runner('tests/fake-repo-pre/', '--no-input')
70
    assert result.exit_code == 0
71
    assert os.path.isdir('fake-project')
72
    with open(os.path.join('fake-project', 'README.rst')) as f:
73
        assert 'Project name: **Fake Project**' in f.read()
74
75
76
@pytest.mark.usefixtures('remove_fake_project_dir')
77
def test_cli_verbose(cli_runner):
78
    """Test cli invocation display log if called with `verbose` flag."""
79
    result = cli_runner('tests/fake-repo-pre/', '--no-input', '-v')
80
    assert result.exit_code == 0
81
    assert os.path.isdir('fake-project')
82
    with open(os.path.join('fake-project', 'README.rst')) as f:
83
        assert 'Project name: **Fake Project**' in f.read()
84
85
86 View Code Duplication
@pytest.mark.usefixtures('remove_fake_project_dir')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
87
def test_cli_replay(mocker, cli_runner):
88
    """Test cli invocation display log with `verbose` and `replay` flags."""
89
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
90
91
    template_path = 'tests/fake-repo-pre/'
92
    result = cli_runner(template_path, '--replay', '-v')
93
94
    assert result.exit_code == 0
95
    mock_cookiecutter.assert_called_once_with(
96
        template_path,
97
        None,
98
        False,
99
        replay=True,
100
        overwrite_if_exists=False,
101
        skip_if_file_exists=False,
102
        output_dir='.',
103
        config_file=None,
104
        default_config=False,
105
        extra_context=None,
106
        password=None,
107
        directory=None,
108
        accept_hooks=True,
109
    )
110
111
112
@pytest.mark.usefixtures('remove_fake_project_dir')
113
def test_cli_replay_file(mocker, cli_runner):
114
    """Test cli invocation correctly pass --replay-file option."""
115
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
116
117
    template_path = 'tests/fake-repo-pre/'
118
    result = cli_runner(template_path, '--replay-file', '~/custom-replay-file', '-v')
119
120
    assert result.exit_code == 0
121
    mock_cookiecutter.assert_called_once_with(
122
        template_path,
123
        None,
124
        False,
125
        replay='~/custom-replay-file',
126
        overwrite_if_exists=False,
127
        skip_if_file_exists=False,
128
        output_dir='.',
129
        config_file=None,
130
        default_config=False,
131
        extra_context=None,
132
        password=None,
133
        directory=None,
134
        accept_hooks=True,
135
    )
136
137
138
@pytest.mark.usefixtures('remove_fake_project_dir')
139
def test_cli_exit_on_noinput_and_replay(mocker, cli_runner):
140
    """Test cli invocation fail if both `no-input` and `replay` flags passed."""
141
    mock_cookiecutter = mocker.patch(
142
        'cookiecutter.cli.cookiecutter', side_effect=cookiecutter
143
    )
144
145
    template_path = 'tests/fake-repo-pre/'
146
    result = cli_runner(template_path, '--no-input', '--replay', '-v')
147
148
    assert result.exit_code == 1
149
150
    expected_error_msg = (
151
        "You can not use both replay and no_input or extra_context at the same time."
152
    )
153
154
    assert expected_error_msg in result.output
155
156
    mock_cookiecutter.assert_called_once_with(
157
        template_path,
158
        None,
159
        True,
160
        replay=True,
161
        overwrite_if_exists=False,
162
        skip_if_file_exists=False,
163
        output_dir='.',
164
        config_file=None,
165
        default_config=False,
166
        extra_context=None,
167
        password=None,
168
        directory=None,
169
        accept_hooks=True,
170
    )
171
172
173
@pytest.fixture(params=['-f', '--overwrite-if-exists'])
174
def overwrite_cli_flag(request):
175
    """Pytest fixture return all `overwrite-if-exists` invocation options."""
176
    return request.param
177
178
179
@pytest.mark.usefixtures('remove_fake_project_dir')
180
def test_run_cookiecutter_on_overwrite_if_exists_and_replay(
181
    mocker, cli_runner, overwrite_cli_flag
182
):
183
    """Test cli invocation with `overwrite-if-exists` and `replay` flags."""
184
    mock_cookiecutter = mocker.patch(
185
        'cookiecutter.cli.cookiecutter', side_effect=cookiecutter
186
    )
187
188
    template_path = 'tests/fake-repo-pre/'
189
    result = cli_runner(template_path, '--replay', '-v', overwrite_cli_flag)
190
191
    assert result.exit_code == 0
192
193
    mock_cookiecutter.assert_called_once_with(
194
        template_path,
195
        None,
196
        False,
197
        replay=True,
198
        overwrite_if_exists=True,
199
        skip_if_file_exists=False,
200
        output_dir='.',
201
        config_file=None,
202
        default_config=False,
203
        extra_context=None,
204
        password=None,
205
        directory=None,
206
        accept_hooks=True,
207
    )
208
209
210
@pytest.mark.usefixtures('remove_fake_project_dir')
211
def test_cli_overwrite_if_exists_when_output_dir_does_not_exist(
212
    cli_runner, overwrite_cli_flag
213
):
214
    """Test cli invocation with `overwrite-if-exists` and `no-input` flags.
215
216
    Case when output dir not exist.
217
    """
218
    result = cli_runner('tests/fake-repo-pre/', '--no-input', overwrite_cli_flag)
219
220
    assert result.exit_code == 0
221
    assert os.path.isdir('fake-project')
222
223
224
@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
225
def test_cli_overwrite_if_exists_when_output_dir_exists(cli_runner, overwrite_cli_flag):
226
    """Test cli invocation with `overwrite-if-exists` and `no-input` flags.
227
228
    Case when output dir already exist.
229
    """
230
    result = cli_runner('tests/fake-repo-pre/', '--no-input', overwrite_cli_flag)
231
    assert result.exit_code == 0
232
    assert os.path.isdir('fake-project')
233
234
235
@pytest.fixture(params=['-o', '--output-dir'])
236
def output_dir_flag(request):
237
    """Pytest fixture return all output-dir invocation options."""
238
    return request.param
239
240
241
@pytest.fixture
242
def output_dir(tmpdir):
243
    """Pytest fixture return `output_dir` argument as string."""
244
    return str(tmpdir.mkdir('output'))
245
246
247 View Code Duplication
def test_cli_output_dir(mocker, cli_runner, output_dir_flag, output_dir):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
248
    """Test cli invocation with `output-dir` flag changes output directory."""
249
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
250
251
    template_path = 'tests/fake-repo-pre/'
252
    result = cli_runner(template_path, output_dir_flag, output_dir)
253
254
    assert result.exit_code == 0
255
    mock_cookiecutter.assert_called_once_with(
256
        template_path,
257
        None,
258
        False,
259
        replay=False,
260
        overwrite_if_exists=False,
261
        skip_if_file_exists=False,
262
        output_dir=output_dir,
263
        config_file=None,
264
        default_config=False,
265
        extra_context=None,
266
        password=None,
267
        directory=None,
268
        accept_hooks=True,
269
    )
270
271
272
@pytest.fixture(params=['-h', '--help', 'help'])
273
def help_cli_flag(request):
274
    """Pytest fixture return all help invocation options."""
275
    return request.param
276
277
278
def test_cli_help(cli_runner, help_cli_flag):
279
    """Test cli invocation display help message with `help` flag."""
280
    result = cli_runner(help_cli_flag)
281
    assert result.exit_code == 0
282
    assert result.output.startswith('Usage')
283
284
285
@pytest.fixture
286
def user_config_path(tmpdir):
287
    """Pytest fixture return `user_config` argument as string."""
288
    return str(tmpdir.join('tests/config.yaml'))
289
290
291 View Code Duplication
def test_user_config(mocker, cli_runner, user_config_path):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
292
    """Test cli invocation works with `config-file` option."""
293
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
294
295
    template_path = 'tests/fake-repo-pre/'
296
    result = cli_runner(template_path, '--config-file', user_config_path)
297
298
    assert result.exit_code == 0
299
    mock_cookiecutter.assert_called_once_with(
300
        template_path,
301
        None,
302
        False,
303
        replay=False,
304
        overwrite_if_exists=False,
305
        skip_if_file_exists=False,
306
        output_dir='.',
307
        config_file=user_config_path,
308
        default_config=False,
309
        extra_context=None,
310
        password=None,
311
        directory=None,
312
        accept_hooks=True,
313
    )
314
315
316 View Code Duplication
def test_default_user_config_overwrite(mocker, cli_runner, user_config_path):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
317
    """Test cli invocation ignores `config-file` if `default-config` passed."""
318
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
319
320
    template_path = 'tests/fake-repo-pre/'
321
    result = cli_runner(
322
        template_path, '--config-file', user_config_path, '--default-config',
323
    )
324
325
    assert result.exit_code == 0
326
    mock_cookiecutter.assert_called_once_with(
327
        template_path,
328
        None,
329
        False,
330
        replay=False,
331
        overwrite_if_exists=False,
332
        skip_if_file_exists=False,
333
        output_dir='.',
334
        config_file=user_config_path,
335
        default_config=True,
336
        extra_context=None,
337
        password=None,
338
        directory=None,
339
        accept_hooks=True,
340
    )
341
342
343 View Code Duplication
def test_default_user_config(mocker, cli_runner):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
344
    """Test cli invocation accepts `default-config` flag correctly."""
345
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
346
347
    template_path = 'tests/fake-repo-pre/'
348
    result = cli_runner(template_path, '--default-config')
349
350
    assert result.exit_code == 0
351
    mock_cookiecutter.assert_called_once_with(
352
        template_path,
353
        None,
354
        False,
355
        replay=False,
356
        overwrite_if_exists=False,
357
        skip_if_file_exists=False,
358
        output_dir='.',
359
        config_file=None,
360
        default_config=True,
361
        extra_context=None,
362
        password=None,
363
        directory=None,
364
        accept_hooks=True,
365
    )
366
367
368
@pytest.mark.skipif(
369
    sys.version_info[0] == 3 and sys.version_info[1] == 6 and sys.version_info[2] == 1,
370
    reason="Outdated pypy3 version on Travis CI/CD with wrong OrderedDict syntax.",
371
)
372
def test_echo_undefined_variable_error(tmpdir, cli_runner):
373
    """Cli invocation return error if variable undefined in template."""
374
    output_dir = str(tmpdir.mkdir('output'))
375
    template_path = 'tests/undefined-variable/file-name/'
376
377
    result = cli_runner(
378
        '--no-input', '--default-config', '--output-dir', output_dir, template_path,
379
    )
380
381
    assert result.exit_code == 1
382
383
    error = "Unable to create file '{{cookiecutter.foobar}}'"
384
    assert error in result.output
385
386
    message = (
387
        "Error message: 'collections.OrderedDict object' has no attribute 'foobar'"
388
    )
389
    assert message in result.output
390
391
    context = {
392
        'cookiecutter': {
393
            'github_username': 'hackebrot',
394
            'project_slug': 'testproject',
395
            '_template': template_path,
396
            '_output_dir': output_dir,
397
        }
398
    }
399
    context_str = json.dumps(context, indent=4, sort_keys=True)
400
    assert context_str in result.output
401
402
403
def test_echo_unknown_extension_error(tmpdir, cli_runner):
404
    """Cli return error if extension incorrectly defined in template."""
405
    output_dir = str(tmpdir.mkdir('output'))
406
    template_path = 'tests/test-extensions/unknown/'
407
408
    result = cli_runner(
409
        '--no-input', '--default-config', '--output-dir', output_dir, template_path,
410
    )
411
412
    assert result.exit_code == 1
413
414
    assert 'Unable to load extension: ' in result.output
415
416
417
@pytest.mark.usefixtures('remove_fake_project_dir')
418
def test_cli_extra_context(cli_runner):
419
    """Cli invocation replace content if called with replacement pairs."""
420
    result = cli_runner(
421
        'tests/fake-repo-pre/', '--no-input', '-v', 'project_name=Awesomez',
422
    )
423
    assert result.exit_code == 0
424
    assert os.path.isdir('fake-project')
425
    with open(os.path.join('fake-project', 'README.rst')) as f:
426
        assert 'Project name: **Awesomez**' in f.read()
427
428
429
@pytest.mark.usefixtures('remove_fake_project_dir')
430
def test_cli_extra_context_invalid_format(cli_runner):
431
    """Cli invocation raise error if called with unknown argument."""
432
    result = cli_runner(
433
        'tests/fake-repo-pre/', '--no-input', '-v', 'ExtraContextWithNoEqualsSoInvalid',
434
    )
435
    assert result.exit_code == 2
436
    assert "Error: Invalid value for '[EXTRA_CONTEXT]...'" in result.output
437
    assert 'should contain items of the form key=value' in result.output
438
439
440
@pytest.fixture
441
def debug_file(tmpdir):
442
    """Pytest fixture return `debug_file` argument as path object."""
443
    return tmpdir.join('fake-repo.log')
444
445
446 View Code Duplication
@pytest.mark.usefixtures('remove_fake_project_dir')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
447
def test_debug_file_non_verbose(cli_runner, debug_file):
448
    """Test cli invocation writes log to `debug-file` if flag enabled.
449
450
    Case for normal log output.
451
    """
452
    assert not debug_file.exists()
453
454
    result = cli_runner(
455
        '--no-input', '--debug-file', str(debug_file), 'tests/fake-repo-pre/',
456
    )
457
    assert result.exit_code == 0
458
459
    assert debug_file.exists()
460
461
    context_log = (
462
        "DEBUG cookiecutter.main: context_file is "
463
        "tests/fake-repo-pre/cookiecutter.json"
464
    )
465
    assert context_log in debug_file.readlines(cr=False)
466
    assert context_log not in result.output
467
468
469 View Code Duplication
@pytest.mark.usefixtures('remove_fake_project_dir')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
470
def test_debug_file_verbose(cli_runner, debug_file):
471
    """Test cli invocation writes log to `debug-file` if flag enabled.
472
473
    Case for verbose log output.
474
    """
475
    assert not debug_file.exists()
476
477
    result = cli_runner(
478
        '--verbose',
479
        '--no-input',
480
        '--debug-file',
481
        str(debug_file),
482
        'tests/fake-repo-pre/',
483
    )
484
    assert result.exit_code == 0
485
486
    assert debug_file.exists()
487
488
    context_log = (
489
        "DEBUG cookiecutter.main: context_file is "
490
        "tests/fake-repo-pre/cookiecutter.json"
491
    )
492
    assert context_log in debug_file.readlines(cr=False)
493
    assert context_log in result.output
494
495
496
@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
497
def test_debug_list_installed_templates(cli_runner, debug_file, user_config_path):
498
    """Verify --list-installed command correct invocation."""
499
    fake_template_dir = os.path.dirname(os.path.abspath('fake-project'))
500
    os.makedirs(os.path.dirname(user_config_path))
501
    with open(user_config_path, 'w') as config_file:
502
        config_file.write('cookiecutters_dir: "%s"' % fake_template_dir)
503
    open(os.path.join('fake-project', 'cookiecutter.json'), 'w').write('{}')
504
505
    result = cli_runner(
506
        '--list-installed', '--config-file', user_config_path, str(debug_file),
507
    )
508
509
    assert "1 installed templates:" in result.output
510
    assert result.exit_code == 0
511
512
513
def test_debug_list_installed_templates_failure(
514
    cli_runner, debug_file, user_config_path
515
):
516
    """Verify --list-installed command error on invocation."""
517
    os.makedirs(os.path.dirname(user_config_path))
518
    with open(user_config_path, 'w') as config_file:
519
        config_file.write('cookiecutters_dir: "/notarealplace/"')
520
521
    result = cli_runner(
522
        '--list-installed', '--config-file', user_config_path, str(debug_file)
523
    )
524
525
    assert "Error: Cannot list installed templates." in result.output
526
    assert result.exit_code == -1
527
528
529
@pytest.mark.usefixtures('remove_fake_project_dir')
530
def test_directory_repo(cli_runner):
531
    """Test cli invocation works with `directory` option."""
532
    result = cli_runner(
533
        'tests/fake-repo-dir/', '--no-input', '-v', '--directory=my-dir',
534
    )
535
    assert result.exit_code == 0
536
    assert os.path.isdir("fake-project")
537
    with open(os.path.join("fake-project", "README.rst")) as f:
538
        assert "Project name: **Fake Project**" in f.read()
539
540
541
cli_accept_hook_arg_testdata = [
542
    ("--accept-hooks=yes", None, True),
543
    ("--accept-hooks=no", None, False),
544
    ("--accept-hooks=ask", "yes", True),
545
    ("--accept-hooks=ask", "no", False),
546
]
547
548
549
@pytest.mark.parametrize(
550
    "accept_hooks_arg,user_input,expected", cli_accept_hook_arg_testdata
551
)
552
def test_cli_accept_hooks(
553
    mocker,
554
    cli_runner,
555
    output_dir_flag,
556
    output_dir,
557
    accept_hooks_arg,
558
    user_input,
559
    expected,
560
):
561
    """Test cli invocation works with `accept-hooks` option."""
562
    mock_cookiecutter = mocker.patch("cookiecutter.cli.cookiecutter")
563
564
    template_path = "tests/fake-repo-pre/"
565
    result = cli_runner(
566
        template_path, output_dir_flag, output_dir, accept_hooks_arg, input=user_input
567
    )
568
569
    assert result.exit_code == 0
570
    mock_cookiecutter.assert_called_once_with(
571
        template_path,
572
        None,
573
        False,
574
        replay=False,
575
        overwrite_if_exists=False,
576
        output_dir=output_dir,
577
        config_file=None,
578
        default_config=False,
579
        extra_context=None,
580
        password=None,
581
        directory=None,
582
        skip_if_file_exists=False,
583
        accept_hooks=expected,
584
    )
585