diff options
Diffstat (limited to 'ipatests/test_ipalib/test_parameters.py')
-rw-r--r-- | ipatests/test_ipalib/test_parameters.py | 1533 |
1 files changed, 1533 insertions, 0 deletions
diff --git a/ipatests/test_ipalib/test_parameters.py b/ipatests/test_ipalib/test_parameters.py new file mode 100644 index 000000000..71acfce71 --- /dev/null +++ b/ipatests/test_ipalib/test_parameters.py @@ -0,0 +1,1533 @@ +# -*- coding: utf-8 -*- +# Authors: +# Jason Gerard DeRose <jderose@redhat.com> +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Test the `ipalib.parameters` module. +""" + +import re +import sys +from types import NoneType +from decimal import Decimal +from inspect import isclass +from ipatests.util import raises, ClassChecker, read_only +from ipatests.util import dummy_ugettext, assert_equal +from ipatests.data import binary_bytes, utf8_bytes, unicode_str +from ipalib import parameters, text, errors, config +from ipalib.constants import TYPE_ERROR, CALLABLE_ERROR, NULLS +from ipalib.errors import ValidationError, ConversionError +from ipalib import _ +from xmlrpclib import MAXINT, MININT + +class test_DefaultFrom(ClassChecker): + """ + Test the `ipalib.parameters.DefaultFrom` class. + """ + _cls = parameters.DefaultFrom + + def test_init(self): + """ + Test the `ipalib.parameters.DefaultFrom.__init__` method. + """ + def callback(*args): + return args + keys = ('givenname', 'sn') + o = self.cls(callback, *keys) + assert read_only(o, 'callback') is callback + assert read_only(o, 'keys') == keys + lam = lambda first, last: first[0] + last + o = self.cls(lam) + assert read_only(o, 'keys') == ('first', 'last') + + # Test that TypeError is raised when callback isn't callable: + e = raises(TypeError, self.cls, 'whatever') + assert str(e) == CALLABLE_ERROR % ('callback', 'whatever', str) + + # Test that TypeError is raised when a key isn't an str: + e = raises(TypeError, self.cls, callback, 'givenname', 17) + assert str(e) == TYPE_ERROR % ('keys', str, 17, int) + + # Test that ValueError is raised when inferring keys from a callback + # which has *args: + e = raises(ValueError, self.cls, lambda foo, *args: None) + assert str(e) == "callback: variable-length argument list not allowed" + + # Test that ValueError is raised when inferring keys from a callback + # which has **kwargs: + e = raises(ValueError, self.cls, lambda foo, **kwargs: None) + assert str(e) == "callback: variable-length argument list not allowed" + + def test_repr(self): + """ + Test the `ipalib.parameters.DefaultFrom.__repr__` method. + """ + def stuff(one, two): + pass + + o = self.cls(stuff) + assert repr(o) == "DefaultFrom(stuff, 'one', 'two')" + + o = self.cls(stuff, 'aye', 'bee', 'see') + assert repr(o) == "DefaultFrom(stuff, 'aye', 'bee', 'see')" + + cb = lambda first, last: first[0] + last + + o = self.cls(cb) + assert repr(o) == "DefaultFrom(<lambda>, 'first', 'last')" + + o = self.cls(cb, 'aye', 'bee', 'see') + assert repr(o) == "DefaultFrom(<lambda>, 'aye', 'bee', 'see')" + + def test_call(self): + """ + Test the `ipalib.parameters.DefaultFrom.__call__` method. + """ + def callback(givenname, sn): + return givenname[0] + sn[0] + keys = ('givenname', 'sn') + o = self.cls(callback, *keys) + kw = dict( + givenname='John', + sn='Public', + hello='world', + ) + assert o(**kw) == 'JP' + assert o() is None + for key in ('givenname', 'sn'): + kw_copy = dict(kw) + del kw_copy[key] + assert o(**kw_copy) is None + + # Test using implied keys: + o = self.cls(lambda first, last: first[0] + last) + assert o(first='john', last='doe') == 'jdoe' + assert o(first='', last='doe') is None + assert o(one='john', two='doe') is None + + # Test that co_varnames slice is used: + def callback2(first, last): + letter = first[0] + return letter + last + o = self.cls(callback2) + assert o.keys == ('first', 'last') + assert o(first='john', last='doe') == 'jdoe' + + +def test_parse_param_spec(): + """ + Test the `ipalib.parameters.parse_param_spec` function. + """ + f = parameters.parse_param_spec + assert f('name') == ('name', dict(required=True, multivalue=False)) + assert f('name?') == ('name', dict(required=False, multivalue=False)) + assert f('name*') == ('name', dict(required=False, multivalue=True)) + assert f('name+') == ('name', dict(required=True, multivalue=True)) + + # Make sure other "funny" endings are *not* treated special: + assert f('name^') == ('name^', dict(required=True, multivalue=False)) + + # Test that TypeError is raised if spec isn't an str: + e = raises(TypeError, f, u'name?') + assert str(e) == TYPE_ERROR % ('spec', str, u'name?', unicode) + + +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.parameters.Param` class. + """ + _cls = parameters.Param + + def test_init(self): + """ + Test the `ipalib.parameters.Param.__init__` method. + """ + name = 'my_param' + o = self.cls(name) + assert o.param_spec is name + assert o.name is name + assert o.nice == "Param('my_param')" + assert o.password is False + assert o.__islocked__() is True + + # Test default rules: + assert o.rules == tuple() + assert o.class_rules == tuple() + assert o.all_rules == tuple() + + # Test default kwarg values: + assert o.cli_name is name + assert o.label.msg == 'my_param' + assert o.doc.msg == 'my_param' + assert o.required is True + assert o.multivalue is False + assert o.primary_key is False + assert o.normalizer is None + assert o.default is None + assert o.default_from is None + assert o.autofill is False + assert o.query is False + assert o.attribute is False + assert o.include is None + assert o.exclude is None + assert o.flags == frozenset() + assert o.sortorder == 2 + assert o.csv is False + + # Test that doc defaults from label: + o = self.cls('my_param', doc=_('Hello world')) + assert o.label.msg == 'my_param' + assert o.doc.msg == 'Hello world' + + o = self.cls('my_param', label='My Param') + assert o.label == 'My Param' + assert o.doc == 'My Param' + + + # Test that ValueError is raised when a kwarg from a subclass + # conflicts with an attribute: + class Subclass(self.cls): + kwargs = self.cls.kwargs + ( + ('convert', callable, None), + ) + e = raises(ValueError, Subclass, name) + assert str(e) == "kwarg 'convert' conflicts with attribute on Subclass" + + # Test type validation of keyword arguments: + class Subclass(self.cls): + kwargs = self.cls.kwargs + ( + ('extra1', bool, True), + ('extra2', str, 'Hello'), + ('extra3', (int, float), 42), + ('extra4', callable, lambda whatever: whatever + 7), + ) + o = Subclass('my_param') # Test with no **kw: + for (key, kind, default) in o.kwargs: + # Test with a type invalid for all: + value = object() + kw = {key: value} + e = raises(TypeError, Subclass, 'my_param', **kw) + if kind is callable: + assert str(e) == CALLABLE_ERROR % (key, value, type(value)) + else: + assert str(e) == TYPE_ERROR % (key, kind, value, type(value)) + # Test with None: + kw = {key: None} + Subclass('my_param', **kw) + + # Test when using unknown kwargs: + e = raises(TypeError, self.cls, 'my_param', + flags=['hello', 'world'], + whatever=u'Hooray!', + ) + assert str(e) == \ + "Param('my_param'): takes no such kwargs: 'whatever'" + e = raises(TypeError, self.cls, 'my_param', great='Yes', ape='he is!') + assert str(e) == \ + "Param('my_param'): takes no such kwargs: 'ape', 'great'" + + # Test that ValueError is raised if you provide both include and + # exclude: + e = raises(ValueError, self.cls, 'my_param', + include=['server', 'foo'], + exclude=['client', 'bar'], + ) + assert str(e) == '%s: cannot have both %s=%r and %s=%r' % ( + "Param('my_param')", + 'include', frozenset(['server', 'foo']), + 'exclude', frozenset(['client', 'bar']), + ) + + # Test that ValueError is raised if csv is set and multivalue is not set: + e = raises(ValueError, self.cls, 'my_param', csv=True) + assert str(e) == '%s: cannot have csv without multivalue' % "Param('my_param')" + + # Test that default_from gets set: + call = lambda first, last: first[0] + last + o = self.cls('my_param', default_from=call) + assert type(o.default_from) is parameters.DefaultFrom + assert o.default_from.callback is call + + def test_repr(self): + """ + Test the `ipalib.parameters.Param.__repr__` method. + """ + for name in ['name', 'name?', 'name*', 'name+']: + o = self.cls(name) + assert repr(o) == 'Param(%r)' % name + o = self.cls('name', required=False) + assert repr(o) == "Param('name', required=False)" + o = self.cls('name', multivalue=True) + assert repr(o) == "Param('name', multivalue=True)" + + def test_use_in_context(self): + """ + Test the `ipalib.parameters.Param.use_in_context` method. + """ + set1 = ('one', 'two', 'three') + set2 = ('four', 'five', 'six') + param1 = self.cls('param1') + param2 = self.cls('param2', include=set1) + param3 = self.cls('param3', exclude=set2) + for context in set1: + env = config.Env() + env.context = context + assert param1.use_in_context(env) is True, context + assert param2.use_in_context(env) is True, context + assert param3.use_in_context(env) is True, context + for context in set2: + env = config.Env() + env.context = context + assert param1.use_in_context(env) is True, context + assert param2.use_in_context(env) is False, context + assert param3.use_in_context(env) is False, context + + def test_safe_value(self): + """ + Test the `ipalib.parameters.Param.safe_value` method. + """ + values = (unicode_str, binary_bytes, utf8_bytes) + o = self.cls('my_param') + for value in values: + assert o.safe_value(value) is value + assert o.safe_value(None) is None + p = parameters.Password('my_passwd') + for value in values: + assert_equal(p.safe_value(value), u'********') + assert p.safe_value(None) is None + + def test_clone(self): + """ + Test the `ipalib.parameters.Param.clone` method. + """ + # Test with the defaults + orig = self.cls('my_param') + clone = orig.clone() + assert clone is not orig + assert type(clone) is self.cls + assert clone.name is orig.name + for (key, kind, default) in self.cls.kwargs: + assert getattr(clone, key) is getattr(orig, key) + + # Test with a param spec: + orig = self.cls('my_param*') + assert orig.param_spec == 'my_param*' + clone = orig.clone() + assert clone.param_spec == 'my_param' + assert clone is not orig + assert type(clone) is self.cls + for (key, kind, default) in self.cls.kwargs: + assert getattr(clone, key) is getattr(orig, key) + + # Test with overrides: + orig = self.cls('my_param*') + assert orig.required is False + assert orig.multivalue is True + clone = orig.clone(required=True) + assert clone is not orig + assert type(clone) is self.cls + assert clone.required is True + assert clone.multivalue is True + assert clone.param_spec == 'my_param' + assert clone.name == 'my_param' + + def test_clone_rename(self): + """ + Test the `ipalib.parameters.Param.clone` method. + """ + new_name = 'my_new_param' + + # Test with the defaults + orig = self.cls('my_param') + clone = orig.clone_rename(new_name) + assert clone is not orig + assert type(clone) is self.cls + assert clone.name == new_name + for (key, kind, default) in self.cls.kwargs: + assert getattr(clone, key) is getattr(orig, key) + + # Test with overrides: + orig = self.cls('my_param*') + assert orig.required is False + assert orig.multivalue is True + clone = orig.clone_rename(new_name, required=True) + assert clone is not orig + assert type(clone) is self.cls + assert clone.required is True + assert clone.multivalue is True + assert clone.param_spec == new_name + assert clone.name == new_name + + + def test_convert(self): + """ + Test the `ipalib.parameters.Param.convert` method. + """ + okay = ('Hello', u'Hello', 0, 4.2, True, False, unicode_str) + class Subclass(self.cls): + def _convert_scalar(self, value, index=None): + return value + + # Test when multivalue=False: + o = Subclass('my_param') + for value in NULLS: + assert o.convert(value) is None + assert o.convert(None) is None + for value in okay: + assert o.convert(value) is value + + # Test when multivalue=True: + o = Subclass('my_param', multivalue=True) + for value in NULLS: + assert o.convert(value) is None + assert o.convert(okay) == okay + assert o.convert(NULLS) is None + assert o.convert(okay + NULLS) == okay + assert o.convert(NULLS + okay) == okay + for value in okay: + assert o.convert(value) == (value,) + assert o.convert([None, value]) == (value,) + assert o.convert([value, None]) == (value,) + + def test_convert_scalar(self): + """ + Test the `ipalib.parameters.Param._convert_scalar` method. + """ + dummy = dummy_ugettext() + + # Test with correct type: + o = self.cls('my_param') + assert o._convert_scalar(None) is None + assert dummy.called() is False + # Test with incorrect type + e = raises(errors.ConversionError, o._convert_scalar, 'hello', index=17) + + def test_validate(self): + """ + Test the `ipalib.parameters.Param.validate` method. + """ + + # Test in default state (with no rules, no kwarg): + o = self.cls('my_param') + e = raises(errors.RequirementError, o.validate, None, 'cli') + assert e.name == 'my_param' + + # Test in default state that cli_name gets returned in the exception + # when context == 'cli' + o = self.cls('my_param', cli_name='short') + e = raises(errors.RequirementError, o.validate, None, 'cli') + assert e.name == 'short' + + # Test with required=False + o = self.cls('my_param', required=False) + assert o.required is False + assert o.validate(None, 'cli') is None + + # Test with query=True: + o = self.cls('my_param', query=True) + assert o.query is True + e = raises(errors.RequirementError, o.validate, None, 'cli') + assert_equal(e.name, 'my_param') + + # Test with multivalue=True: + o = self.cls('my_param', multivalue=True) + e = raises(TypeError, o.validate, [], 'cli') + assert str(e) == TYPE_ERROR % ('value', tuple, [], list) + e = raises(ValueError, o.validate, tuple(), 'cli') + 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), 'cli') + assert str(e) == TYPE_ERROR % ('my_param', NoneType, 42, int) + o = self.cls('my_param') + e = raises(TypeError, o.validate, 'Hello', 'cli') + assert str(e) == TYPE_ERROR % ('my_param', 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, 'cli') is None + assert pass1.calls == [(text.ugettext, 11)] + assert pass2.calls == [(text.ugettext, 11)] + pass1.reset() + pass2.reset() + o = Example('example', pass1, pass2, fail) + e = raises(errors.ValidationError, o.validate, 42, 'cli') + assert e.name == 'example' + assert e.error == u'no good' + assert e.index is None + assert pass1.calls == [(text.ugettext, 42)] + assert pass2.calls == [(text.ugettext, 42)] + assert fail.calls == [(text.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), 'cli') is None + assert pass1.calls == [ + (text.ugettext, 3), + (text.ugettext, 9), + ] + assert pass2.calls == [ + (text.ugettext, 3), + (text.ugettext, 9), + ] + pass1.reset() + pass2.reset() + o = Example('multi_example', pass1, pass2, fail, multivalue=True) + assert o.multivalue is True + e = raises(errors.ValidationError, o.validate, (3, 9), 'cli') + assert e.name == 'multi_example' + assert e.error == u'this one is not good' + assert e.index == 0 + assert pass1.calls == [(text.ugettext, 3)] + assert pass2.calls == [(text.ugettext, 3)] + assert fail.calls == [(text.ugettext, 3)] + + def test_validate_scalar(self): + """ + Test the `ipalib.parameters.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 % ('my_param', bool, 0, int) + e = raises(TypeError, o._validate_scalar, 'Hi', index=4) + assert str(e) == TYPE_ERROR % ('my_param', 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 == [ + (text.ugettext, True), + (text.ugettext, False), + ] + + # Test with a failing rule: + okay = DummyRule() + fail = DummyRule(u'this describes the error') + o = MyParam('my_param', okay, fail) + e = raises(errors.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(errors.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 == [ + (text.ugettext, True), + (text.ugettext, False), + ] + assert fail.calls == [ + (text.ugettext, True), + (text.ugettext, False), + ] + + def test_get_default(self): + """ + Test the `ipalib.parameters.Param.get_default` method. + """ + class PassThrough(object): + value = None + + def __call__(self, value): + assert self.value is None + assert value is not None + self.value = value + return value + + def reset(self): + assert self.value is not None + self.value = None + + class Str(self.cls): + type = unicode + + def __init__(self, name, **kw): + self._convert_scalar = PassThrough() + super(Str, self).__init__(name, **kw) + + # Test with only a static default: + o = Str('my_str', + normalizer=PassThrough(), + default=u'Static Default', + ) + assert_equal(o.get_default(), u'Static Default') + assert o._convert_scalar.value is None + assert o.normalizer.value is None + + # Test with default_from: + o = Str('my_str', + normalizer=PassThrough(), + default=u'Static Default', + default_from=lambda first, last: first[0] + last, + ) + assert_equal(o.get_default(), u'Static Default') + assert o._convert_scalar.value is None + assert o.normalizer.value is None + default = o.get_default(first=u'john', last='doe') + assert_equal(default, u'jdoe') + assert o._convert_scalar.value is default + assert o.normalizer.value is default + + +class test_Flag(ClassChecker): + """ + Test the `ipalib.parameters.Flag` class. + """ + _cls = parameters.Flag + + def test_init(self): + """ + Test the `ipalib.parameters.Flag.__init__` method. + """ + # Test with no kwargs: + o = self.cls('my_flag') + assert o.type is bool + assert isinstance(o, parameters.Bool) + assert o.autofill is True + assert o.default is False + + # Test that TypeError is raise if default is not a bool: + e = raises(TypeError, self.cls, 'my_flag', default=None) + assert str(e) == TYPE_ERROR % ('default', bool, None, NoneType) + + # Test with autofill=False, default=True + o = self.cls('my_flag', autofill=False, default=True) + assert o.autofill is True + assert o.default is True + + # Test when cloning: + orig = self.cls('my_flag') + for clone in [orig.clone(), orig.clone(autofill=False)]: + assert clone.autofill is True + assert clone.default is False + assert clone is not orig + assert type(clone) is self.cls + + # Test when cloning with default=True/False + orig = self.cls('my_flag') + assert orig.clone().default is False + assert orig.clone(default=True).default is True + orig = self.cls('my_flag', default=True) + assert orig.clone().default is True + assert orig.clone(default=False).default is False + + +class test_Data(ClassChecker): + """ + Test the `ipalib.parameters.Data` class. + """ + _cls = parameters.Data + + def test_init(self): + """ + Test the `ipalib.parameters.Data.__init__` method. + """ + o = self.cls('my_data') + assert o.type is NoneType + assert o.password is False + assert o.rules == tuple() + assert o.class_rules == tuple() + assert o.all_rules == tuple() + assert o.minlength is None + assert o.maxlength is None + assert o.length is None + assert o.pattern is None + + # Test mixing length with minlength or maxlength: + o = self.cls('my_data', length=5) + assert o.length == 5 + permutations = [ + dict(minlength=3), + dict(maxlength=7), + dict(minlength=3, maxlength=7), + ] + for kw in permutations: + o = self.cls('my_data', **kw) + for (key, value) in kw.iteritems(): + assert getattr(o, key) == value + e = raises(ValueError, self.cls, 'my_data', length=5, **kw) + assert str(e) == \ + "Data('my_data'): cannot mix length with minlength or maxlength" + + # Test when minlength or maxlength are less than 1: + e = raises(ValueError, self.cls, 'my_data', minlength=0) + assert str(e) == "Data('my_data'): minlength must be >= 1; got 0" + e = raises(ValueError, self.cls, 'my_data', maxlength=0) + assert str(e) == "Data('my_data'): maxlength must be >= 1; got 0" + + # Test when minlength > maxlength: + e = raises(ValueError, self.cls, 'my_data', minlength=22, maxlength=15) + assert str(e) == \ + "Data('my_data'): minlength > maxlength (minlength=22, maxlength=15)" + + # Test when minlength == maxlength + e = raises(ValueError, self.cls, 'my_data', minlength=7, maxlength=7) + assert str(e) == \ + "Data('my_data'): minlength == maxlength; use length=7 instead" + + +class test_Bytes(ClassChecker): + """ + Test the `ipalib.parameters.Bytes` class. + """ + _cls = parameters.Bytes + + def test_init(self): + """ + Test the `ipalib.parameters.Bytes.__init__` method. + """ + o = self.cls('my_bytes') + assert o.type is str + assert o.password is False + assert o.rules == tuple() + assert o.class_rules == tuple() + assert o.all_rules == tuple() + assert o.minlength is None + assert o.maxlength is None + assert o.length is None + assert o.pattern is None + assert o.re is None + + # Test mixing length with minlength or maxlength: + o = self.cls('my_bytes', length=5) + assert o.length == 5 + assert len(o.class_rules) == 1 + assert len(o.rules) == 0 + assert len(o.all_rules) == 1 + permutations = [ + dict(minlength=3), + dict(maxlength=7), + dict(minlength=3, maxlength=7), + ] + for kw in permutations: + o = self.cls('my_bytes', **kw) + assert len(o.class_rules) == len(kw) + assert len(o.rules) == 0 + assert len(o.all_rules) == len(kw) + for (key, value) in kw.iteritems(): + assert getattr(o, key) == value + e = raises(ValueError, self.cls, 'my_bytes', length=5, **kw) + assert str(e) == \ + "Bytes('my_bytes'): cannot mix length with minlength or maxlength" + + # Test when minlength or maxlength are less than 1: + e = raises(ValueError, self.cls, 'my_bytes', minlength=0) + assert str(e) == "Bytes('my_bytes'): minlength must be >= 1; got 0" + e = raises(ValueError, self.cls, 'my_bytes', maxlength=0) + assert str(e) == "Bytes('my_bytes'): maxlength must be >= 1; got 0" + + # Test when minlength > maxlength: + e = raises(ValueError, self.cls, 'my_bytes', minlength=22, maxlength=15) + assert str(e) == \ + "Bytes('my_bytes'): minlength > maxlength (minlength=22, maxlength=15)" + + # Test when minlength == maxlength + e = raises(ValueError, self.cls, 'my_bytes', minlength=7, maxlength=7) + assert str(e) == \ + "Bytes('my_bytes'): minlength == maxlength; use length=7 instead" + + def test_rule_minlength(self): + """ + Test the `ipalib.parameters.Bytes._rule_minlength` method. + """ + o = self.cls('my_bytes', minlength=3) + assert o.minlength == 3 + rule = o._rule_minlength + translation = u'minlength=%(minlength)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in ('abc', 'four', '12345'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in ('', 'a', '12'): + assert_equal( + rule(dummy, value), + translation % dict(minlength=3) + ) + assert dummy.message == 'must be at least %(minlength)d bytes' + assert dummy.called() is True + dummy.reset() + + def test_rule_maxlength(self): + """ + Test the `ipalib.parameters.Bytes._rule_maxlength` method. + """ + o = self.cls('my_bytes', maxlength=4) + assert o.maxlength == 4 + rule = o._rule_maxlength + translation = u'maxlength=%(maxlength)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in ('ab', '123', 'four'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in ('12345', 'sixsix'): + assert_equal( + rule(dummy, value), + translation % dict(maxlength=4) + ) + assert dummy.message == 'can be at most %(maxlength)d bytes' + assert dummy.called() is True + dummy.reset() + + def test_rule_length(self): + """ + Test the `ipalib.parameters.Bytes._rule_length` method. + """ + o = self.cls('my_bytes', length=4) + assert o.length == 4 + rule = o._rule_length + translation = u'length=%(length)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in ('1234', 'four'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in ('ab', '123', '12345', 'sixsix'): + assert_equal( + rule(dummy, value), + translation % dict(length=4), + ) + assert dummy.message == 'must be exactly %(length)d bytes' + assert dummy.called() is True + dummy.reset() + + def test_rule_pattern(self): + """ + Test the `ipalib.parameters.Bytes._rule_pattern` method. + """ + # Test our assumptions about Python re module and Unicode: + pat = '\w+$' + r = re.compile(pat) + assert r.match('Hello_World') is not None + assert r.match(utf8_bytes) is None + assert r.match(binary_bytes) is None + + # Create instance: + o = self.cls('my_bytes', pattern=pat) + assert o.pattern is pat + rule = o._rule_pattern + translation = u'pattern=%(pattern)r' + dummy = dummy_ugettext(translation) + + # Test with passing values: + for value in ('HELLO', 'hello', 'Hello_World'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in ('Hello!', 'Hello World', utf8_bytes, binary_bytes): + assert_equal( + rule(dummy, value), + translation % dict(pattern=pat), + ) + assert_equal(dummy.message, 'must match pattern "%(pattern)s"') + assert dummy.called() is True + dummy.reset() + + +class test_Str(ClassChecker): + """ + Test the `ipalib.parameters.Str` class. + """ + _cls = parameters.Str + + def test_init(self): + """ + Test the `ipalib.parameters.Str.__init__` method. + """ + o = self.cls('my_str') + assert o.type is unicode + assert o.password is False + assert o.minlength is None + assert o.maxlength is None + assert o.length is None + assert o.pattern is None + + def test_convert_scalar(self): + """ + Test the `ipalib.parameters.Str._convert_scalar` method. + """ + o = self.cls('my_str') + mthd = o._convert_scalar + for value in (u'Hello', 42, 1.2, unicode_str): + assert mthd(value) == unicode(value) + bad = [True, 'Hello', dict(one=1), utf8_bytes] + for value in bad: + e = raises(errors.ConversionError, mthd, value) + assert e.name == 'my_str' + assert e.index is None + assert_equal(unicode(e.error), u'must be Unicode text') + e = raises(errors.ConversionError, mthd, value, index=18) + assert e.name == 'my_str' + assert e.index == 18 + assert_equal(unicode(e.error), u'must be Unicode text') + bad = [(u'Hello',), [42.3]] + for value in bad: + e = raises(errors.ConversionError, mthd, value) + assert e.name == 'my_str' + assert e.index is None + assert_equal(unicode(e.error), u'Only one value is allowed') + assert o.convert(None) is None + + def test_rule_minlength(self): + """ + Test the `ipalib.parameters.Str._rule_minlength` method. + """ + o = self.cls('my_str', minlength=3) + assert o.minlength == 3 + rule = o._rule_minlength + translation = u'minlength=%(minlength)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (u'abc', u'four', u'12345'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (u'', u'a', u'12'): + assert_equal( + rule(dummy, value), + translation % dict(minlength=3) + ) + assert dummy.message == 'must be at least %(minlength)d characters' + assert dummy.called() is True + dummy.reset() + + def test_rule_maxlength(self): + """ + Test the `ipalib.parameters.Str._rule_maxlength` method. + """ + o = self.cls('my_str', maxlength=4) + assert o.maxlength == 4 + rule = o._rule_maxlength + translation = u'maxlength=%(maxlength)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (u'ab', u'123', u'four'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (u'12345', u'sixsix'): + assert_equal( + rule(dummy, value), + translation % dict(maxlength=4) + ) + assert dummy.message == 'can be at most %(maxlength)d characters' + assert dummy.called() is True + dummy.reset() + + def test_rule_length(self): + """ + Test the `ipalib.parameters.Str._rule_length` method. + """ + o = self.cls('my_str', length=4) + assert o.length == 4 + rule = o._rule_length + translation = u'length=%(length)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (u'1234', u'four'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (u'ab', u'123', u'12345', u'sixsix'): + assert_equal( + rule(dummy, value), + translation % dict(length=4), + ) + assert dummy.message == 'must be exactly %(length)d characters' + assert dummy.called() is True + dummy.reset() + + def test_rule_pattern(self): + """ + Test the `ipalib.parameters.Str._rule_pattern` method. + """ + # Test our assumptions about Python re module and Unicode: + pat = '\w{5}$' + r1 = re.compile(pat) + r2 = re.compile(pat, re.UNICODE) + assert r1.match(unicode_str) is None + assert r2.match(unicode_str) is not None + + # Create instance: + o = self.cls('my_str', pattern=pat) + assert o.pattern is pat + rule = o._rule_pattern + translation = u'pattern=%(pattern)r' + dummy = dummy_ugettext(translation) + + # Test with passing values: + for value in (u'HELLO', u'hello', unicode_str): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (u'H LLO', u'***lo', unicode_str + unicode_str): + assert_equal( + rule(dummy, value), + translation % dict(pattern=pat), + ) + assert_equal(dummy.message, 'must match pattern "%(pattern)s"') + assert dummy.called() is True + dummy.reset() + + +class test_Password(ClassChecker): + """ + Test the `ipalib.parameters.Password` class. + """ + _cls = parameters.Password + + def test_init(self): + """ + Test the `ipalib.parameters.Password.__init__` method. + """ + o = self.cls('my_password') + assert o.type is unicode + assert o.minlength is None + assert o.maxlength is None + assert o.length is None + assert o.pattern is None + assert o.password is True + + def test_convert_scalar(self): + """ + Test the `ipalib.parameters.Password._convert_scalar` method. + """ + o = self.cls('my_password') + e = raises(errors.PasswordMismatch, o._convert_scalar, [u'one', u'two']) + assert e.name == 'my_password' + assert e.index is None + assert o._convert_scalar([u'one', u'one']) == u'one' + assert o._convert_scalar(u'one') == u'one' + + +class test_StrEnum(ClassChecker): + """ + Test the `ipalib.parameters.StrEnum` class. + """ + _cls = parameters.StrEnum + + def test_init(self): + """ + Test the `ipalib.parameters.StrEnum.__init__` method. + """ + values = (u'Hello', u'naughty', u'nurse!') + o = self.cls('my_strenum', values=values) + assert o.type is unicode + assert o.values is values + assert o.class_rules == (o._rule_values,) + assert o.rules == tuple() + assert o.all_rules == (o._rule_values,) + + badvalues = (u'Hello', 'naughty', u'nurse!') + e = raises(TypeError, self.cls, 'my_enum', values=badvalues) + assert str(e) == TYPE_ERROR % ( + "StrEnum('my_enum') values[1]", unicode, 'naughty', str + ) + + # Test that ValueError is raised when list of values is empty + badvalues = tuple() + e = raises(ValueError, self.cls, 'empty_enum', values=badvalues) + assert_equal(str(e), "StrEnum('empty_enum'): list of values must not " + "be empty") + + def test_rules_values(self): + """ + Test the `ipalib.parameters.StrEnum._rule_values` method. + """ + values = (u'Hello', u'naughty', u'nurse!') + o = self.cls('my_enum', values=values) + rule = o._rule_values + translation = u"values='Hello', 'naughty', 'nurse!'" + dummy = dummy_ugettext(translation) + + # Test with passing values: + for v in values: + assert rule(dummy, v) is None + assert dummy.called() is False + + # Test with failing values: + for val in (u'Howdy', u'quiet', u'library!'): + assert_equal( + rule(dummy, val), + translation % dict(values=values), + ) + assert_equal(dummy.message, "must be one of %(values)s") + dummy.reset() + + # test a special case when we have just one allowed value + values = (u'Hello', ) + o = self.cls('my_enum', values=values) + rule = o._rule_values + translation = u"value='Hello'" + dummy = dummy_ugettext(translation) + + for val in (u'Howdy', u'quiet', u'library!'): + assert_equal( + rule(dummy, val), + translation % dict(values=values), + ) + assert_equal(dummy.message, "must be '%(value)s'") + dummy.reset() + + +class test_Number(ClassChecker): + """ + Test the `ipalib.parameters.Number` class. + """ + _cls = parameters.Number + + def test_init(self): + """ + Test the `ipalib.parameters.Number.__init__` method. + """ + o = self.cls('my_number') + assert o.type is NoneType + assert o.password is False + assert o.rules == tuple() + assert o.class_rules == tuple() + assert o.all_rules == tuple() + + + +class test_Int(ClassChecker): + """ + Test the `ipalib.parameters.Int` class. + """ + _cls = parameters.Int + + def test_init(self): + """ + Test the `ipalib.parameters.Int.__init__` method. + """ + # Test with no kwargs: + o = self.cls('my_number') + assert o.type is int + assert isinstance(o, parameters.Int) + assert o.minvalue == int(MININT) + assert o.maxvalue == int(MAXINT) + + # Test when min > max: + e = raises(ValueError, self.cls, 'my_number', minvalue=22, maxvalue=15) + assert str(e) == \ + "Int('my_number'): minvalue > maxvalue (minvalue=22, maxvalue=15)" + + def test_rule_minvalue(self): + """ + Test the `ipalib.parameters.Int._rule_minvalue` method. + """ + o = self.cls('my_number', minvalue=3) + assert o.minvalue == 3 + rule = o._rule_minvalue + translation = u'minvalue=%(minvalue)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (4, 99, 1001): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (-1, 0, 2): + assert_equal( + rule(dummy, value), + translation % dict(minvalue=3) + ) + assert dummy.message == 'must be at least %(minvalue)d' + assert dummy.called() is True + dummy.reset() + + def test_rule_maxvalue(self): + """ + Test the `ipalib.parameters.Int._rule_maxvalue` method. + """ + o = self.cls('my_number', maxvalue=4) + assert o.maxvalue == 4 + rule = o._rule_maxvalue + translation = u'maxvalue=%(maxvalue)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (-1, 0, 4): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (5, 99, 1009): + assert_equal( + rule(dummy, value), + translation % dict(maxvalue=4) + ) + assert dummy.message == 'can be at most %(maxvalue)d' + assert dummy.called() is True + dummy.reset() + + def test_convert_scalar(self): + """ + Test the `ipalib.parameters.Int._convert_scalar` method. + Assure radix prefixes work, str objects fail, + floats (native & string) are truncated, + large magnitude values are promoted to long, + empty strings & invalid numerical representations fail + """ + o = self.cls('my_number') + # Assure invalid inputs raise error + for bad in ['hello', u'hello', True, None, u'', u'.']: + e = raises(errors.ConversionError, o._convert_scalar, bad) + assert e.name == 'my_number' + assert e.index is None + # Assure large magnatude values are handled correctly + assert type(o._convert_scalar(sys.maxint*2)) == long + assert o._convert_scalar(sys.maxint*2) == sys.maxint*2 + assert o._convert_scalar(unicode(sys.maxint*2)) == sys.maxint*2 + assert o._convert_scalar(long(16)) == 16 + # Assure normal conversions produce expected result + assert o._convert_scalar(u'16.99') == 16 + assert o._convert_scalar(16.99) == 16 + assert o._convert_scalar(u'16') == 16 + assert o._convert_scalar(u'0x10') == 16 + assert o._convert_scalar(u'020') == 16 + +class test_Decimal(ClassChecker): + """ + Test the `ipalib.parameters.Decimal` class. + """ + _cls = parameters.Decimal + + def test_init(self): + """ + Test the `ipalib.parameters.Decimal.__init__` method. + """ + # Test with no kwargs: + o = self.cls('my_number') + assert o.type is Decimal + assert isinstance(o, parameters.Decimal) + assert o.minvalue is None + assert o.maxvalue is None + + # Test when min > max: + e = raises(ValueError, self.cls, 'my_number', minvalue=Decimal('22.5'), maxvalue=Decimal('15.1')) + assert str(e) == \ + "Decimal('my_number'): minvalue > maxvalue (minvalue=22.5, maxvalue=15.1)" + + def test_rule_minvalue(self): + """ + Test the `ipalib.parameters.Decimal._rule_minvalue` method. + """ + o = self.cls('my_number', minvalue='3.1') + assert o.minvalue == Decimal('3.1') + rule = o._rule_minvalue + translation = u'minvalue=%(minvalue)s' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (Decimal('3.2'), Decimal('99.0')): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (Decimal('-1.2'), Decimal('0.0'), Decimal('3.0')): + assert_equal( + rule(dummy, value), + translation % dict(minvalue=Decimal('3.1')) + ) + assert dummy.message == 'must be at least %(minvalue)s' + assert dummy.called() is True + dummy.reset() + + def test_rule_maxvalue(self): + """ + Test the `ipalib.parameters.Decimal._rule_maxvalue` method. + """ + o = self.cls('my_number', maxvalue='4.7') + assert o.maxvalue == Decimal('4.7') + rule = o._rule_maxvalue + translation = u'maxvalue=%(maxvalue)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + + # Test with passing values: + for value in (Decimal('-1.0'), Decimal('0.1'), Decimal('4.2')): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (Decimal('5.3'), Decimal('99.9')): + assert_equal( + rule(dummy, value), + translation % dict(maxvalue=Decimal('4.7')) + ) + assert dummy.message == 'can be at most %(maxvalue)s' + assert dummy.called() is True + dummy.reset() + + def test_precision(self): + """ + Test the `ipalib.parameters.Decimal` precision attribute + """ + # precission is None + param = self.cls('my_number') + + for value in (Decimal('0'), Decimal('4.4'), Decimal('4.67')): + assert_equal( + param(value), + value) + + # precision is 0 + param = self.cls('my_number', precision=0) + for original,expected in ((Decimal('0'), '0'), + (Decimal('1.1'), '1'), + (Decimal('4.67'), '5')): + assert_equal( + str(param(original)), + expected) + + # precision is 1 + param = self.cls('my_number', precision=1) + for original,expected in ((Decimal('0'), '0.0'), + (Decimal('1.1'), '1.1'), + (Decimal('4.67'), '4.7')): + assert_equal( + str(param(original)), + expected) + + # value has too many digits + param = self.cls('my_number', precision=1) + e = raises(ConversionError, param, '123456789012345678901234567890') + + assert str(e) == \ + "invalid 'my_number': quantize result has too many digits for current context" + + def test_exponential(self): + """ + Test the `ipalib.parameters.Decimal` exponential attribute + """ + param = self.cls('my_number', exponential=True) + for original,expected in ((Decimal('0'), '0'), + (Decimal('1E3'), '1E+3'), + (Decimal('3.4E2'), '3.4E+2')): + assert_equal( + str(param(original)), + expected) + + + param = self.cls('my_number', exponential=False) + for original,expected in ((Decimal('0'), '0'), + (Decimal('1E3'), '1000'), + (Decimal('3.4E2'), '340')): + assert_equal( + str(param(original)), + expected) + + def test_numberclass(self): + """ + Test the `ipalib.parameters.Decimal` numberclass attribute + """ + # test default value: '-Normal', '+Zero', '+Normal' + param = self.cls('my_number') + for value,raises_verror in ((Decimal('0'), False), + (Decimal('-0'), True), + (Decimal('1E8'), False), + (Decimal('-1.1'), False), + (Decimal('-Infinity'), True), + (Decimal('+Infinity'), True), + (Decimal('NaN'), True)): + if raises_verror: + raises(ValidationError, param, value) + else: + param(value) + + + param = self.cls('my_number', exponential=True, + numberclass=('-Normal', '+Zero', '+Infinity')) + for value,raises_verror in ((Decimal('0'), False), + (Decimal('-0'), True), + (Decimal('1E8'), True), + (Decimal('-1.1'), False), + (Decimal('-Infinity'), True), + (Decimal('+Infinity'), False), + (Decimal('NaN'), True)): + if raises_verror: + raises(ValidationError, param, value) + else: + param(value) + +class test_AccessTime(ClassChecker): + """ + Test the `ipalib.parameters.AccessTime` class. + """ + _cls = parameters.AccessTime + + def test_init(self): + """ + Test the `ipalib.parameters.AccessTime.__init__` method. + """ + # Test with no kwargs: + o = self.cls('my_time') + assert o.type is unicode + assert isinstance(o, parameters.AccessTime) + assert o.multivalue is False + translation = u'length=%(length)r' + dummy = dummy_ugettext(translation) + assert dummy.translation is translation + rule = o._rule_required + + # Check some good rules + for value in (u'absolute 201012161032 ~ 201012161033', + u'periodic monthly week 2 day Sat,Sun 0900-1300', + u'periodic yearly month 4 day 1-31 0800-1400', + u'periodic weekly day 7 0800-1400', + u'periodic daily 0800-1400', + ): + assert rule(dummy, value) is None + assert dummy.called() is False + + # And some bad ones + for value in (u'absolute 201012161032 - 201012161033', + u'absolute 201012161032 ~', + u'periodic monthly day Sat,Sun 0900-1300', + u'periodical yearly month 4 day 1-31 0800-1400', + u'periodic weekly day 8 0800-1400', + ): + e = raises(ValidationError, o._rule_required, None, value) + +def test_create_param(): + """ + Test the `ipalib.parameters.create_param` function. + """ + f = parameters.create_param + + # Test that Param instances are returned unchanged: + params = ( + parameters.Param('one?'), + parameters.Int('two+'), + parameters.Str('three*'), + parameters.Bytes('four'), + ) + for p in params: + assert f(p) is p + + # Test that the spec creates an Str instance: + for spec in ('one?', 'two+', 'three*', 'four'): + (name, kw) = parameters.parse_param_spec(spec) + p = f(spec) + assert p.param_spec is spec + assert p.name == name + assert p.required is kw['required'] + assert p.multivalue is kw['multivalue'] + + # Test that TypeError is raised when spec is neither a Param nor a str: + for spec in (u'one', 42, parameters.Param, parameters.Str): + e = raises(TypeError, f, spec) + assert str(e) == \ + TYPE_ERROR % ('spec', (str, parameters.Param), spec, type(spec)) + + +def test_messages(): + """ + Test module level message in `ipalib.parameters`. + """ + for name in dir(parameters): + if name.startswith('_'): + continue + attr = getattr(parameters, name) + if not (isclass(attr) and issubclass(attr, parameters.Param)): + continue + assert type(attr.type_error) is str + assert attr.type_error in parameters.__messages + + +class test_IA5Str(ClassChecker): + """ + Test the `ipalib.parameters.IA5Str` class. + """ + _cls = parameters.IA5Str + + def test_convert_scalar(self): + """ + Test the `ipalib.parameters.IA5Str._convert_scalar` method. + """ + o = self.cls('my_str') + mthd = o._convert_scalar + for value in (u'Hello', 42, 1.2): + assert mthd(value) == unicode(value) + bad = ['HelloĆ”'] + for value in bad: + e = raises(errors.ConversionError, mthd, value) + assert e.name == 'my_str' + assert e.index is None + assert_equal(e.error, "The character '\\xc3' is not allowed.") |