diff --git a/awx/conf/fields.py b/awx/conf/fields.py index 6a17d8b0ce..50aca441e0 100644 --- a/awx/conf/fields.py +++ b/awx/conf/fields.py @@ -61,32 +61,37 @@ class StringListBooleanField(ListField): child = CharField() def to_representation(self, value): - if isinstance(value, (list, tuple)): - return super(StringListBooleanField, self).to_representation(value) - elif value in fields.NullBooleanField.TRUE_VALUES: - return True - elif value in fields.NullBooleanField.FALSE_VALUES: - return False - elif value in fields.NullBooleanField.NULL_VALUES: - return None - elif isinstance(value, basestring): - return self.child.to_representation(value) - else: - self.fail('type_error', input_type=type(value)) + try: + if isinstance(value, (list, tuple)): + return super(StringListBooleanField, self).to_representation(value) + elif value in NullBooleanField.TRUE_VALUES: + return True + elif value in NullBooleanField.FALSE_VALUES: + return False + elif value in NullBooleanField.NULL_VALUES: + return None + elif isinstance(value, basestring): + return self.child.to_representation(value) + except TypeError: + pass + + self.fail('type_error', input_type=type(value)) def to_internal_value(self, data): - if isinstance(data, (list, tuple)): - return super(StringListBooleanField, self).to_internal_value(data) - elif data in fields.NullBooleanField.TRUE_VALUES: - return True - elif data in fields.NullBooleanField.FALSE_VALUES: - return False - elif data in fields.NullBooleanField.NULL_VALUES: - return None - elif isinstance(data, basestring): - return self.child.run_validation(data) - else: - self.fail('type_error', input_type=type(data)) + try: + if isinstance(data, (list, tuple)): + return super(StringListBooleanField, self).to_internal_value(data) + elif data in NullBooleanField.TRUE_VALUES: + return True + elif data in NullBooleanField.FALSE_VALUES: + return False + elif data in NullBooleanField.NULL_VALUES: + return None + elif isinstance(data, basestring): + return self.child.run_validation(data) + except TypeError: + pass + self.fail('type_error', input_type=type(data)) class URLField(CharField): @@ -140,7 +145,7 @@ class KeyValueField(DictField): class ListTuplesField(ListField): default_error_messages = { - 'type_error': _('Expected a list of tuples but got {input_type} instead.'), + 'type_error': _('Expected a list of tuples of max length 2 but got {input_type} instead.'), } def to_representation(self, value): @@ -152,7 +157,7 @@ class ListTuplesField(ListField): def to_internal_value(self, data): if isinstance(data, list): for x in data: - if not isinstance(x, (list, tuple)): + if not isinstance(x, (list, tuple)) or len(x) > 2: self.fail('type_error', input_type=type(x)) return super(ListTuplesField, self).to_internal_value(data) diff --git a/awx/conf/tests/unit/test_fields.py b/awx/conf/tests/unit/test_fields.py new file mode 100644 index 0000000000..0126723e97 --- /dev/null +++ b/awx/conf/tests/unit/test_fields.py @@ -0,0 +1,86 @@ +import pytest + +from rest_framework.fields import ValidationError +from awx.conf.fields import StringListBooleanField, ListTuplesField + + +class TestStringListBooleanField(): + + FIELD_VALUES = [ + ("hello", "hello"), + (("a", "b"), ["a", "b"]), + (["a", "b", 1, 3.13, "foo", "bar", "foobar"], ["a", "b", "1", "3.13", "foo", "bar", "foobar"]), + ("True", True), + ("TRUE", True), + ("true", True), + (True, True), + ("False", False), + ("FALSE", False), + ("false", False), + (False, False), + ("", None), + ("null", None), + ("NULL", None), + ] + + FIELD_VALUES_INVALID = [ + 1.245, + {"a": "b"}, + ] + + @pytest.mark.parametrize("value_in, value_known", FIELD_VALUES) + def test_to_internal_value_valid(self, value_in, value_known): + field = StringListBooleanField() + v = field.to_internal_value(value_in) + assert v == value_known + + @pytest.mark.parametrize("value", FIELD_VALUES_INVALID) + def test_to_internal_value_invalid(self, value): + field = StringListBooleanField() + with pytest.raises(ValidationError) as e: + field.to_internal_value(value) + assert e.value.detail[0] == "Expected None, True, False, a string or list " \ + "of strings but got {} instead.".format(type(value)) + + @pytest.mark.parametrize("value_in, value_known", FIELD_VALUES) + def test_to_representation_valid(self, value_in, value_known): + field = StringListBooleanField() + v = field.to_representation(value_in) + assert v == value_known + + @pytest.mark.parametrize("value", FIELD_VALUES_INVALID) + def test_to_representation_invalid(self, value): + field = StringListBooleanField() + with pytest.raises(ValidationError) as e: + field.to_representation(value) + assert e.value.detail[0] == "Expected None, True, False, a string or list " \ + "of strings but got {} instead.".format(type(value)) + + +class TestListTuplesField(): + + FIELD_VALUES = [ + ([('a', 'b'), ('abc', '123')], [("a", "b"), ("abc", "123")]), + ] + + FIELD_VALUES_INVALID = [ + ("abc", type("abc")), + ([('a', 'b', 'c'), ('abc', '123', '456')], type(('a',))), + (['a', 'b'], type('a')), + (123, type(123)), + ] + + @pytest.mark.parametrize("value_in, value_known", FIELD_VALUES) + def test_to_internal_value_valid(self, value_in, value_known): + field = ListTuplesField() + v = field.to_internal_value(value_in) + assert v == value_known + + @pytest.mark.parametrize("value, t", FIELD_VALUES_INVALID) + def test_to_internal_value_invalid(self, value, t): + field = ListTuplesField() + with pytest.raises(ValidationError) as e: + field.to_internal_value(value) + assert e.value.detail[0] == "Expected a list of tuples of max length 2 " \ + "but got {} instead.".format(t) +