1# -*- coding: utf-8 -*-
2
3"""Tests for `cookiecutter.utils` module."""
4
5import os
6import stat
7import sys
8
9import pytest
10
11from cookiecutter import utils
12
13
14def make_readonly(path):
15    """Change the access permissions to readonly for a given file."""
16    mode = os.stat(path).st_mode
17    os.chmod(path, mode & ~stat.S_IWRITE)
18
19
20def test_rmtree():
21    """Verify `utils.rmtree` remove files marked as read-only."""
22    os.mkdir('foo')
23    with open('foo/bar', "w") as f:
24        f.write("Test data")
25    make_readonly('foo/bar')
26    utils.rmtree('foo')
27    assert not os.path.exists('foo')
28
29
30def test_make_sure_path_exists():
31    """Verify correct True/False response from `utils.make_sure_path_exists`.
32
33    Should return True if directory exist or created.
34    Should return False if impossible to create directory (for example protected)
35    """
36    if sys.platform.startswith('win'):
37        existing_directory = os.path.abspath(os.curdir)
38        uncreatable_directory = 'a*b'
39    else:
40        existing_directory = '/usr/'
41        uncreatable_directory = '/this-doesnt-exist-and-cant-be-created/'
42
43    assert utils.make_sure_path_exists(existing_directory)
44    assert utils.make_sure_path_exists('tests/blah')
45    assert utils.make_sure_path_exists('tests/trailingslash/')
46    assert not utils.make_sure_path_exists(uncreatable_directory)
47    utils.rmtree('tests/blah/')
48    utils.rmtree('tests/trailingslash/')
49
50
51def test_workin():
52    """Verify returning to original folder after `utils.work_in` use."""
53    cwd = os.getcwd()
54    ch_to = 'tests/files'
55
56    class TestException(Exception):
57        pass
58
59    def test_work_in():
60        with utils.work_in(ch_to):
61            test_dir = os.path.join(cwd, ch_to).replace("/", os.sep)
62            assert test_dir == os.getcwd()
63            raise TestException()
64
65    # Make sure we return to the correct folder
66    assert cwd == os.getcwd()
67
68    # Make sure that exceptions are still bubbled up
69    with pytest.raises(TestException):
70        test_work_in()
71
72
73def test_prompt_should_ask_and_rm_repo_dir(mocker, tmpdir):
74    """In `prompt_and_delete()`, if the user agrees to delete/reclone the \
75    repo, the repo should be deleted."""
76    mock_read_user = mocker.patch(
77        'cookiecutter.utils.read_user_yes_no', return_value=True, autospec=True
78    )
79    repo_dir = tmpdir.mkdir('repo')
80
81    deleted = utils.prompt_and_delete(str(repo_dir))
82
83    assert mock_read_user.called
84    assert not repo_dir.exists()
85    assert deleted
86
87
88def test_prompt_should_ask_and_exit_on_user_no_answer(mocker, tmpdir):
89    """In `prompt_and_delete()`, if the user decline to delete/reclone the \
90    repo, cookiecutter should exit."""
91    mock_read_user = mocker.patch(
92        'cookiecutter.utils.read_user_yes_no', return_value=False,
93    )
94    mock_sys_exit = mocker.patch('sys.exit', return_value=True)
95    repo_dir = tmpdir.mkdir('repo')
96
97    deleted = utils.prompt_and_delete(str(repo_dir))
98
99    assert mock_read_user.called
100    assert repo_dir.exists()
101    assert not deleted
102    assert mock_sys_exit.called
103
104
105def test_prompt_should_ask_and_rm_repo_file(mocker, tmpdir):
106    """In `prompt_and_delete()`, if the user agrees to delete/reclone a \
107    repo file, the repo should be deleted."""
108    mock_read_user = mocker.patch(
109        'cookiecutter.utils.read_user_yes_no', return_value=True, autospec=True
110    )
111
112    repo_file = tmpdir.join('repo.zip')
113    repo_file.write('this is zipfile content')
114
115    deleted = utils.prompt_and_delete(str(repo_file))
116
117    assert mock_read_user.called
118    assert not repo_file.exists()
119    assert deleted
120
121
122def test_prompt_should_ask_and_keep_repo_on_no_reuse(mocker, tmpdir):
123    """In `prompt_and_delete()`, if the user wants to keep their old \
124    cloned template repo, it should not be deleted."""
125    mock_read_user = mocker.patch(
126        'cookiecutter.utils.read_user_yes_no', return_value=False, autospec=True
127    )
128    repo_dir = tmpdir.mkdir('repo')
129
130    with pytest.raises(SystemExit):
131        utils.prompt_and_delete(str(repo_dir))
132
133    assert mock_read_user.called
134    assert repo_dir.exists()
135
136
137def test_prompt_should_ask_and_keep_repo_on_reuse(mocker, tmpdir):
138    """In `prompt_and_delete()`, if the user wants to keep their old \
139    cloned template repo, it should not be deleted."""
140
141    def answer(question, default):
142        if 'okay to delete' in question:
143            return False
144        else:
145            return True
146
147    mock_read_user = mocker.patch(
148        'cookiecutter.utils.read_user_yes_no', side_effect=answer, autospec=True
149    )
150    repo_dir = tmpdir.mkdir('repo')
151
152    deleted = utils.prompt_and_delete(str(repo_dir))
153
154    assert mock_read_user.called
155    assert repo_dir.exists()
156    assert not deleted
157
158
159def test_prompt_should_not_ask_if_no_input_and_rm_repo_dir(mocker, tmpdir):
160    """Prompt should not ask if no input and rm dir.
161
162    In `prompt_and_delete()`, if `no_input` is True, the call to
163    `prompt.read_user_yes_no()` should be suppressed.
164    """
165    mock_read_user = mocker.patch(
166        'cookiecutter.prompt.read_user_yes_no', return_value=True, autospec=True
167    )
168    repo_dir = tmpdir.mkdir('repo')
169
170    deleted = utils.prompt_and_delete(str(repo_dir), no_input=True)
171
172    assert not mock_read_user.called
173    assert not repo_dir.exists()
174    assert deleted
175
176
177def test_prompt_should_not_ask_if_no_input_and_rm_repo_file(mocker, tmpdir):
178    """Prompt should not ask if no input and rm file.
179
180    In `prompt_and_delete()`, if `no_input` is True, the call to
181    `prompt.read_user_yes_no()` should be suppressed.
182    """
183    mock_read_user = mocker.patch(
184        'cookiecutter.prompt.read_user_yes_no', return_value=True, autospec=True
185    )
186
187    repo_file = tmpdir.join('repo.zip')
188    repo_file.write('this is zipfile content')
189
190    deleted = utils.prompt_and_delete(str(repo_file), no_input=True)
191
192    assert not mock_read_user.called
193    assert not repo_file.exists()
194    assert deleted
195