Skip to content

Commit

Permalink
Updated cc_override's tests
Browse files Browse the repository at this point in the history
  • Loading branch information
coordt committed Jun 29, 2022
1 parent 99470e9 commit 5388df7
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 0 deletions.
3 changes: 3 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,13 @@ pytest==7.1.2
# -r test.txt
# pytest-cov
# pytest-env
# pytest-mock
pytest-cov==3.0.0
# via -r test.txt
pytest-env==0.6.2
# via -r test.txt
pytest-mock==3.8.1
# via -r test.txt
python-dateutil==2.8.2
# via
# -r docs.txt
Expand Down
1 change: 1 addition & 0 deletions requirements/test.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,13 @@ pytest==7.1.2
# -r test.in
# pytest-cov
# pytest-env
# pytest-mock
pytest-cov==3.0.0
# via -r test.in
pytest-env==0.6.2
# via -r test.in
pytest-mock==3.8.1
# via -r test.in
python-dateutil==2.8.2
# via
# -r prod.txt
Expand Down
324 changes: 324 additions & 0 deletions tests/test_cc_overrides.py
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

0 comments on commit 5388df7

Please sign in to comment.