-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ flake8>=4.0.1 | |
pre-commit>=2.15.0 | ||
pytest-cov>=3.0.0 | ||
pytest-env | ||
pytest-mock | ||
pytest>=6.0.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,324 @@ | ||
"""Tests of the cc_overrides module.""" | ||
import platform | ||
from collections import OrderedDict | ||
|
||
import pytest | ||
|
||
from cookie_composer import cc_overrides, data_merge | ||
from cookie_composer.data_merge import Context | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def patch_readline_on_win(monkeypatch): | ||
"""Fixture. Overwrite windows end of line to linux standard.""" | ||
if "windows" in platform.platform().lower(): | ||
monkeypatch.setattr("sys.stdin.readline", lambda: "\n") | ||
|
||
|
||
def test_jsonify_context(): | ||
"""Contexts return a dict.""" | ||
context = data_merge.Context( | ||
{ | ||
"project_name": "Fake Project Template2", | ||
"repo_name": "fake-project-template2", | ||
"project_slug": "fake-project-template-two", | ||
"_requirements": OrderedDict([("bar", ">=5.0.0"), ("baz", "")]), | ||
"lower_project_name": "fake project template2", | ||
}, | ||
{ | ||
"project_name": "Fake Project Template2", | ||
"repo_name": "fake-project-template2", | ||
"repo_slug": "fake-project-template-two", | ||
"_requirements": {"foo": "", "bar": ">=5.0.0"}, | ||
}, | ||
) | ||
expected = { | ||
"project_name": "Fake Project Template2", | ||
"repo_name": "fake-project-template2", | ||
"project_slug": "fake-project-template-two", | ||
"repo_slug": "fake-project-template-two", | ||
"_requirements": OrderedDict( | ||
[ | ||
("bar", ">=5.0.0"), | ||
("baz", ""), | ||
("foo", ""), | ||
] | ||
), | ||
"lower_project_name": "fake project template2", | ||
} | ||
assert cc_overrides.jsonify_context(context) == expected | ||
|
||
|
||
def test_jsonify_context_non_context(): | ||
"""Passing a non-context raises a ValueError.""" | ||
with pytest.raises(TypeError): | ||
cc_overrides.jsonify_context({"a": 1}) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"context", | ||
[ | ||
{"full_name": "Your Name"}, | ||
{"full_name": "Řekni či napiš své jméno"}, | ||
], | ||
ids=["ASCII default prompt/input", "Unicode default prompt/input"], | ||
) | ||
def test_prompt_for_config(mocker, context): | ||
"""Verify `prompt_for_config` call `read_user_variable` on text request.""" | ||
m = mocker.patch("cookie_composer.cc_overrides.read_user_variable") | ||
m.return_value = context["full_name"] | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
assert cookiecutter_dict == context | ||
|
||
|
||
def test_prompt_for_config_dict(monkeypatch): | ||
"""Verify `prompt_for_config` call `read_user_variable` on dict request.""" | ||
monkeypatch.setattr( | ||
"cookie_composer.cc_overrides.read_user_dict", | ||
lambda var, default: {"key": "value", "integer": 37}, | ||
) | ||
context = {"details": {}} | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
assert cookiecutter_dict == {"details": {"key": "value", "integer": 37}} | ||
|
||
|
||
def test_should_render_dict(): | ||
"""Verify template inside dictionary variable rendered.""" | ||
context = { | ||
"project_name": "Slartibartfast", | ||
"details": {"{{cookiecutter.project_name}}": "{{cookiecutter.project_name}}"}, | ||
} | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({}), no_input=True) | ||
assert cookiecutter_dict == { | ||
"project_name": "Slartibartfast", | ||
"details": {"Slartibartfast": "Slartibartfast"}, | ||
} | ||
|
||
|
||
def test_should_render_deep_dict(): | ||
"""Verify nested structures like dict in dict, rendered correctly.""" | ||
context = { | ||
"project_name": "Slartibartfast", | ||
"details": { | ||
"key": "value", | ||
"integer_key": 37, | ||
"other_name": "{{cookiecutter.project_name}}", | ||
"dict_key": { | ||
"deep_key": "deep_value", | ||
"deep_integer": 42, | ||
"deep_other_name": "{{cookiecutter.project_name}}", | ||
"deep_list": [ | ||
"deep value 1", | ||
"{{cookiecutter.project_name}}", | ||
"deep value 3", | ||
], | ||
}, | ||
"list_key": [ | ||
"value 1", | ||
"{{cookiecutter.project_name}}", | ||
"value 3", | ||
], | ||
}, | ||
} | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({}), no_input=True) | ||
assert cookiecutter_dict == { | ||
"project_name": "Slartibartfast", | ||
"details": { | ||
"key": "value", | ||
"integer_key": "37", | ||
"other_name": "Slartibartfast", | ||
"dict_key": { | ||
"deep_key": "deep_value", | ||
"deep_integer": "42", | ||
"deep_other_name": "Slartibartfast", | ||
"deep_list": ["deep value 1", "Slartibartfast", "deep value 3"], | ||
}, | ||
"list_key": ["value 1", "Slartibartfast", "value 3"], | ||
}, | ||
} | ||
|
||
|
||
def test_prompt_for_templated_config(monkeypatch): | ||
"""Verify Jinja2 templating works in unicode prompts.""" | ||
monkeypatch.setattr("cookie_composer.cc_overrides.read_user_variable", lambda var, default: default) | ||
context = OrderedDict( | ||
[ | ||
("project_name", "A New Project"), | ||
( | ||
"pkg_name", | ||
'{{ cookiecutter.project_name|lower|replace(" ", "") }}', | ||
), | ||
] | ||
) | ||
|
||
exp_cookiecutter_dict = { | ||
"project_name": "A New Project", | ||
"pkg_name": "anewproject", | ||
} | ||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
assert cookiecutter_dict == exp_cookiecutter_dict | ||
|
||
|
||
def test_dont_prompt_for_private_context_var(monkeypatch): | ||
"""Verify `read_user_variable` not called for private context variables.""" | ||
monkeypatch.setattr( | ||
"cookie_composer.cc_overrides.read_user_variable", | ||
lambda var, default: pytest.fail("Should not try to read a response for private context var"), | ||
) | ||
context = {"_copy_without_render": ["*.html"]} | ||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
assert cookiecutter_dict == {"_copy_without_render": ["*.html"]} | ||
|
||
|
||
def test_should_render_private_variables_with_two_underscores(): | ||
"""Test rendering of private variables with two underscores. | ||
There are three cases: | ||
1. Variables beginning with a single underscore are private and not rendered. | ||
2. Variables beginning with a double underscore are private and are rendered. | ||
3. Variables beginning with anything other than underscores are not private and | ||
are rendered. | ||
""" | ||
context = OrderedDict( | ||
[ | ||
("foo", "Hello world"), | ||
("bar", 123), | ||
("rendered_foo", "{{ cookiecutter.foo|lower }}"), | ||
("rendered_bar", 123), | ||
("_hidden_foo", "{{ cookiecutter.foo|lower }}"), | ||
("_hidden_bar", 123), | ||
("__rendered_hidden_foo", "{{ cookiecutter.foo|lower }}"), | ||
("__rendered_hidden_bar", 123), | ||
] | ||
) | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({}), no_input=True) | ||
assert cookiecutter_dict == OrderedDict( | ||
[ | ||
("foo", "Hello world"), | ||
("bar", "123"), | ||
("rendered_foo", "hello world"), | ||
("rendered_bar", "123"), | ||
("_hidden_foo", "{{ cookiecutter.foo|lower }}"), | ||
("_hidden_bar", 123), | ||
("__rendered_hidden_foo", "hello world"), | ||
("__rendered_hidden_bar", "123"), | ||
] | ||
) | ||
|
||
|
||
def test_should_not_render_private_variables(): | ||
"""Verify private(underscored) variables not rendered by `prompt_for_config`. | ||
Private variables designed to be raw, same as context input. | ||
""" | ||
context = { | ||
"project_name": "Skip render", | ||
"_skip_jinja_template": "{{cookiecutter.project_name}}", | ||
"_skip_float": 123.25, | ||
"_skip_integer": 123, | ||
"_skip_boolean": True, | ||
"_skip_nested": True, | ||
} | ||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({}), no_input=True) | ||
assert cookiecutter_dict == context | ||
|
||
|
||
def test_raises_exception_on_missing_variable(): | ||
"""A missing variable raises an error.""" | ||
from cookiecutter.exceptions import UndefinedVariableInTemplate | ||
|
||
context = {"project_name": "{{ cookiecutter.i_dont_exist }}"} | ||
with pytest.raises(UndefinedVariableInTemplate): | ||
cc_overrides.prompt_for_config(context, Context({}), no_input=True) | ||
|
||
|
||
def test_raises_exception_on_missing_variable_dict(): | ||
"""A missing variable raises an error.""" | ||
from cookiecutter.exceptions import UndefinedVariableInTemplate | ||
|
||
context = {"key_a": {"key_b": "{{ cookiecutter.i_dont_exist }}"}} | ||
with pytest.raises(UndefinedVariableInTemplate): | ||
cc_overrides.prompt_for_config(context, Context({}), no_input=True) | ||
|
||
|
||
class TestReadUserChoice: | ||
"""Class to unite choices prompt related tests.""" | ||
|
||
def test_should_invoke_read_user_choice(self, mocker): | ||
"""Verify correct function called for select(list) variables.""" | ||
prompt_choice = mocker.patch( | ||
"cookie_composer.cc_overrides.prompt_choice_for_config", | ||
wraps=cc_overrides.prompt_choice_for_config, | ||
) | ||
|
||
read_user_choice = mocker.patch("cookiecutter.prompt.read_user_choice") | ||
read_user_choice.return_value = "all" | ||
|
||
read_user_variable = mocker.patch("cookiecutter.prompt.read_user_variable") | ||
|
||
choices = ["landscape", "portrait", "all"] | ||
context = {"orientation": choices} | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
|
||
assert not read_user_variable.called | ||
assert prompt_choice.called | ||
read_user_choice.assert_called_once_with("orientation", choices) | ||
assert cookiecutter_dict == {"orientation": "all"} | ||
|
||
def test_should_invoke_read_user_variable(self, mocker): | ||
"""Verify correct function called for string input variables.""" | ||
read_user_variable = mocker.patch("cookie_composer.cc_overrides.read_user_variable") | ||
read_user_variable.return_value = "Audrey Roy" | ||
|
||
prompt_choice = mocker.patch("cookie_composer.cc_overrides.prompt_choice_for_config") | ||
|
||
read_user_choice = mocker.patch("cookiecutter.prompt.read_user_choice") | ||
|
||
context = {"full_name": "Your Name"} | ||
|
||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
|
||
assert not prompt_choice.called | ||
assert not read_user_choice.called | ||
read_user_variable.assert_called_once_with("full_name", "Your Name") | ||
assert cookiecutter_dict == {"full_name": "Audrey Roy"} | ||
|
||
def test_should_render_choices(self, mocker): | ||
"""Verify Jinja2 templating engine works inside choices variables.""" | ||
read_user_choice = mocker.patch("cookiecutter.prompt.read_user_choice") | ||
read_user_choice.return_value = "anewproject" | ||
|
||
read_user_variable = mocker.patch("cookie_composer.cc_overrides.read_user_variable") | ||
read_user_variable.return_value = "A New Project" | ||
|
||
rendered_choices = ["foo", "anewproject", "bar"] | ||
|
||
context = OrderedDict( | ||
[ | ||
("project_name", "A New Project"), | ||
( | ||
"pkg_name", | ||
[ | ||
"foo", | ||
'{{ cookiecutter.project_name|lower|replace(" ", "") }}', | ||
"bar", | ||
], | ||
), | ||
] | ||
) | ||
|
||
expected = { | ||
"project_name": "A New Project", | ||
"pkg_name": "anewproject", | ||
} | ||
cookiecutter_dict = cc_overrides.prompt_for_config(context, Context({})) | ||
|
||
read_user_variable.assert_called_once_with("project_name", "A New Project") | ||
read_user_choice.assert_called_once_with("pkg_name", rendered_choices) | ||
assert cookiecutter_dict == expected |