----- Original Message -----
> On 01/15/2016 03:41 PM, Filip Skola wrote:
> > Hi,
> >
> > sending rebased patch on top of 58c42ddac0964a8cce7c1e1faa7516da53f028ad.
> >
> > Includes a "fix" for the rename-to-invalid-username issue for the new
> > version.
> >
> > F.
> >
> > ----- Original Message -----
> >> Hi,
> >>
> >> I don't know what is causing the \r\n issue. I use vim and than send each
> >> email with claws-mail. Didn't spot this issue when trying emailing the
> >> patch
> >> to my other address. I'm trying to send it from zimbra now, let me know if
> >> that helped pls.
> >>
> >> Fix for the stageuser plugin issues caused by this patch should have been
> >> included in the last update; I think the remaining issue is not caused by
> >> UserTracker changes. Please correct me, if I'm wrong.
> >>
> >>> There is some issue with "test_rename_to_too_long_login" test. It fails
> >>> but
> >>> actually this is false positive because it is possible to create login
> >>> upto
> >>> 255 characters. I don't know why test mentions 32 characters without any
> >>> other modified setup.
> >>> NACK for now.
> >>>   - alich -
> >> This has been changed. This test still fails, though.
> >>
> >> Filip
> >>
> >>>
> >>> ----- Original Message -----
> >>>> From: "Aleš Mareček" <amare...@redhat.com>
> >>>> To: "Filip Škola" <fsk...@redhat.com>
> >>>> Cc: freeipa-devel@redhat.com, "Milan Kubík" <mku...@redhat.com>
> >>>> Sent: Thursday, December 10, 2015 4:11:47 PM
> >>>> Subject: Re: [Freeipa-devel] [PATCH] 0001 Refactor test_user_plugin
> >>>>
> >>>> Ah, sorry, haven't realized there had been devel list attached.
> >>>> Ok, there is some problem with \r\n in the patch.
> >>>> Filip, please take a look at it...
> >>>> Thanks...
> >>>>   - alich -
> >>>>
> >>>> ----- Original Message -----
> >>>>> From: "Filip Škola" <fsk...@redhat.com>
> >>>>> To: "Aleš Mareček" <amare...@redhat.com>
> >>>>> Cc: freeipa-devel@redhat.com, "Milan Kubík" <mku...@redhat.com>
> >>>>> Sent: Thursday, December 10, 2015 11:29:52 AM
> >>>>> Subject: Re: [Freeipa-devel] [PATCH] 0001 Refactor test_user_plugin
> >>>>>
> >>>>> Hi,
> >>>>>
> >>>>> this if fixed. Also issues with test_stageuser_plugin caused by
> >>>>> UserTracker changes should be fixed here.
> >>>>>
> >>>>> Filip
> >>>>>
> >>>>>
> >>>>> On Mon, 7 Dec 2015 09:29:31 -0500 (EST)
> >>>>> Aleš Mareček <amare...@redhat.com> wrote:
> >>>>>
> >>>>>> NACK.
> >>>>>>
> >>>>>> $ ./make-lint
> >>>>>> ************* Module ipatests.test_xmlrpc.test_user_plugin
> >>>>>> ipatests/test_xmlrpc/test_user_plugin.py:42:
> >>>>>> [E0611(no-name-in-module), ] No name 'ldaptracker' in module
> >>>>>> 'ipatests.test_xmlrpc')
> >>>>>>
> >>>>>> $ grep ldaptracker ipatests/test_xmlrpc/test_user_plugin.py
> >>>>>> from ipatests.test_xmlrpc.ldaptracker import Tracker
> >>>>>> $ ls ipatests/test_xmlrpc/ldaptracker*
> >>>>>> ls: cannot access ipatests/test_xmlrpc/ldaptracker*: No such file or
> >>>>>> directory
> >>>>>>
> >>>>>>
> >>>>>> ----- Original Message -----
> >>>>>>> From: "Filip Škola" <fsk...@redhat.com>
> >>>>>>> To: "Milan Kubík" <mku...@redhat.com>
> >>>>>>> Cc: freeipa-devel@redhat.com
> >>>>>>> Sent: Thursday, December 3, 2015 5:38:43 PM
> >>>>>>> Subject: Re: [Freeipa-devel] [PATCH] 0001 Refactor test_user_plugin
> >>>>>>>
> >>>>>>> Hi,
> >>>>>>>
> >>>>>>> sending corrected version.
> >>>>>>>
> >>>>>>> F.
> >>>>>>>
> >>>>>>> On Thu, 12 Nov 2015 14:03:19 +0100
> >>>>>>> Milan Kubík <mku...@redhat.com> wrote:
> >>>>>>>
> >>>>>>>> On 11/10/2015 12:13 PM, Filip Škola wrote:
> >>>>>>>>> Hi,
> >>>>>>>>>
> >>>>>>>>> fixed.
> >>>>>>>>>
> >>>>>>>>> F.
> >>>>>>>>>
> >>>>>>>>> On Tue, 10 Nov 2015 10:52:45 +0100
> >>>>>>>>> Milan Kubík <mku...@redhat.com> wrote:
> >>>>>>>>>
> >>>>>>>>>> On 11/09/2015 04:35 PM, Filip Škola wrote:
> >>>>>>>>>>> Another patch was applied in the meantime.
> >>>>>>>>>>>
> >>>>>>>>>>> Attaching an updated version.
> >>>>>>>>>>>
> >>>>>>>>>>> F.
> >>>>>>>>>>>
> >>>>>>>>>>> On Mon, 9 Nov 2015 13:35:02 +0100
> >>>>>>>>>>> Milan Kubík <mku...@redhat.com> wrote:
> >>>>>>>>>>>
> >>>>>>>>>>>> On 11/06/2015 11:32 AM, Filip Škola wrote:
> >>>>>>>>>>>> Hi,
> >>>>>>>>>>>> the patch doesn't apply.
> >>>>>>>>>>>>
> >>>>>>>>>> Please fix this.
> >>>>>>>>>>
> >>>>>>>>>>        ipatests/test_xmlrpc/test_user_plugin.py:1419:
> >>>>>>>>>> [E0602(undefined-variable),
> >>>>>>>>>> TestDeniedBindWithExpiredPrincipal.teardown_class] Undefined
> >>>>>>>>>> variable 'user1')
> >>>>>>>>>>
> >>>>>>>>>> Also, use the version numbers for your changed patches.
> >>>>>>>>>>
> >>>>>>>>>
> >>>>>>>> Thanks for the patch. Several issues:
> >>>>>>>>
> >>>>>>>> 1. Use dict.items instead of dict.iteritems, for python3
> >>>>>>>> compatibility
> >>>>>>>>
> >>>>>>>> 2. What is the purpose of TestPrepare class? The 'purge' methods
> >>>>>>>> do not call any ipa commands.
> >>>>>>>> Tracker.make_fixture should be used to make the Tracked resources
> >>>>>>>> clean themselves up when they're out of scope.
> >>>>>>>>
> >>>>>>>> 3. Why reference the resources by hardcoded name if they have a
> >>>>>>>> fixture representation?
> >>>>>>>>
> >>>>>>>> 4. Rewrite {create,delete}_test_group to a fixture. You may want
> >>>>>>>> to use different scope (or not).
> >>>>>>>>
> >>>>>>>> 5. In `def atest_rename_to_invalid_login(self, user):` - use
> >>>>>>>> pytest.skipif decorator and provide a reason if you must,
> >>>>>>>> do not obfuscate method name in order not to run it.
> >>>>>>>>
> >>>>>>>>
> >>>>>>>
> >>>>>>> --
> >>>>>>> Manage your subscription for the Freeipa-devel mailing list:
> >>>>>>> https://www.redhat.com/mailman/listinfo/freeipa-devel
> >>>>>>> Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code
> >>>>>
> >> --
> >> Manage your subscription for the Freeipa-devel mailing list:
> >> https://www.redhat.com/mailman/listinfo/freeipa-devel
> >> Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code
> NACK, there are errors occuring that do not appear in the respective
> test cases in the declarative test.
> In the original module the ` Test a login name that is too long` and
> `Try to rename to a username that is too long` do not use {add,set}attr.
> Why do you use them?
> 
> I'm also postponing the review of your other patches as they depend on
> changes in this one.
> 
> --
> Milan Kubik
> 
> 

To be honest, I have no idea why I did use setattr in those places. Update of 
'rename' attribute was used instead in this version of the patch and that seems 
to work.

Filip
From bcc9a2631e7a066c9452d4436cafce61aa5d27db Mon Sep 17 00:00:00 2001
From: Filip Skola <fsk...@redhat.com>
Date: Fri, 6 Nov 2015 10:57:37 +0100
Subject: [PATCH] Refactor test_user_plugin, use UserTracker for tests

---
 ipatests/test_xmlrpc/test_user_plugin.py    | 2377 ++++++++++-----------------
 ipatests/test_xmlrpc/tracker/user_plugin.py |  169 +-
 2 files changed, 1026 insertions(+), 1520 deletions(-)

diff --git a/ipatests/test_xmlrpc/test_user_plugin.py b/ipatests/test_xmlrpc/test_user_plugin.py
index 084fb83c42d362204ff4547357226c8f56d217fb..355573138fdf35d7f54471875dc955036ec5c64d 100644
--- a/ipatests/test_xmlrpc/test_user_plugin.py
+++ b/ipatests/test_xmlrpc/test_user_plugin.py
@@ -2,6 +2,7 @@
 #   Rob Crittenden <rcrit...@redhat.com>
 #   Pavel Zuna <pz...@redhat.com>
 #   Jason Gerard DeRose <jder...@redhat.com>
+#   Filip Skola <fsk...@redhat.com>
 #
 # Copyright (C) 2008, 2009  Red Hat
 # see file 'COPYING' for use and warranty information
