mirror of
https://github.com/copier-org/copier.git
synced 2025-05-05 15:32:54 +00:00
419 lines
12 KiB
Python
419 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from contextlib import AbstractContextManager, nullcontext as does_not_raise
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
from jinja2.ext import Extension
|
|
from plumbum import local
|
|
|
|
from copier._cli import CopierApp
|
|
from copier._main import run_copy, run_update
|
|
from copier._types import AnyByStrDict
|
|
from copier._user_data import load_answersfile_data
|
|
from copier.errors import UnsafeTemplateError
|
|
|
|
from .helpers import build_file_tree, git
|
|
|
|
|
|
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 potentially unsafe feature: tasks.",
|
|
),
|
|
),
|
|
(
|
|
{"_migrations": []},
|
|
does_not_raise(),
|
|
),
|
|
(
|
|
{
|
|
"_migrations": [
|
|
{
|
|
"version": "v1",
|
|
"when": "{{ _stage == 'before' }}",
|
|
"command": "touch v1-before.txt",
|
|
},
|
|
{
|
|
"version": "v1",
|
|
"when": "{{ _stage == 'after' }}",
|
|
"command": "touch v1-after.txt",
|
|
},
|
|
]
|
|
},
|
|
does_not_raise(),
|
|
),
|
|
(
|
|
{"_jinja_extensions": []},
|
|
does_not_raise(),
|
|
),
|
|
(
|
|
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: jinja_extensions.",
|
|
),
|
|
),
|
|
(
|
|
{
|
|
"_tasks": ["touch task.txt"],
|
|
"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"],
|
|
},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe features: jinja_extensions, tasks.",
|
|
),
|
|
),
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("unsafe", [False, True])
|
|
def test_copy(
|
|
tmp_path_factory: pytest.TempPathFactory,
|
|
unsafe: bool,
|
|
spec: AnyByStrDict,
|
|
expected: AbstractContextManager[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])
|
|
@pytest.mark.parametrize("trusted_from_settings", [False, True])
|
|
def test_copy_cli(
|
|
tmp_path_factory: pytest.TempPathFactory,
|
|
capsys: pytest.CaptureFixture[str],
|
|
unsafe: bool,
|
|
trusted_from_settings: bool,
|
|
settings_path: Path,
|
|
) -> None:
|
|
src, dst = map(tmp_path_factory.mktemp, ["src", "dst"])
|
|
build_file_tree(
|
|
{(src / "copier.yaml"): yaml.safe_dump({"_tasks": ["touch task.txt"]})}
|
|
)
|
|
if trusted_from_settings:
|
|
settings_path.write_text(f"trust: ['{src}']")
|
|
|
|
_, retcode = CopierApp.run(
|
|
["copier", "copy", *(["--UNSAFE"] if unsafe else []), str(src), str(dst)],
|
|
exit=False,
|
|
)
|
|
if unsafe or trusted_from_settings:
|
|
assert retcode == 0
|
|
else:
|
|
assert retcode == 4
|
|
_, err = capsys.readouterr()
|
|
assert "Template uses potentially 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 potentially unsafe feature: tasks.",
|
|
),
|
|
),
|
|
(
|
|
{},
|
|
{"_tasks": ["touch task-new.txt"]},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: tasks.",
|
|
),
|
|
),
|
|
(
|
|
{"_tasks": ["touch task-old.txt"]},
|
|
{"_tasks": ["touch task-new.txt"]},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: tasks.",
|
|
),
|
|
),
|
|
(
|
|
{"_migrations": []},
|
|
{"_migrations": []},
|
|
does_not_raise(),
|
|
),
|
|
(
|
|
{},
|
|
{
|
|
"_migrations": [
|
|
{
|
|
"version": "v0",
|
|
"command": "touch v0-before.txt",
|
|
"when": "{{ _stage == 'before' }}",
|
|
},
|
|
{
|
|
"version": "v0",
|
|
"command": "touch v0-after.txt",
|
|
"when": "{{ _stage == 'after' }}",
|
|
},
|
|
]
|
|
},
|
|
does_not_raise(),
|
|
),
|
|
(
|
|
{},
|
|
{
|
|
"_migrations": [
|
|
{
|
|
"version": "v2",
|
|
"before": [],
|
|
"after": [],
|
|
}
|
|
]
|
|
},
|
|
# This case only exists on legacy migrations and raises a DeprecationWarning
|
|
pytest.deprecated_call(),
|
|
),
|
|
(
|
|
{},
|
|
{
|
|
"_migrations": [
|
|
{
|
|
"version": "v2",
|
|
"when": "{{ _stage == 'before' }}",
|
|
"command": "touch v2-before.txt",
|
|
}
|
|
]
|
|
},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: migrations.",
|
|
),
|
|
),
|
|
(
|
|
{},
|
|
{
|
|
"_migrations": [
|
|
{
|
|
"version": "v2",
|
|
"when": "{{ _stage == 'after' }}",
|
|
"command": "touch v2-after.txt",
|
|
}
|
|
]
|
|
},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: migrations.",
|
|
),
|
|
),
|
|
(
|
|
{},
|
|
{
|
|
"_migrations": [
|
|
{
|
|
"version": "v2",
|
|
"when": "{{ _stage == 'before' }}",
|
|
"command": "touch v2-before.txt",
|
|
},
|
|
{
|
|
"version": "v2",
|
|
"when": "{{ _stage == 'after' }}",
|
|
"command": "touch v2-after.txt",
|
|
},
|
|
]
|
|
},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: migrations.",
|
|
),
|
|
),
|
|
(
|
|
{"_jinja_extensions": []},
|
|
{"_jinja_extensions": []},
|
|
does_not_raise(),
|
|
),
|
|
(
|
|
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
|
|
{},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: jinja_extensions.",
|
|
),
|
|
),
|
|
(
|
|
{},
|
|
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: jinja_extensions.",
|
|
),
|
|
),
|
|
(
|
|
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
|
|
{"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"]},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match="Template uses potentially unsafe feature: jinja_extensions",
|
|
),
|
|
),
|
|
(
|
|
{},
|
|
{
|
|
"_tasks": ["touch task-new.txt"],
|
|
"_migrations": [
|
|
{
|
|
"version": "v2",
|
|
"when": "{{ _stage == 'before' }}",
|
|
"command": "touch v2-before.txt",
|
|
},
|
|
{
|
|
"version": "v2",
|
|
"when": "{{ _stage == 'after' }}",
|
|
"command": "touch v2-after.txt",
|
|
},
|
|
],
|
|
"_jinja_extensions": ["tests.test_unsafe.JinjaExtension"],
|
|
},
|
|
pytest.raises(
|
|
UnsafeTemplateError,
|
|
match=(
|
|
"Template uses potentially 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: AbstractContextManager[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 load_answersfile_data(dst).get("_commit") == "v1"
|
|
|
|
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"])
|
|
@pytest.mark.parametrize("trusted_from_settings", [False, True])
|
|
def test_update_cli(
|
|
tmp_path_factory: pytest.TempPathFactory,
|
|
capsys: pytest.CaptureFixture[str],
|
|
unsafe: bool | str,
|
|
trusted_from_settings: bool,
|
|
settings_path: Path,
|
|
) -> 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")
|
|
|
|
if trusted_from_settings:
|
|
settings_path.write_text(f"trust: ['{src}']")
|
|
|
|
_, retcode = CopierApp.run(
|
|
["copier", "copy", str(src), str(dst)] + unsafe_args,
|
|
exit=False,
|
|
)
|
|
assert retcode == 0
|
|
assert load_answersfile_data(dst).get("_commit") == "v1"
|
|
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 or trusted_from_settings:
|
|
assert retcode == 0
|
|
else:
|
|
assert retcode == 4
|
|
_, err = capsys.readouterr()
|
|
assert "Template uses potentially unsafe feature: tasks." in err
|