1import pytest
2
3from jinja2.environment import Environment
4from jinja2.exceptions import TemplateNotFound
5from jinja2.exceptions import TemplatesNotFound
6from jinja2.exceptions import TemplateSyntaxError
7from jinja2.exceptions import UndefinedError
8from jinja2.loaders import DictLoader
9
10
11@pytest.fixture
12def test_env():
13    env = Environment(
14        loader=DictLoader(
15            dict(
16                module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}",
17                header="[{{ foo }}|{{ 23 }}]",
18                o_printer="({{ o }})",
19            )
20        )
21    )
22    env.globals["bar"] = 23
23    return env
24
25
26class TestImports:
27    def test_context_imports(self, test_env):
28        t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
29        assert t.render(foo=42) == "[|23]"
30        t = test_env.from_string(
31            '{% import "module" as m without context %}{{ m.test() }}'
32        )
33        assert t.render(foo=42) == "[|23]"
34        t = test_env.from_string(
35            '{% import "module" as m with context %}{{ m.test() }}'
36        )
37        assert t.render(foo=42) == "[42|23]"
38        t = test_env.from_string('{% from "module" import test %}{{ test() }}')
39        assert t.render(foo=42) == "[|23]"
40        t = test_env.from_string(
41            '{% from "module" import test without context %}{{ test() }}'
42        )
43        assert t.render(foo=42) == "[|23]"
44        t = test_env.from_string(
45            '{% from "module" import test with context %}{{ test() }}'
46        )
47        assert t.render(foo=42) == "[42|23]"
48
49    def test_import_needs_name(self, test_env):
50        test_env.from_string('{% from "foo" import bar %}')
51        test_env.from_string('{% from "foo" import bar, baz %}')
52
53        with pytest.raises(TemplateSyntaxError):
54            test_env.from_string('{% from "foo" import %}')
55
56    def test_no_trailing_comma(self, test_env):
57        with pytest.raises(TemplateSyntaxError):
58            test_env.from_string('{% from "foo" import bar, %}')
59
60        with pytest.raises(TemplateSyntaxError):
61            test_env.from_string('{% from "foo" import bar,, %}')
62
63        with pytest.raises(TemplateSyntaxError):
64            test_env.from_string('{% from "foo" import, %}')
65
66    def test_trailing_comma_with_context(self, test_env):
67        test_env.from_string('{% from "foo" import bar, baz with context %}')
68        test_env.from_string('{% from "foo" import bar, baz, with context %}')
69        test_env.from_string('{% from "foo" import bar, with context %}')
70        test_env.from_string('{% from "foo" import bar, with, context %}')
71        test_env.from_string('{% from "foo" import bar, with with context %}')
72
73        with pytest.raises(TemplateSyntaxError):
74            test_env.from_string('{% from "foo" import bar,, with context %}')
75
76        with pytest.raises(TemplateSyntaxError):
77            test_env.from_string('{% from "foo" import bar with context, %}')
78
79    def test_exports(self, test_env):
80        m = test_env.from_string(
81            """
82            {% macro toplevel() %}...{% endmacro %}
83            {% macro __private() %}...{% endmacro %}
84            {% set variable = 42 %}
85            {% for item in [1] %}
86                {% macro notthere() %}{% endmacro %}
87            {% endfor %}
88        """
89        ).module
90        assert m.toplevel() == "..."
91        assert not hasattr(m, "__missing")
92        assert m.variable == 42
93        assert not hasattr(m, "notthere")
94
95    def test_not_exported(self, test_env):
96        t = test_env.from_string("{% from 'module' import nothing %}{{ nothing() }}")
97
98        with pytest.raises(UndefinedError, match="does not export the requested name"):
99            t.render()
100
101    def test_import_with_globals(self, test_env):
102        env = Environment(
103            loader=DictLoader(
104                {
105                    "macros": "{% macro test() %}foo: {{ foo }}{% endmacro %}",
106                    "test": "{% import 'macros' as m %}{{ m.test() }}",
107                    "test1": "{% import 'macros' as m %}{{ m.test() }}",
108                }
109            )
110        )
111        tmpl = env.get_template("test", globals={"foo": "bar"})
112        assert tmpl.render() == "foo: bar"
113
114        tmpl = env.get_template("test1")
115        assert tmpl.render() == "foo: "
116
117    def test_import_with_globals_override(self, test_env):
118        env = Environment(
119            loader=DictLoader(
120                {
121                    "macros": "{% set foo = '42' %}{% macro test() %}"
122                    "foo: {{ foo }}{% endmacro %}",
123                    "test": "{% from 'macros' import test %}{{ test() }}",
124                }
125            )
126        )
127        tmpl = env.get_template("test", globals={"foo": "bar"})
128        assert tmpl.render() == "foo: 42"
129
130    def test_from_import_with_globals(self, test_env):
131        env = Environment(
132            loader=DictLoader(
133                {
134                    "macros": "{% macro testing() %}foo: {{ foo }}{% endmacro %}",
135                    "test": "{% from 'macros' import testing %}{{ testing() }}",
136                }
137            )
138        )
139        tmpl = env.get_template("test", globals={"foo": "bar"})
140        assert tmpl.render() == "foo: bar"
141
142
143class TestIncludes:
144    def test_context_include(self, test_env):
145        t = test_env.from_string('{% include "header" %}')
146        assert t.render(foo=42) == "[42|23]"
147        t = test_env.from_string('{% include "header" with context %}')
148        assert t.render(foo=42) == "[42|23]"
149        t = test_env.from_string('{% include "header" without context %}')
150        assert t.render(foo=42) == "[|23]"
151
152    def test_choice_includes(self, test_env):
153        t = test_env.from_string('{% include ["missing", "header"] %}')
154        assert t.render(foo=42) == "[42|23]"
155
156        t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}')
157        assert t.render(foo=42) == ""
158
159        t = test_env.from_string('{% include ["missing", "missing2"] %}')
160        pytest.raises(TemplateNotFound, t.render)
161        with pytest.raises(TemplatesNotFound) as e:
162            t.render()
163
164        assert e.value.templates == ["missing", "missing2"]
165        assert e.value.name == "missing2"
166
167        def test_includes(t, **ctx):
168            ctx["foo"] = 42
169            assert t.render(ctx) == "[42|23]"
170
171        t = test_env.from_string('{% include ["missing", "header"] %}')
172        test_includes(t)
173        t = test_env.from_string("{% include x %}")
174        test_includes(t, x=["missing", "header"])
175        t = test_env.from_string('{% include [x, "header"] %}')
176        test_includes(t, x="missing")
177        t = test_env.from_string("{% include x %}")
178        test_includes(t, x="header")
179        t = test_env.from_string("{% include [x] %}")
180        test_includes(t, x="header")
181
182    def test_include_ignoring_missing(self, test_env):
183        t = test_env.from_string('{% include "missing" %}')
184        pytest.raises(TemplateNotFound, t.render)
185        for extra in "", "with context", "without context":
186            t = test_env.from_string(
187                '{% include "missing" ignore missing ' + extra + " %}"
188            )
189            assert t.render() == ""
190
191    def test_context_include_with_overrides(self, test_env):
192        env = Environment(
193            loader=DictLoader(
194                dict(
195                    main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
196                    item="{{ item }}",
197                )
198            )
199        )
200        assert env.get_template("main").render() == "123"
201
202    def test_unoptimized_scopes(self, test_env):
203        t = test_env.from_string(
204            """
205            {% macro outer(o) %}
206            {% macro inner() %}
207            {% include "o_printer" %}
208            {% endmacro %}
209            {{ inner() }}
210            {% endmacro %}
211            {{ outer("FOO") }}
212        """
213        )
214        assert t.render().strip() == "(FOO)"
215
216    def test_import_from_with_context(self):
217        env = Environment(
218            loader=DictLoader({"a": "{% macro x() %}{{ foobar }}{% endmacro %}"})
219        )
220        t = env.from_string(
221            "{% set foobar = 42 %}{% from 'a' import x with context %}{{ x() }}"
222        )
223        assert t.render() == "42"
224