<?php
require_once __DIR__ . '/Net_LDAP2_TestBase.php';
require_once 'Net/LDAP2.php';
require_once 'Net/LDAP2/Entry.php';

/**
 * Test class for Net_LDAP2.
 * Generated by PHPUnit_Util_Skeleton on 2007-10-09 at 10:32:36.
 */
class Net_LDAP2Test extends Net_LDAP2_TestBase {
    /**
    * Stores the LDAP configuration
    */
    var $ldapcfg = false;

    /**
     * Load ldap config and adjust appropriately
     *
     * @access protected
     */
    protected function setUp() {
        $this->ldapcfg = $this->getTestConfig();
    }

    /**
     * Tears down the fixture, for example, close a network connection.
     * This method is called after a test is executed.
     *
     * @access protected
     */
    protected function tearDown() {
    }

    /**
     * This checks if a valid LDAP testconfig is present and loads it.
     *
     * If so, it is loaded and returned as array. If not, false is returned.
     *
     * @return false|array
     */
    public function getTestConfig() {
        $config = false;
        $file = dirname(__FILE__).'/ldapconfig.ini';
        if (file_exists($file) && is_readable($file)) {
            $config = parse_ini_file($file, true);
        } else {
            return false;
        }
        // validate ini
        $v_error = $file.' is probably invalid. Did you quoted values correctly?';
        $this->assertTrue(array_key_exists('global', $config), $v_error);
        $this->assertTrue(array_key_exists('test', $config), $v_error);
        $this->assertEquals(7, count($config['global']), $v_error);
        $this->assertEquals(7, count($config['test']), $v_error);

        // reformat things a bit, for convinience
        $config['global']['server_binddn'] =
            $config['global']['server_binddn'].','.$config['global']['server_base_dn'];
        $config['test']['existing_attrmv'] = explode('|', $config['test']['existing_attrmv']);
        return $config;
    }

    /**
    * Establishes a working connection
    *
    * @return Net_LDAP2
    */
    public function &connect() {
        // Check extension
        if (true !== Net_LDAP2::checkLDAPExtension()) {
            $this->markTestSkipped('PHP LDAP extension not found or not loadable. Skipped Test.');
        }

        // Simple working connect and privilegued bind
        $lcfg = array(
                'host'   => $this->ldapcfg['global']['server_address'],
                'port'   => $this->ldapcfg['global']['server_port'],
                'basedn' => $this->ldapcfg['global']['server_base_dn'],
                'binddn' => $this->ldapcfg['global']['server_binddn'],
                'bindpw' => $this->ldapcfg['global']['server_bindpw'],
                'filter' => '(ou=*)',
            );
        $ldap = Net_LDAP2::connect($lcfg);
        $this->assertInstanceOf('Net_LDAP2', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!');
        return $ldap;
    }

/* ---------- TESTS ---------- */

    /**
     * testCheckLDAPExtension().
     *
     * @todo can we unload modules at runtime??
     */
    public function testCheckLDAPExtension() {
        if (extension_loaded('ldap')) {
            // If extension is already loaded, then we must get true.
            $this->assertTrue(Net_LDAP2::checkLDAPExtension());
        } else {
            // If not, we should be able to load it - but may fail
            $this->assertThat(Net_LDAP2::checkLDAPExtension(),
                $this->logicalOr($this->isInstanceOf('Net_LDAP2_Error'), $this->equalTo(true)));
        }
    }

    /**
     * Tests if getVersion() works correctly
     */
    public function testGetVersion() {
        $this->assertTrue(defined('NET_LDAP2_VERSION'));
        $this->assertEquals(NET_LDAP2_VERSION, Net_LDAP2::getVersion());
    }

    /**
     * Tests if the server can connect and bind correctly
     */
    public function testConnectAndPrivileguedBind() {
        // Check extension
        if (true !== Net_LDAP2::checkLDAPExtension()) {
            $this->markTestSkipped('PHP LDAP extension not found or not loadable. Skipped Test.');
        }

        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            // This connect is supposed to fail
            $lcfg = array(
                    'host' => '0.0.0.1',
                );
            $ldap = Net_LDAP2::connect($lcfg);
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap, 'Connect succeeded but was supposed to fail!');

            // Failing with multiple hosts
            $lcfg = array(
                    'host' => array('0.0.0.1', '0.0.0.2'),
                );
            $ldap = Net_LDAP2::connect($lcfg);
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap, 'Connect succeeded but was supposed to fail!');

