summaryrefslogtreecommitdiffstats
path: root/tests/test_ipalib
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2008-10-07 20:41:15 -0600
committerJason Gerard DeRose <jderose@redhat.com>2008-10-07 20:41:15 -0600
commit7721443a625b2efd0744ad347c62795e5ba6bb91 (patch)
tree96b40720fbe59f88cbae855ed26de50c8cebea9c /tests/test_ipalib
parent2617f89b6ad4dc64e52bcd83cef77ef7d3f3003f (diff)
downloadfreeipa-7721443a625b2efd0744ad347c62795e5ba6bb91.tar.gz
freeipa-7721443a625b2efd0744ad347c62795e5ba6bb91.tar.xz
freeipa-7721443a625b2efd0744ad347c62795e5ba6bb91.zip
Moved ipalib/tests/ into tests/test_ipalib/
Diffstat (limited to 'tests/test_ipalib')
-rw-r--r--tests/test_ipalib/__init__.py22
-rw-r--r--tests/test_ipalib/test_backend.py37
-rw-r--r--tests/test_ipalib/test_cli.py138
-rw-r--r--tests/test_ipalib/test_config.py101
-rw-r--r--tests/test_ipalib/test_crud.py168
-rw-r--r--tests/test_ipalib/test_errors.py274
-rw-r--r--tests/test_ipalib/test_frontend.py1080
-rw-r--r--tests/test_ipalib/test_ipa_types.py371
-rw-r--r--tests/test_ipalib/test_plugable.py896
-rw-r--r--tests/test_ipalib/test_tstutil.py148
-rw-r--r--tests/test_ipalib/test_util.py49
-rw-r--r--tests/test_ipalib/tstutil.py147
12 files changed, 3431 insertions, 0 deletions
diff --git a/tests/test_ipalib/__init__.py b/tests/test_ipalib/__init__.py
new file mode 100644
index 000000000..d3658c451
--- /dev/null
+++ b/tests/test_ipalib/__init__.py
@@ -0,0 +1,22 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib` package.
+"""
diff --git a/tests/test_ipalib/test_backend.py b/tests/test_ipalib/test_backend.py
new file mode 100644
index 000000000..967e9fdfb
--- /dev/null
+++ b/tests/test_ipalib/test_backend.py
@@ -0,0 +1,37 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.backend` module.
+"""
+
+from ipalib import backend, plugable, errors
+from tstutil import ClassChecker
+
+
+class test_Backend(ClassChecker):
+ """
+ Test the `backend.Backend` class.
+ """
+
+ _cls = backend.Backend
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.Plugin,)
+ assert self.cls.__proxy__ is False
diff --git a/tests/test_ipalib/test_cli.py b/tests/test_ipalib/test_cli.py
new file mode 100644
index 000000000..90c66d416
--- /dev/null
+++ b/tests/test_ipalib/test_cli.py
@@ -0,0 +1,138 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.cli` module.
+"""
+
+from tstutil import raises, getitem, no_set, no_del, read_only, ClassChecker
+from ipalib import cli, plugable
+
+
+def test_to_cli():
+ """
+ Tests the `cli.to_cli` function.
+ """
+ f = cli.to_cli
+ assert f('initialize') == 'initialize'
+ assert f('user_add') == 'user-add'
+
+
+def test_from_cli():
+ """
+ Tests the `cli.from_cli` function.
+ """
+ f = cli.from_cli
+ assert f('initialize') == 'initialize'
+ assert f('user-add') == 'user_add'
+
+
+def get_cmd_name(i):
+ return 'cmd_%d' % i
+
+class DummyCommand(object):
+ def __init__(self, name):
+ self.__name = name
+
+ def __get_name(self):
+ return self.__name
+ name = property(__get_name)
+
+class DummyAPI(object):
+ def __init__(self, cnt):
+ self.__cmd = plugable.NameSpace(self.__cmd_iter(cnt))
+
+ def __get_cmd(self):
+ return self.__cmd
+ Command = property(__get_cmd)
+
+ def __cmd_iter(self, cnt):
+ for i in xrange(cnt):
+ yield DummyCommand(get_cmd_name(i))
+
+ def finalize(self):
+ pass
+
+ def register(self, *args, **kw):
+ pass
+
+
+
+
+
+class test_CLI(ClassChecker):
+ """
+ Tests the `cli.CLI` class.
+ """
+ _cls = cli.CLI
+
+ def test_class(self):
+ assert type(self.cls.api) is property
+
+ def test_api(self):
+ """
+ Tests the `cli.CLI.api` property.
+ """
+ api = 'the plugable.API instance'
+ o = self.cls(api)
+ assert read_only(o, 'api') is api
+
+ def dont_parse(self):
+ """
+ Tests the `cli.CLI.parse` method.
+ """
+ o = self.cls(None)
+ args = ['hello', 'naughty', 'nurse']
+ kw = dict(
+ first_name='Naughty',
+ last_name='Nurse',
+ )
+ opts = ['--%s=%s' % (k.replace('_', '-'), v) for (k, v) in kw.items()]
+ assert o.parse(args + []) == (args, {})
+ assert o.parse(opts + []) == ([], kw)
+ assert o.parse(args + opts) == (args, kw)
+ assert o.parse(opts + args) == (args, kw)
+
+ def test_mcl(self):
+ """
+ Tests the `cli.CLI.mcl` (Max Command Length) property .
+ """
+ cnt = 100
+ api = DummyAPI(cnt)
+ len(api.Command) == cnt
+ o = self.cls(api)
+ assert o.mcl is None
+ o.build_map()
+ assert o.mcl == 6 # len('cmd_99')
+
+ def test_dict(self):
+ """
+ Tests the `cli.CLI.__contains__` and `cli.CLI.__getitem__` methods.
+ """
+ cnt = 25
+ api = DummyAPI(cnt)
+ assert len(api.Command) == cnt
+ o = self.cls(api)
+ o.build_map()
+ for cmd in api.Command():
+ key = cli.to_cli(cmd.name)
+ assert key in o
+ assert o[key] is cmd
+ assert cmd.name not in o
+ raises(KeyError, getitem, o, cmd.name)
diff --git a/tests/test_ipalib/test_config.py b/tests/test_ipalib/test_config.py
new file mode 100644
index 000000000..de7d4c22c
--- /dev/null
+++ b/tests/test_ipalib/test_config.py
@@ -0,0 +1,101 @@
+# Authors:
+# Martin Nagy <mnagy@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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.config` module.
+"""
+
+import types
+
+from tstutil import raises
+from ipalib import config
+
+
+def test_generate_env():
+ """
+ Test the `config.generate_env` function
+ """
+
+ # Make sure we don't overwrite any properties
+ env = dict(
+ query_dns = False,
+ server = ('first', 'second'),
+ realm = 'myrealm',
+ )
+ d = config.generate_env(env)
+ assert d['query_dns'] == False
+
+ # Make sure the servers is overwrote properly (that it is still LazyProp)
+ iter = d['server'].get_value()
+ assert iter.next() == 'first'
+ assert iter.next() == 'second'
+
+
+def test_LazyProp():
+ """
+ Test the `config.LazyProp` class
+ """
+
+ def dummy():
+ return 1
+
+ # Basic sanity testing with no initial value
+ prop = config.LazyProp(dummy)
+ assert prop.get_value() == 1
+ prop.set_value(2)
+ assert prop.get_value() == 2
+
+ # Basic sanity testing with initial value
+ prop = config.LazyProp(dummy, 3)
+ assert prop.get_value() == 3
+ prop.set_value(4)
+ assert prop.get_value() == 4
+
+
+def test_LazyIter():
+ """
+ Test the `config.LazyIter` class
+ """
+
+ def dummy():
+ yield 1
+ yield 2
+
+ # Basic sanity testing with no initial value
+ prop = config.LazyIter(dummy)
+ iter = prop.get_value()
+ assert iter.next() == 1
+ assert iter.next() == 2
+ raises(StopIteration, iter.next)
+
+ # Basic sanity testing with initial value
+ prop = config.LazyIter(dummy, 0)
+ iter = prop.get_value()
+ assert iter.next() == 0
+ assert iter.next() == 1
+ assert iter.next() == 2
+ raises(StopIteration, iter.next)
+
+
+def test_read_config():
+ """
+ Test the `config.read_config` class
+ """
+
+ raises(AssertionError, config.read_config, 1)
diff --git a/tests/test_ipalib/test_crud.py b/tests/test_ipalib/test_crud.py
new file mode 100644
index 000000000..9355f237e
--- /dev/null
+++ b/tests/test_ipalib/test_crud.py
@@ -0,0 +1,168 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.crud` module.
+"""
+
+from tstutil import read_only, raises, ClassChecker
+from ipalib import crud, frontend, plugable, config
+
+def get_api():
+ api = plugable.API(
+ frontend.Object,
+ frontend.Method,
+ frontend.Property,
+ )
+ api.env.update(config.generate_env())
+ class user(frontend.Object):
+ takes_params = (
+ 'givenname',
+ 'sn',
+ frontend.Param('uid', primary_key=True),
+ 'initials',
+ )
+ api.register(user)
+ return api
+
+
+class test_Add(ClassChecker):
+ """
+ Test the `crud.Add` class.
+ """
+
+ _cls = crud.Add
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Method,)
+
+ def test_options_args(self):
+ """
+ Test `crud.Add.get_args` and `crud.Add.get_options` methods.
+ """
+ api = get_api()
+ class user_add(self.cls):
+ pass
+ api.register(user_add)
+ api.finalize()
+ assert list(api.Method.user_add.args) == ['uid']
+ assert list(api.Method.user_add.options) == \
+ ['givenname', 'sn', 'initials']
+ for param in api.Method.user_add.options():
+ assert param.required is True
+
+
+class test_Get(ClassChecker):
+ """
+ Test the `crud.Get` class.
+ """
+
+ _cls = crud.Get
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Method,)
+
+ def test_options_args(self):
+ """
+ Test `crud.Get.get_args` and `crud.Get.get_options` methods.
+ """
+ api = get_api()
+ class user_get(self.cls):
+ pass
+ api.register(user_get)
+ api.finalize()
+ assert list(api.Method.user_get.args) == ['uid']
+ assert list(api.Method.user_get.options) == []
+
+
+class test_Del(ClassChecker):
+ """
+ Test the `crud.Del` class.
+ """
+
+ _cls = crud.Del
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Method,)
+
+ def test_options_args(self):
+ """
+ Test `crud.Del.get_args` and `crud.Del.get_options` methods.
+ """
+ api = get_api()
+ class user_del(self.cls):
+ pass
+ api.register(user_del)
+ api.finalize()
+ assert list(api.Method.user_del.args) == ['uid']
+ assert list(api.Method.user_del.options) == []
+
+
+class test_Mod(ClassChecker):
+ """
+ Test the `crud.Mod` class.
+ """
+
+ _cls = crud.Mod
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Method,)
+
+ def test_options_args(self):
+ """
+ Test `crud.Mod.get_args` and `crud.Mod.get_options` methods.
+ """
+ api = get_api()
+ class user_mod(self.cls):
+ pass
+ api.register(user_mod)
+ api.finalize()
+ assert list(api.Method.user_mod.args) == ['uid']
+ assert api.Method.user_mod.args[0].required is True
+ assert list(api.Method.user_mod.options) == \
+ ['givenname', 'sn', 'initials']
+ for param in api.Method.user_mod.options():
+ assert param.required is False
+
+
+class test_Find(ClassChecker):
+ """
+ Test the `crud.Find` class.
+ """
+
+ _cls = crud.Find
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Method,)
+
+ def test_options_args(self):
+ """
+ Test `crud.Find.get_args` and `crud.Find.get_options` methods.
+ """
+ api = get_api()
+ class user_find(self.cls):
+ pass
+ api.register(user_find)
+ api.finalize()
+ assert list(api.Method.user_find.args) == ['uid']
+ assert api.Method.user_find.args[0].required is True
+ assert list(api.Method.user_find.options) == \
+ ['givenname', 'sn', 'initials']
+ for param in api.Method.user_find.options():
+ assert param.required is False
diff --git a/tests/test_ipalib/test_errors.py b/tests/test_ipalib/test_errors.py
new file mode 100644
index 000000000..7d2df4dfe
--- /dev/null
+++ b/tests/test_ipalib/test_errors.py
@@ -0,0 +1,274 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.errors` module.
+"""
+
+from tstutil import raises, ClassChecker
+from ipalib import errors
+
+
+type_format = '%s: need a %r; got %r'
+
+
+def check_TypeError(f, value, type_, name, **kw):
+ e = raises(TypeError, f, value, type_, name, **kw)
+ assert e.value is value
+ assert e.type is type_
+ assert e.name is name
+ assert str(e) == type_format % (name, type_, value)
+
+
+def test_raise_TypeError():
+ """
+ Tests the `errors.raise_TypeError` function.
+ """
+ f = errors.raise_TypeError
+ value = 'Hello.'
+ type_ = unicode
+ name = 'message'
+
+ check_TypeError(f, value, type_, name)
+
+ # name not an str
+ fail_name = 42
+ e = raises(AssertionError, f, value, type_, fail_name)
+ assert str(e) == type_format % ('name', str, fail_name), str(e)
+
+ # type_ not a type:
+ fail_type = unicode()
+ e = raises(AssertionError, f, value, fail_type, name)
+ assert str(e) == type_format % ('type_', type, fail_type)
+
+ # type(value) is type_:
+ fail_value = u'How are you?'
+ e = raises(AssertionError, f, fail_value, type_, name)
+ assert str(e) == 'value: %r is a %r' % (fail_value, type_)
+
+
+def test_check_type():
+ """
+ Tests the `errors.check_type` function.
+ """
+ f = errors.check_type
+ value = 'How are you?'
+ type_ = str
+ name = 'greeting'
+
+ # Should pass:
+ assert value is f(value, type_, name)
+ assert None is f(None, type_, name, allow_none=True)
+
+ # Should raise TypeError
+ check_TypeError(f, None, type_, name)
+ check_TypeError(f, value, basestring, name)
+ check_TypeError(f, value, unicode, name)
+
+ # name not an str
+ fail_name = unicode(name)
+ e = raises(AssertionError, f, value, type_, fail_name)
+ assert str(e) == type_format % ('name', str, fail_name)
+
+ # type_ not a type:
+ fail_type = 42
+ e = raises(AssertionError, f, value, fail_type, name)
+ assert str(e) == type_format % ('type_', type, fail_type)
+
+ # allow_none not a bool:
+ fail_bool = 0
+ e = raises(AssertionError, f, value, type_, name, allow_none=fail_bool)
+ assert str(e) == type_format % ('allow_none', bool, fail_bool)
+
+
+def test_check_isinstance():
+ """
+ Tests the `errors.check_isinstance` function.
+ """
+ f = errors.check_isinstance
+ value = 'How are you?'
+ type_ = str
+ name = 'greeting'
+
+ # Should pass:
+ assert value is f(value, type_, name)
+ assert value is f(value, basestring, name)
+ assert None is f(None, type_, name, allow_none=True)
+
+ # Should raise TypeError
+ check_TypeError(f, None, type_, name)
+ check_TypeError(f, value, unicode, name)
+
+ # name not an str
+ fail_name = unicode(name)
+ e = raises(AssertionError, f, value, type_, fail_name)
+ assert str(e) == type_format % ('name', str, fail_name)
+
+ # type_ not a type:
+ fail_type = 42
+ e = raises(AssertionError, f, value, fail_type, name)
+ assert str(e) == type_format % ('type_', type, fail_type)
+
+ # allow_none not a bool:
+ fail_bool = 0
+ e = raises(AssertionError, f, value, type_, name, allow_none=fail_bool)
+ assert str(e) == type_format % ('allow_none', bool, fail_bool)
+
+
+class test_IPAError(ClassChecker):
+ """
+ Tests the `errors.IPAError` exception.
+ """
+ _cls = errors.IPAError
+
+ def test_class(self):
+ assert self.cls.__bases__ == (Exception,)
+
+ def test_init(self):
+ """
+ Tests the `errors.IPAError.__init__` method.
+ """
+ args = ('one fish', 'two fish')
+ e = self.cls(*args)
+ assert e.args == args
+ assert self.cls().args == tuple()
+
+ def test_str(self):
+ """
+ Tests the `errors.IPAError.__str__` method.
+ """
+ f = 'The %s color is %s.'
+ class custom_error(self.cls):
+ format = f
+ for args in [('sexiest', 'red'), ('most-batman-like', 'black')]:
+ e = custom_error(*args)
+ assert e.args == args
+ assert str(e) == f % args
+
+
+class test_ValidationError(ClassChecker):
+ """
+ Tests the `errors.ValidationError` exception.
+ """
+ _cls = errors.ValidationError
+
+ def test_class(self):
+ assert self.cls.__bases__ == (errors.IPAError,)
+
+ def test_init(self):
+ """
+ Tests the `errors.ValidationError.__init__` method.
+ """
+ name = 'login'
+ value = 'Whatever'
+ error = 'Must be lowercase.'
+ for index in (None, 3):
+ e = self.cls(name, value, error, index=index)
+ assert e.name is name
+ assert e.value is value
+ assert e.error is error
+ assert e.index is index
+ assert str(e) == 'invalid %r value %r: %s' % (name, value, error)
+ # Check that index default is None:
+ assert self.cls(name, value, error).index is None
+ # Check non str name raises AssertionError:
+ raises(AssertionError, self.cls, unicode(name), value, error)
+ # Check non int index raises AssertionError:
+ raises(AssertionError, self.cls, name, value, error, index=5.0)
+ # Check negative index raises AssertionError:
+ raises(AssertionError, self.cls, name, value, error, index=-2)
+
+
+class test_ConversionError(ClassChecker):
+ """
+ Tests the `errors.ConversionError` exception.
+ """
+ _cls = errors.ConversionError
+
+ def test_class(self):
+ assert self.cls.__bases__ == (errors.ValidationError,)
+
+ def test_init(self):
+ """
+ Tests the `errors.ConversionError.__init__` method.
+ """
+ name = 'some_arg'
+ value = '42.0'
+ class type_(object):
+ conversion_error = 'Not an integer'
+ for index in (None, 7):
+ e = self.cls(name, value, type_, index=index)
+ assert e.name is name
+ assert e.value is value
+ assert e.type is type_
+ assert e.error is type_.conversion_error
+ assert e.index is index
+ assert str(e) == 'invalid %r value %r: %s' % (name, value,
+ type_.conversion_error)
+ # Check that index default is None:
+ assert self.cls(name, value, type_).index is None
+
+
+class test_RuleError(ClassChecker):
+ """
+ Tests the `errors.RuleError` exception.
+ """
+ _cls = errors.RuleError
+
+ def test_class(self):
+ assert self.cls.__bases__ == (errors.ValidationError,)
+
+ def test_init(self):
+ """
+ Tests the `errors.RuleError.__init__` method.
+ """
+ name = 'whatever'
+ value = 'The smallest weird number.'
+ def my_rule(value):
+ return 'Value is bad.'
+ error = my_rule(value)
+ for index in (None, 42):
+ e = self.cls(name, value, error, my_rule, index=index)
+ assert e.name is name
+ assert e.value is value
+ assert e.error is error
+ assert e.rule is my_rule
+ # Check that index default is None:
+ assert self.cls(name, value, error, my_rule).index is None
+
+
+class test_RequirementError(ClassChecker):
+ """
+ Tests the `errors.RequirementError` exception.
+ """
+ _cls = errors.RequirementError
+
+ def test_class(self):
+ assert self.cls.__bases__ == (errors.ValidationError,)
+
+ def test_init(self):
+ """
+ Tests the `errors.RequirementError.__init__` method.
+ """
+ name = 'givenname'
+ e = self.cls(name)
+ assert e.name is name
+ assert e.value is None
+ assert e.error == 'Required'
+ assert e.index is None
diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py
new file mode 100644
index 000000000..c70cc00d7
--- /dev/null
+++ b/tests/test_ipalib/test_frontend.py
@@ -0,0 +1,1080 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.frontend` module.
+"""
+
+from tstutil import raises, getitem, no_set, no_del, read_only, ClassChecker
+from tstutil import check_TypeError
+from ipalib import frontend, backend, plugable, errors, ipa_types, config
+
+
+def test_RULE_FLAG():
+ assert frontend.RULE_FLAG == 'validation_rule'
+
+
+def test_rule():
+ """
+ Tests the `frontend.rule` function.
+ """
+ flag = frontend.RULE_FLAG
+ rule = frontend.rule
+ def my_func():
+ pass
+ assert not hasattr(my_func, flag)
+ rule(my_func)
+ assert getattr(my_func, flag) is True
+ @rule
+ def my_func2():
+ pass
+ assert getattr(my_func2, flag) is True
+
+
+def test_is_rule():
+ """
+ Tests the `frontend.is_rule` function.
+ """
+ is_rule = frontend.is_rule
+ flag = frontend.RULE_FLAG
+
+ class no_call(object):
+ def __init__(self, value):
+ if value is not None:
+ assert value in (True, False)
+ setattr(self, flag, value)
+
+ class call(no_call):
+ def __call__(self):
+ pass
+
+ assert is_rule(call(True))
+ assert not is_rule(no_call(True))
+ assert not is_rule(call(False))
+ assert not is_rule(call(None))
+
+
+class test_DefaultFrom(ClassChecker):
+ """
+ Tests the `frontend.DefaultFrom` class.
+ """
+ _cls = frontend.DefaultFrom
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.ReadOnly,)
+
+ def test_init(self):
+ """
+ Tests the `frontend.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')
+
+ def test_call(self):
+ """
+ Tests the `frontend.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
+ 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
+
+
+def test_parse_param_spec():
+ """
+ Test the `frontend.parse_param_spec` function.
+ """
+ f = frontend.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))
+
+
+class test_Param(ClassChecker):
+ """
+ Test the `frontend.Param` class.
+ """
+ _cls = frontend.Param
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.ReadOnly,)
+
+ def test_init(self):
+ """
+ Test the `frontend.Param.__init__` method.
+ """
+ name = 'sn'
+ o = self.cls(name)
+ assert o.__islocked__() is True
+
+ # Test default values
+ assert read_only(o, 'name') is name
+ assert isinstance(read_only(o, 'type'), ipa_types.Unicode)
+ assert read_only(o, 'doc') == ''
+ assert read_only(o, 'required') is True
+ assert read_only(o, 'multivalue') is False
+ assert read_only(o, 'default') is None
+ assert read_only(o, 'default_from') is None
+ assert read_only(o, 'rules') == tuple()
+ assert len(read_only(o, 'all_rules')) == 1
+ assert read_only(o, 'primary_key') is False
+
+ # Test all kw args:
+ t = ipa_types.Int()
+ assert self.cls(name, type=t).type is t
+ assert self.cls(name, doc='the doc').doc == 'the doc'
+ assert self.cls(name, required=False).required is False
+ assert self.cls(name, multivalue=True).multivalue is True
+ assert self.cls(name, default=u'Hello').default == u'Hello'
+ df = frontend.DefaultFrom(lambda f, l: f + l,
+ 'first', 'last',
+ )
+ lam = lambda first, last: first + last
+ for cb in (df, lam):
+ o = self.cls(name, default_from=cb)
+ assert type(o.default_from) is frontend.DefaultFrom
+ assert o.default_from.keys == ('first', 'last')
+ assert o.default_from.callback('butt', 'erfly') == 'butterfly'
+ rules = (lambda whatever: 'Not okay!',)
+ o = self.cls(name, rules=rules)
+ assert o.rules is rules
+ assert o.all_rules[1:] == rules
+ assert self.cls(name, primary_key=True).primary_key is True
+
+ # Test default type_:
+ o = self.cls(name)
+ assert isinstance(o.type, ipa_types.Unicode)
+
+ # Test param spec parsing:
+ o = self.cls('name?')
+ assert o.name == 'name'
+ assert o.required is False
+ assert o.multivalue is False
+
+ o = self.cls('name*')
+ assert o.name == 'name'
+ assert o.required is False
+ assert o.multivalue is True
+
+ o = self.cls('name+')
+ assert o.name == 'name'
+ assert o.required is True
+ assert o.multivalue is True
+
+ e = raises(TypeError, self.cls, name, whatever=True, another=False)
+ assert str(e) == \
+ 'Param.__init__() takes no such kwargs: another, whatever'
+
+ def test_clone(self):
+ """
+ Test the `frontend.Param.__clone__` method.
+ """
+ def compare(o, kw):
+ for (k, v) in kw.iteritems():
+ assert getattr(o, k) == v, (k, v, getattr(o, k))
+ default = dict(
+ required=False,
+ multivalue=False,
+ default=None,
+ default_from=None,
+ rules=tuple(),
+ )
+ name = 'hair_color?'
+ type_ = ipa_types.Int()
+ o = self.cls(name, type=type_)
+ compare(o, default)
+
+ override = dict(multivalue=True, default=42)
+ d = dict(default)
+ d.update(override)
+ clone = o.__clone__(**override)
+ assert clone.name == 'hair_color'
+ assert clone.type is o.type
+ compare(clone, d)
+
+ def test_convert(self):
+ """
+ Test the `frontend.Param.convert` method.
+ """
+ name = 'some_number'
+ type_ = ipa_types.Int()
+ okay = (7, 7L, 7.0, ' 7 ')
+ fail = ('7.0', '7L', 'whatever', object)
+ none = (None, '', u'', tuple(), [])
+
+ # Scenario 1: multivalue=False
+ o = self.cls(name, type=type_)
+ for n in none:
+ assert o.convert(n) is None
+ for value in okay:
+ new = o.convert(value)
+ assert new == 7
+ assert type(new) is int
+ for value in fail:
+ e = raises(errors.ConversionError, o.convert, value)
+ assert e.name is name
+ assert e.value is value
+ assert e.error is type_.conversion_error
+ assert e.index is None
+
+ # Scenario 2: multivalue=True
+ o = self.cls(name, type=type_, multivalue=True)
+ for n in none:
+ assert o.convert(n) is None
+ for value in okay:
+ assert o.convert((value,)) == (7,)
+ assert o.convert([value]) == (7,)
+ assert o.convert(okay) == tuple(int(v) for v in okay)
+ cnt = 5
+ for value in fail:
+ for i in xrange(cnt):
+ others = list(7 for x in xrange(cnt))
+ others[i] = value
+ for v in [tuple(others), list(others)]:
+ e = raises(errors.ConversionError, o.convert, v)
+ assert e.name is name
+ assert e.value is value
+ assert e.error is type_.conversion_error
+ assert e.index == i
+
+ def test_normalize(self):
+ """
+ Test the `frontend.Param.normalize` method.
+ """
+ name = 'sn'
+ callback = lambda value: value.lower()
+ values = (None, u'Hello', (u'Hello',), 'hello', ['hello'])
+ none = (None, '', u'', tuple(), [])
+
+ # Scenario 1: multivalue=False, normalize=None
+ o = self.cls(name)
+ for v in values:
+ # When normalize=None, value is returned, no type checking:
+ assert o.normalize(v) is v
+
+ # Scenario 2: multivalue=False, normalize=callback
+ o = self.cls(name, normalize=callback)
+ for v in (u'Hello', u'hello', 'Hello'): # Okay
+ assert o.normalize(v) == 'hello'
+ for v in [None, 42, (u'Hello',)]: # Not basestring
+ assert o.normalize(v) is v
+ for n in none:
+ assert o.normalize(n) is None
+
+ # Scenario 3: multivalue=True, normalize=None
+ o = self.cls(name, multivalue=True)
+ for v in values:
+ # When normalize=None, value is returned, no type checking:
+ assert o.normalize(v) is v
+
+ # Scenario 4: multivalue=True, normalize=callback
+ o = self.cls(name, multivalue=True, normalize=callback)
+ assert o.normalize([]) is None
+ assert o.normalize(tuple()) is None
+ for value in [(u'Hello',), (u'hello',), 'Hello', ['Hello']]: # Okay
+ assert o.normalize(value) == (u'hello',)
+ fail = 42 # Not basestring
+ for v in [[fail], (u'hello', fail)]: # Non basestring member
+ assert o.normalize(v) == tuple(v)
+ for n in none:
+ assert o.normalize(n) is None
+
+ def test_validate(self):
+ """
+ Tests the `frontend.Param.validate` method.
+ """
+ name = 'sn'
+ type_ = ipa_types.Unicode()
+ def case_rule(value):
+ if not value.islower():
+ return 'Must be lower case'
+ my_rules = (case_rule,)
+ okay = u'whatever'
+ fail_case = u'Whatever'
+ fail_type = 'whatever'
+
+ # Scenario 1: multivalue=False
+ o = self.cls(name, type=type_, rules=my_rules)
+ assert o.rules == my_rules
+ assert o.all_rules == (type_.validate, case_rule)
+ o.validate(okay)
+ e = raises(errors.RuleError, o.validate, fail_case)
+ assert e.name is name
+ assert e.value is fail_case
+ assert e.error == 'Must be lower case'
+ assert e.rule is case_rule
+ assert e.index is None
+ check_TypeError(fail_type, unicode, 'value', o.validate, fail_type)
+
+ ## Scenario 2: multivalue=True
+ o = self.cls(name, type=type_, multivalue=True, rules=my_rules)
+ o.validate((okay,))
+ cnt = 5
+ for i in xrange(cnt):
+ others = list(okay for x in xrange(cnt))
+ others[i] = fail_case
+ value = tuple(others)
+ e = raises(errors.RuleError, o.validate, value)
+ assert e.name is name
+ assert e.value is fail_case
+ assert e.error == 'Must be lower case'
+ assert e.rule is case_rule
+ assert e.index == i
+ for not_tuple in (okay, [okay]):
+ check_TypeError(not_tuple, tuple, 'value', o.validate, not_tuple)
+ for has_str in [(fail_type,), (okay, fail_type)]:
+ check_TypeError(fail_type, unicode, 'value', o.validate, has_str)
+
+ def test_get_default(self):
+ """
+ Tests the `frontend.Param.get_default` method.
+ """
+ name = 'greeting'
+ default = u'Hello, world!'
+ default_from = frontend.DefaultFrom(
+ lambda first, last: u'Hello, %s %s!' % (first, last),
+ 'first', 'last'
+ )
+
+ # Scenario 1: multivalue=False
+ o = self.cls(name,
+ default=default,
+ default_from=default_from,
+ )
+ assert o.default is default
+ assert o.default_from is default_from
+ assert o.get_default() == default
+ assert o.get_default(first='John', last='Doe') == 'Hello, John Doe!'
+
+ # Scenario 2: multivalue=True
+ default = (default,)
+ o = self.cls(name,
+ default=default,
+ default_from=default_from,
+ multivalue=True,
+ )
+ assert o.default is default
+ assert o.default_from is default_from
+ assert o.get_default() == default
+ assert o.get_default(first='John', last='Doe') == ('Hello, John Doe!',)
+
+ def test_get_value(self):
+ """
+ Tests the `frontend.Param.get_values` method.
+ """
+ name = 'status'
+ values = (u'Active', u'Inactive')
+ o = self.cls(name, type=ipa_types.Unicode())
+ assert o.get_values() == tuple()
+ o = self.cls(name, type=ipa_types.Enum(*values))
+ assert o.get_values() == values
+
+
+def test_create_param():
+ """
+ Test the `frontend.create_param` function.
+ """
+ f = frontend.create_param
+ for name in ['arg', 'arg?', 'arg*', 'arg+']:
+ o = f(name)
+ assert type(o) is frontend.Param
+ assert type(o.type) is ipa_types.Unicode
+ assert o.name == 'arg'
+ assert f(o) is o
+ o = f('arg')
+ assert o.required is True
+ assert o.multivalue is False
+ o = f('arg?')
+ assert o.required is False
+ assert o.multivalue is False
+ o = f('arg*')
+ assert o.required is False
+ assert o.multivalue is True
+ o = f('arg+')
+ assert o.required is True
+ assert o.multivalue is True
+
+
+class test_Command(ClassChecker):
+ """
+ Tests the `frontend.Command` class.
+ """
+ _cls = frontend.Command
+
+ def get_subcls(self):
+ class Rule(object):
+ def __init__(self, name):
+ self.name = name
+
+ def __call__(self, value):
+ if value != self.name:
+ return 'must equal %s' % self.name
+
+ default_from = frontend.DefaultFrom(
+ lambda arg: arg,
+ 'default_from'
+ )
+ normalize = lambda value: value.lower()
+
+ class example(self.cls):
+ takes_options = (
+ frontend.Param('option0',
+ normalize=normalize,
+ default_from=default_from,
+ rules=(Rule('option0'),)
+ ),
+ frontend.Param('option1',
+ normalize=normalize,
+ default_from=default_from,
+ rules=(Rule('option1'),),
+ required=True,
+ ),
+ )
+ return example
+
+ def get_instance(self, args=tuple(), options=tuple()):
+ """
+ Helper method used to test args and options.
+ """
+ class example(self.cls):
+ takes_args = args
+ takes_options = options
+ o = example()
+ o.finalize()
+ return o
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.Plugin,)
+ assert self.cls.takes_options == tuple()
+ assert self.cls.takes_args == tuple()
+
+ def test_get_args(self):
+ """
+ Tests the `frontend.Command.get_args` method.
+ """
+ assert list(self.cls().get_args()) == []
+ args = ('login', 'stuff')
+ o = self.get_instance(args=args)
+ assert o.get_args() is args
+
+ def test_get_options(self):
+ """
+ Tests the `frontend.Command.get_options` method.
+ """
+ assert list(self.cls().get_options()) == []
+ options = ('verbose', 'debug')
+ o = self.get_instance(options=options)
+ assert o.get_options() is options
+
+ def test_args(self):
+ """
+ Tests the ``Command.args`` instance attribute.
+ """
+ assert 'args' in self.cls.__public__ # Public
+ assert self.cls().args is None
+ o = self.cls()
+ o.finalize()
+ assert type(o.args) is plugable.NameSpace
+ assert len(o.args) == 0
+ args = ('destination', 'source?')
+ ns = self.get_instance(args=args).args
+ assert type(ns) is plugable.NameSpace
+ assert len(ns) == len(args)
+ assert list(ns) == ['destination', 'source']
+ assert type(ns.destination) is frontend.Param
+ assert type(ns.source) is frontend.Param
+ assert ns.destination.required is True
+ assert ns.destination.multivalue is False
+ assert ns.source.required is False
+ assert ns.source.multivalue is False
+
+ # Test TypeError:
+ e = raises(TypeError, self.get_instance, args=(u'whatever',))
+ assert str(e) == \
+ 'create_param() takes %r or %r; got %r' % (str, frontend.Param, u'whatever')
+
+ # Test ValueError, required after optional:
+ e = raises(ValueError, self.get_instance, args=('arg1?', 'arg2'))
+ assert str(e) == 'arg2: required argument after optional'
+
+ # Test ValueError, scalar after multivalue:
+ e = raises(ValueError, self.get_instance, args=('arg1+', 'arg2'))
+ assert str(e) == 'arg2: only final argument can be multivalue'
+
+ def test_max_args(self):
+ """
+ Test the ``Command.max_args`` instance attribute.
+ """
+ o = self.get_instance()
+ assert o.max_args == 0
+ o = self.get_instance(args=('one?',))
+ assert o.max_args == 1
+ o = self.get_instance(args=('one', 'two?'))
+ assert o.max_args == 2
+ o = self.get_instance(args=('one', 'multi+',))
+ assert o.max_args is None
+ o = self.get_instance(args=('one', 'multi*',))
+ assert o.max_args is None
+
+ def test_options(self):
+ """
+ Tests the ``Command.options`` instance attribute.
+ """
+ assert 'options' in self.cls.__public__ # Public
+ assert self.cls().options is None
+ o = self.cls()
+ o.finalize()
+ assert type(o.options) is plugable.NameSpace
+ assert len(o.options) == 0
+ options = ('target', 'files*')
+ ns = self.get_instance(options=options).options
+ assert type(ns) is plugable.NameSpace
+ assert len(ns) == len(options)
+ assert list(ns) == ['target', 'files']
+ assert type(ns.target) is frontend.Param
+ assert type(ns.files) is frontend.Param
+ assert ns.target.required is True
+ assert ns.target.multivalue is False
+ assert ns.files.required is False
+ assert ns.files.multivalue is True
+
+ def test_convert(self):
+ """
+ Tests the `frontend.Command.convert` method.
+ """
+ assert 'convert' in self.cls.__public__ # Public
+ kw = dict(
+ option0='option0',
+ option1='option1',
+ )
+ expected = dict(kw)
+ expected.update(dict(option0=u'option0', option1=u'option1'))
+ o = self.subcls()
+ o.finalize()
+ for (key, value) in o.convert(**kw).iteritems():
+ v = expected[key]
+ assert value == v
+ assert type(value) is type(v)
+
+ def test_normalize(self):
+ """
+ Tests the `frontend.Command.normalize` method.
+ """
+ assert 'normalize' in self.cls.__public__ # Public
+ kw = dict(
+ option0=u'OPTION0',
+ option1=u'OPTION1',
+ )
+ norm = dict((k, v.lower()) for (k, v) in kw.items())
+ sub = self.subcls()
+ sub.finalize()
+ assert sub.normalize(**kw) == norm
+
+ def test_get_default(self):
+ """
+ Tests the `frontend.Command.get_default` method.
+ """
+ assert 'get_default' in self.cls.__public__ # Public
+ no_fill = dict(
+ option0='value0',
+ option1='value1',
+ whatever='hello world',
+ )
+ fill = dict(
+ default_from='the default',
+ )
+ default = dict(
+ option0='the default',
+ option1='the default',
+ )
+ sub = self.subcls()
+ sub.finalize()
+ assert sub.get_default(**no_fill) == {}
+ assert sub.get_default(**fill) == default
+
+ def test_validate(self):
+ """
+ Tests the `frontend.Command.validate` method.
+ """
+ assert 'validate' in self.cls.__public__ # Public
+
+ sub = self.subcls()
+ sub.finalize()
+
+ # Check with valid args
+ okay = dict(
+ option0=u'option0',
+ option1=u'option1',
+ another_option='some value',
+ )
+ sub.validate(**okay)
+
+ # Check with an invalid arg
+ fail = dict(okay)
+ fail['option0'] = u'whatever'
+ e = raises(errors.RuleError, sub.validate, **fail)
+ assert e.name == 'option0'
+ assert e.value == u'whatever'
+ assert e.error == 'must equal option0'
+ assert e.rule.__class__.__name__ == 'Rule'
+ assert e.index is None
+
+ # Check with a missing required arg
+ fail = dict(okay)
+ fail.pop('option1')
+ e = raises(errors.RequirementError, sub.validate, **fail)
+ assert e.name == 'option1'
+ assert e.value is None
+ assert e.index is None
+
+ def test_execute(self):
+ """
+ Tests the `frontend.Command.execute` method.
+ """
+ assert 'execute' in self.cls.__public__ # Public
+
+ def test_args_to_kw(self):
+ """
+ Test the `frontend.Command.args_to_kw` method.
+ """
+ assert 'args_to_kw' in self.cls.__public__ # Public
+ o = self.get_instance(args=('one', 'two?'))
+ assert o.args_to_kw(1) == dict(one=1)
+ assert o.args_to_kw(1, 2) == dict(one=1, two=2)
+
+ o = self.get_instance(args=('one', 'two*'))
+ assert o.args_to_kw(1) == dict(one=1)
+ assert o.args_to_kw(1, 2) == dict(one=1, two=(2,))
+ assert o.args_to_kw(1, 2, 3) == dict(one=1, two=(2, 3))
+
+ o = self.get_instance(args=('one', 'two+'))
+ assert o.args_to_kw(1) == dict(one=1)
+ assert o.args_to_kw(1, 2) == dict(one=1, two=(2,))
+ assert o.args_to_kw(1, 2, 3) == dict(one=1, two=(2, 3))
+
+ o = self.get_instance()
+ e = raises(errors.ArgumentError, o.args_to_kw, 1)
+ assert str(e) == 'example takes no arguments'
+
+ o = self.get_instance(args=('one?',))
+ e = raises(errors.ArgumentError, o.args_to_kw, 1, 2)
+ assert str(e) == 'example takes at most 1 argument'
+
+ o = self.get_instance(args=('one', 'two?'))
+ e = raises(errors.ArgumentError, o.args_to_kw, 1, 2, 3)
+ assert str(e) == 'example takes at most 2 arguments'
+
+ def test_kw_to_args(self):
+ """
+ Tests the `frontend.Command.kw_to_args` method.
+ """
+ assert 'kw_to_args' in self.cls.__public__ # Public
+ o = self.get_instance(args=('one', 'two?'))
+ assert o.kw_to_args() == (None, None)
+ assert o.kw_to_args(whatever='hello') == (None, None)
+ assert o.kw_to_args(one='the one') == ('the one', None)
+ assert o.kw_to_args(two='the two') == (None, 'the two')
+ assert o.kw_to_args(whatever='hello', two='Two', one='One') == \
+ ('One', 'Two')
+
+ def test_run(self):
+ """
+ Test the `frontend.Command.run` method.
+ """
+ class my_cmd(self.cls):
+ def execute(self, *args, **kw):
+ return ('execute', args, kw)
+
+ def forward(self, *args, **kw):
+ return ('forward', args, kw)
+
+ args = ('Hello,', 'world,')
+ kw = dict(how_are='you', on_this='fine day?')
+
+ # Test in server context:
+ api = plugable.API(self.cls)
+ api.env.update(dict(server_context=True))
+ api.finalize()
+ o = my_cmd()
+ o.set_api(api)
+ assert o.run.im_func is self.cls.run.im_func
+ assert ('execute', args, kw) == o.run(*args, **kw)
+ assert o.run.im_func is my_cmd.execute.im_func
+
+ # Test in non-server context
+ api = plugable.API(self.cls)
+ api.env.update(dict(server_context=False))
+ api.finalize()
+ o = my_cmd()
+ o.set_api(api)
+ assert o.run.im_func is self.cls.run.im_func
+ assert ('forward', args, kw) == o.run(*args, **kw)
+ assert o.run.im_func is my_cmd.forward.im_func
+
+
+class test_Object(ClassChecker):
+ """
+ Test the `frontend.Object` class.
+ """
+ _cls = frontend.Object
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.Plugin,)
+ assert self.cls.backend is None
+ assert self.cls.methods is None
+ assert self.cls.properties is None
+ assert self.cls.params is None
+ assert self.cls.params_minus_pk is None
+ assert self.cls.takes_params == tuple()
+
+ def test_init(self):
+ """
+ Test the `frontend.Object.__init__` method.
+ """
+ o = self.cls()
+ assert o.backend is None
+ assert o.methods is None
+ assert o.properties is None
+ assert o.params is None
+ assert o.params_minus_pk is None
+ assert o.properties is None
+
+ def test_set_api(self):
+ """
+ Test the `frontend.Object.set_api` method.
+ """
+ # Setup for test:
+ class DummyAttribute(object):
+ def __init__(self, obj_name, attr_name, name=None):
+ self.obj_name = obj_name
+ self.attr_name = attr_name
+ if name is None:
+ self.name = '%s_%s' % (obj_name, attr_name)
+ else:
+ self.name = name
+ self.param = frontend.create_param(attr_name)
+
+ def __clone__(self, attr_name):
+ return self.__class__(
+ self.obj_name,
+ self.attr_name,
+ getattr(self, attr_name)
+ )
+
+ def get_attributes(cnt, format):
+ for name in ['other', 'user', 'another']:
+ for i in xrange(cnt):
+ yield DummyAttribute(name, format % i)
+
+ cnt = 10
+ formats = dict(
+ methods='method_%d',
+ properties='property_%d',
+ )
+
+
+ _d = dict(
+ Method=plugable.NameSpace(
+ get_attributes(cnt, formats['methods'])
+ ),
+ Property=plugable.NameSpace(
+ get_attributes(cnt, formats['properties'])
+ ),
+ )
+ api = plugable.MagicDict(_d)
+ assert len(api.Method) == cnt * 3
+ assert len(api.Property) == cnt * 3
+
+ class user(self.cls):
+ pass
+
+ # Actually perform test:
+ o = user()
+ o.set_api(api)
+ assert read_only(o, 'api') is api
+ for name in ['methods', 'properties']:
+ namespace = getattr(o, name)
+ assert isinstance(namespace, plugable.NameSpace)
+ assert len(namespace) == cnt
+ f = formats[name]
+ for i in xrange(cnt):
+ attr_name = f % i
+ attr = namespace[attr_name]
+ assert isinstance(attr, DummyAttribute)
+ assert attr is getattr(namespace, attr_name)
+ assert attr.obj_name == 'user'
+ assert attr.attr_name == attr_name
+ assert attr.name == attr_name
+
+ # Test params instance attribute
+ o = self.cls()
+ o.set_api(api)
+ ns = o.params
+ assert type(ns) is plugable.NameSpace
+ assert len(ns) == 0
+ class example(self.cls):
+ takes_params = ('banana', 'apple')
+ o = example()
+ o.set_api(api)
+ ns = o.params
+ assert type(ns) is plugable.NameSpace
+ assert len(ns) == 2, repr(ns)
+ assert list(ns) == ['banana', 'apple']
+ for p in ns():
+ assert type(p) is frontend.Param
+ assert p.required is True
+ assert p.multivalue is False
+
+ def test_primary_key(self):
+ """
+ Test the `frontend.Object.primary_key` attribute.
+ """
+ api = plugable.API(
+ frontend.Method,
+ frontend.Property,
+ )
+ api.env.update(config.generate_env())
+ api.finalize()
+
+ # Test with no primary keys:
+ class example1(self.cls):
+ takes_params = (
+ 'one',
+ 'two',
+ )
+ o = example1()
+ o.set_api(api)
+ assert o.primary_key is None
+ assert o.params_minus_pk is None
+
+ # Test with 1 primary key:
+ class example2(self.cls):
+ takes_params = (
+ 'one',
+ 'two',
+ frontend.Param('three',
+ primary_key=True,
+ ),
+ 'four',
+ )
+ o = example2()
+ o.set_api(api)
+ pk = o.primary_key
+ assert isinstance(pk, frontend.Param)
+ assert pk.name == 'three'
+ assert pk.primary_key is True
+ assert o.params[2] is o.primary_key
+ assert isinstance(o.params_minus_pk, plugable.NameSpace)
+ assert list(o.params_minus_pk) == ['one', 'two', 'four']
+
+ # Test with multiple primary_key:
+ class example3(self.cls):
+ takes_params = (
+ frontend.Param('one', primary_key=True),
+ frontend.Param('two', primary_key=True),
+ 'three',
+ frontend.Param('four', primary_key=True),
+ )
+ o = example3()
+ e = raises(ValueError, o.set_api, api)
+ assert str(e) == \
+ 'example3 (Object) has multiple primary keys: one, two, four'
+
+ def test_backend(self):
+ """
+ Test the `frontend.Object.backend` attribute.
+ """
+ api = plugable.API(
+ frontend.Object,
+ frontend.Method,
+ frontend.Property,
+ backend.Backend,
+ )
+ api.env.update(config.generate_env())
+ class ldap(backend.Backend):
+ whatever = 'It worked!'
+ api.register(ldap)
+ class user(frontend.Object):
+ backend_name = 'ldap'
+ api.register(user)
+ api.finalize()
+ b = api.Object.user.backend
+ assert isinstance(b, ldap)
+ assert b.whatever == 'It worked!'
+
+
+class test_Attribute(ClassChecker):
+ """
+ Tests the `frontend.Attribute` class.
+ """
+ _cls = frontend.Attribute
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.Plugin,)
+ assert type(self.cls.obj) is property
+ assert type(self.cls.obj_name) is property
+ assert type(self.cls.attr_name) is property
+
+ def test_init(self):
+ """
+ Tests the `frontend.Attribute.__init__` method.
+ """
+ class user_add(self.cls):
+ pass
+ o = user_add()
+ assert read_only(o, 'obj') is None
+ assert read_only(o, 'obj_name') == 'user'
+ assert read_only(o, 'attr_name') == 'add'
+
+ def test_set_api(self):
+ """
+ Tests the `frontend.Attribute.set_api` method.
+ """
+ user_obj = 'The user frontend.Object instance'
+ class api(object):
+ Object = dict(user=user_obj)
+ class user_add(self.cls):
+ pass
+ o = user_add()
+ assert read_only(o, 'api') is None
+ assert read_only(o, 'obj') is None
+ o.set_api(api)
+ assert read_only(o, 'api') is api
+ assert read_only(o, 'obj') is user_obj
+
+
+class test_Method(ClassChecker):
+ """
+ Test the `frontend.Method` class.
+ """
+ _cls = frontend.Method
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Attribute, frontend.Command)
+ assert self.cls.implements(frontend.Command)
+ assert self.cls.implements(frontend.Attribute)
+
+ def test_init(self):
+ """
+ Test the `frontend.Method.__init__` method.
+ """
+ class user_add(self.cls):
+ pass
+ o = user_add()
+ assert o.name == 'user_add'
+ assert o.obj_name == 'user'
+ assert o.attr_name == 'add'
+ assert frontend.Command.implemented_by(o)
+ assert frontend.Attribute.implemented_by(o)
+
+
+class test_Property(ClassChecker):
+ """
+ Tests the `frontend.Property` class.
+ """
+ _cls = frontend.Property
+
+ def get_subcls(self):
+ class user_givenname(self.cls):
+ 'User first name'
+
+ @frontend.rule
+ def rule0_lowercase(self, value):
+ if not value.islower():
+ return 'Must be lowercase'
+ return user_givenname
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Attribute,)
+ assert isinstance(self.cls.type, ipa_types.Unicode)
+ assert self.cls.required is False
+ assert self.cls.multivalue is False
+ assert self.cls.default is None
+ assert self.cls.default_from is None
+ assert self.cls.normalize is None
+
+ def test_init(self):
+ """
+ Tests the `frontend.Property.__init__` method.
+ """
+ o = self.subcls()
+ assert len(o.rules) == 1
+ assert o.rules[0].__name__ == 'rule0_lowercase'
+ param = o.param
+ assert isinstance(param, frontend.Param)
+ assert param.name == 'givenname'
+ assert param.doc == 'User first name'
+
+
+class test_Application(ClassChecker):
+ """
+ Tests the `frontend.Application` class.
+ """
+ _cls = frontend.Application
+
+ def test_class(self):
+ assert self.cls.__bases__ == (frontend.Command,)
+ assert type(self.cls.application) is property
+
+ def test_application(self):
+ """
+ Tests the `frontend.Application.application` property.
+ """
+ assert 'application' in self.cls.__public__ # Public
+ assert 'set_application' in self.cls.__public__ # Public
+ app = 'The external application'
+ class example(self.cls):
+ 'A subclass'
+ for o in (self.cls(), example()):
+ assert read_only(o, 'application') is None
+ e = raises(TypeError, o.set_application, None)
+ assert str(e) == (
+ '%s.application cannot be None' % o.__class__.__name__
+ )
+ o.set_application(app)
+ assert read_only(o, 'application') is app
+ e = raises(AttributeError, o.set_application, app)
+ assert str(e) == (
+ '%s.application can only be set once' % o.__class__.__name__
+ )
+ assert read_only(o, 'application') is app
diff --git a/tests/test_ipalib/test_ipa_types.py b/tests/test_ipalib/test_ipa_types.py
new file mode 100644
index 000000000..b8e996a72
--- /dev/null
+++ b/tests/test_ipalib/test_ipa_types.py
@@ -0,0 +1,371 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.ipa_types` module.
+"""
+
+from tstutil import raises, getitem, no_set, no_del, read_only, ClassChecker
+from ipalib import ipa_types, errors, plugable
+
+
+def test_check_min_max():
+ """
+ Tests the `ipa_types.check_min_max` function.
+ """
+ f = ipa_types.check_min_max
+ okay = [
+ (None, -5),
+ (-20, None),
+ (-20, -5),
+ ]
+ for (l, h) in okay:
+ assert f(l, h, 'low', 'high') is None
+ fail_type = [
+ '10',
+ 10.0,
+ 10L,
+ True,
+ False,
+ object,
+ ]
+ for value in fail_type:
+ e = raises(TypeError, f, value, None, 'low', 'high')
+ assert str(e) == 'low must be an int or None, got: %r' % value
+ e = raises(TypeError, f, None, value, 'low', 'high')
+ assert str(e) == 'high must be an int or None, got: %r' % value
+ fail_value = [
+ (10, 5),
+ (-5, -10),
+ (5, -10),
+ ]
+ for (l, h) in fail_value:
+ e = raises(ValueError, f, l, h, 'low', 'high')
+ assert str(e) == 'low > high: low=%r, high=%r' % (l, h)
+
+
+class test_Type(ClassChecker):
+ """
+ Tests the `ipa_types.Type` class.
+ """
+ _cls = ipa_types.Type
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.ReadOnly,)
+
+ def test_init(self):
+ okay = (bool, int, float, unicode)
+ for t in okay:
+ o = self.cls(t)
+ assert o.__islocked__() is True
+ assert read_only(o, 'type') is t
+ assert read_only(o, 'name') is 'Type'
+
+ type_errors = (None, True, 8, 8.0, u'hello')
+ for t in type_errors:
+ e = raises(TypeError, self.cls, t)
+ assert str(e) == '%r is not %r' % (type(t), type)
+
+ value_errors = (long, complex, str, tuple, list, dict, set, frozenset)
+ for t in value_errors:
+ e = raises(ValueError, self.cls, t)
+ assert str(e) == 'not an allowed type: %r' % t
+
+ def test_validate(self):
+ o = self.cls(unicode)
+ for value in (None, u'Hello', 'Hello', 42, False):
+ assert o.validate(value) is None
+
+
+class test_Bool(ClassChecker):
+ _cls = ipa_types.Bool
+
+ def test_class(self):
+ assert self.cls.__bases__ == (ipa_types.Type,)
+
+ def test_init(self):
+ o = self.cls()
+ assert o.__islocked__() is True
+ assert read_only(o, 'type') is bool
+ assert read_only(o, 'name') == 'Bool'
+ assert read_only(o, 'true') == 'Yes'
+ assert read_only(o, 'false') == 'No'
+
+ keys = ('true', 'false')
+ val = 'some value'
+ for key in keys:
+ # Check that kwarg sets appropriate attribute:
+ o = self.cls(**{key: val})
+ assert read_only(o, key) is val
+ # Check that None raises TypeError:
+ e = raises(TypeError, self.cls, **{key: None})
+ assert str(e) == '`%s` cannot be None' % key
+
+ # Check that ValueError is raise if true == false:
+ e = raises(ValueError, self.cls, true=1L, false=1.0)
+ assert str(e) == 'cannot be equal: true=1L, false=1.0'
+
+ def test_call(self):
+ o = self.cls()
+ assert o(True) is True
+ assert o('Yes') is True
+ assert o(False) is False
+ assert o('No') is False
+ for value in (0, 1, 'True', 'False', 'yes', 'no'):
+ # value is not be converted, so None is returned
+ assert o(value) is None
+
+
+class test_Int(ClassChecker):
+ _cls = ipa_types.Int
+
+ def test_class(self):
+ assert self.cls.__bases__ == (ipa_types.Type,)
+
+ def test_init(self):
+ o = self.cls()
+ assert o.__islocked__() is True
+ assert read_only(o, 'type') is int
+ assert read_only(o, 'name') == 'Int'
+ assert read_only(o, 'min_value') is None
+ assert read_only(o, 'max_value') is None
+
+ okay = [
+ (None, -5),
+ (-20, None),
+ (-20, -5),
+ ]
+ for (l, h) in okay:
+ o = self.cls(min_value=l, max_value=h)
+ assert o.min_value is l
+ assert o.max_value is h
+
+ fail_type = [
+ '10',
+ 10.0,
+ 10L,
+ True,
+ False,
+ object,
+ ]
+ for value in fail_type:
+ e = raises(TypeError, self.cls, min_value=value)
+ assert str(e) == (
+ 'min_value must be an int or None, got: %r' % value
+ )
+ e = raises(TypeError, self.cls, max_value=value)
+ assert str(e) == (
+ 'max_value must be an int or None, got: %r' % value
+ )
+
+ fail_value = [
+ (10, 5),
+ (5, -5),
+ (-5, -10),
+ ]
+ for (l, h) in fail_value:
+ e = raises(ValueError, self.cls, min_value=l, max_value=h)
+ assert str(e) == (
+ 'min_value > max_value: min_value=%d, max_value=%d' % (l, h)
+ )
+
+ def test_call(self):
+ o = self.cls()
+
+ # Test calling with None
+ e = raises(TypeError, o, None)
+ assert str(e) == 'value cannot be None'
+
+ # Test with values that can be converted:
+ okay = [
+ 3,
+ '3',
+ ' 3 ',
+ 3L,
+ 3.0,
+ ]
+ for value in okay:
+ assert o(value) == 3
+
+ # Test with values that cannot be converted:
+ fail = [
+ object,
+ '3.0',
+ '3L',
+ 'whatever',
+ ]
+ for value in fail:
+ assert o(value) is None
+
+
+ def test_validate(self):
+ o = self.cls(min_value=2, max_value=7)
+ assert o.validate(2) is None
+ assert o.validate(5) is None
+ assert o.validate(7) is None
+ assert o.validate(1) == 'Cannot be smaller than 2'
+ assert o.validate(8) == 'Cannot be larger than 7'
+ for val in ['5', 5.0, 5L, None, True, False, object]:
+ assert o.validate(val) == 'Must be an integer'
+
+
+class test_Unicode(ClassChecker):
+ _cls = ipa_types.Unicode
+
+ def test_class(self):
+ assert self.cls.__bases__ == (ipa_types.Type,)
+
+ def test_init(self):
+ o = self.cls()
+ assert o.__islocked__() is True
+ assert read_only(o, 'type') is unicode
+ assert read_only(o, 'name') == 'Unicode'
+ assert read_only(o, 'min_length') is None
+ assert read_only(o, 'max_length') is None
+ assert read_only(o, 'pattern') is None
+ assert read_only(o, 'regex') is None
+
+ # Test min_length, max_length:
+ okay = (
+ (0, 1),
+ (8, 8),
+ )
+ for (l, h) in okay:
+ o = self.cls(min_length=l, max_length=h)
+ assert o.min_length == l
+ assert o.max_length == h
+
+ fail_type = [
+ '10',
+ 10.0,
+ 10L,
+ True,
+ False,
+ object,
+ ]
+ for value in fail_type:
+ e = raises(TypeError, self.cls, min_length=value)
+ assert str(e) == (
+ 'min_length must be an int or None, got: %r' % value
+ )
+ e = raises(TypeError, self.cls, max_length=value)
+ assert str(e) == (
+ 'max_length must be an int or None, got: %r' % value
+ )
+
+ fail_value = [
+ (10, 5),
+ (5, -5),
+ (0, -10),
+ ]
+ for (l, h) in fail_value:
+ e = raises(ValueError, self.cls, min_length=l, max_length=h)
+ assert str(e) == (
+ 'min_length > max_length: min_length=%d, max_length=%d' % (l, h)
+ )
+
+ for (key, lower) in [('min_length', 0), ('max_length', 1)]:
+ value = lower - 1
+ kw = {key: value}
+ e = raises(ValueError, self.cls, **kw)
+ assert str(e) == '%s must be >= %d, got: %d' % (key, lower, value)
+
+ # Test pattern:
+ okay = [
+ '(hello|world)',
+ u'(take the blue pill|take the red pill)',
+ ]
+ for value in okay:
+ o = self.cls(pattern=value)
+ assert o.pattern is value
+ assert o.regex is not None
+
+ fail = [
+ 42,
+ True,
+ False,
+ object,
+ ]
+ for value in fail:
+ e = raises(TypeError, self.cls, pattern=value)
+ assert str(e) == (
+ 'pattern must be a basestring or None, got: %r' % value
+ )
+
+ # Test regex:
+ pat = '^(hello|world)$'
+ o = self.cls(pattern=pat)
+ for value in ('hello', 'world'):
+ m = o.regex.match(value)
+ assert m.group(1) == value
+ for value in ('hello beautiful', 'world!'):
+ assert o.regex.match(value) is None
+
+ def test_validate(self):
+ pat = '^a_*b$'
+ o = self.cls(min_length=3, max_length=4, pattern=pat)
+ assert o.validate(u'a_b') is None
+ assert o.validate(u'a__b') is None
+ assert o.validate('a_b') == 'Must be a string'
+ assert o.validate(u'ab') == 'Must be at least 3 characters long'
+ assert o.validate(u'a___b') == 'Can be at most 4 characters long'
+ assert o.validate(u'a-b') == 'Must match %r' % pat
+ assert o.validate(u'a--b') == 'Must match %r' % pat
+
+
+class test_Enum(ClassChecker):
+ _cls = ipa_types.Enum
+
+ def test_class(self):
+ assert self.cls.__bases__ == (ipa_types.Type,)
+
+ def test_init(self):
+ for t in (unicode, int, float):
+ values = (t(1), t(2), t(3))
+ o = self.cls(*values)
+ assert o.__islocked__() is True
+ assert read_only(o, 'type') is t
+ assert read_only(o, 'name') is 'Enum'
+ assert read_only(o, 'values') == values
+ assert read_only(o, 'frozenset') == frozenset(values)
+
+ # Check that ValueError is raised when no values are given:
+ e = raises(ValueError, self.cls)
+ assert str(e) == 'Enum requires at least one value'
+
+ # Check that TypeError is raised when type of first value is not
+ # allowed:
+ e = raises(TypeError, self.cls, 'hello')
+ assert str(e) == '%r: %r not unicode, int, nor float' % ('hello', str)
+ #self.cls('hello')
+
+ # Check that TypeError is raised when subsequent values aren't same
+ # type as first:
+ e = raises(TypeError, self.cls, u'hello', 'world')
+ assert str(e) == '%r: %r is not %r' % ('world', str, unicode)
+
+ def test_validate(self):
+ values = (u'hello', u'naughty', u'nurse')
+ o = self.cls(*values)
+ for value in values:
+ assert o.validate(value) is None
+ assert o.validate(str(value)) == 'Incorrect type'
+ for value in (u'one fish', u'two fish'):
+ assert o.validate(value) == 'Invalid value'
+ assert o.validate(str(value)) == 'Incorrect type'
diff --git a/tests/test_ipalib/test_plugable.py b/tests/test_ipalib/test_plugable.py
new file mode 100644
index 000000000..fd3c3c887
--- /dev/null
+++ b/tests/test_ipalib/test_plugable.py
@@ -0,0 +1,896 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.plugable` module.
+"""
+
+from tstutil import raises, no_set, no_del, read_only
+from tstutil import getitem, setitem, delitem
+from tstutil import ClassChecker
+from ipalib import plugable, errors
+
+
+class test_ReadOnly(ClassChecker):
+ """
+ Test the `plugable.ReadOnly` class
+ """
+ _cls = plugable.ReadOnly
+
+ def test_class(self):
+ assert self.cls.__bases__ == (object,)
+ assert callable(self.cls.__lock__)
+ assert callable(self.cls.__islocked__)
+
+ def test_lock(self):
+ """
+ Test the `plugable.ReadOnly.__lock__` method.
+ """
+ o = self.cls()
+ assert o._ReadOnly__locked is False
+ o.__lock__()
+ assert o._ReadOnly__locked is True
+ e = raises(AssertionError, o.__lock__) # Can only be locked once
+ assert str(e) == '__lock__() can only be called once'
+ assert o._ReadOnly__locked is True # This should still be True
+
+ def test_lock(self):
+ """
+ Test the `plugable.ReadOnly.__islocked__` method.
+ """
+ o = self.cls()
+ assert o.__islocked__() is False
+ o.__lock__()
+ assert o.__islocked__() is True
+
+ def test_setattr(self):
+ """
+ Test the `plugable.ReadOnly.__setattr__` method.
+ """
+ o = self.cls()
+ o.attr1 = 'Hello, world!'
+ assert o.attr1 == 'Hello, world!'
+ o.__lock__()
+ for name in ('attr1', 'attr2'):
+ e = raises(AttributeError, setattr, o, name, 'whatever')
+ assert str(e) == 'read-only: cannot set ReadOnly.%s' % name
+ assert o.attr1 == 'Hello, world!'
+
+ def test_delattr(self):
+ """
+ Test the `plugable.ReadOnly.__delattr__` method.
+ """
+ o = self.cls()
+ o.attr1 = 'Hello, world!'
+ o.attr2 = 'How are you?'
+ assert o.attr1 == 'Hello, world!'
+ assert o.attr2 == 'How are you?'
+ del o.attr1
+ assert not hasattr(o, 'attr1')
+ o.__lock__()
+ e = raises(AttributeError, delattr, o, 'attr2')
+ assert str(e) == 'read-only: cannot del ReadOnly.attr2'
+ assert o.attr2 == 'How are you?'
+
+
+def test_lock():
+ """
+ Tests the `plugable.lock` function.
+ """
+ f = plugable.lock
+
+ # Test on a ReadOnly instance:
+ o = plugable.ReadOnly()
+ assert not o.__islocked__()
+ assert f(o) is o
+ assert o.__islocked__()
+
+ # Test on something not subclassed from ReadOnly:
+ class not_subclass(object):
+ def __lock__(self):
+ pass
+ def __islocked__(self):
+ return True
+ o = not_subclass()
+ raises(ValueError, f, o)
+
+ # Test that it checks __islocked__():
+ class subclass(plugable.ReadOnly):
+ def __islocked__(self):
+ return False
+ o = subclass()
+ raises(AssertionError, f, o)
+
+
+class test_SetProxy(ClassChecker):
+ """
+ Tests the `plugable.SetProxy` class.
+ """
+ _cls = plugable.SetProxy
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.ReadOnly,)
+
+ def test_init(self):
+ okay = (set, frozenset, dict)
+ fail = (list, tuple)
+ for t in okay:
+ self.cls(t())
+ raises(TypeError, self.cls, t)
+ for t in fail:
+ raises(TypeError, self.cls, t())
+ raises(TypeError, self.cls, t)
+
+ def test_SetProxy(self):
+ def get_key(i):
+ return 'key_%d' % i
+
+ cnt = 10
+ target = set()
+ proxy = self.cls(target)
+ for i in xrange(cnt):
+ key = get_key(i)
+
+ # Check initial state
+ assert len(proxy) == len(target)
+ assert list(proxy) == sorted(target)
+ assert key not in proxy
+ assert key not in target
+
+ # Add and test again
+ target.add(key)
+ assert len(proxy) == len(target)
+ assert list(proxy) == sorted(target)
+ assert key in proxy
+ assert key in target
+
+
+class test_DictProxy(ClassChecker):
+ """
+ Tests the `plugable.DictProxy` class.
+ """
+ _cls = plugable.DictProxy
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.SetProxy,)
+
+ def test_init(self):
+ self.cls(dict())
+ raises(TypeError, self.cls, dict)
+ fail = (set, frozenset, list, tuple)
+ for t in fail:
+ raises(TypeError, self.cls, t())
+ raises(TypeError, self.cls, t)
+
+ def test_DictProxy(self):
+ def get_kv(i):
+ return (
+ 'key_%d' % i,
+ 'val_%d' % i,
+ )
+ cnt = 10
+ target = dict()
+ proxy = self.cls(target)
+ for i in xrange(cnt):
+ (key, val) = get_kv(i)
+
+ # Check initial state
+ assert len(proxy) == len(target)
+ assert list(proxy) == sorted(target)
+ assert list(proxy()) == [target[k] for k in sorted(target)]
+ assert key not in proxy
+ raises(KeyError, getitem, proxy, key)
+
+ # Add and test again
+ target[key] = val
+ assert len(proxy) == len(target)
+ assert list(proxy) == sorted(target)
+ assert list(proxy()) == [target[k] for k in sorted(target)]
+
+ # Verify TypeError is raised trying to set/del via proxy
+ raises(TypeError, setitem, proxy, key, val)
+ raises(TypeError, delitem, proxy, key)
+
+
+class test_MagicDict(ClassChecker):
+ """
+ Tests the `plugable.MagicDict` class.
+ """
+ _cls = plugable.MagicDict
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.DictProxy,)
+ for non_dict in ('hello', 69, object):
+ raises(TypeError, self.cls, non_dict)
+
+ def test_MagicDict(self):
+ cnt = 10
+ keys = []
+ d = dict()
+ dictproxy = self.cls(d)
+ for i in xrange(cnt):
+ key = 'key_%d' % i
+ val = 'val_%d' % i
+ keys.append(key)
+
+ # Test thet key does not yet exist
+ assert len(dictproxy) == i
+ assert key not in dictproxy
+ assert not hasattr(dictproxy, key)
+ raises(KeyError, getitem, dictproxy, key)
+ raises(AttributeError, getattr, dictproxy, key)
+
+ # Test that items/attributes cannot be set on dictproxy:
+ raises(TypeError, setitem, dictproxy, key, val)
+ raises(AttributeError, setattr, dictproxy, key, val)
+
+ # Test that additions in d are reflected in dictproxy:
+ d[key] = val
+ assert len(dictproxy) == i + 1
+ assert key in dictproxy
+ assert hasattr(dictproxy, key)
+ assert dictproxy[key] is val
+ assert read_only(dictproxy, key) is val
+
+ # Test __iter__
+ assert list(dictproxy) == keys
+
+ for key in keys:
+ # Test that items cannot be deleted through dictproxy:
+ raises(TypeError, delitem, dictproxy, key)
+ raises(AttributeError, delattr, dictproxy, key)
+
+ # Test that deletions in d are reflected in dictproxy
+ del d[key]
+ assert len(dictproxy) == len(d)
+ assert key not in dictproxy
+ raises(KeyError, getitem, dictproxy, key)
+ raises(AttributeError, getattr, dictproxy, key)
+
+
+class test_Plugin(ClassChecker):
+ """
+ Tests the `plugable.Plugin` class.
+ """
+ _cls = plugable.Plugin
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.ReadOnly,)
+ assert self.cls.__public__ == frozenset()
+ assert type(self.cls.name) is property
+ assert type(self.cls.doc) is property
+ assert type(self.cls.api) is property
+
+ def test_name(self):
+ """
+ Tests the `plugable.Plugin.name` property.
+ """
+ assert read_only(self.cls(), 'name') == 'Plugin'
+
+ class some_subclass(self.cls):
+ pass
+ assert read_only(some_subclass(), 'name') == 'some_subclass'
+
+ def test_doc(self):
+ """
+ Tests the `plugable.Plugin.doc` property.
+ """
+ class some_subclass(self.cls):
+ 'here is the doc string'
+ assert read_only(some_subclass(), 'doc') == 'here is the doc string'
+
+ def test_implements(self):
+ """
+ Tests the `plugable.Plugin.implements` classmethod.
+ """
+ class example(self.cls):
+ __public__ = frozenset((
+ 'some_method',
+ 'some_property',
+ ))
+ class superset(self.cls):
+ __public__ = frozenset((
+ 'some_method',
+ 'some_property',
+ 'another_property',
+ ))
+ class subset(self.cls):
+ __public__ = frozenset((
+ 'some_property',
+ ))
+ class any_object(object):
+ __public__ = frozenset((
+ 'some_method',
+ 'some_property',
+ ))
+
+ for ex in (example, example()):
+ # Test using str:
+ assert ex.implements('some_method')
+ assert not ex.implements('another_method')
+
+ # Test using frozenset:
+ assert ex.implements(frozenset(['some_method']))
+ assert not ex.implements(
+ frozenset(['some_method', 'another_method'])
+ )
+
+ # Test using another object/class with __public__ frozenset:
+ assert ex.implements(example)
+ assert ex.implements(example())
+
+ assert ex.implements(subset)
+ assert not subset.implements(ex)
+
+ assert not ex.implements(superset)
+ assert superset.implements(ex)
+
+ assert ex.implements(any_object)
+ assert ex.implements(any_object())
+
+ def test_implemented_by(self):
+ """
+ Tests the `plugable.Plugin.implemented_by` classmethod.
+ """
+ class base(self.cls):
+ __public__ = frozenset((
+ 'attr0',
+ 'attr1',
+ 'attr2',
+ ))
+
+ class okay(base):
+ def attr0(self):
+ pass
+ def __get_attr1(self):
+ assert False # Make sure property isn't accesed on instance
+ attr1 = property(__get_attr1)
+ attr2 = 'hello world'
+ another_attr = 'whatever'
+
+ class fail(base):
+ def __init__(self):
+ # Check that class, not instance is inspected:
+ self.attr2 = 'hello world'
+ def attr0(self):
+ pass
+ def __get_attr1(self):
+ assert False # Make sure property isn't accesed on instance
+ attr1 = property(__get_attr1)
+ another_attr = 'whatever'
+
+ # Test that AssertionError is raised trying to pass something not
+ # subclass nor instance of base:
+ raises(AssertionError, base.implemented_by, object)
+
+ # Test on subclass with needed attributes:
+ assert base.implemented_by(okay) is True
+ assert base.implemented_by(okay()) is True
+
+ # Test on subclass *without* needed attributes:
+ assert base.implemented_by(fail) is False
+ assert base.implemented_by(fail()) is False
+
+ def test_set_api(self):
+ """
+ Tests the `plugable.Plugin.set_api` method.
+ """
+ api = 'the api instance'
+ o = self.cls()
+ assert o.api is None
+ e = raises(AssertionError, o.set_api, None)
+ assert str(e) == 'set_api() argument cannot be None'
+ o.set_api(api)
+ assert o.api is api
+ e = raises(AssertionError, o.set_api, api)
+ assert str(e) == 'set_api() can only be called once'
+
+ def test_finalize(self):
+ """
+ Tests the `plugable.Plugin.finalize` method.
+ """
+ o = self.cls()
+ assert not o.__islocked__()
+ o.finalize()
+ assert o.__islocked__()
+
+
+class test_PluginProxy(ClassChecker):
+ """
+ Tests the `plugable.PluginProxy` class.
+ """
+ _cls = plugable.PluginProxy
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.SetProxy,)
+
+ def test_proxy(self):
+ # Setup:
+ class base(object):
+ __public__ = frozenset((
+ 'public_0',
+ 'public_1',
+ '__call__',
+ ))
+
+ def public_0(self):
+ return 'public_0'
+
+ def public_1(self):
+ return 'public_1'
+
+ def __call__(self, caller):
+ return 'ya called it, %s.' % caller
+
+ def private_0(self):
+ return 'private_0'
+
+ def private_1(self):
+ return 'private_1'
+
+ class plugin(base):
+ name = 'user_add'
+ attr_name = 'add'
+ doc = 'add a new user'
+
+ # Test that TypeError is raised when base is not a class:
+ raises(TypeError, self.cls, base(), None)
+
+ # Test that ValueError is raised when target is not instance of base:
+ raises(ValueError, self.cls, base, object())
+
+ # Test with correct arguments:
+ i = plugin()
+ p = self.cls(base, i)
+ assert read_only(p, 'name') is plugin.name
+ assert read_only(p, 'doc') == plugin.doc
+ assert list(p) == sorted(base.__public__)
+
+ # Test normal methods:
+ for n in xrange(2):
+ pub = 'public_%d' % n
+ priv = 'private_%d' % n
+ assert getattr(i, pub)() == pub
+ assert getattr(p, pub)() == pub
+ assert hasattr(p, pub)
+ assert getattr(i, priv)() == priv
+ assert not hasattr(p, priv)
+
+ # Test __call__:
+ value = 'ya called it, dude.'
+ assert i('dude') == value
+ assert p('dude') == value
+ assert callable(p)
+
+ # Test name_attr='name' kw arg
+ i = plugin()
+ p = self.cls(base, i, 'attr_name')
+ assert read_only(p, 'name') == 'add'
+
+ def test_implements(self):
+ """
+ Tests the `plugable.PluginProxy.implements` method.
+ """
+ class base(object):
+ __public__ = frozenset()
+ name = 'base'
+ doc = 'doc'
+ @classmethod
+ def implements(cls, arg):
+ return arg + 7
+
+ class sub(base):
+ @classmethod
+ def implements(cls, arg):
+ """
+ Defined to make sure base.implements() is called, not
+ target.implements()
+ """
+ return arg
+
+ o = sub()
+ p = self.cls(base, o)
+ assert p.implements(3) == 10
+
+ def test_clone(self):
+ """
+ Tests the `plugable.PluginProxy.__clone__` method.
+ """
+ class base(object):
+ __public__ = frozenset()
+ class sub(base):
+ name = 'some_name'
+ doc = 'doc'
+ label = 'another_name'
+
+ p = self.cls(base, sub())
+ assert read_only(p, 'name') == 'some_name'
+ c = p.__clone__('label')
+ assert isinstance(c, self.cls)
+ assert c is not p
+ assert read_only(c, 'name') == 'another_name'
+
+
+def test_check_name():
+ """
+ Tests the `plugable.check_name` function.
+ """
+ f = plugable.check_name
+ okay = [
+ 'user_add',
+ 'stuff2junk',
+ 'sixty9',
+ ]
+ nope = [
+ '_user_add',
+ '__user_add',
+ 'user_add_',
+ 'user_add__',
+ '_user_add_',
+ '__user_add__',
+ '60nine',
+ ]
+ for name in okay:
+ assert name is f(name)
+ e = raises(TypeError, f, unicode(name))
+ assert str(e) == errors.TYPE_FORMAT % ('name', str, unicode(name))
+ for name in nope:
+ raises(errors.NameSpaceError, f, name)
+ for name in okay:
+ raises(errors.NameSpaceError, f, name.upper())
+
+class DummyMember(object):
+ def __init__(self, i):
+ assert type(i) is int
+ self.name = 'member_%02d' % i
+
+
+class test_NameSpace(ClassChecker):
+ """
+ Tests the `plugable.NameSpace` class.
+ """
+ _cls = plugable.NameSpace
+
+ def test_class(self):
+ assert self.cls.__bases__ == (plugable.ReadOnly,)
+
+ def test_init(self):
+ """
+ Tests the `plugable.NameSpace.__init__` method.
+ """
+ o = self.cls(tuple())
+ assert list(o) == []
+ assert list(o()) == []
+ for cnt in (10, 25):
+ members = tuple(DummyMember(cnt - i) for i in xrange(cnt))
+ for sort in (True, False):
+ o = self.cls(members, sort=sort)
+ if sort:
+ ordered = tuple(sorted(members, key=lambda m: m.name))
+ else:
+ ordered = members
+ names = tuple(m.name for m in ordered)
+ assert o.__todict__() == dict((o.name, o) for o in ordered)
+
+ # Test __len__:
+ assert len(o) == cnt
+
+ # Test __contains__:
+ for name in names:
+ assert name in o
+ assert ('member_00') not in o
+
+ # Test __iter__, __call__:
+ assert tuple(o) == names
+ assert tuple(o()) == ordered
+
+ # Test __getitem__, getattr:
+ for (i, member) in enumerate(ordered):
+ assert o[i] is member
+ name = member.name
+ assert o[name] is member
+ assert read_only(o, name) is member
+
+ # Test negative indexes:
+ for i in xrange(1, cnt + 1):
+ assert o[-i] is ordered[-i]
+
+ # Test slices:
+ assert o[2:cnt-5] == ordered[2:cnt-5]
+ assert o[::3] == ordered[::3]
+
+ # Test __repr__:
+ assert repr(o) == \
+ 'NameSpace(<%d members>, sort=%r)' % (cnt, sort)
+
+
+def test_Environment():
+ """
+ Tests the `plugable.Environment` class.
+ """
+ # This has to be the same as iter_cnt
+ control_cnt = 0
+ class prop_class:
+ def __init__(self, val):
+ self._val = val
+ def get_value(self):
+ return self._val
+
+ class iter_class(prop_class):
+ # Increment this for each time iter_class yields
+ iter_cnt = 0
+ def get_value(self):
+ for item in self._val:
+ self.__class__.iter_cnt += 1
+ yield item
+
+ # Tests for basic functionality
+ basic_tests = (
+ ('a', 1),
+ ('b', 'basic_foo'),
+ ('c', ('basic_bar', 'basic_baz')),
+ )
+ # Tests with prop classes
+ prop_tests = (
+ ('d', prop_class(2), 2),
+ ('e', prop_class('prop_foo'), 'prop_foo'),
+ ('f', prop_class(('prop_bar', 'prop_baz')), ('prop_bar', 'prop_baz')),
+ )
+ # Tests with iter classes
+ iter_tests = (
+ ('g', iter_class((3, 4, 5)), (3, 4, 5)),
+ ('h', iter_class(('iter_foo', 'iter_bar', 'iter_baz')),
+ ('iter_foo', 'iter_bar', 'iter_baz')
+ ),
+ )
+
+ # Set all the values
+ env = plugable.Environment()
+ for name, val in basic_tests:
+ env[name] = val
+ for name, val, dummy in prop_tests:
+ env[name] = val
+ for name, val, dummy in iter_tests:
+ env[name] = val
+
+ # Test if the values are correct
+ for name, val in basic_tests:
+ assert env[name] == val
+ for name, dummy, val in prop_tests:
+ assert env[name] == val
+ # Test if the get_value() function is called only when needed
+ for name, dummy, correct_values in iter_tests:
+ values_in_env = []
+ for val in env[name]:
+ control_cnt += 1
+ assert iter_class.iter_cnt == control_cnt
+ values_in_env.append(val)
+ assert tuple(values_in_env) == correct_values
+
+ # Test __setattr__()
+ env.spam = 'ham'
+ assert env.spam == 'ham'
+
+ # Test if we throw AttributeError exception when trying to overwrite
+ # existing value, or delete it
+ raises(AttributeError, setitem, env, 'a', 1)
+ raises(AttributeError, setattr, env, 'a', 1)
+ raises(AttributeError, delitem, env, 'a')
+ raises(AttributeError, delattr, env, 'a')
+ raises(AttributeError, plugable.Environment.update, env, dict(a=1000))
+ # This should be silently ignored
+ env.update(dict(a=1000), True)
+ assert env.a != 1000
+
+def test_Registrar():
+ class Base1(object):
+ pass
+ class Base2(object):
+ pass
+ class Base3(object):
+ pass
+ class plugin1(Base1):
+ pass
+ class plugin2(Base2):
+ pass
+ class plugin3(Base3):
+ pass
+
+ # Test creation of Registrar:
+ r = plugable.Registrar(Base1, Base2)
+
+ # Test __iter__:
+ assert list(r) == ['Base1', 'Base2']
+
+ # Test __hasitem__, __getitem__:
+ for base in [Base1, Base2]:
+ name = base.__name__
+ assert name in r
+ assert r[name] is base
+ magic = getattr(r, name)
+ assert type(magic) is plugable.MagicDict
+ assert len(magic) == 0
+
+ # Check that TypeError is raised trying to register something that isn't
+ # a class:
+ raises(TypeError, r, plugin1())
+
+ # Check that SubclassError is raised trying to register a class that is
+ # not a subclass of an allowed base:
+ raises(errors.SubclassError, r, plugin3)
+
+ # Check that registration works
+ r(plugin1)
+ assert len(r.Base1) == 1
+ assert r.Base1['plugin1'] is plugin1
+ assert r.Base1.plugin1 is plugin1
+
+ # Check that DuplicateError is raised trying to register exact class
+ # again:
+ raises(errors.DuplicateError, r, plugin1)
+
+ # Check that OverrideError is raised trying to register class with same
+ # name and same base:
+ orig1 = plugin1
+ class base1_extended(Base1):
+ pass
+ class plugin1(base1_extended):
+ pass
+ raises(errors.OverrideError, r, plugin1)
+
+ # Check that overriding works
+ r(plugin1, override=True)
+ assert len(r.Base1) == 1
+ assert r.Base1.plugin1 is plugin1
+ assert r.Base1.plugin1 is not orig1
+
+ # Check that MissingOverrideError is raised trying to override a name
+ # not yet registerd:
+ raises(errors.MissingOverrideError, r, plugin2, override=True)
+
+ # Test that another plugin can be registered:
+ assert len(r.Base2) == 0
+ r(plugin2)
+ assert len(r.Base2) == 1
+ assert r.Base2.plugin2 is plugin2
+
+ # Setup to test more registration:
+ class plugin1a(Base1):
+ pass
+ r(plugin1a)
+
+ class plugin1b(Base1):
+ pass
+ r(plugin1b)
+
+ class plugin2a(Base2):
+ pass
+ r(plugin2a)
+
+ class plugin2b(Base2):
+ pass
+ r(plugin2b)
+
+ # Again test __hasitem__, __getitem__:
+ for base in [Base1, Base2]:
+ name = base.__name__
+ assert name in r
+ assert r[name] is base
+ magic = getattr(r, name)
+ assert len(magic) == 3
+ for key in magic:
+ klass = magic[key]
+ assert getattr(magic, key) is klass
+ assert issubclass(klass, base)
+
+
+
+def test_API():
+ assert issubclass(plugable.API, plugable.ReadOnly)
+
+ # Setup the test bases, create the API:
+ class base0(plugable.Plugin):
+ __public__ = frozenset((
+ 'method',
+ ))
+
+ def method(self, n):
+ return n
+
+ class base1(plugable.Plugin):
+ __public__ = frozenset((
+ 'method',
+ ))
+
+ def method(self, n):
+ return n + 1
+
+ api = plugable.API(base0, base1)
+ r = api.register
+ assert isinstance(r, plugable.Registrar)
+ assert read_only(api, 'register') is r
+
+ class base0_plugin0(base0):
+ pass
+ r(base0_plugin0)
+
+ class base0_plugin1(base0):
+ pass
+ r(base0_plugin1)
+
+ class base0_plugin2(base0):
+ pass
+ r(base0_plugin2)
+
+ class base1_plugin0(base1):
+ pass
+ r(base1_plugin0)
+
+ class base1_plugin1(base1):
+ pass
+ r(base1_plugin1)
+
+ class base1_plugin2(base1):
+ pass
+ r(base1_plugin2)
+
+ # Test API instance:
+ api.finalize()
+
+ def get_base(b):
+ return 'base%d' % b
+
+ def get_plugin(b, p):
+ return 'base%d_plugin%d' % (b, p)
+
+ for b in xrange(2):
+ base_name = get_base(b)
+ ns = getattr(api, base_name)
+ assert isinstance(ns, plugable.NameSpace)
+ assert read_only(api, base_name) is ns
+ assert len(ns) == 3
+ for p in xrange(3):
+ plugin_name = get_plugin(b, p)
+ proxy = ns[plugin_name]
+ assert isinstance(proxy, plugable.PluginProxy)
+ assert proxy.name == plugin_name
+ assert read_only(ns, plugin_name) is proxy
+ assert read_only(proxy, 'method')(7) == 7 + b
+
+ # Test that calling finilize again raises AssertionError:
+ raises(AssertionError, api.finalize)
+
+ # Test with base class that doesn't request a proxy
+ class NoProxy(plugable.Plugin):
+ __proxy__ = False
+ api = plugable.API(NoProxy)
+ class plugin0(NoProxy):
+ pass
+ api.register(plugin0)
+ class plugin1(NoProxy):
+ pass
+ api.register(plugin1)
+ api.finalize()
+ names = ['plugin0', 'plugin1']
+ assert list(api.NoProxy) == names
+ for name in names:
+ plugin = api.NoProxy[name]
+ assert getattr(api.NoProxy, name) is plugin
+ assert isinstance(plugin, plugable.Plugin)
+ assert not isinstance(plugin, plugable.PluginProxy)
diff --git a/tests/test_ipalib/test_tstutil.py b/tests/test_ipalib/test_tstutil.py
new file mode 100644
index 000000000..5916f9d24
--- /dev/null
+++ b/tests/test_ipalib/test_tstutil.py
@@ -0,0 +1,148 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for test-helper `tests.tstutil` module.
+"""
+
+import tstutil
+
+
+class Prop(object):
+ def __init__(self, *ops):
+ self.__ops = frozenset(ops)
+ self.__prop = 'prop value'
+
+ def __get_prop(self):
+ if 'get' not in self.__ops:
+ raise AttributeError('get prop')
+ return self.__prop
+
+ def __set_prop(self, value):
+ if 'set' not in self.__ops:
+ raise AttributeError('set prop')
+ self.__prop = value
+
+ def __del_prop(self):
+ if 'del' not in self.__ops:
+ raise AttributeError('del prop')
+ self.__prop = None
+
+ prop = property(__get_prop, __set_prop, __del_prop)
+
+
+def test_yes_raised():
+ f = tstutil.raises
+
+ class SomeError(Exception):
+ pass
+
+ class AnotherError(Exception):
+ pass
+
+ def callback1():
+ 'raises correct exception'
+ raise SomeError()
+
+ def callback2():
+ 'raises wrong exception'
+ raise AnotherError()
+
+ def callback3():
+ 'raises no exception'
+
+ f(SomeError, callback1)
+
+ raised = False
+ try:
+ f(SomeError, callback2)
+ except AnotherError:
+ raised = True
+ assert raised
+
+ raised = False
+ try:
+ f(SomeError, callback3)
+ except tstutil.ExceptionNotRaised:
+ raised = True
+ assert raised
+
+
+def test_no_set():
+ # Tests that it works when prop cannot be set:
+ tstutil.no_set(Prop('get', 'del'), 'prop')
+
+ # Tests that ExceptionNotRaised is raised when prop *can* be set:
+ raised = False
+ try:
+ tstutil.no_set(Prop('set'), 'prop')
+ except tstutil.ExceptionNotRaised:
+ raised = True
+ assert raised
+
+
+def test_no_del():
+ # Tests that it works when prop cannot be deleted:
+ tstutil.no_del(Prop('get', 'set'), 'prop')
+
+ # Tests that ExceptionNotRaised is raised when prop *can* be set:
+ raised = False
+ try:
+ tstutil.no_del(Prop('del'), 'prop')
+ except tstutil.ExceptionNotRaised:
+ raised = True
+ assert raised
+
+
+def test_read_only():
+ # Test that it works when prop is read only:
+ assert tstutil.read_only(Prop('get'), 'prop') == 'prop value'
+
+ # Test that ExceptionNotRaised is raised when prop can be set:
+ raised = False
+ try:
+ tstutil.read_only(Prop('get', 'set'), 'prop')
+ except tstutil.ExceptionNotRaised:
+ raised = True
+ assert raised
+
+ # Test that ExceptionNotRaised is raised when prop can be deleted:
+ raised = False
+ try:
+ tstutil.read_only(Prop('get', 'del'), 'prop')
+ except tstutil.ExceptionNotRaised:
+ raised = True
+ assert raised
+
+ # Test that ExceptionNotRaised is raised when prop can be both set and
+ # deleted:
+ raised = False
+ try:
+ tstutil.read_only(Prop('get', 'del'), 'prop')
+ except tstutil.ExceptionNotRaised:
+ raised = True
+ assert raised
+
+ # Test that AttributeError is raised when prop can't be read:
+ raised = False
+ try:
+ tstutil.read_only(Prop(), 'prop')
+ except AttributeError:
+ raised = True
+ assert raised
diff --git a/tests/test_ipalib/test_util.py b/tests/test_ipalib/test_util.py
new file mode 100644
index 000000000..f8ee0bf4d
--- /dev/null
+++ b/tests/test_ipalib/test_util.py
@@ -0,0 +1,49 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Unit tests for `ipalib.util` module.
+"""
+
+from tstutil import raises
+from ipalib import util
+
+
+def test_xmlrpc_marshal():
+ """
+ Test the `util.xmlrpc_marshal` function.
+ """
+ f = util.xmlrpc_marshal
+ assert f() == ({},)
+ assert f('one', 'two') == ({}, 'one', 'two')
+ assert f(one=1, two=2) == (dict(one=1, two=2),)
+ assert f('one', 'two', three=3, four=4) == \
+ (dict(three=3, four=4), 'one', 'two')
+
+
+def test_xmlrpc_unmarshal():
+ """
+ Test the `util.xmlrpc_unmarshal` function.
+ """
+ f = util.xmlrpc_unmarshal
+ assert f() == (tuple(), {})
+ assert f({}, 'one', 'two') == (('one', 'two'), {})
+ assert f(dict(one=1, two=2)) == (tuple(), dict(one=1, two=2))
+ assert f(dict(three=3, four=4), 'one', 'two') == \
+ (('one', 'two'), dict(three=3, four=4))
diff --git a/tests/test_ipalib/tstutil.py b/tests/test_ipalib/tstutil.py
new file mode 100644
index 000000000..743716a08
--- /dev/null
+++ b/tests/test_ipalib/tstutil.py
@@ -0,0 +1,147 @@
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Utility functions for the unit tests.
+"""
+
+import inspect
+from ipalib import errors
+
+class ExceptionNotRaised(Exception):
+ """
+ Exception raised when an *expected* exception is *not* raised during a
+ unit test.
+ """
+ msg = 'expected %s'
+
+ def __init__(self, expected):
+ self.expected = expected
+
+ def __str__(self):
+ return self.msg % self.expected.__name__
+
+
+def raises(exception, callback, *args, **kw):
+ """
+ Tests that the expected exception is raised; raises ExceptionNotRaised
+ if test fails.
+ """
+ raised = False
+ try:
+ callback(*args, **kw)
+ except exception, e:
+ raised = True
+ if not raised:
+ raise ExceptionNotRaised(exception)
+ return e
+
+
+def getitem(obj, key):
+ """
+ Works like getattr but for dictionary interface. Use this in combination
+ with raises() to test that, for example, KeyError is raised.
+ """
+ return obj[key]
+
+
+def setitem(obj, key, value):
+ """
+ Works like setattr but for dictionary interface. Use this in combination
+ with raises() to test that, for example, TypeError is raised.
+ """
+ obj[key] = value
+
+
+def delitem(obj, key):
+ """
+ Works like delattr but for dictionary interface. Use this in combination
+ with raises() to test that, for example, TypeError is raised.
+ """
+ del obj[key]
+
+
+def no_set(obj, name, value='some_new_obj'):
+ """
+ Tests that attribute cannot be set.
+ """
+ raises(AttributeError, setattr, obj, name, value)
+
+
+def no_del(obj, name):
+ """
+ Tests that attribute cannot be deleted.
+ """
+ raises(AttributeError, delattr, obj, name)
+
+
+def read_only(obj, name, value='some_new_obj'):
+ """
+ Tests that attribute is read-only. Returns attribute.
+ """
+ # Test that it cannot be set:
+ no_set(obj, name, value)
+
+ # Test that it cannot be deleted:
+ no_del(obj, name)
+
+ # Return the attribute
+ return getattr(obj, name)
+
+
+def is_prop(prop):
+ return type(prop) is property
+
+
+class ClassChecker(object):
+ __cls = None
+ __subcls = None
+
+ def __get_cls(self):
+ if self.__cls is None:
+ self.__cls = self._cls
+ assert inspect.isclass(self.__cls)
+ return self.__cls
+ cls = property(__get_cls)
+
+ def __get_subcls(self):
+ if self.__subcls is None:
+ self.__subcls = self.get_subcls()
+ assert inspect.isclass(self.__subcls)
+ return self.__subcls
+ subcls = property(__get_subcls)
+
+ def get_subcls(self):
+ raise NotImplementedError(
+ self.__class__.__name__,
+ 'get_subcls()'
+ )
+
+
+def check_TypeError(value, type_, name, callback, *args, **kw):
+ """
+ Tests a standard TypeError raised with `errors.raise_TypeError`.
+ """
+ e = raises(TypeError, callback, *args, **kw)
+ assert e.value is value
+ assert e.type is type_
+ assert e.name == name
+ assert type(e.name) is str
+ assert str(e) == errors.TYPE_FORMAT % (name, type_, value)
+ return e