# -*- 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 decimal import Decimal from inspect import isclass import pytest import six # pylint: disable=import-error from six.moves.xmlrpc_client import MAXINT, MININT # pylint: enable=import-error 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 _ if six.PY3: unicode = str long = int NULLS = (None, b'', u'', tuple(), []) pytestmark = pytest.mark.tier0 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('one', 'two')" o = self.cls(stuff, 'aye', 'bee', 'see') assert repr(o) == "DefaultFrom('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: if six.PY2: bad_value = u'name?' else: bad_value = b'name?' e = raises(TypeError, f, bad_value) assert str(e) == TYPE_ERROR % ('spec', str, bad_value, type(bad_value)) 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 # 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 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?')" o = self.cls('name', multivalue=True) assert repr(o) == "Param('name+')" 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) == 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) == 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: if key in ('cli_name', 'label', 'doc', 'cli_metavar'): continue 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 == "{0}+".format(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 raises(errors.ConversionError, o._convert_scalar, 'hello') 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) assert e.name == 'my_param' # Test with required=False o = self.cls('my_param', required=False) assert o.required is False assert o.validate(None) 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) assert_equal(e.name, u'my_param') # 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 % ('my_param', type(None), 42, int) o = self.cls('my_param') e = raises(TypeError, o.validate, 'Hello') assert str(e) == TYPE_ERROR % ('my_param', type(None), '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 == [(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) assert e.name == 'example' assert e.error == u'no good' 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)) 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)) assert e.name == 'multi_example' assert e.error == u'this one is not good' 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) # Test with passing rule: assert o._validate_scalar(True) is None assert o._validate_scalar(False) 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' e = raises(errors.ValidationError, o._validate_scalar, False) assert e.name == 'my_param' assert e.error == u'this describes the error' 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, type(None)) # 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 type(None) 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 # 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 bytes 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 (b'abc', b'four', b'12345'): assert rule(dummy, value) is None assert dummy.called() is False # Test with failing values: for value in (b'', b'a', b'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 (b'ab', b'123', b'four'): assert rule(dummy, value) is None assert dummy.called() is False # Test with failing values: for value in (b'12345', b'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 (b'1234', b'four'): assert rule(dummy, value) is None assert dummy.called() is False # Test with failing values: for value in (b'ab', b'123', b'12345', b'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 = b'\w+$' r = re.compile(pat) assert r.match(b'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 (b'HELLO', b'hello', b'Hello_World'): assert rule(dummy, value) is None assert dummy.called() is False # Test with failing values: for value in (b'Hello!', b'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, b'Hello', dict(one=1), utf8_bytes] for value in bad: e = raises(errors.ConversionError, mthd, value) assert e.name == 'my_str' 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_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) if six.PY2: assert r1.match(unicode_str) is None else: assert r1.match(unicode_str) is not 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 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', b'naughty', u'nurse!' _bad_type = bytes _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' # Assure large magnitude values are handled correctly assert type(o._convert_scalar(sys.maxsize * 2)) == long assert o._convert_scalar(sys.maxsize * 2) == sys.maxsize * 2 assert o._convert_scalar(unicode(sys.maxsize * 2)) == sys.maxsize * 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 assert o._convert_scalar(u'0o20') == 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 type(None) 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).startswith("invalid 'my_number': ") 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', ): 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 == 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: if six.PY2: bad_value = u'one' else: bad_value = b'one' for spec in (bad_value, 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' if six.PY2: assert_equal(e.error, u"The character '\\xc3' is not allowed.") else: assert_equal(e.error, u"The character 'á' 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)