1
0
mirror of synced 2026-01-01 09:02:59 -05:00
Files
airbyte/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_jinja.py
Alexandre Girard d548587161 [ISSUE #27289] Document macros output in the manifest schema (#27600)
* Add example for macros

* Update changelog

* Revert "Update changelog"

This reverts commit 2993e5820e.
2023-06-22 09:14:17 -07:00

220 lines
8.6 KiB
Python

#
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
#
import datetime
import pytest
from airbyte_cdk.sources.declarative.interpolation.jinja import JinjaInterpolation
from freezegun import freeze_time
from jinja2.exceptions import TemplateSyntaxError
interpolation = JinjaInterpolation()
def test_get_value_from_config():
s = "{{ config['date'] }}"
config = {"date": "2022-01-01"}
val = interpolation.eval(s, config)
assert val == "2022-01-01"
def test_get_value_from_stream_slice():
s = "{{ stream_slice['date'] }}"
config = {"date": "2022-01-01"}
stream_slice = {"date": "2020-09-09"}
val = interpolation.eval(s, config, **{"stream_slice": stream_slice})
assert val == "2020-09-09"
def test_get_value_from_a_list_of_mappings():
s = "{{ records[0]['date'] }}"
config = {"date": "2022-01-01"}
records = [{"date": "2020-09-09"}]
val = interpolation.eval(s, config, **{"records": records})
assert val == "2020-09-09"
@pytest.mark.parametrize(
"s, value",
[
pytest.param("{{1}}", 1, id="test_number"),
pytest.param("{{1}}", 1, id="test_number"),
pytest.param("{{[1,2]}}", [1, 2], id="test_list"),
pytest.param("{{ {1:2} }}", {1: 2}, id="test_dict"),
pytest.param("{{ 1+2 }}", 3, id="test_addition"),
],
)
def test_literals(s, value):
val = interpolation.eval(s, None)
assert val == value
@pytest.mark.parametrize(
"context, input_string, expected_value",
[
pytest.param(
{"stream_slice": {"stream_slice_key": "hello"}},
"{{ stream_slice['stream_slice_key'] }}",
"hello",
id="test_get_value_from_stream_slice",
),
pytest.param(
{"stream_slice": {"stream_slice_key": "hello"}},
"{{ stream_partition['stream_slice_key'] }}",
"hello",
id="test_get_value_from_stream_slicer",
),
pytest.param(
{"stream_slice": {"stream_slice_key": "hello"}},
"{{ stream_interval['stream_slice_key'] }}",
"hello",
id="test_get_value_from_stream_interval",
),
],
)
def test_stream_slice_alias(context, input_string, expected_value):
config = {}
val = interpolation.eval(input_string, config, **context)
assert val == expected_value
@pytest.mark.parametrize(
"alias",
[
pytest.param("stream_interval", id="test_error_is_raised_if_stream_interval_in_context"),
pytest.param("stream_partition", id="test_error_is_raised_if_stream_partition_in_context"),
],
)
def test_error_is_raised_if_alias_is_already_in_context(alias):
config = {}
context = {alias: "a_value"}
with pytest.raises(ValueError):
interpolation.eval("a_key", config, **context)
def test_positive_day_delta():
delta_template = "{{ day_delta(25) }}"
interpolation = JinjaInterpolation()
val = interpolation.eval(delta_template, {})
# We need to assert against an earlier delta since the interpolation function runs datetime.now() a few milliseconds earlier
assert val > (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=24, hours=23)).strftime("%Y-%m-%dT%H:%M:%S.%f%z")
def test_positive_day_delta_with_format():
delta_template = "{{ day_delta(25,format='%Y-%m-%d') }}"
with freeze_time("2021-01-01 03:04:05"):
val = interpolation.eval(delta_template, {})
assert val == "2021-01-26"
def test_negative_day_delta():
delta_template = "{{ day_delta(-25) }}"
val = interpolation.eval(delta_template, {})
assert val <= (datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=25)).strftime("%Y-%m-%dT%H:%M:%S.%f%z")
@pytest.mark.parametrize(
"s, expected_value",
[
pytest.param("{{ timestamp(1621439283) }}", 1621439283, id="test_timestamp_from_timestamp"),
pytest.param("{{ timestamp('2021-05-19') }}", 1621382400, id="test_timestamp_from_string"),
pytest.param("{{ timestamp('2017-01-01T00:00:00.0Z') }}", 1483228800, id="test_timestamp_from_rfc3339"),
pytest.param("{{ max(1,2) }}", 2, id="test_max"),
],
)
def test_macros(s, expected_value):
config = {}
val = interpolation.eval(s, config)
assert val == expected_value
@pytest.mark.parametrize(
"template_string",
[
pytest.param("{{ import os) }}", id="test_jinja_with_import"),
pytest.param("{{ [a for a in range(1000000000)] }}", id="test_jinja_with_list_comprehension"),
],
)
def test_invalid_jinja_statements(template_string):
config = {"key": "value"}
with pytest.raises(TemplateSyntaxError):
interpolation.eval(template_string, config=config)
@pytest.mark.parametrize(
"template_string",
[
# This test stalls if range is removed from JinjaInterpolation.RESTRICTED_BUILTIN_FUNCTIONS
pytest.param(
"""
{% set a = 1 %}
{% set b = 1 %}
{% for i in range(1000000000) %}
{% endfor %}
{{ a }}""",
id="test_jinja_with_very_long_running_compute",
),
pytest.param("{{ eval ('2+2') }}", id="test_jinja_with_eval"),
pytest.param("{{ getattr(config, 'key') }}", id="test_getattr"),
pytest.param("{{ setattr(config, 'password', 'hunter2') }}", id="test_setattr"),
pytest.param("{{ globals() }}", id="test_jinja_with_globals"),
pytest.param("{{ locals() }}", id="test_jinja_with_globals"),
pytest.param("{{ eval ('2+2') }}", id="test_jinja_with_eval"),
pytest.param("{{ eval }}", id="test_jinja_with_eval"),
],
)
def test_restricted_builtin_functions_are_not_executed(template_string):
config = {"key": JinjaInterpolation}
with pytest.raises(ValueError):
interpolation.eval(template_string, config=config)
@pytest.mark.parametrize(
"template_string, expected_value, expected_error",
[
pytest.param("{{ to_be }}", "that_is_the_question", None, id="valid_template_variable"),
pytest.param("{{ missingno }}", None, ValueError, id="undeclared_template_variable"),
pytest.param("{{ to_be and or_not_to_be }}", None, ValueError, id="one_undeclared_template_variable"),
],
)
def test_undeclared_variables(template_string, expected_error, expected_value):
config = {"key": JinjaInterpolation}
if expected_error:
with pytest.raises(expected_error):
interpolation.eval(template_string, config=config, **{"to_be": "that_is_the_question"})
else:
actual_value = interpolation.eval(template_string, config=config, **{"to_be": "that_is_the_question"})
assert actual_value == expected_value
@freeze_time("2021-09-01")
@pytest.mark.parametrize("template_string, expected_value",[
pytest.param("{{ now_utc() }}", "2021-09-01 00:00:00+00:00", id="test_now_utc"),
pytest.param("{{ now_utc().strftime('%Y-%m-%d') }}", "2021-09-01", id="test_now_utc_strftime"),
pytest.param("{{ today_utc() }}", "2021-09-01", id="test_today_utc"),
pytest.param("{{ today_utc().strftime('%Y/%m/%d') }}", "2021/09/01", id="test_todat_utc_stftime"),
pytest.param("{{ timestamp(1646006400) }}", 1646006400, id="test_timestamp_from_timestamp"),
pytest.param("{{ timestamp('2022-02-28') }}", 1646006400, id="test_timestamp_from_timestamp"),
pytest.param("{{ timestamp('2022-02-28T00:00:00Z') }}", 1646006400, id="test_timestamp_from_timestamp"),
pytest.param("{{ timestamp('2022-02-28 00:00:00Z') }}", 1646006400, id="test_timestamp_from_timestamp"),
pytest.param("{{ timestamp('2022-02-28T00:00:00-08:00') }}", 1646035200, id="test_timestamp_from_date_with_tz"),
pytest.param("{{ max(2, 3) }}", 3, id="test_max_with_arguments"),
pytest.param("{{ max([2, 3]) }}", 3, id="test_max_with_list"),
pytest.param("{{ day_delta(1) }}", "2021-09-02T00:00:00.000000+0000", id="test_day_delta"),
pytest.param("{{ day_delta(-1) }}", "2021-08-31T00:00:00.000000+0000", id="test_day_delta_negative"),
pytest.param("{{ day_delta(1, format='%Y-%m-%d') }}", "2021-09-02", id="test_day_delta_with_format"),
pytest.param("{{ duration('P1D') }}", "1 day, 0:00:00", id="test_duration_one_day"),
pytest.param("{{ duration('P6DT23H') }}", "6 days, 23:00:00", id="test_duration_six_days_and_23_hours"),
pytest.param("{{ (now_utc() - duration('P1D')).strftime('%Y-%m-%dT%H:%M:%SZ') }}", "2021-08-31T00:00:00Z", id="test_now_utc_with_duration_and_format"),
])
def test_macros_examples(template_string, expected_value):
# The outputs of this test are referenced in declarative_component_schema.yaml
# If you change the expected output, you must also change the expected output in declarative_component_schema.yaml
now_utc = interpolation.eval(template_string, {})
assert now_utc == expected_value