            // Simple working connect and privilegued bind
            $ldap = $this->connect();

            // Working connect and privilegued bind with first host down
            $lcfg = array(
                    'host'   => array(
                            '0.0.0.1',
                            $this->ldapcfg['global']['server_address']
                        ),
                    'port'   => $this->ldapcfg['global']['server_port'],
                    'binddn' => $this->ldapcfg['global']['server_binddn'],
                    'bindpw' => $this->ldapcfg['global']['server_bindpw'],
                );
            $ldap = Net_LDAP2::connect($lcfg);
            $this->assertInstanceOf('Net_LDAP2', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!');
        }
    }

    /**
     * Tests if the server can connect and bind anonymously, if supported (->cfg and ldap mode)
     */
    public function testConnectAndAnonymousBind() {
        // Check extension
        if (true !== Net_LDAP2::checkLDAPExtension()) {
            $this->markTestSkipped('PHP LDAP extension not found or not loadable. Skipped Test.');
        }

        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } elseif ($this->ldapcfg['global']['server_cap_anonymous'] == true) {
            // Simple working connect and anonymous bind
            $lcfg = array(
                    'host'   => $this->ldapcfg['global']['server_address'],
                    'port'   => $this->ldapcfg['global']['server_port'],
                );
            $ldap = Net_LDAP2::connect($lcfg);
            $this->assertInstanceOf('Net_LDAP2', $ldap, 'Connect failed but was supposed to work. Check address and if server supports anonymous bind. If those are correct, file a bug!');
        } else {
            $this->markTestSkipped('Server does not support anonymous bind (see ldapconfig.ini). Skipping test.');
        }
    }

    /**
     * testStartTLS() if server supports it
     */
    public function testStartTLS() {
        // Check extension
        if (true !== Net_LDAP2::checkLDAPExtension()) {
            $this->markTestSkipped('PHP LDAP extension not found or not loadable. Skipped Test.');
        }

        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } elseif ($this->ldapcfg['global']['server_cap_tls'] == true) {
            // Simple working connect and privilegued bind
            $lcfg = array(
                    'host'     => $this->ldapcfg['global']['server_address'],
                    'port'     => $this->ldapcfg['global']['server_port'],
                    'binddn'   => $this->ldapcfg['global']['server_binddn'].','.$this->ldapcfg['global']['server_binddn'],
                    'bindpw'   => $this->ldapcfg['global']['server_bindpw'],
                    'starttls' => true
                );
            $ldap = Net_LDAP2::connect();
            $this->assertInstanceOf('Net_LDAP2', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!');
        } else {
             $this->markTestSkipped('Server does not support TLS (see ldapconfig.ini). Skipping test.');
        }
    }

    /**
     * Test if adding and deleting a fresh entry works
     */
    public function testAdd() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();

            // Adding a fresh entry
            $cn          = 'Net-LDAP-TestEntry';
            $dn          = 'cn='.$cn.','.$this->ldapcfg['global']['server_base_dn'];
            $fresh_entry = Net_LDAP2_Entry::createFresh($dn,
                array(
                    'objectClass' => array('top','person'),
                    'cn'          => $cn,
                    'sn'          => 'TestEntry'
                )
            );
            $this->assertInstanceOf('Net_LDAP2_Entry', $fresh_entry);
            $this->assertTrue($ldap->add($fresh_entry));

            // Deleting this Entry
            $this->assertTrue($ldap->delete($fresh_entry), 'Deletion of entry failed!');
        }
    }

    /**
     * testDelete().
     *
     * Basic deletion is tested in testAdd(), so here we just test if
     * advanced deletion tasks work properly.
     */
    public function testDelete() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            // some parameter checks
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->delete(1234));
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->delete($ldap));

            // in order to test subtree deletion, we need some little tree
            // which we need to establish first
            $base   = $this->ldapcfg['global']['server_base_dn'];
            $testdn = 'ou=Net_LDAP2_Test_subdelete,'.$base;

            $ou = Net_LDAP2_Entry::createFresh($testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_subdelete'
                ));
            $ou_1 = Net_LDAP2_Entry::createFresh('ou=test1,'.$testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'test1'
                ));
            $ou_1_l1 = Net_LDAP2_Entry::createFresh('l=subtest,ou=test1,'.$testdn,
                array(
                    'objectClass' => array('top','locality'),
                    'l' => 'test1'
                ));
            $ou_2 = Net_LDAP2_Entry::createFresh('ou=test2,'.$testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'test2'
                ));
            $ou_3 = Net_LDAP2_Entry::createFresh('ou=test3,'.$testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'test3'
                ));
            $this->assertTrue($ldap->add($ou));
            $this->assertTrue($ldap->add($ou_1));
            $this->assertTrue($ldap->add($ou_1_l1));
            $this->assertTrue($ldap->add($ou_2));
            $this->assertTrue($ldap->add($ou_3));
            $this->assertTrue($ldap->dnExists($ou->dn()));
            $this->assertTrue($ldap->dnExists($ou_1->dn()));
            $this->assertTrue($ldap->dnExists($ou_1_l1->dn()));
            $this->assertTrue($ldap->dnExists($ou_2->dn()));
            $this->assertTrue($ldap->dnExists($ou_3->dn()));
            // Tree established now. We can run some tests now :D

            // Try to delete some non existent entry inside that subtree (fails)
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->delete(
                'cn=not_existent,ou=test1,'.$testdn));

            // Try to delete main test ou without recursive set (fails too)
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->delete($testdn));

            // Retry with subtree delete, this should work
            $this->assertTrue($ldap->delete($testdn, true));

            // The DN is not allowed to exist anymore
            $this->assertFalse($ldap->dnExists($testdn));
        }
    }

    /**
     * testModify().
     */
    public function testModify() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            // We need a test entry:
            $local_entry = Net_LDAP2_Entry::createFresh(
                'ou=Net_LDAP2_Test_modify,'.$this->ldapcfg['global']['server_base_dn'],
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou'              => 'Net_LDAP2_Test_modify',
                    'street'          => 'Beniroad',
                    'telephoneNumber' => array('1234', '5678'),
                    'postalcode'      => '12345',
                    'postalAddress'   => 'someAddress',
                    'facsimileTelephoneNumber' => array('123','456')
                ));
            $this->assertTrue($ldap->add($local_entry));
            $this->assertTrue($ldap->dnExists($local_entry->dn()));

            // Prepare some changes
            $changes = array(
                'add' => array(
                                'businessCategory' => array('foocat', 'barcat'),
                                'description' => 'testval'
                ),
                'delete' => array('postalAddress'),
                'replace' => array('telephoneNumber' => array('345', '567')),
                'changes' => array(
                                'replace' => array('street' => 'Highway to Hell'),
                                'add' => array('l' => 'someLocality'),
                                'delete' => array(
                                    'postalcode',
                                    'facsimileTelephoneNumber' => array('123'))
                )
            );

            // Perform those changes
            $this->assertTrue($ldap->modify($local_entry, $changes));

            // verify correct attribute changes
            $actual_entry = $ldap->getEntry($local_entry->dn(), array(
                'objectClass', 'ou','postalAddress', 'street', 'telephoneNumber', 'postalcode',
                'facsimileTelephoneNumber', 'l', 'businessCategory', 'description'));
            $this->assertInstanceOf('Net_LDAP2_Entry', $actual_entry);
            $expected_attributes = array(
                'objectClass' => array('top', 'organizationalUnit'),
                'ou' => 'Net_LDAP2_Test_modify',
                'street' => 'Highway to Hell',
                'l'      => 'someLocality',
                'telephoneNumber' => array('345', '567'),
                'businessCategory' => array('foocat', 'barcat'),
                'description' => 'testval',
                'facsimileTelephoneNumber' => '456'
            );

            $local_attributes  = $local_entry->getValues();
            $actual_attributes = $actual_entry->getValues();

            // to enable easy check, we need to sort the
            // values of the remaining multival attrs as
            // well as the attribute names
            ksort($expected_attributes);
            ksort($local_attributes);
            ksort($actual_attributes);
            sort($expected_attributes['businessCategory']);
            sort($local_attributes['businessCategory']);
            sort($actual_attributes['businessCategory']);

            // cleanup directory
            $this->assertTrue($ldap->delete($actual_entry),
                'Cleanup of test entry failed. Please remove manually: '.$local_entry->dn());

            // The attributes must match the expected values.
            // Both, the entry inside the directory and our
            // apps local copy must reflect the same values
            $this->assertEquals($expected_attributes, $actual_attributes, 'The directory entries attributes are not OK!');
            $this->assertEquals($expected_attributes, $local_attributes, 'The local entries attributes are not OK!');
        }
    }

    /**
     * testSearch().
     */
    public function testSearch() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();

            // some testdata, so we can test sizelimit
            $base = $this->ldapcfg['global']['server_base_dn'];
            $ou1  = Net_LDAP2_Entry::createFresh('ou=Net_LDAP2_Test_search1,'.$base,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_search1'
                ));
            $ou1_1  = Net_LDAP2_Entry::createFresh('ou=Net_LDAP2_Test_search1_1,'.$ou1->dn(),
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_search2'
                ));
            $ou2  = Net_LDAP2_Entry::createFresh('ou=Net_LDAP2_Test_search2,'.$base,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_search2'
                ));
            $this->assertTrue($ldap->add($ou1));
            $this->assertTrue($ldap->dnExists($ou1->dn()));
            $this->assertTrue($ldap->add($ou1_1));
            $this->assertTrue($ldap->dnExists($ou1_1->dn()));
            $this->assertTrue($ldap->add($ou2));
            $this->assertTrue($ldap->dnExists($ou2->dn()));


            // Search for testfilter, should at least return our two test entries
            $res = $ldap->search(null, '(ou=Net_LDAP2*)',
                array('attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertThat($res->count(), $this->greaterThanOrEqual(2));

            // Same, but with Net_LDAP2_Filter object
            $filtero = Net_LDAP2_Filter::create('ou', 'begins', 'Net_LDAP2');
            $this->assertInstanceOf('Net_LDAP2_Filter', $filtero);
            $res = $ldap->search(null, $filtero,
                array('attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertThat($res->count(), $this->greaterThanOrEqual(2));

            // Search using default filter for base-onelevel scope
            // should at least return our two test entries
            $res = $ldap->search(null, null,
                array('scope' => 'one', 'attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertThat($res->count(), $this->greaterThanOrEqual(2));

            // Base-search using custom base (string)
            // should only return the test entry $ou1 and not the entry below it.
            $res = $ldap->search($ou1->dn(), null,
                array('scope' => 'base', 'attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertEquals(1, $res->count());

            // Search using custom base, this time using an entry object
            // This tests if passing an entry object as base works
            // should only return the test entry $ou1
            $res = $ldap->search($ou1, '(ou=*)',
                array('scope' => 'base', 'attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertEquals(1, $res->count());

            // Search using default filter for base-onelevel scope with sizelimit
            // should of course return more than one entry,
            // but not more than sizelimit
            $res = $ldap->search(null, null,
                array('scope' => 'one', 'sizelimit' => 1, 'attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertEquals(1, $res->count());
            $this->assertTrue($res->sizeLimitExceeded()); // sizelimit should be exceeded now

            // Bad filter
            $res = $ldap->search(null, 'somebadfilter',
                array('attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Error', $res);

            // Bad base
            $res = $ldap->search('badbase', null,
                array('attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Error', $res);

            // Passing Error object as base and as filter object
            $error = new Net_LDAP2_Error('Testerror');
            $res = $ldap->search($error, null, // error base
                array('attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Error', $res);
            $res = $ldap->search(null, $error, // error filter
                array('attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Error', $res);

            // Nullresult
            $res = $ldap->search(null, '(cn=nevermatching_filter)',
                array('scope' => 'base', 'attributes' => '1.1')
            );
            $this->assertInstanceOf('Net_LDAP2_Search', $res);
            $this->assertEquals(0, $res->count());


            // cleanup
            $this->assertTrue($ldap->delete($ou1_1), 'Cleanup failed, please delete manually');
            $this->assertTrue($ldap->delete($ou1),   'Cleanup failed, please delete manually');
            $this->assertTrue($ldap->delete($ou2),   'Cleanup failed, please delete manually');
        }
    }

    /**
     * @todo Implement testSetOption().
     */
    public function testSetOption() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $this->markTestIncomplete("This test has not been implemented yet.");
        }
    }

    /**
     * @todo Implement testGetOption().
     */
    public function testGetOption() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $this->markTestIncomplete("This test has not been implemented yet.");
        }
    }

    /**
     * @todo Implement testGetLDAPVersion().
     */
    public function testGetLDAPVersion() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $this->markTestIncomplete("This test has not been implemented yet.");
        }
    }

    /**
     * @todo Implement testSetLDAPVersion().
     */
    public function testSetLDAPVersion() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $this->markTestIncomplete("This test has not been implemented yet.");
        }
    }

    /**
     * testDnExists().
     */
    public function testDnExists() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            $dn   = $this->ldapcfg['test']['existing_entry'].','.$this->ldapcfg['global']['server_base_dn'];

            // Testing existing and not existing DN; neither should produce an error
            $this->assertTrue($ldap->dnExists($dn));
            $this->assertFalse($ldap->dnExists('cn=not_existent,'.$dn));

            // Passing an Entry object (should work)
            // It should return false, because don't add the test entry
            $base = $this->ldapcfg['global']['server_base_dn'];
            $ou1  = Net_LDAP2_Entry::createFresh('ou=Net_LDAP2_Test_search1,'.$base,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_search1'
                ));
            $this->assertFalse($ldap->dnExists($ou1));

            // Passing an float instead of a string
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->dnExists(1.234));

            // Pasing an error object
            $error = new Net_LDAP2_Error('Testerror');
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->dnExists($error));
        }
    }

    /**
     * testGetEntry().
     */
    public function testGetEntry() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            $dn   = $this->ldapcfg['test']['existing_entry'].','.$this->ldapcfg['global']['server_base_dn'];

            // existing DN
            $this->assertInstanceOf('Net_LDAP2_Entry', $ldap->getEntry($dn), "$dn was supposed to be found. Please check your ldapconfig.ini!");

            // Not existing DN
            $this->assertInstanceOf('Net_LDAP2_Error',
                $ldap->getEntry('cn=notexistent,'.$this->ldapcfg['global']['server_base_dn']));
        }
    }

    /**
     * testMove().
     */
    public function testMove() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();

            // For Moving tests, we need some little tree again
            $base   = $this->ldapcfg['global']['server_base_dn'];
            $testdn = 'ou=Net_LDAP2_Test_moves,'.$base;

            $ou = Net_LDAP2_Entry::createFresh($testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_moves'
                ));
            $ou_1 = Net_LDAP2_Entry::createFresh('ou=source,'.$testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'source'
                ));
            $ou_1_l1 = Net_LDAP2_Entry::createFresh('l=moveitem,ou=source,'.$testdn,
                array(
                    'objectClass' => array('top','locality'),
                    'l' => 'moveitem',
                    'description' => 'movetest'
                ));
            $ou_2 = Net_LDAP2_Entry::createFresh('ou=target,'.$testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'target'
                ));
            $ou_3 = Net_LDAP2_Entry::createFresh('ou=target_otherdir,'.$testdn,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'target_otherdir'
                ));
            $this->assertTrue($ldap->add($ou));
            $this->assertTrue($ldap->add($ou_1));
            $this->assertTrue($ldap->add($ou_1_l1));
            $this->assertTrue($ldap->add($ou_2));
            $this->assertTrue($ldap->add($ou_3));
            $this->assertTrue($ldap->dnExists($ou->dn()));
            $this->assertTrue($ldap->dnExists($ou_1->dn()));
            $this->assertTrue($ldap->dnExists($ou_1_l1->dn()));
            $this->assertTrue($ldap->dnExists($ou_2->dn()));
            $this->assertTrue($ldap->dnExists($ou_3->dn()));
            // Tree established

            // Local rename
            $olddn = $ou_1_l1->currentDN();
            $this->assertTrue($ldap->move($ou_1_l1,
                str_replace('moveitem', 'move_item', $ou_1_l1->dn())));
            $this->assertTrue($ldap->dnExists($ou_1_l1->dn()));
            $this->assertFalse($ldap->dnExists($olddn));

            // Local move
            $olddn = $ou_1_l1->currentDN();
            $this->assertTrue($ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn()));
            $this->assertTrue($ldap->dnExists($ou_1_l1->dn()));
            $this->assertFalse($ldap->dnExists($olddn));

            // Local move backward, with rename
            // Here we use the DN of the object, to test DN conversion.
            // Note that this will outdate the object since it does not
            // has knowledge about the move.
            $olddn = $ou_1_l1->currentDN();
            $newdn = 'l=moveditem,'.$ou_2->dn();
            $this->assertTrue($ldap->move($olddn, $newdn));
            $this->assertTrue($ldap->dnExists($newdn));
            $this->assertFalse($ldap->dnExists($olddn));
            $ou_1_l1 = $ldap->getEntry($newdn); // Refetch since the objects DN was outdated

            // Fake-cross directory move using two separate
            // links to the same directory.
            // This other directory is represented by ou=target_otherdir
            $ldap2 = $this->connect();
            $olddn = $ou_1_l1->currentDN();
            $this->assertTrue($ldap->move($ou_1_l1, 'l=movedcrossdir,'.$ou_3->dn(), $ldap2));
            $this->assertFalse($ldap->dnExists($olddn));
            $this->assertTrue($ldap2->dnExists($ou_1_l1->dn()));

            // Try to move over an existing entry
             $this->assertInstanceOf('Net_LDAP2_Error', $ldap->move($ou_2, $ou_3->dn(), $ldap2));

            // Try cross directory move without providing an valid entry but a DN
            $this->assertInstanceOf('Net_LDAP2_Error',
                $ldap->move($ou_1_l1->dn(), 'l=movedcrossdir2,'.$ou_2->dn(), $ldap2));

            // Try passing an invalid entry object
            $this->assertInstanceOf('Net_LDAP2_Error',
                $ldap->move($ldap, 'l=move_item,'.$ou_2->dn()));

            // Try passing an invalid ldap object
            $this->assertInstanceOf('Net_LDAP2_Error',
                $ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn(), $ou_1));

            // cleanup test tree
            $this->assertTrue($ldap->delete($testdn, true), "Could not delete $testdn, please cleanup manually");

        }
    }

    /**
     * testCopy().
     */
    public function testCopy() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();

            // some testdata...
            $base = $this->ldapcfg['global']['server_base_dn'];
            $ou1  = Net_LDAP2_Entry::createFresh('ou=Net_LDAP2_Test_pool,'.$base,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_copy'
                ));
            $ou2  = Net_LDAP2_Entry::createFresh('ou=Net_LDAP2_Test_tgt,'.$base,
                array(
                    'objectClass' => array('top','organizationalUnit'),
                    'ou' => 'Net_LDAP2_Test_copy'
                ));
            $this->assertTrue($ldap->add($ou1));
            $this->assertTrue($ldap->dnExists($ou1->dn()));
            $this->assertTrue($ldap->add($ou2));
            $this->assertTrue($ldap->dnExists($ou2->dn()));
            $entry  = Net_LDAP2_Entry::createFresh('l=cptest,'.$ou1->dn(),
                array(
                    'objectClass' => array('top','locality'),
                    'l' => 'cptest'
                ));
            $this->assertTrue($ldap->add($entry));
            $this->assertTrue($ldap->dnExists($entry->dn()));

            // copy over the entry to another tree with rename
            $entrycp = $ldap->copy($entry, 'l=test_copied,'.$ou2->dn());
            $this->assertInstanceOf('Net_LDAP2_Entry', $entrycp);
            $this->assertNotEquals($entry->dn(), $entrycp->dn());
            $this->assertTrue($ldap->dnExists($entrycp->dn()));

            // copy same again (fails, entry exists)
            $entrycp_f = $ldap->copy($entry, 'l=test_copied,'.$ou2->dn());
            $this->assertInstanceOf('Net_LDAP2_Error', $entrycp_f);

            // use only DNs to copy (fails)
            $entrycp = $ldap->copy($entry->dn(), 'l=test_copied2,'.$ou2->dn());

            //cleanup
            $this->assertTrue($ldap->delete($ou1, true));
            $this->assertTrue($ldap->delete($ou2, true));
        }
    }

    /**
     * testIsError().
     */
    public function testIsError() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $error = PEAR::raiseError('TestError');
            $this->assertTrue(Net_LDAP2::isError($error));
            $this->assertFalse(Net_LDAP2::isError('noerror'));
        }
    }

    /**
     * checks retrival of RootDSE object
     */
    public function testRootDse() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            $this->assertInstanceOf('Net_LDAP2_RootDSE', $ldap->rootDSE());
        }
    }

    /**
     * Checks retrival of schema through LDAP object
     */
    public function testSchema() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            $this->assertInstanceOf('Net_LDAP2_Schema', $ldap->schema());
        }
    }

    /**
     * testUtf8Encode()
     */
    public function testUtf8Encode() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap    = $this->connect();
            $utf8    = array('cn' => 'this needs utf8: '.base64_decode('w7bDpMO8')); //öäü
            $no_utf8 = array('cn' => 'this needs no utf8');

            $this->assertNotEquals($utf8, $ldap->utf8Encode($utf8));
            $this->assertEquals($no_utf8, $ldap->utf8Encode($no_utf8));

            // wrong parameter
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->utf8Encode('foobar'));
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->utf8Encode(array('foobar')));
        }
    }

    /**
     * testUtf8Decode().
     */
    public function testUtf8Decode() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap  = $this->connect();
            $entry = $ldap->getEntry($this->ldapcfg['test']['existing_entry'].','.$this->ldapcfg['global']['server_base_dn'],
                array($this->ldapcfg['test']['utf8_attr'], $this->ldapcfg['test']['noutf8_attr']));
            $this->assertInstanceOf('Net_LDAP2_Entry', $entry, 'Unable to fetch test entry, check ldapconfig.ini');
            $raw_utf8 = array($this->ldapcfg['test']['utf8_attr'] => $entry->getValue($this->ldapcfg['test']['utf8_attr'], 'single'));
            $this->assertTrue(is_string($raw_utf8[$this->ldapcfg['test']['utf8_attr']]));
            $no_utf8  = array($this->ldapcfg['test']['noutf8_attr'] => $entry->getValue($this->ldapcfg['test']['noutf8_attr'], 'single'));
            $this->assertTrue(is_string($no_utf8[$this->ldapcfg['test']['noutf8_attr']]));

            $this->assertNotEquals($raw_utf8, $ldap->utf8Decode($raw_utf8));
            $this->assertEquals($no_utf8, $ldap->utf8Decode($no_utf8));

            // wrong parameter
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->utf8Decode('foobar'));
            $this->assertInstanceOf('Net_LDAP2_Error', $ldap->utf8Decode(array('foobar')));
        }
    }

    /**
     * testGetLink().
     */
    public function testGetLink() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();
            $this->assertTrue(is_resource($ldap->getLink()));
        }
    }

    /**
    * Test for bug #18202: "Adding attributes to a Fresh Entry saving and laterly updating fails"
    */
    public function testEntryAddIsNotPersistent() {
        if (!$this->ldapcfg) {
            $this->markTestSkipped('No ldapconfig.ini found. Skipping test!');
        } else {
            $ldap = $this->connect();

            // setup test entry
            $cn   = 'Net_LDAP2_Test_bug_18202';
            $dn   = 'cn='.$cn.','.$this->ldapcfg['global']['server_base_dn'];
            $data = array(
                'objectClass' => array('top', 'inetOrgPerson'),
                'givenName'   => 'bug 18202',
                'sn'          => 'testentry',
                'cn'          => $cn
            );

            // Test case
            $entry = Net_LDAP2_Entry::createFresh($dn, $data);
            $this->assertInstanceOf('Net_LDAP2_Entry', $entry);
            $this->assertTrue( $entry->add(array('uid' => 'Fu Bar')) );
            $this->assertTrue( $ldap->add($entry) );
            $this->assertTrue( $entry->replace(array('uid' => 'Foo Bar')) );
            $this->assertTrue( $result = $entry->update() );
    
            // cleanup
            $this->assertTrue($ldap->delete($entry),
                    'Cleanup of test entry failed. Please remove manually: '.$entry->dn());
        }
    }

}
?>
