Passed
Push — master ( 90434f...947a7f )
by Andrey
01:04
created

tests.test_cli.test_cli_accept_hooks()   A

Complexity

Conditions 1

Size

Total Lines 35
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 35
rs 9.184
c 0
b 0
f 0
cc 1
nop 7
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_exit_on_noinput_and_replay(mocker, cli_runner):
114
    """Test cli invocation fail if both `no-input` and `replay` flags passed."""
115
    mock_cookiecutter = mocker.patch(
116
        'cookiecutter.cli.cookiecutter', side_effect=cookiecutter
117
    )
118
119
    template_path = 'tests/fake-repo-pre/'
120
    result = cli_runner(template_path, '--no-input', '--replay', '-v')
121
122
    assert result.exit_code == 1
123
124
    expected_error_msg = (
125
        "You can not use both replay and no_input or extra_context at the same time."
126
    )
127
128
    assert expected_error_msg in result.output
129
130
    mock_cookiecutter.assert_called_once_with(
131
        template_path,
132
        None,
133
        True,
134
        replay=True,
135
        overwrite_if_exists=False,
136
        skip_if_file_exists=False,
137
        output_dir='.',
138
        config_file=None,
139
        default_config=False,
140
        extra_context=None,
141
        password=None,
142
        directory=None,
143
        accept_hooks=True,
144
    )
145
146
147
@pytest.fixture(params=['-f', '--overwrite-if-exists'])
148
def overwrite_cli_flag(request):
149
    """Pytest fixture return all `overwrite-if-exists` invocation options."""
150
    return request.param
151
152
153
@pytest.mark.usefixtures('remove_fake_project_dir')
154
def test_run_cookiecutter_on_overwrite_if_exists_and_replay(
155
    mocker, cli_runner, overwrite_cli_flag
156
):
157
    """Test cli invocation with `overwrite-if-exists` and `replay` flags."""
158
    mock_cookiecutter = mocker.patch(
159
        'cookiecutter.cli.cookiecutter', side_effect=cookiecutter
160
    )
161
162
    template_path = 'tests/fake-repo-pre/'
163
    result = cli_runner(template_path, '--replay', '-v', overwrite_cli_flag)
164
165
    assert result.exit_code == 0
166
167
    mock_cookiecutter.assert_called_once_with(
168
        template_path,
169
        None,
170
        False,
171
        replay=True,
172
        overwrite_if_exists=True,
173
        skip_if_file_exists=False,
174
        output_dir='.',
175
        config_file=None,
176
        default_config=False,
177
        extra_context=None,
178
        password=None,
179
        directory=None,
180
        accept_hooks=True,
181
    )
182
183
184
@pytest.mark.usefixtures('remove_fake_project_dir')
185
def test_cli_overwrite_if_exists_when_output_dir_does_not_exist(
186
    cli_runner, overwrite_cli_flag
187
):
188
    """Test cli invocation with `overwrite-if-exists` and `no-input` flags.
189
190
    Case when output dir not exist.
191
    """
192
    result = cli_runner('tests/fake-repo-pre/', '--no-input', overwrite_cli_flag)
193
194
    assert result.exit_code == 0
195
    assert os.path.isdir('fake-project')
196
197
198
@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
199
def test_cli_overwrite_if_exists_when_output_dir_exists(cli_runner, overwrite_cli_flag):
200
    """Test cli invocation with `overwrite-if-exists` and `no-input` flags.
201
202
    Case when output dir already exist.
203
    """
204
    result = cli_runner('tests/fake-repo-pre/', '--no-input', overwrite_cli_flag)
205
    assert result.exit_code == 0
206
    assert os.path.isdir('fake-project')
207
208
209
@pytest.fixture(params=['-o', '--output-dir'])
210
def output_dir_flag(request):
211
    """Pytest fixture return all output-dir invocation options."""
212
    return request.param
213
214
215
@pytest.fixture
216
def output_dir(tmpdir):
217
    """Pytest fixture return `output_dir` argument as string."""
218
    return str(tmpdir.mkdir('output'))
219
220
221 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...
222
    """Test cli invocation with `output-dir` flag changes output directory."""
223
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
224
225
    template_path = 'tests/fake-repo-pre/'
226
    result = cli_runner(template_path, output_dir_flag, output_dir)
227
228
    assert result.exit_code == 0
229
    mock_cookiecutter.assert_called_once_with(
230
        template_path,
231
        None,
232
        False,
233
        replay=False,
234
        overwrite_if_exists=False,
235
        skip_if_file_exists=False,
236
        output_dir=output_dir,
237
        config_file=None,
238
        default_config=False,
239
        extra_context=None,
240
        password=None,
241
        directory=None,
242
        accept_hooks=True,
243
    )
