# -*- coding: utf-8 -*- # Authors: # Jason Gerard DeRose # # 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 . """ Test the `ipalib.parameters` module. """ # FIXME: Pylint errors # pylint: disable=no-member import datetime import re import sys from types import NoneType from decimal import Decimal from inspect import isclass from xmlrpclib import MAXINT, MININT import six 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 from ipalib.errors import ValidationError, ConversionError from ipalib import _ NULLS = (None, '', u'', tuple(), []) 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(, 'first', 'last')" o = self.cls(cb, 'aye', 'bee', 'see') assert repr(o) == "DefaultFrom(, '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 Subclass1(self.cls): kwargs = self.cls.kwargs + ( ('convert', callable, None), ) e = raises(ValueError, Subclass1, name) assert str(e) == "kwarg 'convert' conflicts with attribute on Subclass1" # 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): # (Pylint complains because the superclass is unknowm) # pylint: disable=bad-super-call, super-on-old-class 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.items(): 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.items(): 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 EnumChecker(ClassChecker): """ Test *Enum classes. """ _cls = parameters.StrEnum def test_init(self): """Test the `__init__` method""" values = self._test_values o = self.cls(self._name, values=values) assert o.type is self._datatype assert o.values is values assert o.class_rules == (o._rule_values,) assert o.rules == tuple() assert o.all_rules == (o._rule_values,) def test_bad_types(self): """Test failure with incorrect types""" badvalues = self._bad_type_values e = raises(TypeError, self.cls, 'my_enum', values=badvalues) assert str(e) == TYPE_ERROR % ( "%s('my_enum') values[1]" % self._cls.__name__, self._datatype, badvalues[1], self._bad_type) def test_empty(self): """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), "%s('empty_enum'): list of values must not " "be empty" % self._cls.__name__) def test_rules_values(self): """Test the `_rule_values` method""" def test_rules_with_passing_rules(self): """Test with passing values""" o = self.cls('my_enum', values=self._test_values) rule = o._rule_values dummy = dummy_ugettext(self._translation) for v in self._test_values: assert rule(dummy, v) is None assert dummy.called() is False def test_rules_with_failing_rules(self): """Test with failing values""" o = self.cls('my_enum', values=self._test_values) rule = o._rule_values dummy = dummy_ugettext(self._translation) for val in self._bad_values: assert_equal( rule(dummy, val), self._translation % dict(values=self._test_values), ) assert_equal(dummy.message, "must be one of %(values)s") dummy.reset() def test_one_value(self): """test a special case when we have just one allowed value""" values = (self._test_values[0], ) o = self.cls('my_enum', values=values) rule = o._rule_values dummy = dummy_ugettext(self._single_value_translation) for val in self._bad_values: assert_equal( rule(dummy, val), self._single_value_translation % dict(values=values), ) assert_equal(dummy.message, "must be '%(value)s'") dummy.reset() class test_StrEnum(EnumChecker): """ Test the `ipalib.parameters.StrEnum` class. """ _cls = parameters.StrEnum _name = 'my_strenum' _datatype = unicode _test_values = u'Hello', u'naughty', u'nurse!' _bad_type_values = u'Hello', 'naughty', u'nurse!' _bad_type = str _translation = u"values='Hello', 'naughty', 'nurse!'" _bad_values = u'Howdy', u'quiet', u'library!' _single_value_translation = u"value='Hello'" def check_int_scalar_conversions(o): """ 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 """ # Assure invalid inputs raise error for bad in ['hello', u'hello', True, None, u'', u'.', 8j, ()]: e = raises(errors.ConversionError, o._convert_scalar, bad) assert e.name == 'my_number' assert e.index is None # Assure large magnitude 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_IntEnum(EnumChecker): """ Test the `ipalib.parameters.IntEnum` class. """ _cls = parameters.IntEnum _name = 'my_intenum' _datatype = int _test_values = 1, 2, -3 _bad_type_values = 1, 2.0, -3 _bad_type = float _translation = u"values=1, 2, 3" _bad_values = 4, 5, -6 _single_value_translation = u"value=1" def test_convert_scalar(self): """ Test the `ipalib.parameters.IntEnum._convert_scalar` method. """ param = self.cls('my_number', values=(1, 2, 3, 4, 5)) check_int_scalar_conversions(param) 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 == int assert o.allowed_types == six.integer_types 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. """ param = self.cls('my_number') check_int_scalar_conversions(param) 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.") class test_DateTime(ClassChecker): """ Test the `ipalib.parameters.DateTime` class. """ _cls = parameters.DateTime def test_init(self): """ Test the `ipalib.parameters.DateTime.__init__` method. """ # Test with no kwargs: o = self.cls('my_datetime') assert o.type is datetime.datetime assert isinstance(o, parameters.DateTime) assert o.multivalue is False # Check full time formats date = datetime.datetime(1991, 12, 7, 6, 30, 5) assert date == o.convert(u'19911207063005Z') assert date == o.convert(u'1991-12-07T06:30:05Z') assert date == o.convert(u'1991-12-07 06:30:05Z') # Check time formats without seconds date = datetime.datetime(1991, 12, 7, 6, 30) assert date == o.convert(u'1991-12-07T06:30Z') assert date == o.convert(u'1991-12-07 06:30Z') # Check date formats date = datetime.datetime(1991, 12, 7) assert date == o.convert(u'1991-12-07Z') # Check some wrong formats for value in (u'19911207063005', u'1991-12-07T06:30:05', u'1991-12-07 06:30:05', u'1991-12-07T06:30', u'1991-12-07 06:30', u'1991-12-07', u'1991-31-12Z', u'1991-12-07T25:30:05Z', ): raises(ConversionError, o.convert, value)