diff --git a/awx/main/tests/unit/utils/test_safe_yaml.py b/awx/main/tests/unit/utils/test_safe_yaml.py index 1c06ff5ca1..7f528ea595 100644 --- a/awx/main/tests/unit/utils/test_safe_yaml.py +++ b/awx/main/tests/unit/utils/test_safe_yaml.py @@ -1,11 +1,25 @@ +# -*- coding: utf-8 -*- + from copy import deepcopy +import pytest +import yaml from awx.main.utils.safe_yaml import safe_dump +@pytest.mark.parametrize('value', [None, 1, 1.5, []]) +def test_native_types(value): + # Native non-string types should dump the same way that `yaml.safe_dump` does + assert safe_dump(value) == yaml.safe_dump(value) + + def test_empty(): assert safe_dump({}) == '' +def test_raw_string(): + assert safe_dump('foo') == "!unsafe 'foo'\n" + + def test_kv_int(): assert safe_dump({'a': 1}) == "!unsafe 'a': 1\n" @@ -18,6 +32,10 @@ def test_kv_unsafe(): assert safe_dump({'a': 'b'}) == "!unsafe 'a': !unsafe 'b'\n" +def test_kv_unsafe_unicode(): + assert safe_dump({'a': u'🐉'}) == '!unsafe \'a\': !unsafe "\\U0001F409"\n' + + def test_kv_unsafe_in_list(): assert safe_dump({'a': ['b']}) == "!unsafe 'a':\n- !unsafe 'b'\n" @@ -57,3 +75,11 @@ def test_safe_marking_deep_nesting(): yaml = safe_dump(deep, deepcopy(deep)) for x in ('a', 'b', 'c', 'd', 'e'): assert "!unsafe '{}'".format(x) not in yaml + + +def test_deep_diff_unsafe_marking(): + deep = {'a': [1, [{'b': {'c': [{'d': 'e'}]}}]]} + jt_vars = deepcopy(deep) + deep['a'][1][0]['b']['z'] = 'not safe' + yaml = safe_dump(deep, jt_vars) + assert "!unsafe 'z'" in yaml diff --git a/awx/main/utils/safe_yaml.py b/awx/main/utils/safe_yaml.py index af572e9fd5..1b958b69d0 100644 --- a/awx/main/utils/safe_yaml.py +++ b/awx/main/utils/safe_yaml.py @@ -50,21 +50,25 @@ def safe_dump(x, safe_dict=None): a: b !unsafe 'c': !unsafe 'd' """ - yamls = [] - safe_dict = safe_dict or {} - # Compare the top level keys so that we can find values that have - # equality matches (and consider those branches safe) - for k, v in x.items(): - dumper = yaml.SafeDumper - if safe_dict.get(k) != v: - dumper = SafeStringDumper - yamls.append(yaml.dump_all( - [{k: v}], - None, - Dumper=dumper, - default_flow_style=False, - )) - return ''.join(yamls) + if isinstance(x, dict): + yamls = [] + safe_dict = safe_dict or {} + + # Compare the top level keys so that we can find values that have + # equality matches (and consider those branches safe) + for k, v in x.items(): + dumper = yaml.SafeDumper + if safe_dict.get(k) != v: + dumper = SafeStringDumper + yamls.append(yaml.dump_all( + [{k: v}], + None, + Dumper=dumper, + default_flow_style=False, + )) + return ''.join(yamls) + else: + return yaml.dump_all([x], None, Dumper=SafeStringDumper, default_flow_style=False) def sanitize_jinja(arg):