244
245
246
@pytest.fixture(params=['-h', '--help', 'help'])
247
def help_cli_flag(request):
248
    """Pytest fixture return all help invocation options."""
249
    return request.param
250
251
252
def test_cli_help(cli_runner, help_cli_flag):
253
    """Test cli invocation display help message with `help` flag."""
254
    result = cli_runner(help_cli_flag)
255
    assert result.exit_code == 0
256
    assert result.output.startswith('Usage')
257
258
259
@pytest.fixture
260
def user_config_path(tmpdir):
261
    """Pytest fixture return `user_config` argument as string."""
262
    return str(tmpdir.join('tests/config.yaml'))
263
264
265 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...
266
    """Test cli invocation works with `config-file` option."""
267
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
268
269
    template_path = 'tests/fake-repo-pre/'
270
    result = cli_runner(template_path, '--config-file', user_config_path)
271
272
    assert result.exit_code == 0
273
    mock_cookiecutter.assert_called_once_with(
274
        template_path,
275
        None,
276
        False,
277
        replay=False,
278
        overwrite_if_exists=False,
279
        skip_if_file_exists=False,
280
        output_dir='.',
281
        config_file=user_config_path,
282
        default_config=False,
283
        extra_context=None,
284
        password=None,
285
        directory=None,
286
        accept_hooks=True,
287
    )
288
289
290 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...
291
    """Test cli invocation ignores `config-file` if `default-config` passed."""
292
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
293
294
    template_path = 'tests/fake-repo-pre/'
295
    result = cli_runner(
296
        template_path, '--config-file', user_config_path, '--default-config',
297
    )
298
299
    assert result.exit_code == 0
300
    mock_cookiecutter.assert_called_once_with(
301
        template_path,
302
        None,
303
        False,
304
        replay=False,
305
        overwrite_if_exists=False,
306
        skip_if_file_exists=False,
307
        output_dir='.',
308
        config_file=user_config_path,
309
        default_config=True,
310
        extra_context=None,
311
        password=None,
312
        directory=None,
313
        accept_hooks=True,
314
    )
315
316
317 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...
318
    """Test cli invocation accepts `default-config` flag correctly."""
319
    mock_cookiecutter = mocker.patch('cookiecutter.cli.cookiecutter')
320
321
    template_path = 'tests/fake-repo-pre/'
322
    result = cli_runner(template_path, '--default-config')
323
324
    assert result.exit_code == 0
325
    mock_cookiecutter.assert_called_once_with(
326
        template_path,
327
        None,
328
        False,
329
        replay=False,
330
        overwrite_if_exists=False,
331
        skip_if_file_exists=False,
332
        output_dir='.',
333
        config_file=None,
334
        default_config=True,
335
        extra_context=None,
336
        password=None,
337
        directory=None,
338
        accept_hooks=True,
339
    )
340
341
342
@pytest.mark.skipif(
343
    sys.version_info[0] == 3 and sys.version_info[1] == 6 and sys.version_info[2] == 1,
344
    reason="Outdated pypy3 version on Travis CI/CD with wrong OrderedDict syntax.",
345
)
346
def test_echo_undefined_variable_error(tmpdir, cli_runner):
347
    """Cli invocation return error if variable undefined in template."""
348
    output_dir = str(tmpdir.mkdir('output'))
349
    template_path = 'tests/undefined-variable/file-name/'
350
351
    result = cli_runner(
352
        '--no-input', '--default-config', '--output-dir', output_dir, template_path,
353
    )
354
355
    assert result.exit_code == 1
356
357
    error = "Unable to create file '{{cookiecutter.foobar}}'"
358
    assert error in result.output
359
360
    message = (
361
        "Error message: 'collections.OrderedDict object' has no attribute 'foobar'"
362
    )
363
    assert message in result.output
364
365
    context = {
366
        'cookiecutter': {
367
            'github_username': 'hackebrot',
368
            'project_slug': 'testproject',
369
            '_template': template_path,
370
        }
371
    }
372
    context_str = json.dumps(context, indent=4, sort_keys=True)
373
    assert context_str in result.output
374
375
376
def test_echo_unknown_extension_error(tmpdir, cli_runner):
377
    """Cli return error if extension incorrectly defined in template."""
378
    output_dir = str(tmpdir.mkdir('output'))
379
    template_path = 'tests/test-extensions/unknown/'
380
381
    result = cli_runner(
382
        '--no-input', '--default-config', '--output-dir', output_dir, template_path,
383
    )
