From 5c7c0b35bb2484efad2a8776b42fbf4066618706 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 12 Jan 2009 16:14:46 -0700 Subject: New Param: added Param.validate() and Param._validate_scalar() methods; added corresponding unit tests --- ipalib/errors2.py | 16 ++++ ipalib/parameter.py | 35 +++++++++ tests/test_ipalib/test_parameter.py | 142 +++++++++++++++++++++++++++++++++++- 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/ipalib/errors2.py b/ipalib/errors2.py index b052882da..81b1fb2ed 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -465,9 +465,17 @@ class OptionError(InvocationError): class RequirementError(InvocationError): """ **3005** Raised when a required parameter is not provided. + + For example: + + >>> raise RequirementError(name='givenname') + Traceback (most recent call last): + ... + RequirementError: 'givenname' is required """ errno = 3005 + format = _('%(name)r is required') class ConversionError(InvocationError): @@ -481,9 +489,17 @@ class ConversionError(InvocationError): class ValidationError(InvocationError): """ **3007** Raised when a parameter value fails a validation rule. + + For example: + + >>> raise ValidationError(name='sn', error='can be at most 128 characters') + Traceback (most recent call last): + ... + ValidationError: invalid 'sn': can be at most 128 characters """ errno = 3007 + format = _('invalid %(name)r: %(error)s') diff --git a/ipalib/parameter.py b/ipalib/parameter.py index 204fda66f..0890160a0 100644 --- a/ipalib/parameter.py +++ b/ipalib/parameter.py @@ -25,6 +25,7 @@ from types import NoneType from util import make_repr from request import ugettext from plugable import ReadOnly, lock, check_name +from errors2 import RequirementError, ValidationError from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR @@ -207,6 +208,7 @@ class Param(ReadOnly): ('primary_key', bool, False), ('normalizer', callable, None), ('default_from', callable, None), + ('create_default', callable, None), ('flags', frozenset, frozenset()), # The 'default' kwarg gets appended in Param.__init__(): @@ -432,6 +434,39 @@ class Param(ReadOnly): :param value: A proposed value for this parameter. """ + if value is None: + if self.required: + raise RequirementError(name=self.name) + return + if self.multivalue: + if type(value) is not tuple: + raise TypeError( + TYPE_ERROR % ('value', tuple, value, type(value)) + ) + if len(value) < 1: + raise ValueError('value: empty tuple must be converted to None') + for (i, v) in enumerate(value): + self._validate_scalar(v, i) + else: + self._validate_scalar(value) + + def _validate_scalar(self, value, index=None): + if type(value) is not self.type: + if index is None: + name = 'value' + else: + name = 'value[%d]' % index + raise TypeError( + TYPE_ERROR % (name, self.type, value, type(value)) + ) + if index is not None and type(index) is not int: + raise TypeError( + TYPE_ERROR % ('index', int, index, type(index)) + ) + for rule in self.all_rules: + error = rule(ugettext, value) + if error is not None: + raise ValidationError(name=self.name, error=error, index=index) class Bool(Param): diff --git a/tests/test_ipalib/test_parameter.py b/tests/test_ipalib/test_parameter.py index e2016f06d..b379379cd 100644 --- a/tests/test_ipalib/test_parameter.py +++ b/tests/test_ipalib/test_parameter.py @@ -21,10 +21,11 @@ Test the `ipalib.parameter` module. """ +from types import NoneType from tests.util import raises, ClassChecker, read_only from tests.util import dummy_ugettext, assert_equal from tests.data import binary_bytes, utf8_bytes, unicode_str -from ipalib import parameter, request +from ipalib import parameter, request, errors2 from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, NULLS @@ -113,6 +114,20 @@ def test_parse_param_spec(): assert str(e) == "spec must be at least 2 characters; got 'n'" +class DummyRule(object): + def __init__(self, error=None): + assert error is None or type(error) is unicode + self.error = error + self.reset() + + def __call__(self, *args): + self.calls.append(args) + return self.error + + def reset(self): + self.calls = [] + + class test_Param(ClassChecker): """ Test the `ipalib.parameter.Param` class. @@ -145,6 +160,7 @@ class test_Param(ClassChecker): assert o.normalizer is None assert o.default is None assert o.default_from is None + assert o.create_default is None assert o.flags == frozenset() # Test that ValueError is raised when a kwarg from a subclass @@ -287,6 +303,130 @@ class test_Param(ClassChecker): e = raises(NotImplementedError, o._convert_scalar, 'some value') assert str(e) == 'Subclass._convert_scalar()' + def test_validate(self): + """ + Test the `ipalib.parameter.Param.validate` method. + """ + + # Test with required=True/False: + o = self.cls('my_param') + assert o.required is True + e = raises(errors2.RequirementError, o.validate, None) + assert e.name == 'my_param' + o = self.cls('my_param', required=False) + assert o.required is False + assert o.validate(None) is None + + # Test with multivalue=True: + o = self.cls('my_param', multivalue=True) + e = raises(TypeError, o.validate, []) + assert str(e) == TYPE_ERROR % ('value', tuple, [], list) + e = raises(ValueError, o.validate, tuple()) + assert str(e) == 'value: empty tuple must be converted to None' + + # Test with wrong (scalar) type: + e = raises(TypeError, o.validate, (None, None, 42, None)) + assert str(e) == TYPE_ERROR % ('value[2]', NoneType, 42, int) + o = self.cls('my_param') + e = raises(TypeError, o.validate, 'Hello') + assert str(e) == TYPE_ERROR % ('value', NoneType, 'Hello', str) + + class Example(self.cls): + type = int + + # Test with some rules and multivalue=False + pass1 = DummyRule() + pass2 = DummyRule() + fail = DummyRule(u'no good') + o = Example('example', pass1, pass2) + assert o.multivalue is False + assert o.validate(11) is None + assert pass1.calls == [(request.ugettext, 11)] + assert pass2.calls == [(request.ugettext, 11)] + pass1.reset() + pass2.reset() + o = Example('example', pass1, pass2, fail) + e = raises(errors2.ValidationError, o.validate, 42) + assert e.name == 'example' + assert e.error == u'no good' + assert e.index is None + assert pass1.calls == [(request.ugettext, 42)] + assert pass2.calls == [(request.ugettext, 42)] + assert fail.calls == [(request.ugettext, 42)] + + # Test with some rules and multivalue=True + pass1 = DummyRule() + pass2 = DummyRule() + fail = DummyRule(u'this one is not good') + o = Example('example', pass1, pass2, multivalue=True) + assert o.multivalue is True + assert o.validate((3, 9)) is None + assert pass1.calls == [ + (request.ugettext, 3), + (request.ugettext, 9), + ] + assert pass2.calls == [ + (request.ugettext, 3), + (request.ugettext, 9), + ] + pass1.reset() + pass2.reset() + o = Example('multi_example', pass1, pass2, fail, multivalue=True) + assert o.multivalue is True + e = raises(errors2.ValidationError, o.validate, (3, 9)) + assert e.name == 'multi_example' + assert e.error == u'this one is not good' + assert e.index == 0 + assert pass1.calls == [(request.ugettext, 3)] + assert pass2.calls == [(request.ugettext, 3)] + assert fail.calls == [(request.ugettext, 3)] + + def test_validate_scalar(self): + """ + Test the `ipalib.parameter.Param._validate_scalar` method. + """ + class MyParam(self.cls): + type = bool + okay = DummyRule() + o = MyParam('my_param', okay) + + # Test that TypeError is appropriately raised: + e = raises(TypeError, o._validate_scalar, 0) + assert str(e) == TYPE_ERROR % ('value', bool, 0, int) + e = raises(TypeError, o._validate_scalar, 'Hi', index=4) + assert str(e) == TYPE_ERROR % ('value[4]', bool, 'Hi', str) + e = raises(TypeError, o._validate_scalar, True, index=3.0) + assert str(e) == TYPE_ERROR % ('index', int, 3.0, float) + + # Test with passing rule: + assert o._validate_scalar(True, index=None) is None + assert o._validate_scalar(False, index=None) is None + assert okay.calls == [ + (request.ugettext, True), + (request.ugettext, False), + ] + + # Test with a failing rule: + okay = DummyRule() + fail = DummyRule(u'this describes the error') + o = MyParam('my_param', okay, fail) + e = raises(errors2.ValidationError, o._validate_scalar, True) + assert e.name == 'my_param' + assert e.error == u'this describes the error' + assert e.index is None + e = raises(errors2.ValidationError, o._validate_scalar, False, index=2) + assert e.name == 'my_param' + assert e.error == u'this describes the error' + assert e.index == 2 + assert okay.calls == [ + (request.ugettext, True), + (request.ugettext, False), + ] + assert fail.calls == [ + (request.ugettext, True), + (request.ugettext, False), + ] + class test_Bytes(ClassChecker): """ -- cgit