copier/tests/test_unsafe.py
Jairo Llopis aaf6cf3843
feat: add --trust as a less scary alternative to --UNSAFE (#1179)
See rationale in https://github.com/copier-org/copier/issues/1137#issuecomment-1579304713.

Co-authored-by: Sigurd Spieckermann <2206639+sisp@users.noreply.github.com>
2023-06-28 21:17:32 +02:00

376 lines
10 KiB
Python

from contextlib import nullcontext as does_not_raise
from typing import ContextManager, Union
import pytest
import yaml
from jinja2.ext import Extension
from plumbum import local
from plumbum.cmd import git
from copier.cli import CopierApp
from copier.errors import UnsafeTemplateError
from copier.main import run_copy, run_update
from copier.types import AnyByStrDict
from .helpers import build_file_tree
class JinjaExtension(Extension):
...
@pytest.mark.parametrize(
("spec", "expected"),
[
(
{},
does_not_raise(),
),
(
{"_tasks": []},
does_not_raise(),
),
(
{"_tasks": ["touch task.txt"]},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: tasks"
),
),
(
{"_migrations": []},
does_not_raise(),
),
(
{
"_migrations": [
{
"version": "v1",
"before": ["touch v1-before.txt"],
"after": ["touch v1-after.txt"],
}
]
},
does_not_raise(),
),
(
{"_jinja_extensions": []},
does_not_raise(),
),
(
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
pytest.raises(
UnsafeTemplateError,
match="Template uses unsafe feature: jinja_extensions",
),
),
(
{
"_tasks": ["touch task.txt"],
"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"],
},
pytest.raises(
UnsafeTemplateError,
match="Template uses unsafe features: jinja_extensions, tasks",
),
),
],
)
@pytest.mark.parametrize("unsafe", [False, True])
def test_copy(
tmp_path_factory: pytest.TempPathFactory,
unsafe: bool,
spec: AnyByStrDict,
expected: ContextManager[None],
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree({(src / "copier.yaml"): yaml.safe_dump(spec)})
with does_not_raise() if unsafe else expected:
run_copy(str(src), dst, unsafe=unsafe)
@pytest.mark.parametrize("unsafe", [False, True])
def test_copy_cli(
tmp_path_factory: pytest.TempPathFactory,
capsys: pytest.CaptureFixture[str],
unsafe: bool,
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
build_file_tree(
{(src / "copier.yaml"): yaml.safe_dump({"_tasks": ["touch task.txt"]})}
)
_, retcode = CopierApp.run(
["copier", "copy", *(["--UNSAFE"] if unsafe else []), str(src), str(dst)],
exit=False,
)
if unsafe:
assert retcode == 0
else:
assert retcode == 2
_, err = capsys.readouterr()
assert "Template uses unsafe feature: tasks" in err
@pytest.mark.parametrize(
("spec_old", "spec_new", "expected"),
[
(
{},
{},
does_not_raise(),
),
(
{"_tasks": []},
{"_tasks": []},
does_not_raise(),
),
(
{"_tasks": ["touch task-old.txt"]},
{},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: tasks"
),
),
(
{},
{"_tasks": ["touch task-new.txt"]},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: tasks"
),
),
(
{"_tasks": ["touch task-old.txt"]},
{"_tasks": ["touch task-new.txt"]},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: tasks"
),
),
(
{"_migrations": []},
{"_migrations": []},
does_not_raise(),
),
(
{},
{
"_migrations": [
{
"version": "v0",
"before": ["touch v0-before.txt"],
"after": ["touch v0-after.txt"],
}
]
},
does_not_raise(),
),
(
{},
{
"_migrations": [
{
"version": "v2",
"before": [],
"after": [],
}
]
},
does_not_raise(),
),
(
{},
{
"_migrations": [
{
"version": "v2",
"before": ["touch v2-before.txt"],
}
]
},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: migrations"
),
),
(
{},
{
"_migrations": [
{
"version": "v2",
"after": ["touch v2-after.txt"],
}
]
},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: migrations"
),
),
(
{},
{
"_migrations": [
{
"version": "v2",
"before": ["touch v2-before.txt"],
"after": ["touch v2-after.txt"],
}
]
},
pytest.raises(
UnsafeTemplateError, match="Template uses unsafe feature: migrations"
),
),
(
{"_jinja_extensions": []},
{"_jinja_extensions": []},
does_not_raise(),
),
(
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
{},
pytest.raises(
UnsafeTemplateError,
match="Template uses unsafe feature: jinja_extensions",
),
),
(
{},
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
pytest.raises(
UnsafeTemplateError,
match="Template uses unsafe feature: jinja_extensions",
),
),
(
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
pytest.raises(
UnsafeTemplateError,
match="Template uses unsafe feature: jinja_extensions",
),
),
(
{},
{
"_tasks": ["touch task-new.txt"],
"_migrations": [
{
"version": "v2",
"before": ["touch v2-before.txt"],
"after": ["touch v2-after.txt"],
}
],
"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"],
},
pytest.raises(
UnsafeTemplateError,
match=(
"Template uses unsafe features: "
"jinja_extensions, migrations, tasks"
),
),
),
],
)
@pytest.mark.parametrize("unsafe", [False, True])
def test_update(
tmp_path_factory: pytest.TempPathFactory,
unsafe: bool,
spec_old: AnyByStrDict,
spec_new: AnyByStrDict,
expected: ContextManager[None],
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
with local.cwd(src):
build_file_tree(
{
"{{ _copier_conf.answers_file }}.jinja": "{{ _copier_answers | to_nice_yaml }}",
"copier.yaml": yaml.safe_dump(spec_old),
}
)
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "v1")
run_copy(str(src), dst, unsafe=True)
assert "_commit: v1" in (dst / ".copier-answers.yml").read_text()
with local.cwd(dst):
git("init")
git("add", ".")
git("commit", "-m1")
with local.cwd(src):
build_file_tree(
{
"copier.yaml": yaml.safe_dump(spec_new),
}
)
git("add", ".")
git("commit", "-m2", "--allow-empty")
git("tag", "v2")
with does_not_raise() if unsafe else expected:
run_update(dst, overwrite=True, unsafe=unsafe)
@pytest.mark.parametrize("unsafe", [False, "--trust", "--UNSAFE"])
def test_update_cli(
tmp_path_factory: pytest.TempPathFactory,
capsys: pytest.CaptureFixture[str],
unsafe: Union[bool, str],
) -> None:
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
unsafe_args = [unsafe] if unsafe else []
with local.cwd(src):
build_file_tree(
{
"{{ _copier_conf.answers_file }}.jinja": "{{ _copier_answers | to_nice_yaml }}",
"copier.yaml": "",
}
)
git("init")
git("add", ".")
git("commit", "-m1")
git("tag", "v1")
_, retcode = CopierApp.run(
["copier", "copy", str(src), str(dst)] + unsafe_args,
exit=False,
)
assert retcode == 0
assert "_commit: v1" in (dst / ".copier-answers.yml").read_text()
capsys.readouterr()
with local.cwd(dst):
git("init")
git("add", ".")
git("commit", "-m1")
with local.cwd(src):
build_file_tree(
{
"copier.yaml": yaml.safe_dump({"_tasks": ["touch task-new.txt"]}),
}
)
git("add", ".")
git("commit", "-m2")
git("tag", "v2")
_, retcode = CopierApp.run(
[
"copier",
"update",
str(dst),
]
+ unsafe_args,
exit=False,
)
if unsafe:
assert retcode == 0
else:
assert retcode == 2
_, err = capsys.readouterr()
assert "Template uses unsafe feature: tasks" in err