@@ -23,6 +24,7 @@
 Test the `ipalib/plugins/user.py` module.
 """
 
+import pytest
 import datetime
 import ldap
 import re
@@ -30,42 +32,41 @@ import re
 from ipalib import api, errors
 from ipatests.test_xmlrpc import objectclasses
 from ipatests.util import (
-    assert_equal, assert_not_equal, raises)
+    assert_deepequal, assert_equal, assert_not_equal, raises)
 from xmlrpc_test import (
-    XMLRPC_test, Declarative, fuzzy_digits, fuzzy_uuid, fuzzy_password,
-    fuzzy_string, fuzzy_dergeneralizedtime, add_sid, add_oc)
+    XMLRPC_test, fuzzy_digits, fuzzy_uuid, fuzzy_password,
+    fuzzy_string, fuzzy_dergeneralizedtime, add_sid, add_oc, raises_exact)
 from ipapython.dn import DN
-import pytest
 
-user1 = u'tuser1'
-user2 = u'tuser2'
+from ipatests.test_xmlrpc.tracker.base import Tracker
+from ipatests.test_xmlrpc.tracker.group_plugin import GroupTracker
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
+
 admin1 = u'admin'
-admin2 = u'admin2'
-renameduser1 = u'tuser'
-group1 = u'group1'
-admins_group = u'admins'
+admin_group = u'admins'
 
 invaliduser1 = u'+tuser1'
-invaliduser2 = u'tuser1234567890123456789012345678901234567890'
+invaliduser2 = u''.join(['a' for n in range(256)])
 
 sshpubkey = (u'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDGAX3xAeLeaJggwTqMjxNwa6X'
-              'HBUAikXPGMzEpVrlLDCZtv00djsFTBi38PkgxBJVkgRWMrcBsr/35lq7P6w8KGI'
-              'wA8GI48Z0qBS2NBMJ2u9WQ2hjLN6GdMlo77O0uJY3251p12pCVIS/bHRSq8kHO2'
-              'No8g7KA9fGGcagPfQH+ee3t7HUkpbQkFTmbPPN++r3V8oVUk5LxbryB3UIIVzNm'
-              'cSIn3JrXynlvui4MixvrtX6zx+O/bBo68o8/eZD26QrahVbA09fivrn/4h3TM01'
-              '9Eu/c2jOdckfU3cHUV/3Tno5d6JicibyaoDDK7S/yjdn5jhaz8MSEayQvFkZkiF'
-              '0L public key test')
+             'HBUAikXPGMzEpVrlLDCZtv00djsFTBi38PkgxBJVkgRWMrcBsr/35lq7P6w8KGI'
+             'wA8GI48Z0qBS2NBMJ2u9WQ2hjLN6GdMlo77O0uJY3251p12pCVIS/bHRSq8kHO2'
+             'No8g7KA9fGGcagPfQH+ee3t7HUkpbQkFTmbPPN++r3V8oVUk5LxbryB3UIIVzNm'
+             'cSIn3JrXynlvui4MixvrtX6zx+O/bBo68o8/eZD26QrahVbA09fivrn/4h3TM01'
+             '9Eu/c2jOdckfU3cHUV/3Tno5d6JicibyaoDDK7S/yjdn5jhaz8MSEayQvFkZkiF'
+             '0L public key test')
 sshpubkeyfp = (u'13:67:6B:BF:4E:A2:05:8E:AE:25:8B:A1:31:DE:6F:1B '
-                'public key test (ssh-rsa)')
+               'public key test (ssh-rsa)')
 
-validlanguage1 = u'en-US;q=0.987 , en, abcdfgh-abcdefgh;q=1        , a;q=1.000'
-validlanguage2 = u'*'
+validlanguages = {
+    u'en-US;q=0.987 , en, abcdfgh-abcdefgh;q=1        , a;q=1.000',
+    u'*'
+    }
 
-invalidlanguage1 = u'abcdfghji-abcdfghji'
-invalidlanguage2 = u'en-us;q=0,123'
-invalidlanguage3 = u'en-us;q=0.1234'
-invalidlanguage4 = u'en-us;q=1.1'
-invalidlanguage5 = u'en-us;q=1.0000'
+invalidlanguages = {
+    u'abcdfghji-abcdfghji', u'en-us;q=0,123',
+    u'en-us;q=0.1234', u'en-us;q=1.1', u'en-us;q=1.0000'
+    }
 
 principal_expiration_string = "2020-12-07T19:54:13Z"
 principal_expiration_date = datetime.datetime(2020, 12, 7, 19, 54, 13)
@@ -77,6 +78,856 @@ expired_expiration_string = "1991-12-07T19:54:13Z"
 isodate_re = re.compile('^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$')
 
 
+@pytest.fixture(scope='class')
+def user(request):
+    tracker = UserTracker(name=u'user1', givenname=u'Test', sn=u'User1')
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def user2(request):
+    tracker = UserTracker(name=u'user2', givenname=u'Test2', sn=u'User2')
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def renameduser(request):
+    tracker = UserTracker(name=u'ruser1', givenname=u'Ruser', sn=u'Ruser1')
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def admin2(request):
+    tracker = UserTracker(name=u'admin2', givenname=u'Second', sn=u'Admin')
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def user_npg(request, group):
+    """ User tracker fixture for testing users with no private group """
+    tracker = UserTracker(name=u'npguser1', givenname=u'Npguser',
+                          sn=u'Npguser1', noprivate=True)
+    tracker.track_create()
+    del tracker.attrs['mepmanagedentry']
+    tracker.attrs.update(
+        description=[], memberof_group=[group.cn],
+        objectclass=add_oc(objectclasses.user_base, u'ipantuserattrs')
+    )
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def user_npg2(request, group):
+    """ User tracker fixture for testing users with no private group """
+    tracker = UserTracker(name=u'npguser2', givenname=u'Npguser',
+                          sn=u'Npguser2', noprivate=True, gidnumber=1000)
+    tracker.track_create()
+    del tracker.attrs['mepmanagedentry']
+    tracker.attrs.update(
+        gidnumber=[u'1000'], description=[], memberof_group=[group.cn],
+        objectclass=add_oc(objectclasses.user_base, u'ipantuserattrs')
+    )
+    return tracker.make_fixture(request)
+
+
+@pytest.fixture(scope='class')
+def group(request):
+    tracker = GroupTracker(name=u'group1')
+    return tracker.make_fixture(request)
+
+
+@pytest.mark.tier1
+class TestNonexistentUser(XMLRPC_test):
+    def test_retrieve_nonexistent(self, user):
+        """ Try to retrieve a non-existent user """
+        user.ensure_missing()
+        command = user.make_retrieve_command()
+        with raises_exact(errors.NotFound(
+                reason=u'%s: user not found' % user.uid)):
+            command()
+
+    def test_update_nonexistent(self, user):
+        """ Try to update a non-existent user """
+        user.ensure_missing()
+        command = user.make_update_command(
+            updates=dict(givenname=u'changed'))
+        with raises_exact(errors.NotFound(
+                reason=u'%s: user not found' % user.uid)):
+            command()
+
+    def test_delete_nonexistent(self, user):
+        """ Try to delete a non-existent user """
+        user.ensure_missing()
+        command = user.make_delete_command()
+        with raises_exact(errors.NotFound(
+                reason=u'%s: user not found' % user.uid)):
+            command()
+
+    def test_rename_nonexistent(self, user, renameduser):
+        """ Try to rename a non-existent user """
+        user.ensure_missing()
+        command = user.make_update_command(
+            updates=dict(setattr=u'uid=%s' % renameduser.uid))
+        with raises_exact(errors.NotFound(
+                reason=u'%s: user not found' % user.uid)):
+            command()
+
+
+@pytest.mark.tier1
+class TestUser(XMLRPC_test):
+    def test_retrieve(self, user):
+        """ Create user and try to retrieve it """
+        user.retrieve()
+
+    def test_delete(self, user):
+        """ Delete user """
+        user.delete()
+
+    def test_query_status(self, user):
+        """ Query user_status on a user """
+        user.ensure_exists()
+        result = user.run_command('user_status', user.uid)
+        assert_deepequal(dict(
+            count=1,
+            result=[dict(
+                dn=user.dn,
+                krblastfailedauth=[u'N/A'],
+                krblastsuccessfulauth=[u'N/A'],
+                krbloginfailedcount=u'0',
+                now=isodate_re.match,
+                server=api.env.host,
+                ), ],
+            summary=u'Account disabled: False',
+            truncated=False,
+        ), result)
+        user.delete()
+
+    def test_remove_userclass(self, user):
+        """ Remove attribute userclass from user entry """
+        user.ensure_exists()
+        result = user.run_command(
+            'user_mod', user.uid, **dict(userclass=u'')
+        )
+        user.check_update(result)
+        user.delete()
+
+
+@pytest.mark.tier1
+class TestFind(XMLRPC_test):
+    def test_find(self, user):
+        """ Basic check of user-find """
+        user.find()
+
+    def test_find_with_all(self, user):
+        """ Basic check of user-find with --all """
+        user.find(all=True)
+
+    def test_find_with_pkey_only(self, user):
+        """ Basic check of user-find with primary keys only """
+        user.ensure_exists()
+        command = user.make_find_command(
+            uid=user.uid, pkey_only=True
+        )
+        result = command()
+        user.check_find(result, pkey_only=True)
+
+
+@pytest.mark.tier1
+class TestActive(XMLRPC_test):
+    def test_disable(self, user):
+        """ Disable user using user-disable """
+        user.ensure_exists()
+        user.disable()
+        command = user.make_retrieve_command()
+        result = command()
+        user.check_retrieve(result)
+
+    def test_enable(self, user):
+        """ Enable user using user-enable """
+        user.ensure_exists()
+        user.enable()
+        command = user.make_retrieve_command()
+        result = command()
+        user.check_retrieve(result)
+
+    def test_disable_using_setattr(self, user):
+        """ Disable user using setattr """
+        user.ensure_exists()
+        # we need to update the track manually
+        user.attrs['nsaccountlock'] = True
+
+        command = user.make_update_command(
+            updates=dict(setattr=u'nsaccountlock=True')
+        )
+        result = command()
+        user.check_update(result)
+
+    def test_enable_using_setattr(self, user):
+        """ Enable user using setattr """
+        user.ensure_exists()
+        user.attrs['nsaccountlock'] = False
+
+        command = user.make_update_command(
+            updates=dict(setattr=u'nsaccountlock=False')
+        )
+        result = command()
+        user.check_update(result)
+
+    def test_disable_using_usermod(self, user):
+        """ Disable user using user-mod """
+        user.update(dict(nsaccountlock=True), dict(nsaccountlock=True))
+
+    def test_enable_using_usermod(self, user):
+        """ Enable user using user-mod """
+        user.update(dict(nsaccountlock=False), dict(nsaccountlock=False))
+
+
+@pytest.mark.tier1
+class TestUpdate(XMLRPC_test):
+    def test_set_virtual_attribute(self, user):
+        """ Try to assign an invalid virtual attribute """
+        attr = 'random'
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(setattr=(u'%s=xyz123' % attr))
+        )
+        with raises_exact(errors.ObjectclassViolation(
+                info=u'attribute "%s" not allowed' % attr)):
+            command()
+
+    def test_update(self, user):
+        """ Update a user attribute """
+        user.update(dict(givenname=u'Franta'))
+
+    def test_update_krb_ticket_policy(self, user):
+        """ Try to update krbmaxticketlife """
+        attr = 'krbmaxticketlife'
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(setattr=(u'%s=88000' % attr))
+        )
+        with raises_exact(errors.ObjectclassViolation(
+                info=u'attribute "%s" not allowed' % attr)):
+            command()
+
+    def test_rename(self, user, renameduser):
+        """ Rename user and than rename it back """
+        user.ensure_exists()
+        renameduser.ensure_missing()
+        olduid = user.uid
+
+        # using user.update(dict(uid=value)) results in
+        # OverlapError: overlapping arguments and options: ['uid']
+        user.attrs.update(uid=[renameduser.uid])
+        command = user.make_update_command(
+            updates=dict(setattr=(u'uid=%s' % renameduser.uid))
+        )
+        result = command()
+        user.check_update(result)
+        user.uid = renameduser.uid
+
+        # rename the test user back so it gets properly deleted
+        user.attrs.update(uid=[olduid])
+        command = user.make_update_command(
+            updates=dict(setattr=(u'uid=%s' % olduid))
+        )
+        result = command()
+        user.check_update(result)
+        user.uid = olduid
+
+    def test_rename_to_the_same_value(self, user):
+        """ Try to rename user to the same value """
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(setattr=(u'uid=%s' % user.uid))
+        )
+        with raises_exact(errors.EmptyModlist()):
+            command()
+
+    def test_rename_to_the_same_with_other_mods(self, user):
+        """ Try to rename user to the same value while
+        including other modifications that should be done """
+        user.ensure_exists()
+        user.attrs.update(loginshell=[u'/bin/false'])
+        command = user.make_update_command(
+            updates=dict(setattr=u'uid=%s' % user.uid,
+                         loginshell=u'/bin/false')
+        )
+        result = command()
+        user.check_update(result)
+
+    def test_rename_to_too_long_login(self, user):
+        """ Try to change user login to too long value """
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(rename=invaliduser2)
+            # no exception raised, user is renamed
+        )
+        with raises_exact(errors.ValidationError(
+                name='rename',
+                error=u'can be at most 255 characters')):
+            command()
+
+    def test_update_illegal_ssh_pubkey(self, user):
+        """ Try to update user with an illegal SSH public key """
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(ipasshpubkey=[u"anal nathrach orth' bhais's bethad "
+                                       "do che'l de'nmha"])
+        )
+        with raises_exact(errors.ValidationError(
+                name='sshpubkey',
+                error=u'invalid SSH public key')):
+            command()
+
+    def test_set_ipauserauthtype(self, user):
+        """ Set ipauserauthtype to 'password' and than back to None """
+        user.ensure_exists()
+        user.update(dict(ipauserauthtype=u'password'))
+        user.retrieve()
+
+        user.update(dict(ipauserauthtype=None))
+        user.delete()
+
+    def test_set_random_password(self, user):
+        """ Modify user with random password """
+        user.ensure_exists()
+        user.attrs.update(
+            randompassword=fuzzy_password,
+            has_keytab=True,
+            has_password=True
+        )
+        user.update(
+            dict(random=True),
+            dict(random=None, randompassword=fuzzy_password)
+        )
+        user.delete()
+
+    def test_rename_to_invalid_login(self, user):
+        """ Try to change user login to an invalid value """
+        user.ensure_exists()
+        command = user.make_update_command(
+            updates=dict(rename=invaliduser1)
+        )
+        with raises_exact(errors.ValidationError(
+                name='rename',
+                error=u'may only include letters, numbers, _, -, . and $')):
+            command()
+
+
+@pytest.mark.tier1
+class TestCreate(XMLRPC_test):
+    def test_create_with_krb_ticket_policy(self):
+        """ Try to create user with krbmaxticketlife set """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test',
+            sn=u'Tuser1', setattr=u'krbmaxticketlife=88000'
+        )
+        command = testuser.make_create_command()
+        with raises_exact(errors.ObjectclassViolation(
+                info=u'attribute "%s" not allowed' % 'krbmaxticketlife')):
+            command()
+
+    def test_create_with_ssh_pubkey(self):
+        """ Create user with an assigned SSH public key """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test',
+            sn=u'Tuser1', ipasshpubkey=sshpubkey
+        )
+        testuser.track_create()
+        # fingerprint is expected in the tracker attrs
+        testuser.attrs.update(sshpubkeyfp=[sshpubkeyfp])
+        command = testuser.make_create_command()
+        result = command()
+        testuser.check_create(result)
+        testuser.delete()
+
+    def test_create_with_invalid_login(self):
+        """ Try to create user with an invalid login string """
+        testuser = UserTracker(
+            name=invaliduser1, givenname=u'Test', sn=u'User1'
+        )
+        command = testuser.make_create_command()
+        with raises_exact(errors.ValidationError(
+                name=u'login',
+                error=u'may only include letters, numbers, _, -, . and $')):
+            command()
+
+    def test_create_with_too_long_login(self):
+        """ Try to create user with too long login string """
+        testuser = UserTracker(
+            name=invaliduser2, givenname=u'Test', sn=u'User1'
+        )
+        command = testuser.make_create_command()
+        with raises_exact(errors.ValidationError(
+                name=u'login',
+                error=u'can be at most 255 characters')):
+            command()
+
+    def test_create_with_full_address(self):
+        """ Create user with full address set """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            street=u'123 Maple Rd', l=u'Anytown', st=u'MD',
+            postalcode=u'01234-5678', mobile=u'410-555-1212'
+        )
+        testuser.create()
+        testuser.delete()
+
+    def test_create_with_random_passwd(self):
+        """ Create user with random password """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1', random=True
+        )
+        testuser.track_create()
+        testuser.attrs.update(
+            randompassword=fuzzy_password,
+            has_keytab=True, has_password=True,
+            krbextradata=[fuzzy_string],
+            krbpasswordexpiration=[fuzzy_dergeneralizedtime],
+            krblastpwdchange=[fuzzy_dergeneralizedtime]
+        )
+        command = testuser.make_create_command()
+        result = command()
+        testuser.check_create(result)
+        testuser.delete()
+
+    def test_create_with_different_default_home(self, user):
+        """ Change default home directory and check that a newly created
+            user has his home set properly """
+        user.ensure_missing()
+        user.run_command('config_mod', **{u'ipahomesrootdir': u'/other-home'})
+        user.track_create()
+        user.attrs.update(homedirectory=[u'/other-home/%s' % user.name])
+
+        command = user.make_create_command()
+        result = command()
+        user.check_create(result)
+        user.run_command('config_mod', **{u'ipahomesrootdir': u'/home'})
+        user.delete()
+
+    def test_create_with_different_default_shell(self, user):
+        """ Change default login shell and check that a newly created
+            user is created with correct login shell value """
+        user.ensure_missing()
+        user.run_command(
+            'config_mod', **{u'ipadefaultloginshell': u'/bin/zsh'}
+        )
+        user.track_create()
+        user.attrs.update(loginshell=[u'/bin/zsh'])
+        command = user.make_create_command()
+        result = command()
+        user.check_create(result)
+        user.run_command(
+            'config_mod', **{u'ipadefaultloginshell': u'/bin/sh'}
+        )
+        user.delete()
+
+    def test_create_without_upg(self):
+        """ Try to create user without User's Primary GID """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            noprivate=True
+        )
+        command = testuser.make_create_command()
+        with raises_exact(errors.NotFound(
+                reason=u'Default group for new users is not POSIX')):
+            command()
+
+    def test_create_without_upg_with_gid_set(self):
+        """ Create user without User's Primary GID with GID set """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            noprivate=True, gidnumber=1000
+        )
+        testuser.track_create()
+        del testuser.attrs['mepmanagedentry']
+        testuser.attrs.update(gidnumber=[u'1000'])
+        testuser.attrs.update(
+            description=[],
+            objectclass=add_oc(objectclasses.user_base, u'ipantuserattrs')
+        )
+        command = testuser.make_create_command()
+        result = command()
+        testuser.check_create(result, [u'description'])
+        testuser.delete()
+
+    def test_create_with_uid_999(self):
+        """ Check that server return uid and gid 999
+        when a new client asks for uid 999 """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1', uidnumber=999
+        )
+        testuser.track_create()
+        testuser.attrs.update(
+            uidnumber=[u'999'],
+            gidnumber=[u'999']
+        )
+        command = testuser.make_create_command()
+        result = command()
+        testuser.check_create(result)
+        testuser.delete()
+
+    def test_create_with_old_DNA_MAGIC_999(self):
+        """ Check that server picks suitable uid and gid
+        when an old client asks for the magic uid 999 """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            uidnumber=999, version=u'2.49'
+        )
+        testuser.track_create()
+        testuser.attrs.update(
+            uidnumber=[lambda v: int(v) != 999],
+            gidnumber=[lambda v: int(v) != 999],
+        )
+        command = testuser.make_create_command()
+        result = command()
+        testuser.check_create(result)
+        testuser.delete()
+
+    def test_create_duplicate(self, user):
+        """ Try to create second user with the same name """
+        user.ensure_exists()
+        command = user.make_create_command()
+        with raises_exact(errors.DuplicateEntry(
+                message=u'user with name "%s" already exists' %
+                user.uid)):
+            command()
+
+    def test_create_where_managed_group_exists(self, user, group):
+        """ Create a managed group and then try to create user
+        with the same name the group has """
+        group.create()
+        command = user.make_command(
+            'user_add', group.cn, **dict(givenname=u'Test', sn=u'User1')
+        )
+        with raises_exact(errors.ManagedGroupExistsError(group=group.cn)):
+            command()
+
+
+@pytest.mark.tier1
+class TestUserWithGroup(XMLRPC_test):
+    def test_change_default_user_group(self, group):
+        """ Change default group for TestUserWithGroup class of tests """
+        group.create()
+        group.run_command(
+            'config_mod', **{u'ipadefaultprimarygroup': group.cn}
+        )
+
+    def test_create_without_upg(self, user_npg):
+        """ Try to create user without User's Primary GID
+        after default group was changed """
+        command = user_npg.make_create_command()
+        # User without private group has some different attrs upon creation
+        # so we won't use make_create, but do own check instead
+        # These are set in the fixture
+        result = command()
+        user_npg.check_create(result, [u'description', u'memberof_group'])
+
+    def test_create_without_upg_with_gid_set(self, user_npg2):
+        """ Create user without User's Primary GID with GID set
+        after default group was changed """
+        command = user_npg2.make_create_command()
+        result = command()
+        user_npg2.check_create(result, [u'description', u'memberof_group'])
+
+    def test_set_manager(self, user_npg, user_npg2):
+        """ Update user with own group with manager with own group """
+        user_npg.update(dict(manager=user_npg2.uid))
+
+    def test_check_user_with_renamed_manager(self, user_npg, user_npg2):
+        """ Rename manager with own group, retrieve user and check
+            if its manager is also renamed """
+        renamed_name = u'renamed_npg2'
+        old_name = user_npg2.uid
+        command = user_npg2.make_update_command(dict(rename=renamed_name))
+        result = command()
+        user_npg2.attrs.update(uid=[renamed_name])
+        user_npg2.check_update(result)
+        user_npg.attrs.update(manager=[renamed_name])
+        user_npg.retrieve(all=True)
+
+        command = user_npg2.make_command(
+            'user_mod', renamed_name, **dict(rename=old_name)
+        )
+        # we rename the user back otherwise the tracker is too confused
+        result = command()
+
+    def test_check_if_manager_gets_removed(self, user_npg, user_npg2):
+        """ Delete manager and check if it's gone from user's attributes """
+        user_npg2.delete()
+        del user_npg.attrs[u'manager']
+        del user_npg.attrs[u'description']
+        user_npg.retrieve(all=True)
+
+    def test_change_default_user_group_back(self, user_npg, user_npg2):
+        """ Change default group back to 'ipausers' and clean up members """
+        user_npg.delete()
+        user_npg.run_command(
+            'config_mod', **{u'ipadefaultprimarygroup': u'ipausers'}
+        )
+
+
+@pytest.mark.tier1
+class TestManagers(XMLRPC_test):
+    def test_assign_nonexistent_manager(self, user, user2):
+        """ Try to assign user a non-existent manager """
+        user.ensure_exists()
+        user2.ensure_missing()
+        command = user.make_update_command(
+            updates=dict(manager=user2.uid)
+        )
+        with raises_exact(errors.NotFound(
+                reason=u'manager %s not found' % user2.uid)):
+            command()
+
+    def test_assign_manager(self, user, user2):
+        """ Make user manager of another user """
+        user.ensure_exists()
+        user2.ensure_exists()
+        user.update(dict(manager=user2.uid))
+
+    def test_search_by_manager(self, user, user2):
+        """ Find user by his manager's UID """
+        command = user.make_find_command(manager=user2.uid)
+        result = command()
+        user.check_find(result)
+
+    def test_delete_both_user_and_manager(self, user, user2):
+        """ Delete both user and its manager at once """
+        result = user.run_command(
+            'user_del', [user.uid, user2.uid],
+            preserve=False, no_preserve=True
+        )
+        assert_deepequal(dict(
+            value=[user.uid, user2.uid],
+            summary=u'Deleted user "%s,%s"' % (user.uid, user2.uid),
+            result=dict(failed=[]),
+            ), result)
+        # mark users as deleted
+        user.exists = False
+        user2.exists = False
+
+
+@pytest.mark.tier1
+class TestAdmins(XMLRPC_test):
+    def test_remove_original_admin(self):
+        """ Try to remove the only admin """
+        tracker = Tracker()
+        command = tracker.make_command('user_del', [admin1])
+
+        with raises_exact(errors.LastMemberError(
+                key=admin1, label=u'group', container=admin_group)):
+            command()
+
+    def test_disable_original_admin(self):
+        """ Try to disable the only admin """
+        tracker = Tracker()
+        command = tracker.make_command('user_disable', admin1)
+
+        with raises_exact(errors.LastMemberError(
+                key=admin1, label=u'group', container=admin_group)):
+            command()
+
+    def test_create_admin2(self, admin2):
+        """ Test whether second admin gets created """
+        admin2.ensure_exists()
+        admin2.make_admin()
+        admin2.delete()
+
+    def test_last_admin_preservation(self, admin2):
+        """ Create a second admin, disable it. Then try to disable and
+        remove the original one and receive LastMemberError. Last trial
+        are these ops with second admin removed. """
+        admin2.ensure_exists()
+        admin2.make_admin()
+        admin2.disable()
+        tracker = Tracker()
+
+        with raises_exact(errors.LastMemberError(
+                key=admin1, label=u'group', container=admin_group)):
+            tracker.run_command('user_disable', admin1)
+        with raises_exact(errors.LastMemberError(
+                key=admin1, label=u'group', container=admin_group)):
+            tracker.run_command('user_del', admin1)
+        admin2.delete()
+
+        with raises_exact(errors.LastMemberError(
+                key=admin1, label=u'group', container=admin_group)):
+            tracker.run_command('user_disable', admin1)
+        with raises_exact(errors.LastMemberError(
+                key=admin1, label=u'group', container=admin_group)):
+            tracker.run_command('user_del', admin1)
+
+
+@pytest.mark.tier1
+class TestPreferredLanguages(XMLRPC_test):
+    def test_invalid_preferred_languages(self, user):
+        """ Try to assign various invalid preferred languages to user """
+        user.ensure_exists()
+        for invalidlanguage in invalidlanguages:
+            command = user.make_update_command(
+                dict(preferredlanguage=invalidlanguage)
+            )
+
+            with raises_exact(errors.ValidationError(
+                    name='preferredlanguage',
+                    error=(u'must match RFC 2068 - 14.4, e.g., '
+                           '"da, en-gb;q=0.8, en;q=0.7"')
+            )):
+                command()
+        user.delete()
+
+    def test_valid_preferred_languages(self, user):
+        """ Update user with different preferred languages """
+        for validlanguage in validlanguages:
+            user.update(dict(preferredlanguage=validlanguage))
+        user.delete()
+
+
+@pytest.mark.tier1
+class TestPrincipals(XMLRPC_test):
+    def test_create_with_bad_realm_in_principal(self):
+        """ Try to create user with a bad realm in principal """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            krbprincipalname=u'tus...@notfound.org'
+        )
+
+        command = testuser.make_create_command()
+        with raises_exact(errors.RealmMismatch()):
+            command()
+
+    def test_create_with_malformed_principal(self):
+        """ Try to create user with wrongly formed principal """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            krbprincipalname=u'tuser1@b...@notfound.org'
+        )
+
+        command = testuser.make_create_command()
+        with raises_exact(errors.MalformedUserPrincipal(
+                principal=u'tuser1@b...@notfound.org')):
+            command()
+
+    def test_set_principal_expiration(self, user):
+        """ Set principal expiration for user """
+        user.update(
+            dict(krbprincipalexpiration=principal_expiration_string),
+            dict(krbprincipalexpiration=[principal_expiration_date])
+        )
+
+    def test_set_invalid_principal_expiration(self, user):
+        """ Try to set incorrent principal expiration value for user """
+        user.ensure_exists()
+        command = user.make_update_command(
+            dict(krbprincipalexpiration=invalid_expiration_string)
+        )
+
+        with raises_exact(errors.ConversionError(
+                name='principal_expiration',
+                error=(u'does not match any of accepted formats: '
+                       '%Y%m%d%H%M%SZ, %Y-%m-%dT%H:%M:%SZ, %Y-%m-%dT%H:%MZ, '
+                       '%Y-%m-%dZ, %Y-%m-%d %H:%M:%SZ, %Y-%m-%d %H:%MZ')
+        )):
+            command()
+
+    def test_create_with_uppercase_principal(self):
+        """ Create user with upper-case principal """
+        testuser = UserTracker(
+            name=u'tuser1', givenname=u'Test', sn=u'Tuser1',
+            krbprincipalname=u'tuser1'.upper()
+        )
+        testuser.create()
+        testuser.delete()
+
+
+@pytest.mark.tier1
+class TestValidation(XMLRPC_test):
+    # The assumption for this class of tests is that if we don't
+    # get a validation error then the request was processed normally.
+
+    def test_validation_disabled_on_deletes(self):
+        """ Test that validation is disabled on user deletes """
+        tracker = Tracker()
+        command = tracker.make_command('user_del', invaliduser1)
+        with raises_exact(errors.NotFound(
+                reason=u'%s: user not found' % invaliduser1)):
+            command()
+
+    def test_validation_disabled_on_show(self):
+        """ Test that validation is disabled on user retrieves """
+        tracker = Tracker()
+        command = tracker.make_command('user_show', invaliduser1)
+        with raises_exact(errors.NotFound(
+                reason=u'%s: user not found' % invaliduser1)):
+            command()
+
+    def test_validation_disabled_on_find(self, user):
+        """ Test that validation is disabled on user searches """
+        result = user.run_command('user_find', invaliduser1)
+        user.check_find_nomatch(result)
+
+
+@pytest.mark.tier1
+class TestDeniedBindWithExpiredPrincipal(XMLRPC_test):
+
+    password = u'random'
+
+    @classmethod
+    def setup_class(cls):
+        super(TestDeniedBindWithExpiredPrincipal, cls).setup_class()
+
+        cls.connection = ldap.initialize('ldap://{host}'
+                                         .format(host=api.env.host))
+
+    @classmethod
+    def teardown_class(cls):
+        super(TestDeniedBindWithExpiredPrincipal, cls).teardown_class()
+
+    def test_bind_as_test_user(self, user):
+        """ Bind as user """
+        self.failsafe_add(
+            api.Object.user,
+            user.uid,
+            givenname=u'Test',
+            sn=u'User1',
+            userpassword=self.password,
+            krbprincipalexpiration=principal_expiration_string
+        )
+
+        self.connection.simple_bind_s(
+            str(get_user_dn(user.uid)), self.password
+        )
+
+    def test_bind_as_expired_test_user(self, user):
+        """ Try to bind as expired user """
+        api.Command['user_mod'](
+            user.uid,
+            krbprincipalexpiration=expired_expiration_string
+        )
+
+        raises(ldap.UNWILLING_TO_PERFORM,
+               self.connection.simple_bind_s,
+               str(get_user_dn(user.uid)), self.password
+               )
+
+    def test_bind_as_renewed_test_user(self, user):
+        """ Bind as renewed user """
+        api.Command['user_mod'](
+            user.uid,
+            krbprincipalexpiration=principal_expiration_string
+        )
+
+        self.connection.simple_bind_s(
+            str(get_user_dn(user.uid)), self.password
+        )
+
+# This set of functions (get_*, upg_check, not_upg_check)
+# is mostly for legacy purposes here, tests using UserTracker
+# should not rely on them
+
+
 def get_user_result(uid, givenname, sn, operation='show', omit=[],
                     **overrides):
     """Get a user result for a user-{add,mod,find,show} command
@@ -155,10 +1006,12 @@ def get_admin_result(operation='show', **overrides):
 
 
 def get_user_dn(uid):
+    """ Get user DN by uid """
     return DN(('uid', uid), api.env.container_user, api.env.basedn)
 
 
 def get_group_dn(cn):
+    """ Get group DN by CN """
     return DN(('cn', cn), api.env.container_group, api.env.basedn)
 
 
@@ -178,1477 +1031,3 @@ def not_upg_check(response):
     assert_not_equal(response['result']['uidnumber'],
                      response['result']['gidnumber'])
     return True
-
-
-@pytest.mark.tier1
-class test_user(Declarative):
-
-    cleanup_commands = [
-        ('user_del', [user1, user2, renameduser1, admin2], {'continue': True}),
-        ('group_del', [group1], {}),
-        ('automember_default_group_remove', [], {'type': u'group'}),
-    ]
-
-    tests = [
-
-        dict(
-            desc='Try to retrieve non-existent "%s"' % user1,
-            command=('user_show', [user1], {}),
-            expected=errors.NotFound(reason=u'%s: user not found' % user1),
-        ),
-
-
-        dict(
-            desc='Try to update non-existent "%s"' % user1,
-            command=('user_mod', [user1], dict(givenname=u'Foo')),
-            expected=errors.NotFound(reason=u'%s: user not found' % user1),
-        ),
-
-
-        dict(
-            desc='Try to delete non-existent "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=errors.NotFound(reason=u'%s: user not found' % user1),
-        ),
-
-
-        dict(
-            desc='Try to rename non-existent "%s"' % user1,
-            command=('user_mod', [user1],
-                     dict(setattr=u'uid=%s' % renameduser1)),
-            expected=errors.NotFound(reason=u'%s: user not found' % user1),
-        ),
-
-
-        dict(
-            desc='Create "%s"' % user1,
-            command=(
-                'user_add',
-                [user1],
-                dict(
-                    givenname=u'Test',
-                    sn=u'User1',
-                    userclass=u'testusers'
-                )
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(
-                    user1,
-                    u'Test',
-                    u'User1',
-                    'add',
-                    userclass=[u'testusers'],
-                    objectclass=add_oc(
-                        objectclasses.user,
-                        u'ipantuserattrs'
-                    ) + [u'ipauser']
-                ),
-            ),
-            extra_check = upg_check,
-        ),
-
-
-        dict(
-            desc='Try to create duplicate "%s"' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1')
-            ),
-            expected=errors.DuplicateEntry(
-                message=u'user with name "%s" already exists' % user1),
-        ),
-
-
-        dict(
-            desc='Retrieve "%s"' % user1,
-            command=(
-                'user_show', [user1], {}
-            ),
-            expected=dict(
-                result=get_user_result(
-                    user1,
-                    u'Test',
-                    u'User1',
-                    'show',
-                    userclass=[u'testusers']
-                ),
-                value=user1,
-                summary=None,
-            ),
-        ),
-
-        dict(
-            desc='Remove userclass for user "%s"' % user1,
-            command=('user_mod', [user1], dict(userclass=u'')),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod'),
-                value=user1,
-                summary=u'Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Search for "%s" with all=True' % user1,
-            command=(
-                'user_find', [user1], {'all': True}
-            ),
-            expected=dict(
-                result=[
-                    get_user_result(
-                        user1,
-                        u'Test',
-                        u'User1',
-                        'show-all',
-                        objectclass=add_oc(
-                            objectclasses.user,
-                            u'ipantuserattrs'
-                        ) + [u'ipauser'],
-                        preserved=False
-                    ),
-                ],
-                summary=u'1 user matched',
-                count=1, truncated=False,
-            ),
-        ),
-
-
-        dict(
-            desc='Search for "%s" with pkey-only=True' % user1,
-            command=(
-                'user_find', [user1], {'pkey_only': True}
-            ),
-            expected=dict(
-                result=[
-                    {
-                        'dn': get_user_dn(user1),
-                        'uid': [user1],
-                    },
-                ],
-                summary=u'1 user matched',
-                count=1, truncated=False,
-            ),
-        ),
-
-
-        dict(
-            desc='Search for "%s" with minimal attributes' % user1,
-            command=(
-                'user_find', [user1], {}
-            ),
-            expected=dict(
-                result=[
-                    get_user_result(user1, u'Test', u'User1', 'find'),
-                ],
-                summary=u'1 user matched',
-                count=1,
-                truncated=False,
-            ),
-        ),
-
-
-        dict(
-            desc='Search for all users',
-            command=(
-                'user_find', [], {}
-            ),
-            expected=dict(
-                result=[
-                    get_admin_result('find'),
-                    get_user_result(user1, u'Test', u'User1', 'find'),
-                ],
-                summary=u'2 users matched',
-                count=2,
-                truncated=False,
-            ),
-        ),
-
-
-        dict(
-            desc='Search for all users with a limit of 1',
-            command=(
-                'user_find', [], dict(sizelimit=1,),
-            ),
-            expected=dict(
-                result=[get_admin_result('find')],
-                summary=u'1 user matched',
-                count=1,
-                truncated=True,
-            ),
-        ),
-
-
-        dict(
-            desc='Disable "%s"' % user1,
-            command=(
-                'user_disable', [user1], {}
-            ),
-            expected=dict(
-                result=True,
-                value=user1,
-                summary=u'Disabled user account "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Assert user is disabled',
-            command=('user_find', [user1], {}),
-            expected=dict(
-                result=[get_user_result(user1, u'Test', u'User1', 'find',
-                                        nsaccountlock=True)],
-                summary=u'1 user matched',
-                count=1,
-                truncated=False,
-            ),
-        ),
-
-        dict(
-            desc='Enable "%s"' % user1,
-            command=(
-                'user_enable', [user1], {}
-            ),
-            expected=dict(
-                result=True,
-                value=user1,
-                summary=u'Enabled user account "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Assert user "%s" is enabled' % user1,
-            command=('user_find', [user1], {}),
-            expected=dict(
-                result=[get_user_result(user1, u'Test', u'User1', 'find')],
-                summary=u'1 user matched',
-                count=1,
-                truncated=False,
-            ),
-        ),
-
-        dict(
-            desc='Disable "%s" using setattr' % user1,
-            command=('user_mod', [user1], dict(setattr=u'nsaccountlock=True')),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod',
-                                       nsaccountlock=True),
-                value=user1,
-                summary=u'Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Enable "%s" using setattr' % user1,
-            command=('user_mod', [user1], dict(setattr=u'nsaccountlock=False')),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod'),
-                value=user1,
-                summary=u'Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Disable "%s" using user_mod' % user1,
-            command=('user_mod', [user1], dict(nsaccountlock=True)),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod',
-                                       nsaccountlock=True),
-                value=user1,
-                summary=u'Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Enable "%s" using user_mod' % user1,
-            command=('user_mod', [user1], dict(nsaccountlock=False)),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod'),
-                value=user1,
-                summary=u'Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Try setting virtual attribute on "%s" using setattr' % user1,
-            command=('user_mod', [user1], dict(setattr=u'random=xyz123')),
-            expected=errors.ObjectclassViolation(
-                info='attribute "random" not allowed'),
-        ),
-
-        dict(
-            desc='Update "%s"' % user1,
-            command=(
-                'user_mod', [user1], dict(givenname=u'Finkle')
-            ),
-            expected=dict(
-                result=get_user_result(user1, u'Finkle', u'User1', 'mod'),
-                summary=u'Modified user "%s"' % user1,
-                value=user1,
-            ),
-        ),
-
-
-        dict(
-            desc='Try updating the krb ticket policy of "%s"' % user1,
-            command=(
-                'user_mod', [user1], dict(setattr=u'krbmaxticketlife=88000')
-            ),
-            expected=errors.ObjectclassViolation(
-                info=u'attribute "krbmaxticketlife" not allowed'),
-        ),
-
-
-        dict(
-            desc='Retrieve "%s" to verify update' % user1,
-            command=('user_show', [user1], {}),
-            expected=dict(
-                result=get_user_result(user1, u'Finkle', u'User1', 'show'),
-                summary=None,
-                value=user1,
-            ),
-
-        ),
-
-
-        dict(
-            desc='Rename "%s"' % user1,
-            command=('user_mod', [user1],
-                     dict(setattr=u'uid=%s' % renameduser1)),
-            expected=dict(
-                result=get_user_result(
-                    renameduser1, u'Finkle', u'User1', 'mod',
-                    mail=[u'%s@%s' % (user1, api.env.domain)],
-                    homedirectory=[u'/home/%s' % user1]),
-                summary=u'Modified user "%s"' % user1,
-                value=user1,
-            ),
-        ),
-
-
-        dict(
-            desc='Rename "%s" to same value' % renameduser1,
-            command=('user_mod', [renameduser1],
-                     dict(setattr=u'uid=%s' % renameduser1)),
-            expected=errors.EmptyModlist(),
-        ),
-
-        dict(
-            desc='Rename "%s" to same value, check that other modifications '
-                 'are performed' % renameduser1,
-            command=('user_mod', [renameduser1],
-                     dict(setattr=u'uid=%s' % renameduser1,
-                          loginshell=u'/bin/bash')),
-            expected=dict(
-                result=get_user_result(
-                    renameduser1, u'Finkle', u'User1', 'mod',
-                    mail=[u'%s@%s' % (user1, api.env.domain)],
-                    homedirectory=[u'/home/%s' % user1],
-                    loginshell=[u'/bin/bash']),
-                summary=u'Modified user "%s"' % renameduser1,
-                value=renameduser1,
-            ),
-        ),
-
-
-        dict(
-            desc='Rename back "%s"' % renameduser1,
-            command=('user_mod', [renameduser1],
-                     dict(setattr=u'uid=%s' % user1, loginshell=u'/bin/sh')),
-            expected=dict(
-                result=get_user_result(user1, u'Finkle', u'User1', 'mod'),
-                summary=u'Modified user "%s"' % renameduser1,
-                value=renameduser1,
-            ),
-        ),
-
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-
-        dict(
-            desc='Try to delete non-existent "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=errors.NotFound(reason=u'tuser1: user not found'),
-        ),
-
-
-        dict(
-            desc='Create user "%s" with krb ticket policy' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                setattr=u'krbmaxticketlife=88000')
-            ),
-            expected=errors.ObjectclassViolation(
-                info='attribute "krbmaxticketlife" not allowed'),
-        ),
-
-
-        dict(
-            desc='Create "%s" with SSH public key' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                                          ipasshpubkey=[sshpubkey])
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(
-                    user1, u'Test', u'User1', 'add',
-                    objectclass=add_oc(objectclasses.user, u'ipantuserattrs'),
-                    ipasshpubkey=[sshpubkey],
-                    sshpubkeyfp=[sshpubkeyfp],
-                ),
-            ),
-            extra_check = upg_check,
-        ),
-
-
-        dict(
-            desc='Add an illegal SSH public key to "%r"' % user1,
-            command=('user_mod', [user1],
-                     dict(ipasshpubkey=[u"anal nathrach orth' bhais's bethad "
-                                         "do che'l de'nmha"])),
-            expected=errors.ValidationError(name='sshpubkey',
-                error=u'invalid SSH public key'),
-        ),
-
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-
-        dict(
-            desc='Create "%s"' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1')
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(user1, u'Test', u'User1', 'add'),
-            ),
-            extra_check = upg_check,
-        ),
-
-
-        dict(
-            desc='Create "%s"' % user2,
-            command=(
-                'user_add', [user2], dict(givenname=u'Test', sn=u'User2')
-            ),
-            expected=dict(
-                value=user2,
-                summary=u'Added user "%s"' % user2,
-                result=get_user_result(user2, u'Test', u'User2', 'add'),
-            ),
-            extra_check = upg_check,
-        ),
-
-
-        dict(
-            desc='Make non-existent "%s" the manager of "%s"' % (renameduser1,
-                                                                 user2),
-            command=('user_mod', [user2], dict(manager=renameduser1)),
-            expected=errors.NotFound(
-                reason=u'manager %s not found' % renameduser1),
-        ),
-
-
-        dict(
-            desc='Make "%s" the manager of "%s"' % (user1, user2),
-            command=('user_mod', [user2], dict(manager=user1)),
-            expected=dict(
-                result=get_user_result(user2, u'Test', u'User2', 'mod',
-                                       manager=[user1]),
-                summary=u'Modified user "%s"' % user2,
-                value=user2,
-            ),
-        ),
-
-        dict(
-            desc='Search for "%s" with manager "%s"' % (user2, user1),
-            command=(
-                'user_find', [user2], {'manager': user1}
-            ),
-            expected=dict(
-                result=[get_user_result(user2, u'Test', u'User2', 'find',
-                                        manager=[user1])],
-                summary=u'1 user matched',
-                count=1,
-                truncated=False,
-            ),
-        ),
-
-        dict(
-            desc='Delete "%s" and "%s" at the same time' % (user1, user2),
-            command=('user_del', [user1, user2], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "tuser1,tuser2"',
-                value=[user1, user2],
-            ),
-        ),
-
-        dict(
-            desc='Try to retrieve non-existent "%s"' % user1,
-            command=('user_show', [user1], {}),
-            expected=errors.NotFound(reason=u'%s: user not found' % user1),
-        ),
-
-
-        dict(
-            desc='Try to update non-existent "%s"' % user1,
-            command=('user_mod', [user1], dict(givenname=u'Foo')),
-            expected=errors.NotFound(reason=u'%s: user not found' % user1),
-        ),
-
-
-        dict(
-            desc='Test an invalid login name "%s"' % invaliduser1,
-            command=('user_add', [invaliduser1], dict(givenname=u'Test',
-                                                      sn=u'User1')),
-            expected=errors.ValidationError(name='login',
-                error=u'may only include letters, numbers, _, -, . and $'),
-        ),
-
-
-        dict(
-            desc='Test a login name that is too long "%s"' % invaliduser2,
-            command=('user_add', [invaliduser2],
-                dict(givenname=u'Test', sn=u'User1')),
-            expected=errors.ValidationError(name='login',
-                error='can be at most 32 characters'),
-        ),
-
-
-        # The assumption on these next 4 tests is that if we don't get a
-        # validation error then the request was processed normally.
-        dict(
-            desc='Test that validation is disabled on deletes',
-            command=('user_del', [invaliduser1], {}),
-            expected=errors.NotFound(
-                reason=u'%s: user not found' % invaliduser1),
-        ),
-
-
-        dict(
-            desc='Test that validation is disabled on show',
-            command=('user_show', [invaliduser1], {}),
-            expected=errors.NotFound(
-                reason=u'%s: user not found' % invaliduser1),
-        ),
-
-
-        dict(
-            desc='Test that validation is disabled on find',
-            command=('user_find', [invaliduser1], {}),
-            expected=dict(
-                count=0,
-                truncated=False,
-                summary=u'0 users matched',
-                result=[],
-            ),
-        ),
-
-
-        dict(
-            desc='Try to rename to invalid username "%s"' % user1,
-            command=('user_mod', [user1], dict(rename=invaliduser1)),
-            expected=errors.ValidationError(name='rename',
-                error=u'may only include letters, numbers, _, -, . and $'),
-        ),
-
-
-        dict(
-            desc='Try to rename to a username that is too long "%s"' % user1,
-            command=('user_mod', [user1], dict(rename=invaliduser2)),
-            expected=errors.ValidationError(name='login',
-                error='can be at most 32 characters'),
-        ),
-
-
-        dict(
-            desc='Create "%s"' % group1,
-            command=(
-                'group_add', [group1], dict(description=u'Test desc')
-            ),
-            expected=dict(
-                value=group1,
-                summary=u'Added group "%s"' % group1,
-                result=dict(
-                    cn=[group1],
-                    description=[u'Test desc'],
-                    gidnumber=[fuzzy_digits],
-                    objectclass=objectclasses.group + [u'posixgroup'],
-                    ipauniqueid=[fuzzy_uuid],
-                    dn=get_group_dn(group1),
-                ),
-            ),
-        ),
-
-
-        dict(
-            desc='Try to user "%s" where the managed group exists' % group1,
-            command=(
-                'user_add', [group1], dict(givenname=u'Test', sn=u'User1')
-            ),
-            expected=errors.ManagedGroupExistsError(group=group1)
-        ),
-
-
-        dict(
-            desc='Create "%s" with a full address' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                street=u'123 Maple Rd', l=u'Anytown', st=u'MD',
-                telephonenumber=u'410-555-1212', postalcode=u'01234-5678')
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(
-                    user1, u'Test', u'User1', 'add',
-                    street=[u'123 Maple Rd'], l=[u'Anytown'], st=[u'MD'],
-                    telephonenumber=[u'410-555-1212'],
-                    postalcode=[u'01234-5678'],
-                ),
-            ),
-        ),
-
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-        dict(
-            desc='Create "%s" with random password' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                                          random=True)
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(
-                    user1, u'Test', u'User1', 'add',
-                    randompassword=fuzzy_password,
-                    has_keytab=True, has_password=True,
-                    krbextradata=[fuzzy_string],
-                    krbpasswordexpiration=[fuzzy_dergeneralizedtime],
-                    krblastpwdchange=[fuzzy_dergeneralizedtime]
-                ),
-            ),
-        ),
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-        dict(
-            desc='Create "%s"' % user2,
-            command=(
-                'user_add', [user2], dict(givenname=u'Test', sn=u'User2')
-            ),
-            expected=dict(
-                value=user2,
-                summary=u'Added user "%s"' % user2,
-                result=get_user_result(user2, u'Test', u'User2', 'add'),
-            ),
-        ),
-
-        dict(
-            desc='Modify "%s" with random password' % user2,
-            command=(
-                'user_mod', [user2], dict(random=True)
-            ),
-            expected=dict(
-                result=get_user_result(
-                    user2, u'Test', u'User2', 'mod',
-                    randompassword=fuzzy_password,
-                    has_keytab=True, has_password=True,
-                ),
-                summary=u'Modified user "%s"' % user2,
-                value=user2,
-            ),
-        ),
-
-        dict(
-            desc='Delete "%s"' % user2,
-            command=('user_del', [user2], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user2,
-                value=[user2],
-            ),
-        ),
-
-        dict(
-            desc='Create user "%s" with upper-case principal' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                krbprincipalname=user1.upper())
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(user1, u'Test', u'User1', 'add'),
-            ),
-        ),
-
-
-        dict(
-            desc='Create user "%s" with bad realm in principal' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                krbprincipalname='%s...@notfound.org' % user1)
-            ),
-            expected=errors.RealmMismatch()
-        ),
-
-
-        dict(
-            desc='Create user "%s" with malformed principal' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                krbprincipalname='%s@b...@notfound.org' % user1)
-            ),
-            expected=errors.MalformedUserPrincipal(
-                principal='%s@b...@notfound.org' % user1),
-        ),
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-        dict(
-            desc='Change default home directory',
-            command=(
-                'config_mod', [], dict(ipahomesrootdir=u'/other-home'),
-            ),
-            expected=lambda x, output: x is None,
-        ),
-
-        dict(
-            desc=('Create user "%s" with different default '
-                  'home directory' % user1),
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1')
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(user1, u'Test', u'User1', 'add',
-                                       homedirectory=[u'/other-home/tuser1']),
-            ),
-        ),
-
-
-        dict(
-            desc='Reset default home directory',
-            command=(
-                'config_mod', [], dict(ipahomesrootdir=u'/home'),
-            ),
-            expected=lambda x, output: x is None,
-        ),
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-        dict(
-            desc='Change default login shell',
-            command=(
-                'config_mod', [],
-                dict(ipadefaultloginshell=u'/usr/bin/ipython'),
-            ),
-            expected=lambda x, output: x is None,
-        ),
-
-        dict(
-            desc='Create user "%s" with different default login shell' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1')
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(user1, u'Test', u'User1', 'add',
-                                       loginshell=[u'/usr/bin/ipython']),
-            ),
-        ),
-
-        dict(
-            desc='Reset default login shell',
-            command=(
-                'config_mod', [], dict(ipadefaultloginshell=u'/bin/sh'),
-            ),
-            expected=lambda x, output: x is None,
-        ),
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-        dict(
-            desc='Create "%s" without UPG' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                                          noprivate=True)
-            ),
-            expected=errors.NotFound(
-                reason='Default group for new users is not POSIX'),
-        ),
-
-        dict(
-            desc='Create "%s" without UPG with GID explicitly set' % user2,
-            command=(
-                'user_add', [user2], dict(givenname=u'Test', sn=u'User2',
-                                          noprivate=True, gidnumber=1000)
-            ),
-            expected=dict(
-                value=user2,
-                summary=u'Added user "%s"' % user2,
-                result=get_user_result(
-                    user2, u'Test', u'User2', 'add',
-                    objectclass=add_oc(objectclasses.user_base,
-                                       u'ipantuserattrs'),
-                    gidnumber=[u'1000'],
-                    description=[],
-                    omit=['mepmanagedentry'],
-                ),
-            ),
-        ),
-
-        dict(
-            desc='Delete "%s"' % user2,
-            command=('user_del', [user2], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user2,
-                value=[user2],
-            ),
-        ),
-
-        dict(
-            desc='Change default user group',
-            command=(
-                'config_mod', [], dict(ipadefaultprimarygroup=group1),
-            ),
-            expected=lambda x, output: x is None,
-        ),
-
-        dict(
-            desc='Create "%s" without UPG' % user1,
-            command=(
-                'user_add', [user1], dict(givenname=u'Test', sn=u'User1',
-                                          noprivate=True)
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(
-                    user1, u'Test', u'User1', 'add',
-                    objectclass=add_oc(objectclasses.user_base,
-                                       u'ipantuserattrs'),
-                    description=[],
-                    memberof_group=[group1],
-                    omit=['mepmanagedentry'],
-                ),
-            ),
-            extra_check = not_upg_check,
-        ),
-
-        dict(
-            desc='Create "%s" without UPG with GID explicitly set' % user2,
-            command=(
-                'user_add', [user2], dict(givenname=u'Test', sn=u'User2',
-                                          noprivate=True, gidnumber=1000)
-            ),
-            expected=dict(
-                value=user2,
-                summary=u'Added user "%s"' % user2,
-                result=get_user_result(
-                    user2, u'Test', u'User2', 'add',
-                    objectclass=add_oc(objectclasses.user_base,
-                                       u'ipantuserattrs'),
-                    description=[],
-                    gidnumber=[u'1000'],
-                    memberof_group=[group1],
-                    omit=['mepmanagedentry'],
-                ),
-            ),
-        ),
-
-        dict(
-            desc='Set %r as manager of %r' % (user1, user2),
-            command=(
-                'user_mod', [user2], dict(manager=user1)
-            ),
-            expected=dict(
-                result=get_user_result(user2, u'Test', u'User2', 'mod',
-                                       gidnumber=[u'1000'],
-                                       memberof_group=[group1],
-                                       manager=[user1]),
-                summary=u'Modified user "%s"' % user2,
-                value=user2,
-            ),
-        ),
-
-        dict(
-            desc='Rename "%s"' % user1,
-            command=('user_mod', [user1], dict(rename=renameduser1)),
-            expected=dict(
-                result=get_user_result(
-                    renameduser1, u'Test', u'User1', 'mod',
-                    homedirectory=[u'/home/%s' % user1],
-                    mail=[u'%s@%s' % (user1, api.env.domain)],
-                    memberof_group=[group1],
-                ),
-                summary=u'Modified user "%s"' % user1,
-                value=user1,
-            ),
-        ),
-
-        dict(
-            desc='Retrieve %r and check that manager is renamed' % user2,
-            command=(
-                'user_show', [user2], {'all': True}
-            ),
-            expected=dict(
-                result=get_user_result(
-                    user2, u'Test', u'User2', 'show-all',
-                    gidnumber=[u'1000'],
-                    memberof_group=[group1],
-                    manager=[renameduser1],
-                    objectclass=add_oc(objectclasses.user_base,
-                                       u'ipantuserattrs'),
-                    preserved=False,
-                    omit=['mepmanagedentry'],
-                ),
-                value=user2,
-                summary=None,
-            ),
-        ),
-
-        dict(
-            desc='Delete %r' % renameduser1,
-            command=('user_del', [renameduser1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % renameduser1,
-                value=[renameduser1],
-            ),
-        ),
-
-        dict(
-            desc='Retrieve %r and check that manager is gone' % user2,
-            command=(
-                'user_show', [user2], {'all': True}
-            ),
-            expected=dict(
-                result=get_user_result(
-                    user2, u'Test', u'User2', 'show-all',
-                    gidnumber=[u'1000'],
-                    memberof_group=[group1],
-                    objectclass=add_oc(objectclasses.user_base,
-                                       u'ipantuserattrs'),
-                    preserved=False,
-                    omit=['mepmanagedentry'],
-                ),
-                value=user2,
-                summary=None,
-            ),
-        ),
-
-        dict(
-            desc='Reset default user group',
-            command=(
-                'config_mod', [], dict(ipadefaultprimarygroup=u'ipausers'),
-            ),
-            expected=lambda x, output: x is None,
-        ),
-
-        dict(
-            desc='Try to remove the original admin user "%s"' % admin1,
-            command=('user_del', [admin1], {}),
-            expected=errors.LastMemberError(key=admin1, label=u'group',
-                container=admins_group),
-        ),
-
-        dict(
-            desc='Try to disable the original admin user "%s"' % admin1,
-            command=('user_disable', [admin1], {}),
-            expected=errors.LastMemberError(key=admin1, label=u'group',
-                container=admins_group),
-        ),
-
-
-        dict(
-            desc='Create 2nd admin user "%s"' % admin2,
-            command=(
-                'user_add', [admin2], dict(givenname=u'Second', sn=u'Admin')
-            ),
-            expected=dict(
-                value=admin2,
-                summary=u'Added user "%s"' % admin2,
-                result=get_user_result(admin2, u'Second', u'Admin', 'add'),
-            ),
-        ),
-
-        dict(
-            desc='Add "%s" to the admins group "%s"' % (admin2, admins_group),
-            command=('group_add_member', [admins_group], dict(user=admin2)),
-            expected=dict(
-                completed=1,
-                failed=dict(
-                    member=dict(
-                        group=tuple(),
-                        user=tuple(),
-                    ),
-                ),
-                result={
-                        'dn': get_group_dn(admins_group),
-                        'member_user': [admin1, admin2],
-                        'gidnumber': [fuzzy_digits],
-                        'cn': [admins_group],
-                        'description': [u'Account administrators group'],
-                },
-            ),
-        ),
-
-
-        dict(
-            desc=('Retrieve admins group "%s" to verify membership is '
-                  '"%s","%s"' % (admins_group, admin1, admin2)),
-            command=('group_show', [admins_group], {}),
-            expected=dict(
-                value=admins_group,
-                result=dict(
-                    cn=[admins_group],
-                    gidnumber=[fuzzy_digits],
-                    description=[u'Account administrators group'],
-                    dn=get_group_dn(admins_group),
-                    member_user=[admin1, admin2],
-                ),
-                summary=None,
-            ),
-        ),
-
-        dict(
-            desc=('Disable 2nd admin user "%s", admins group "%s" should also '
-                  'contain enabled "%s"' % (admin2, admins_group, admin1)),
-            command=(
-                'user_disable', [admin2], {}
-            ),
-            expected=dict(
-                result=True,
-                value=admin2,
-                summary=u'Disabled user account "%s"' % admin2,
-            ),
-        ),
-
-        dict(
-            desc='Assert 2nd admin user "%s" is disabled' % admin2,
-            command=('user_find', [admin2], {}),
-            expected=dict(
-                result=[lambda d: d['nsaccountlock'] is True],
-                summary=u'1 user matched',
-                count=1,
-                truncated=False,
-            ),
-        ),
-
-        dict(
-            desc='Try to disable the origin admin user "%s"' % admin1,
-            command=('user_disable', [admin1], {}),
-            expected=errors.LastMemberError(key=admin1, label=u'group',
-                container=admins_group),
-        ),
-
-        dict(
-            desc='Try to remove the original admin user "%s"' % admin1,
-            command=('user_del', [admin1], {}),
-            expected=errors.LastMemberError(key=admin1, label=u'group',
-                container=admins_group),
-        ),
-
-        dict(
-            desc='Delete 2nd admin "%s"' % admin2,
-            command=('user_del', [admin2], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % admin2,
-                value=[admin2],
-            ),
-        ),
-
-        dict(
-            desc=('Retrieve admins group "%s" to verify membership is "%s"'
-                  % (admins_group, admin1)),
-            command=('group_show', [admins_group], {}),
-            expected=dict(
-                value=admins_group,
-                result=dict(
-                    cn=[admins_group],
-                    gidnumber=[fuzzy_digits],
-                    description=[u'Account administrators group'],
-                    dn=get_group_dn(admins_group),
-                    member_user=[admin1],
-                ),
-                summary=None,
-            ),
-        ),
-
-        dict(
-            desc='Assert original admin user "%s" is enabled' % admin1,
-            command=('user_find', [admin1], {}),
-            expected=dict(
-                result=[lambda d: d['nsaccountlock'] is False],
-                summary=u'1 user matched',
-                count=1,
-                truncated=False,
-            ),
-        ),
-
-        dict(
-            desc='Try to remove the original admin user "%s"' % admin1,
-            command=('user_del', [admin1], {}),
-            expected=errors.LastMemberError(key=admin1, label=u'group',
-                container=admins_group),
-        ),
-
-        dict(
-            desc='Try to disable the original admin user "%s"' % admin1,
-            command=('user_disable', [admin1], {}),
-            expected=errors.LastMemberError(key=admin1, label=u'group',
-                container=admins_group),
-        ),
-
-        dict(
-            desc='Set default automember group for groups as ipausers',
-            command=(
-                'automember_default_group_set', [], dict(
-                    type=u'group',
-                    automemberdefaultgroup=u'ipausers'
-                    )
-            ),
-            expected=dict(
-                result=dict(
-                    cn=[u'Group'],
-                    automemberdefaultgroup=[DN(('cn', 'ipausers'),
-                                               ('cn', 'groups'),
-                                               ('cn', 'accounts'),
-                                               api.env.basedn)],
-                ),
-                value=u'group',
-                summary=u'Set default (fallback) group for automember "group"',
-            ),
-        ),
-
-        dict(
-            desc='Delete "%s"' % user2,
-            command=('user_del', [user2], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user2,
-                value=[user2],
-            ),
-        ),
-
-        dict(
-            desc='Create %r' % user2,
-            command=(
-                'user_add', [user2], dict(givenname=u'Test', sn=u'User2')
-            ),
-            expected=dict(
-                value=user2,
-                summary=u'Added user "tuser2"',
-                result=get_user_result(user2, u'Test', u'User2', 'add'),
-            ),
-        ),
-
-        dict(
-            desc='Create "%s" with UID 999' % user1,
-            command=(
-                'user_add', [user1], dict(
-                    givenname=u'Test', sn=u'User1', uidnumber=999)
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(user1, u'Test', u'User1', 'add',
-                                       uidnumber=[u'999'],
-                                       gidnumber=[u'999']),
-            ),
-            extra_check = upg_check,
-        ),
-
-        dict(
-            desc='Delete "%s"' % user1,
-            command=('user_del', [user1], {}),
-            expected=dict(
-                result=dict(failed=[]),
-                summary=u'Deleted user "%s"' % user1,
-                value=[user1],
-            ),
-        ),
-
-        dict(
-            desc='Create "%s" with old DNA_MAGIC uid 999' % user1,
-            command=(
-                'user_add', [user1], dict(
-                    givenname=u'Test', sn=u'User1', uidnumber=999,
-                    version=u'2.49')
-            ),
-            expected=dict(
-                value=user1,
-                summary=u'Added user "%s"' % user1,
-                result=get_user_result(
-                    user1, u'Test', u'User1', 'add',
-                    uidnumber=[lambda v: int(v) != 999],
-                    gidnumber=[lambda v: int(v) != 999],
-                ),
-            ),
-            extra_check = upg_check,
-        ),
-
-        dict(
-            desc='Set ipauserauthtype for "%s"' % user1,
-            command=('user_mod', [user1], dict(ipauserauthtype=u'password')),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod',
-                                       ipauserauthtype=[u'password'],
-                ),
-                value=user1,
-                summary='Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Retrieve "%s" to verify ipauserauthtype' % user1,
-            command=('user_show', [user1], {}),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'show',
-                                       ipauserauthtype=[u'password'],
-                ),
-                value=user1,
-                summary=None,
-            ),
-        ),
-
-        dict(
-            desc='Unset ipauserauthtype for "%s"' % user1,
-            command=('user_mod', [user1], dict(ipauserauthtype=None)),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod'),
-                value=user1,
-                summary='Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Query status of "%s"' % user1,
-            command=('user_status', [user1], {}),
-            expected=dict(
-                count=1,
-                result=[
-                    dict(
-                        dn=get_user_dn(user1),
-                        krblastfailedauth=[u'N/A'],
-                        krblastsuccessfulauth=[u'N/A'],
-                        krbloginfailedcount=u'0',
-                        now=isodate_re.match,
-                        server=api.env.host,
-                    ),
-                ],
-                summary=u'Account disabled: False',
-                truncated=False,
-            ),
-        ),
-
-        dict(
-            desc='Test an invalid preferredlanguage "%s"' % invalidlanguage1,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=invalidlanguage1)),
-            expected=errors.ValidationError(name='preferredlanguage',
-                error=(u'must match RFC 2068 - 14.4, e.g., '
-                        '"da, en-gb;q=0.8, en;q=0.7"')),
-        ),
-
-        dict(
-            desc='Test an invalid preferredlanguage "%s"' % invalidlanguage2,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=invalidlanguage2)),
-            expected=errors.ValidationError(name='preferredlanguage',
-                error=(u'must match RFC 2068 - 14.4, e.g., '
-                        '"da, en-gb;q=0.8, en;q=0.7"')),
-        ),
-
-        dict(
-            desc='Test an invalid preferredlanguage "%s"' % invalidlanguage3,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=invalidlanguage3)),
-            expected=errors.ValidationError(name='preferredlanguage',
-                error=(u'must match RFC 2068 - 14.4, e.g., '
-                        '"da, en-gb;q=0.8, en;q=0.7"')),
-        ),
-
-        dict(
-            desc='Test an invalid preferredlanguage "%s"' % invalidlanguage4,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=invalidlanguage4)),
-            expected=errors.ValidationError(name='preferredlanguage',
-                error=(u'must match RFC 2068 - 14.4, e.g., '
-                        '"da, en-gb;q=0.8, en;q=0.7"')),
-        ),
-
-        dict(
-            desc='Test an invalid preferredlanguage "%s"' % invalidlanguage5,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=invalidlanguage5)),
-            expected=errors.ValidationError(name='preferredlanguage',
-                error=(u'must match RFC 2068 - 14.4, e.g., '
-                        '"da, en-gb;q=0.8, en;q=0.7"')),
-        ),
-
-        dict(
-            desc='Set preferredlanguage "%s"' % validlanguage1,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=validlanguage1)),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod',
-                                       preferredlanguage=[validlanguage1],
-                ),
-                value=user1,
-                summary='Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Set preferredlanguage "%s"' % validlanguage2,
-            command=('user_mod', [user1],
-                     dict(preferredlanguage=validlanguage2)),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod',
-                                       preferredlanguage=[validlanguage2],
-                ),
-                value=user1,
-                summary='Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Set principal expiration "%s"' % principal_expiration_string,
-            command=('user_mod', [user1],
-                     dict(krbprincipalexpiration=principal_expiration_string)),
-            expected=dict(
-                result=get_user_result(user1, u'Test', u'User1', 'mod',
-                    krbprincipalexpiration=[principal_expiration_date],
-                ),
-                value=user1,
-                summary='Modified user "%s"' % user1,
-            ),
-        ),
-
-        dict(
-            desc='Set principal expiration "%s"' % invalid_expiration_string,
-            command=('user_mod', [user1],
-                     dict(krbprincipalexpiration=invalid_expiration_string)),
-            expected=errors.ConversionError(name='principal_expiration',
-                error=(u'does not match any of accepted formats: '
-                        '%Y%m%d%H%M%SZ, %Y-%m-%dT%H:%M:%SZ, %Y-%m-%dT%H:%MZ, '
-                        '%Y-%m-%dZ, %Y-%m-%d %H:%M:%SZ, %Y-%m-%d %H:%MZ')
-            ),
-        ),
-
-    ]
-
-
-@pytest.mark.tier1
-class test_denied_bind_with_expired_principal(XMLRPC_test):
-
-    password = u'random'
-
-    @classmethod
-    def setup_class(cls):
-        super(test_denied_bind_with_expired_principal, cls).setup_class()
-
-        cls.connection = ldap.initialize('ldap://{host}'
-                                         .format(host=api.env.host))
-
-    @classmethod
-    def teardown_class(cls):
-        cls.failsafe_del(api.Object.user, user1)
-        super(test_denied_bind_with_expired_principal, cls).teardown_class()
-
-    def test_1_bind_as_test_user(self):
-        self.failsafe_add(
-            api.Object.user,
-            user1,
-            givenname=u'Test',
-            sn=u'User1',
-            userpassword=self.password,
-            krbprincipalexpiration=principal_expiration_string
-        )
-
-        self.connection.simple_bind_s(str(get_user_dn(user1)), self.password)
-
-    def test_2_bind_as_expired_test_user(self):
-        api.Command['user_mod'](
-                user1,
-                krbprincipalexpiration=expired_expiration_string)
-
-        raises(ldap.UNWILLING_TO_PERFORM,
-               self.connection.simple_bind_s,
-               str(get_user_dn(user1)), self.password)
-
-    def test_3_bind_as_renewed_test_user(self):
-        api.Command['user_mod'](
-                user1,
-                krbprincipalexpiration=principal_expiration_string)
-
-        self.connection.simple_bind_s(str(get_user_dn(user1)), self.password)
diff --git a/ipatests/test_xmlrpc/tracker/user_plugin.py b/ipatests/test_xmlrpc/tracker/user_plugin.py
index bcae2ec787b3d3a54e34917f125be006094e07b0..7758c9b38c52de7936968aaec8660aee4f1a876f 100644
--- a/ipatests/test_xmlrpc/tracker/user_plugin.py
+++ b/ipatests/test_xmlrpc/tracker/user_plugin.py
@@ -24,28 +24,31 @@ class UserTracker(Tracker):
         u'krbprincipalexpiration', u'usercertificate', u'dn', u'has_keytab',
         u'has_password', u'street', u'postalcode', u'facsimiletelephonenumber',
         u'carlicense', u'ipasshpubkey', u'sshpubkeyfp', u'nsaccountlock',
-        u'preserved', u'memberof_group', u'l', u'mobile', u'krbextradata',
-        u'krblastpwdchange', u'krbpasswordexpiration', u'pager', u'st'
-        }
+        u'memberof_group', u'l', u'mobile', u'krbextradata',
+        u'krblastpwdchange', u'krbpasswordexpiration', u'pager', u'st',
+        u'manager', u'preserved'}
 
     retrieve_all_keys = retrieve_keys | {
         u'cn', u'ipauniqueid', u'objectclass', u'mepmanagedentry',
-        u'displayname', u'gecos', u'initials', u'krbprincipalname', u'manager'}
+        u'displayname', u'gecos', u'initials', u'krbprincipalname',
+        u'preserved'}
 
     retrieve_preserved_keys = retrieve_keys - {u'memberof_group'}
     retrieve_preserved_all_keys = retrieve_all_keys - {u'memberof_group'}
 
     create_keys = retrieve_all_keys | {
-        u'randompassword', u'mepmanagedentry',
         u'krbextradata', u'krbpasswordexpiration', u'krblastpwdchange',
-        u'krbprincipalkey', u'randompassword', u'userpassword'
-        }
+        u'krbprincipalkey', u'userpassword', u'randompassword'}
+    create_keys = create_keys - {u'nsaccountlock'}
+
     update_keys = retrieve_keys - {u'dn'}
     activate_keys = retrieve_all_keys - {u'has_keytab', u'has_password',
                                          u'nsaccountlock', u'sshpubkeyfp'}
 
     find_keys = retrieve_keys - {u'mepmanagedentry', u'memberof_group'}
-    find_all_keys = retrieve_all_keys - {u'mepmanagedentry', u'memberof_group'}
+    find_all_keys = retrieve_all_keys
+
+    primary_keys = {u'uid', u'dn'}
 
     def __init__(self, name, givenname, sn, **kwargs):
         super(UserTracker, self).__init__(default_version=None)
@@ -103,11 +106,18 @@ class UserTracker(Tracker):
         """ Make function that enables user using user-enable """
         return self.make_command('user_enable', self.uid)
 
+    def make_disable_command(self):
+        """ Make function that disables user using user-disable """
+        return self.make_command('user_disable', self.uid)
+
     def make_stage_command(self):
         """ Make function that restores preserved user by moving it to
         staged container """
         return self.make_command('user_stage', self.uid)
 
+    def make_group_add_member_command(self, *args, **kwargs):
+        return self.make_command('group_add_member', *args, **kwargs)
+
     def track_create(self):
         """ Update expected state for user creation """
         self.attrs = dict(
@@ -132,25 +142,71 @@ class UserTracker(Tracker):
             has_password=False,
             mepmanagedentry=[get_group_dn(self.uid)],
             memberof_group=[u'ipausers'],
+            nsaccountlock=[u'false'],
             )
 
         for key in self.kwargs:
             if key == u'krbprincipalname':
-                self.attrs[key] = [u'%s@%s' % (
-                    (self.kwargs[key].split('@'))[0].lower(),
-                    (self.kwargs[key].split('@'))[1]
+                try:
+                    self.attrs[key] = [u'%s@%s' % (
+                        (self.kwargs[key].split('@'))[0].lower(),
+                        (self.kwargs[key].split('@'))[1]
+                    )]
+                except IndexError as ex:
+                    # we can provide just principal part
+                    self.attrs[key] = [u'%s@%s' % (
+                        (self.kwargs[key].lower(),
+                         self.api.env.realm)
                     )]
             else:
-                self.attrs[key] = [self.kwargs[key]]
+                if not type(self.kwargs[key]) is list:
+                    self.attrs[key] = [self.kwargs[key]]
+                else:
+                    self.attrs[key] = self.kwargs[key]
 
         self.exists = True
 
-    def check_create(self, result):
+    def update(self, updates, expected_updates=None):
+        """Helper function to update this user and check the result
+
+        Overriding Tracker method for setting self.attrs correctly;
+         * most attributes stores its value in list
+         * the rest can be overridden by expected_updates
+         * allow deleting parametrs if update value is None
+        """
+        if expected_updates is None:
+            expected_updates = {}
+
+        self.ensure_exists()
+        command = self.make_update_command(updates)
+        result = command()
+
+        for key, value in updates.items():
+            if value is None or value is '' or value is u'':
+                del self.attrs[key]
+            else:
+                if type(value) is list:
+                    self.attrs[key] = value
+                else:
+                    self.attrs[key] = [value]
+        for key, value in expected_updates.items():
+            if value is None or value is '' or value is u'':
+                del self.attrs[key]
+            else:
+                self.attrs[key] = value
+
+        self.check_update(
+            result,
+            extra_keys=set(updates.keys()) | set(expected_updates.keys())
+        )
+
+    def check_create(self, result, extra_keys=()):
         """ Check 'user-add' command result """
+        expected = self.filter_attrs(self.create_keys | set(extra_keys))
         assert_deepequal(dict(
             value=self.uid,
             summary=u'Added user "%s"' % self.uid,
-            result=self.filter_attrs(self.create_keys),
+            result=self.filter_attrs(expected),
             ), result)
 
     def check_delete(self, result):
@@ -161,9 +217,8 @@ class UserTracker(Tracker):
             result=dict(failed=[]),
             ), result)
 
-    def check_retrieve(self, result, all=False):
+    def check_retrieve(self, result, all=False, raw=False):
         """ Check 'user-show' command result """
-
         if u'preserved' in self.attrs and self.attrs[u'preserved']:
             self.retrieve_all_keys = self.retrieve_preserved_all_keys
             self.retrieve_keys = self.retrieve_preserved_keys
@@ -190,16 +245,26 @@ class UserTracker(Tracker):
             result=expected,
         ), result)
 
-    def check_find(self, result, all=False, raw=False):
+    def check_find(self, result, all=False, pkey_only=False, raw=False):
         """ Check 'user-find' command result """
-        self.attrs[u'nsaccountlock'] = True
-        self.attrs[u'preserved'] = True
-
         if all:
+            if u'preserved' not in self.attrs:
+                self.attrs.update(preserved=False)
             expected = self.filter_attrs(self.find_all_keys)
+        elif pkey_only:
+            expected = self.filter_attrs(self.primary_keys)
         else:
             expected = self.filter_attrs(self.find_keys)
 
+        if all and self.attrs[u'preserved']:
+            del expected[u'mepmanagedentry']
+
+        if u'nsaccountlock' in expected:
+            if expected[u'nsaccountlock'] == [u'true']:
+                expected[u'nsaccountlock'] = True
+            elif expected[u'nsaccountlock'] == [u'false']:
+                expected[u'nsaccountlock'] = False
+
         assert_deepequal(dict(
             count=1,
             truncated=False,
@@ -218,10 +283,32 @@ class UserTracker(Tracker):
 
     def check_update(self, result, extra_keys=()):
         """ Check 'user-mod' command result """
+        expected = self.filter_attrs(self.update_keys | set(extra_keys))
+        if expected[u'nsaccountlock'] == [u'true']:
+            expected[u'nsaccountlock'] = True
+        elif expected[u'nsaccountlock'] == [u'false']:
+            expected[u'nsaccountlock'] = False
+
         assert_deepequal(dict(
             value=self.uid,
             summary=u'Modified user "%s"' % self.uid,
-            result=self.filter_attrs(self.update_keys | set(extra_keys))
+            result=expected
+        ), result)
+
+    def check_enable(self, result):
+        """ Check result of enable user operation """
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Enabled user account "%s"' % self.name,
+            result=True
+        ), result)
+
+    def check_disable(self, result):
+        """ Check result of disable user operation """
+        assert_deepequal(dict(
+            value=self.name,
+            summary=u'Disabled user account "%s"' % self.name,
+            result=True
         ), result)
 
     def create_from_staged(self, stageduser):
@@ -282,6 +369,22 @@ class UserTracker(Tracker):
             result=True
             ), result)
 
+    def enable(self):
+        """ Enable user account if it was disabled """
+        if (self.attrs['nsaccountlock'] is True or
+                self.attrs['nsaccountlock'] == [u'true']):
+            self.attrs.update(nsaccountlock=False)
+            result = self.make_enable_command()()
+            self.check_enable(result)
+
+    def disable(self):
+        """ Disable user account if it was enabled """
+        if (self.attrs['nsaccountlock'] is False or
+                self.attrs['nsaccountlock'] == [u'false']):
+            self.attrs.update(nsaccountlock=True)
+            result = self.make_disable_command()()
+            self.check_disable(result)
+
     def track_delete(self, preserve=False):
         """Update expected state for host deletion"""
         if preserve:
@@ -343,3 +446,27 @@ class UserTracker(Tracker):
         request.addfinalizer(finish)
 
         return self
+
+    def make_admin(self, admin_group=u'admins'):
+        """ Add user to the administrator's group """
+        result = self.run_command('group_show', admin_group)
+        admin_group_content = result[u'result'][u'member_user']
+        admin_group_expected = list(admin_group_content) + [self.name]
+
+        command = self.make_group_add_member_command(
+            admin_group, **dict(user=self.name)
+        )
+        result = command()
+        assert_deepequal(dict(
+            completed=1,
+            failed=dict(
+                member=dict(group=tuple(), user=tuple())
+            ),
+            result={
+                'dn': get_group_dn(admin_group),
+                'member_user': admin_group_expected,
+                'gidnumber': [fuzzy_digits],
+                'cn': [admin_group],
+                'description': [u'Account administrators group'],
+            },
+        ), result)
-- 
2.5.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to