384
385
    assert result.exit_code == 1
386
387
    assert 'Unable to load extension: ' in result.output
388
389
390
@pytest.mark.usefixtures('remove_fake_project_dir')
391
def test_cli_extra_context(cli_runner):
392
    """Cli invocation replace content if called with replacement pairs."""
393
    result = cli_runner(
394
        'tests/fake-repo-pre/', '--no-input', '-v', 'project_name=Awesomez',
395
    )
396
    assert result.exit_code == 0
397
    assert os.path.isdir('fake-project')
398
    with open(os.path.join('fake-project', 'README.rst')) as f:
399
        assert 'Project name: **Awesomez**' in f.read()
400
401
402
@pytest.mark.usefixtures('remove_fake_project_dir')
403
def test_cli_extra_context_invalid_format(cli_runner):
404
    """Cli invocation raise error if called with unknown argument."""
405
    result = cli_runner(
406
        'tests/fake-repo-pre/', '--no-input', '-v', 'ExtraContextWithNoEqualsSoInvalid',
407
    )
408
    assert result.exit_code == 2
409
    assert "Error: Invalid value for '[EXTRA_CONTEXT]...'" in result.output
410
    assert 'should contain items of the form key=value' in result.output
411
412
413
@pytest.fixture
414
def debug_file(tmpdir):
415
    """Pytest fixture return `debug_file` argument as path object."""
416
    return tmpdir.join('fake-repo.log')
417
418
419 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...
420
def test_debug_file_non_verbose(cli_runner, debug_file):
421
    """Test cli invocation writes log to `debug-file` if flag enabled.
422
423
    Case for normal log output.
424
    """
425
    assert not debug_file.exists()
426
427
    result = cli_runner(
428
        '--no-input', '--debug-file', str(debug_file), 'tests/fake-repo-pre/',
429
    )
430
    assert result.exit_code == 0
431
432
    assert debug_file.exists()
433
434
    context_log = (
435
        "DEBUG cookiecutter.main: context_file is "
436
        "tests/fake-repo-pre/cookiecutter.json"
437
    )
438
    assert context_log in debug_file.readlines(cr=False)
439
    assert context_log not in result.output
440
441
442 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...
443
def test_debug_file_verbose(cli_runner, debug_file):
444
    """Test cli invocation writes log to `debug-file` if flag enabled.
445
446
    Case for verbose log output.
447
    """
448
    assert not debug_file.exists()
449
450
    result = cli_runner(
451
        '--verbose',
452
        '--no-input',
453
        '--debug-file',
454
        str(debug_file),
455
        '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 in result.output
467
468
469
@pytest.mark.usefixtures('remove_fake_project_dir')
470
def test_directory_repo(cli_runner):
471
    """Test cli invocation works with `directory` option."""
472
    result = cli_runner(
473
        'tests/fake-repo-dir/', '--no-input', '-v', '--directory=my-dir',
474
    )
475
    assert result.exit_code == 0
476
    assert os.path.isdir("fake-project")
477
    with open(os.path.join("fake-project", "README.rst")) as f:
478
        assert "Project name: **Fake Project**" in f.read()
479
480
481
cli_accept_hook_arg_testdata = [
482
    ("--accept-hooks=yes", None, True),
483
    ("--accept-hooks=no", None, False),
484
    ("--accept-hooks=ask", "yes", True),
485
    ("--accept-hooks=ask", "no", False),
486
]
487
488
489
@pytest.mark.parametrize(
490
    "accept_hooks_arg,user_input,expected", cli_accept_hook_arg_testdata
491
)
492
def test_cli_accept_hooks(
493
    mocker,
494
    cli_runner,
495
    output_dir_flag,
496
    output_dir,
497
    accept_hooks_arg,
498
    user_input,
499
    expected,
500
):
501
    """Test cli invocation works with `accept-hooks` option."""
502
    mock_cookiecutter = mocker.patch("cookiecutter.cli.cookiecutter")
503
504
    template_path = "tests/fake-repo-pre/"
505
    result = cli_runner(
506
        template_path, output_dir_flag, output_dir, accept_hooks_arg, input=user_input
507
    )
508
509
    assert result.exit_code == 0
510
    mock_cookiecutter.assert_called_once_with(
511
        template_path,
512
        None,
513
        False,
514
        replay=False,
515
        overwrite_if_exists=False,
516
        output_dir=output_dir,
517
        config_file=None,
518
        default_config=False,
519
        extra_context=None,
520
        password=None,
521
        directory=None,
522
        skip_if_file_exists=False,
523
        accept_hooks=expected,
524
    )
525