Add test suite.
authorJan Schneider <jan@horde.org>
Fri, 17 Sep 2010 11:18:01 +0000 (13:18 +0200)
committerJan Schneider <jan@horde.org>
Fri, 17 Sep 2010 12:29:18 +0000 (14:29 +0200)
20 files changed:
.gitignore
framework/Ldap/test/Horde/Ldap/AllTests.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/EntryTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/ExampleTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/FilterTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/LdapTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/LdifTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/SearchTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/TestBase.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/UtilTest.php [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/conf.php.dist [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/changes.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/malformed_encoding.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/malformed_syntax.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/malformed_wrapping.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/sorted_w40.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/sorted_w50.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w30.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50.ldif [new file with mode: 0644]
framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50_WIN.ldif [new file with mode: 0644]

index 2aa932f..baeedfa 100644 (file)
@@ -36,6 +36,7 @@ framework/*/test/*/*/*/*.out
 framework/Alarm/test/Horde/Alarm/conf.php
 framework/Db/test/Horde/Db/Adapter/conf.php
 framework/Kolab_Storage/test/Horde/Kolab/Storage/conf.php
+framework/Ldap/test/Horde/Ldap/conf.php
 
 # Dynamically generated content that may live in the repo directories
 /lib/
diff --git a/framework/Ldap/test/Horde/Ldap/AllTests.php b/framework/Ldap/test/Horde/Ldap/AllTests.php
new file mode 100644 (file)
index 0000000..c8805e0
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Horde_Ldap test suite.
+ *
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+/**
+ * Define the main method
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Ldap_AllTests::main');
+}
+
+/**
+ * Prepare the test setup.
+ */
+require_once 'Horde/Test/AllTests.php';
+
+/**
+ * @package    Horde_Ldap
+ * @subpackage UnitTests
+ */
+class Horde_Ldap_AllTests extends Horde_Test_AllTests
+{
+}
+
+Horde_Ldap_AllTests::init('Horde_Ldap', __FILE__);
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Ldap_AllTests::main') {
+    Horde_Ldap_AllTests::main();
+}
diff --git a/framework/Ldap/test/Horde/Ldap/EntryTest.php b/framework/Ldap/test/Horde/Ldap/EntryTest.php
new file mode 100644 (file)
index 0000000..44bc73c
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_EntryTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @expectedException PHPUnit_Framework_Error
+     */
+    public function testCreateFreshFail()
+    {
+        $entry = Horde_Ldap_Entry::createFresh('cn=test', 'I should be an array');
+    }
+
+    public function testCreateFreshSuccess()
+    {
+        $entry = Horde_Ldap_Entry::createFresh('cn=test',
+                                               array('attr1' => 'single',
+                                                     'attr2' => array('mv1', 'mv2')));
+        $this->assertType('Horde_Ldap_Entry', $entry);
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/ExampleTest.php b/framework/Ldap/test/Horde/Ldap/ExampleTest.php
new file mode 100644 (file)
index 0000000..4d48761
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_ExampleTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+    }
+
+    public function testSomething()
+    {
+    }
+
+    public function tearDown()
+    {
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/FilterTest.php b/framework/Ldap/test/Horde/Ldap/FilterTest.php
new file mode 100644 (file)
index 0000000..1d341b7
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_FilterTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Test correct parsing of filter strings through parse().
+     */
+    public function testParse()
+    {
+        try {
+            Horde_Ldap_Filter::parse('some_damaged_filter_str');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::parse('(invalid=filter)(because=~no-surrounding brackets)');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::parse('((invalid=filter)(because=log_op is missing))');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::parse('(invalid-because-becauseinvalidoperator)');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::parse('(&(filterpart>=ok)(part2=~ok)(filterpart3_notok---becauseinvalidoperator))');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        $parsed1 = Horde_Ldap_Filter::parse('(&(cn=foo)(ou=bar))');
+        $this->assertType('Horde_Ldap_Filter', $parsed1);
+        $this->assertEquals('(&(cn=foo)(ou=bar))', (string)$parsed1);
+
+        // In an earlier version there was a problem with the splitting of the
+        // filter parts if the next part was also an combined filter.
+        $parsed2_str = '(&(&(objectClass=posixgroup)(objectClass=foogroup))(uniquemember=uid=eeggs,ou=people,o=foo))';
+        $parsed2 = Horde_Ldap_Filter::parse($parsed2_str);
+        $this->assertType('Horde_Ldap_Filter', $parsed2);
+        $this->assertEquals($parsed2_str, (string)$parsed2);
+
+        // In an earlier version there was a problem parsing certain
+        // not-combined filter strings.
+        $parsed3_str = '(!(jpegPhoto=*))';
+        $parsed3 = Horde_Ldap_Filter::parse($parsed3_str);
+        $this->assertType('Horde_Ldap_Filter', $parsed3);
+        $this->assertEquals($parsed3_str, (string)$parsed3);
+
+        $parsed3_complex_str = '(&(someAttr=someValue)(!(jpegPhoto=*)))';
+        $parsed3_complex = Horde_Ldap_Filter::parse($parsed3_complex_str);
+        $this->assertType('Horde_Ldap_Filter', $parsed3_complex);
+        $this->assertEquals($parsed3_complex_str, (string)$parsed3_complex);
+    }
+
+    /**
+     * This tests the basic create() method of creating filters.
+     */
+    public function testCreate()
+    {
+        // Test values and an array containing the filter creating methods and
+        // an regex to test the resulting filter.
+        $testattr = 'testattr';
+        $testval  = 'testval';
+        $combinations = array(
+            'equals'         => "/\($testattr=$testval\)/",
+            'begins'         => "/\($testattr=$testval\*\)/",
+            'ends'           => "/\($testattr=\*$testval\)/",
+            'contains'       => "/\($testattr=\*$testval\*\)/",
+            'greater'        => "/\($testattr>$testval\)/",
+            'less'           => "/\($testattr<$testval\)/",
+            'greaterorequal' => "/\($testattr>=$testval\)/",
+            'lessorequal'    => "/\($testattr<=$testval\)/",
+            'approx'         => "/\($testattr~=$testval\)/",
+            'any'            => "/\($testattr=\*\)/"
+        );
+
+        foreach ($combinations as $match => $regex) {
+            // Escaping is tested in util class.
+            $filter = Horde_Ldap_Filter::create($testattr, $match, $testval, false);
+            $this->assertType('Horde_Ldap_Filter', $filter);
+            $this->assertRegExp($regex, (string)$filter, "Filter generation failed for MatchType: $match");
+        }
+
+        // Test creating failure.
+        try {
+            Horde_Ldap_Filter::create($testattr, 'test_undefined_matchingrule', $testval);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+
+    /**
+     * Tests if __toString() works.
+     */
+    public function testToString()
+    {
+        $filter = Horde_Ldap_Filter::create('foo', 'equals', 'bar');
+        $this->assertType('Horde_Ldap_Filter', $filter);
+        $this->assertEquals('(foo=bar)', (string)$filter);
+    }
+
+    /**
+     * This tests the basic combination of filters.
+     */
+    public function testCombine()
+    {
+        // Setup.
+        $filter0 = Horde_Ldap_Filter::create('foo', 'equals', 'bar');
+        $this->assertType('Horde_Ldap_Filter', $filter0);
+
+        $filter1 = Horde_Ldap_Filter::create('bar', 'equals', 'foo');
+        $this->assertType('Horde_Ldap_Filter', $filter1);
+
+        $filter2 = Horde_Ldap_Filter::create('you', 'equals', 'me');
+        $this->assertType('Horde_Ldap_Filter', $filter2);
+
+        $filter3 = Horde_Ldap_Filter::parse('(perlinterface=used)');
+        $this->assertType('Horde_Ldap_Filter', $filter3);
+
+        // Negation test.
+        $filter_not1 = Horde_Ldap_Filter::combine('not', $filter0);
+        $this->assertType('Horde_Ldap_Filter', $filter_not1, 'Negation failed for literal NOT');
+        $this->assertEquals('(!(foo=bar))', (string)$filter_not1);
+
+        $filter_not2 = Horde_Ldap_Filter::combine('!', $filter0);
+        $this->assertType('Horde_Ldap_Filter', $filter_not2, 'Negation failed for logical NOT');
+        $this->assertEquals('(!(foo=bar))', (string)$filter_not2);
+
+        $filter_not3 = Horde_Ldap_Filter::combine('!', (string)$filter0);
+        $this->assertType('Horde_Ldap_Filter', $filter_not3, 'Negation failed for logical NOT');
+        $this->assertEquals('(!' . $filter0 . ')', (string)$filter_not3);
+
+        // Combination test: OR
+        $filter_comb_or1 = Horde_Ldap_Filter::combine('or', array($filter1, $filter2));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_or1, 'Combination failed for literal OR');
+        $this->assertEquals('(|(bar=foo)(you=me))', (string)$filter_comb_or1);
+
+        $filter_comb_or2 = Horde_Ldap_Filter::combine('|', array($filter1, $filter2));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_or2, 'combination failed for logical OR');
+        $this->assertEquals('(|(bar=foo)(you=me))', (string)$filter_comb_or2);
+
+        // Combination test: AND
+        $filter_comb_and1 = Horde_Ldap_Filter::combine('and', array($filter1, $filter2));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_and1, 'Combination failed for literal AND');
+        $this->assertEquals('(&(bar=foo)(you=me))', (string)$filter_comb_and1);
+
+        $filter_comb_and2 = Horde_Ldap_Filter::combine('&', array($filter1, $filter2));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_and2, 'combination failed for logical AND');
+        $this->assertEquals('(&(bar=foo)(you=me))', (string)$filter_comb_and2);
+
+        // Combination test: using filter created with perl interface.
+        $filter_comb_perl1 = Horde_Ldap_Filter::combine('and', array($filter1, $filter3));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_perl1, 'Combination failed for literal AND');
+        $this->assertEquals('(&(bar=foo)(perlinterface=used))', (string)$filter_comb_perl1);
+
+        $filter_comb_perl2 = Horde_Ldap_Filter::combine('&', array($filter1, $filter3));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_perl2, 'combination failed for logical AND');
+        $this->assertEquals('(&(bar=foo)(perlinterface=used))', (string)$filter_comb_perl2);
+
+        // Combination test: using filter_str instead of object
+        $filter_comb_fstr1 = Horde_Ldap_Filter::combine('and', array($filter1, '(filter_str=foo)'));
+        $this->assertType('Horde_Ldap_Filter', $filter_comb_fstr1, 'Combination failed for literal AND using filter_str');
+        $this->assertEquals('(&(bar=foo)(filter_str=foo))', (string)$filter_comb_fstr1);
+
+        // Combination test: deep combination
+        $filter_comp_deep = Horde_Ldap_Filter::combine('and',array($filter2, $filter_not1, $filter_comb_or1, $filter_comb_perl1));
+        $this->assertType('Horde_Ldap_Filter', $filter_comp_deep, 'Deep combination failed!');
+        $this->assertEquals('(&(you=me)(!(foo=bar))(|(bar=foo)(you=me))(&(bar=foo)(perlinterface=used)))', (string)$filter_comp_deep);
+
+        // Test failure in combination
+        try {
+            Horde_Ldap_Filter::create('foo', 'test_undefined_matchingrule', 'bar');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('not', 'damaged_filter_str');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('not', array($filter0, $filter1));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('not', null);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('and', $filter_not1);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('and', array($filter_not1));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('and', $filter_not1);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('or', array($filter_not1));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('some_unknown_method', array($filter_not1));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('and', array($filter_not1, 'some_invalid_filterstring'));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        try {
+            Horde_Ldap_Filter::combine('and', array($filter_not1, null));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/LdapTest.php b/framework/Ldap/test/Horde/Ldap/LdapTest.php
new file mode 100644 (file)
index 0000000..e15a052
--- /dev/null
@@ -0,0 +1,599 @@
+<?php
+
+require_once dirname(__FILE__) . '/TestBase.php';
+
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+class Horde_Ldap_LdapTest extends Horde_Ldap_TestBase
+{
+    public static function tearDownAfterClass()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+        $clean = array('cn=Horde_Ldap_TestEntry,',
+                       'ou=Horde_Ldap_Test_subdelete,',
+                       'ou=Horde_Ldap_Test_modify,',
+                       'ou=Horde_Ldap_Test_search1,',
+                       'ou=Horde_Ldap_Test_search2,',
+                       'ou=Horde_Ldap_Test_exists,',
+                       'ou=Horde_Ldap_Test_getEntry,',
+                       'ou=Horde_Ldap_Test_move,',
+                       'ou=Horde_Ldap_Test_pool,',
+                       'ou=Horde_Ldap_Test_tgt,');
+        foreach ($clean as $dn) {
+            try {
+                $ldap->delete($dn . self::$ldapcfg['server']['basedn'], true);
+            } catch (Exception $e) {}
+        }
+    }
+
+    /**
+     * Tests if the server can connect and bind correctly.
+     */
+    public function testConnectAndPrivilegedBind()
+    {
+        // This connect is supposed to fail.
+        $lcfg = array('host' => 'nonexistant.ldap.horde.org');
+        try {
+            $ldap = new Horde_Ldap($lcfg);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Failing with multiple hosts.
+        $lcfg = array('host' => array('nonexistant1.ldap.horde.org',
+                                      'nonexistant2.ldap.horde.org'));
+        try {
+            $ldap = new Horde_Ldap($lcfg);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Simple working connect and privilegued bind.
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // Working connect and privileged bind with first host down.
+        $lcfg = array('host'   => array('nonexistant.ldap.horde.org',
+                                        self::$ldapcfg['server']['host']),
+                      'port'   => self::$ldapcfg['server']['port'],
+                      'binddn' => self::$ldapcfg['server']['binddn'],
+                      'bindpw' => self::$ldapcfg['server']['bindpw']);
+        $ldap = new Horde_Ldap($lcfg);
+    }
+
+    /**
+     * Tests if the server can connect and bind anonymously, if supported.
+     */
+    public function testConnectAndAnonymousBind()
+    {
+        if (!self::$ldapcfg['capability']['anonymous']) {
+            $this->markTestSkipped('Server does not support anonymous bind');
+        }
+
+        // Simple working connect and anonymous bind.
+        $lcfg = array('host'   => self::$ldapcfg['server']['host'],
+                      'port'   => self::$ldapcfg['server']['port']);
+        $ldap = new Horde_Ldap($lcfg);
+    }
+
+    /**
+     * Tests startTLS() if server supports it.
+     */
+    public function testStartTLS()
+    {
+        if (!self::$ldapcfg['capability']['tls']) {
+            $this->markTestSkipped('Server does not support TLS');
+        }
+
+        // Simple working connect and privileged bind.
+        $lcfg = array('starttls' => true) + self::$ldapcfg['server'];
+        $ldap = new Horde_Ldap($lcfg);
+    }
+
+    /**
+     * Test if adding and deleting a fresh entry works.
+     */
+    public function testAdd()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // Adding a fresh entry.
+        $cn = 'Horde_Ldap_TestEntry';
+        $dn = 'cn=' . $cn . ',' . self::$ldapcfg['server']['basedn'];
+        $fresh_entry = Horde_Ldap_Entry::createFresh(
+            $dn,
+            array('objectClass' => array('top', 'person'),
+                  'cn'          => $cn,
+                  'sn'          => 'TestEntry'));
+        $this->assertType('Horde_Ldap_Entry', $fresh_entry);
+        $ldap->add($fresh_entry);
+
+        // Deleting this entry.
+        $ldap->delete($fresh_entry);
+    }
+
+    /**
+     * Basic deletion is tested in testAdd(), so here we just test if
+     * advanced deletion tasks work properly.
+     */
+    public function testDelete()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // Some parameter checks.
+        try {
+            $ldap->delete(1234);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+        try {
+            $ldap->delete($ldap);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // In order to test subtree deletion, we need some little tree
+        // which we need to establish first.
+        $base   = self::$ldapcfg['server']['basedn'];
+        $testdn = 'ou=Horde_Ldap_Test_subdelete,' . $base;
+
+        $ou = Horde_Ldap_Entry::createFresh(
+            $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_subdelete'));
+        $ou_1 = Horde_Ldap_Entry::createFresh(
+            'ou=test1,' . $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'test1'));
+        $ou_1_l1 = Horde_Ldap_Entry::createFresh(
+            'l=subtest,ou=test1,' . $testdn,
+            array('objectClass' => array('top', 'locality'),
+                  'l' => 'test1'));
+        $ou_2 = Horde_Ldap_Entry::createFresh(
+            'ou=test2,' . $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'test2'));
+        $ou_3 = Horde_Ldap_Entry::createFresh(
+            'ou=test3,' . $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'test3'));
+        $ldap->add($ou);
+        $ldap->add($ou_1);
+        $ldap->add($ou_1_l1);
+        $ldap->add($ou_2);
+        $ldap->add($ou_3);
+        $this->assertTrue($ldap->exists($ou->dn()));
+        $this->assertTrue($ldap->exists($ou_1->dn()));
+        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+        $this->assertTrue($ldap->exists($ou_2->dn()));
+        $this->assertTrue($ldap->exists($ou_3->dn()));
+        // Tree established now. We can run some tests now :D
+
+        // Try to delete some non existent entry inside that subtree (fails).
+        try {
+            $ldap->delete('cn=not_existent,ou=test1,' . $testdn);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {
+            $this->assertEquals('LDAP_NO_SUCH_OBJECT', Horde_Ldap::errorName($e->getCode()));
+        }
+
+        // Try to delete main test ou without recursive set (fails too).
+        try {
+            $ldap->delete($testdn);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {
+            $this->assertEquals('LDAP_NOT_ALLOWED_ON_NONLEAF', Horde_Ldap::errorName($e->getCode()));
+        }
+
+        // Retry with subtree delete, this should work.
+        $ldap->delete($testdn, true);
+
+        // The DN is not allowed to exist anymore.
+        $this->assertFalse($ldap->exists($testdn));
+    }
+
+    /**
+     * Test modify().
+     */
+    public function testModify()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // We need a test entry.
+        $local_entry = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_modify,' . self::$ldapcfg['server']['basedn'],
+            array('objectClass'     => array('top','organizationalUnit'),
+                  'ou'              => 'Horde_Ldap_Test_modify',
+                  'street'          => 'Beniroad',
+                  'telephoneNumber' => array('1234', '5678'),
+                  'postalcode'      => '12345',
+                  'postalAddress'   => 'someAddress',
+                  'facsimileTelephoneNumber' => array('123', '456')));
+        $ldap->add($local_entry);
+        $this->assertTrue($ldap->exists($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.
+        $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->assertType('Horde_Ldap_Entry', $actual_entry);
+        $expected_attributes = array(
+            'objectClass'              => array('top', 'organizationalUnit'),
+            'ou'                       => 'Horde_Ldap_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 attributes 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']);
+
+        // The attributes must match the expected values.  Both, the entry
+        // inside the directory and our 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!');
+    }
+
+    /**
+     * Test search().
+     */
+    public function testSearch()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // Some testdata, so we can test sizelimit.
+        $base = self::$ldapcfg['server']['basedn'];
+        $ou1 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_search1,' . $base,
+            array('objectClass' => array('top','organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_search1'));
+        $ou1_1 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_search1_1,' . $ou1->dn(),
+            array('objectClass' => array('top','organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_search2'));
+        $ou2 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_search2,' . $base,
+            array('objectClass' => array('top','organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_search2'));
+        $ldap->add($ou1);
+        $this->assertTrue($ldap->exists($ou1->dn()));
+        $ldap->add($ou1_1);
+        $this->assertTrue($ldap->exists($ou1_1->dn()));
+        $ldap->add($ou2);
+        $this->assertTrue($ldap->exists($ou2->dn()));
+
+
+        // Search for test filter, should at least return our two test entries.
+        $res = $ldap->search(null, '(ou=Horde_Ldap*)',
+                             array('attributes' => '1.1'));
+        $this->assertType('Horde_Ldap_Search', $res);
+        $this->assertThat($res->count(), $this->greaterThanOrEqual(2));
+
+        // Same, but with Horde_Ldap_Filter object.
+        $filtero = Horde_Ldap_Filter::create('ou', 'begins', 'Horde_Ldap');
+        $this->assertType('Horde_Ldap_Filter', $filtero);
+        $res = $ldap->search(null, $filtero,
+                             array('attributes' => '1.1'));
+        $this->assertType('Horde_Ldap_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->assertType('Horde_Ldap_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->assertType('Horde_Ldap_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->assertType('Horde_Ldap_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->assertType('Horde_Ldap_Search', $res);
+        $this->assertEquals(1, $res->count());
+        // Sizelimit should be exceeded now.
+        $this->assertTrue($res->sizeLimitExceeded());
+
+        // Bad filter.
+        try {
+            $res = $ldap->search(null, 'somebadfilter',
+                                 array('attributes' => '1.1'));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Bad base.
+        try {
+            $res = $ldap->search('badbase', null,
+                                 array('attributes' => '1.1'));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Nullresult.
+        $res = $ldap->search(null, '(cn=nevermatching_filter)',
+                             array('scope' => 'base', 'attributes' => '1.1'));
+        $this->assertType('Horde_Ldap_Search', $res);
+        $this->assertEquals(0, $res->count());
+    }
+
+    /**
+     * Test exists().
+     */
+    public function testExists()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        $dn = 'ou=Horde_Ldap_Test_exists,' . self::$ldapcfg['server']['basedn'];
+
+        // Testing not existing DN.
+        $this->assertFalse($ldap->exists($dn));
+
+        // Passing an entry object (should work). It should return false,
+        // because we didn't add the test entry yet.
+        $ou1 = Horde_Ldap_Entry::createFresh(
+            $dn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_search1'));
+        $this->assertFalse($ldap->exists($ou1));
+
+        // Testing not existing DN.
+        $ldap->add($ou1);
+        $this->assertTrue($ldap->exists($dn));
+
+        // Passing an float instead of a string.
+        try {
+            $ldap->exists(1.234);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+
+    /**
+     * Test getEntry().
+     */
+    public function testGetEntry()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+        $dn = 'ou=Horde_Ldap_Test_getEntry,' . self::$ldapcfg['server']['basedn'];
+        $entry = Horde_Ldap_Entry::createFresh(
+            $dn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_getEntry'));
+        $ldap->add($entry);
+
+        // Existing DN.
+        $this->assertType('Horde_Ldap_Entry', $ldap->getEntry($dn));
+
+        // Not existing DN.
+        try {
+            $ldap->getEntry('cn=notexistent,' . self::$ldapcfg['server']['basedn']);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+
+    /**
+     * Test move().
+     */
+    public function testMove()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // For Moving tests, we need some little tree again.
+        $base   = self::$ldapcfg['server']['basedn'];
+        $testdn = 'ou=Horde_Ldap_Test_move,' . $base;
+
+        $ou = Horde_Ldap_Entry::createFresh(
+            $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_move'));
+        $ou_1 = Horde_Ldap_Entry::createFresh(
+            'ou=source,' . $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'source'));
+        $ou_1_l1 = Horde_Ldap_Entry::createFresh(
+            'l=moveitem,ou=source,' . $testdn,
+            array('objectClass' => array('top','locality'),
+                  'l' => 'moveitem',
+                  'description' => 'movetest'));
+        $ou_2 = Horde_Ldap_Entry::createFresh(
+            'ou=target,' . $testdn,
+            array('objectClass' => array('top', 'organizationalUnit'),
+                  'ou' => 'target'));
+        $ou_3 = Horde_Ldap_Entry::createFresh(
+            'ou=target_otherdir,' . $testdn,
+            array('objectClass' => array('top','organizationalUnit'),
+                  'ou' => 'target_otherdir'));
+        $ldap->add($ou);
+        $ldap->add($ou_1);
+        $ldap->add($ou_1_l1);
+        $ldap->add($ou_2);
+        $ldap->add($ou_3);
+        $this->assertTrue($ldap->exists($ou->dn()));
+        $this->assertTrue($ldap->exists($ou_1->dn()));
+        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+        $this->assertTrue($ldap->exists($ou_2->dn()));
+        $this->assertTrue($ldap->exists($ou_3->dn()));
+        // Tree established.
+
+        // Local rename.
+        $olddn = $ou_1_l1->currentDN();
+        $ldap->move($ou_1_l1, str_replace('moveitem', 'move_item', $ou_1_l1->dn()));
+        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+        $this->assertFalse($ldap->exists($olddn));
+
+        // Local move.
+        $olddn = $ou_1_l1->currentDN();
+        $ldap->move($ou_1_l1, 'l=move_item,' . $ou_2->dn());
+        $this->assertTrue($ldap->exists($ou_1_l1->dn()));
+        $this->assertFalse($ldap->exists($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();
+        $ldap->move($olddn, $newdn);
+        $this->assertTrue($ldap->exists($newdn));
+        $this->assertFalse($ldap->exists($olddn));
+        // Refetch since the object's DN was outdated.
+        $ou_1_l1 = $ldap->getEntry($newdn);
+
+        // Fake-cross directory move using two separate links to the same
+        // directory. This other directory is represented by
+        // ou=target_otherdir.
+        $ldap2 = new Horde_Ldap(self::$ldapcfg['server']);
+        $olddn = $ou_1_l1->currentDN();
+        $ldap->move($ou_1_l1, 'l=movedcrossdir,' . $ou_3->dn(), $ldap2);
+        $this->assertFalse($ldap->exists($olddn));
+        $this->assertTrue($ldap2->exists($ou_1_l1->dn()));
+
+        // Try to move over an existing entry.
+        try {
+            $ldap->move($ou_2, $ou_3->dn(), $ldap2);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Try cross directory move without providing an valid entry but a DN.
+        try {
+            $ldap->move($ou_1_l1->dn(), 'l=movedcrossdir2,'.$ou_2->dn(), $ldap2);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Try passing an invalid entry object.
+        try {
+            $ldap->move($ldap, 'l=move_item,'.$ou_2->dn());
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Try passing an invalid LDAP object.
+        try {
+            $ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn(), $ou_1);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+
+    /**
+     * Test copy().
+     */
+    public function testCopy()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // Some testdata.
+        $base = self::$ldapcfg['server']['basedn'];
+        $ou1 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_pool,' . $base,
+            array('objectClass' => array('top','organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_copy'));
+        $ou2 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_tgt,' . $base,
+            array('objectClass' => array('top','organizationalUnit'),
+                  'ou' => 'Horde_Ldap_Test_copy'));
+        $ldap->add($ou1);
+        $this->assertTrue($ldap->exists($ou1->dn()));
+        $ldap->add($ou2);
+        $this->assertTrue($ldap->exists($ou2->dn()));
+
+        $entry = Horde_Ldap_Entry::createFresh(
+            'l=cptest,' . $ou1->dn(),
+            array('objectClass' => array('top','locality'),
+                  'l' => 'cptest'));
+        $ldap->add($entry);
+        $ldap->exists($entry->dn());
+
+        // Copy over the entry to another tree with rename.
+        $entrycp = $ldap->copy($entry, 'l=test_copied,' . $ou2->dn());
+        $this->assertType('Horde_Ldap_Entry', $entrycp);
+        $this->assertNotEquals($entry->dn(), $entrycp->dn());
+        $this->assertTrue($ldap->exists($entrycp->dn()));
+
+        // Copy same again (fails, entry exists).
+        try {
+            $entrycp_f = $ldap->copy($entry, 'l=test_copied,' . $ou2->dn());
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Use only DNs to copy (fails).
+        try {
+            $entrycp = $ldap->copy($entry->dn(), 'l=test_copied2,' . $ou2->dn());
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+
+    /**
+     * Tests retrieval of root DSE object.
+     */
+    public function testRootDSE()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+        $this->assertType('Horde_Ldap_RootDse', $ldap->rootDSE());
+    }
+
+    /**
+     * Tests retrieval of schema through LDAP object.
+     */
+    public function testSchema()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+        $this->assertType('Horde_Ldap_Schema', $ldap->schema());
+    }
+
+    /**
+     * Test getLink().
+     */
+    public function testGetLink()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+        $this->assertTrue(is_resource($ldap->getLink()));
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/LdifTest.php b/framework/Ldap/test/Horde/Ldap/LdifTest.php
new file mode 100644 (file)
index 0000000..eb1f340
--- /dev/null
@@ -0,0 +1,632 @@
+<?php
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+class Horde_Ldap_LdifTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Default configuration for tests.
+     *
+     * The config is bound to the ldif test file
+     * tests/fixtures/unsorted_w50.ldif, so don't change or tests will fail.
+     *
+     * @var array
+     */
+    protected $_defaultConfig = array(
+        'encode'  => 'base64',
+        'wrap'    => 50,
+        'change'  => 0,
+        'sort'    => 0,
+        'version' => 1
+    );
+
+    /**
+     * Test entries data.
+     *
+     * Please do not just modify these values, they are closely related to the
+     * LDIF test data.
+     *
+     * @var array
+     */
+    protected $_testdata = array(
+        'cn=test1,ou=example,dc=cno' => array(
+            'cn'          => 'test1',
+            'attr3'       => array('foo', 'bar'),
+            'attr1'       => 12345,
+            'attr4'       => 'brrrzztt',
+            'objectclass' => 'oc1',
+            'attr2'       => array('1234', 'baz')),
+
+        'cn=test blabla,ou=example,dc=cno' => array(
+            'cn'          => 'test blabla',
+            'attr3'       => array('foo', 'bar'),
+            'attr1'       => 12345,
+            'attr4'       => 'blablaöäü',
+            'objectclass' => 'oc2',
+            'attr2'       => array('1234', 'baz'),
+            'verylong'    => 'fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738'),
+
+        'cn=test öäü,ou=example,dc=cno' => array(
+            'cn'          => 'test öäü',
+            'attr3'       => array('foo', 'bar'),
+            'attr1'       => 12345,
+            'attr4'       => 'blablaöäü',
+            'objectclass' => 'oc3',
+            'attr2'       => array('1234', 'baz'),
+            'attr5'       => 'endspace ',
+            'attr6'       => ':badinitchar'),
+
+        ':cn=endspace,dc=cno ' => array(
+            'cn'          => 'endspace')
+    );
+
+    /**
+     * Test file written to.
+     *
+     * @var string
+     */
+    protected $_outfile = 'test.out.ldif';
+
+    /**
+     * Test entries.
+     *
+     * They will be created in setUp()
+     *
+     * @var array
+     */
+    protected $_testentries;
+
+    /**
+     * Opens an outfile and ensures correct permissions.
+     */
+    public function setUp()
+    {
+        // Initialize test entries.
+        $this->_testentries = array();
+        foreach ($this->_testdata as $dn => $attrs) {
+            $entry = Horde_Ldap_Entry::createFresh($dn, $attrs);
+            $this->assertType('Horde_Ldap_Entry', $entry);
+            array_push($this->_testentries, $entry);
+        }
+
+        // Create outfile if not exists and enforce proper access rights.
+        if (!file_exists($this->_outfile)) {
+            if (!touch($this->_outfile)) {
+                $this->markTestSkipped('Unable to create ' . $this->_outfile);
+            }
+        }
+        if (!chmod($this->_outfile, 0644)) {
+            $this->markTestSkipped('Unable to chmod(0644) ' . $this->_outfile);
+        }
+    }
+
+    /**
+     * Removes the outfile.
+     */
+    public function tearDown() {
+        @unlink($this->_outfile);
+    }
+
+    /**
+     * Construction tests.
+     *
+     * Construct LDIF object and see if we can get a handle.
+     */
+    public function testConstruction()
+    {
+        $supported_modes = array('r', 'w', 'a');
+        $plus            = array('', '+');
+
+        // Test all open modes, all of them should return a correct handle.
+        foreach ($supported_modes as $mode) {
+            foreach ($plus as $p) {
+                $ldif = new Horde_Ldap_Ldif($this->_outfile, $mode, $this->_defaultConfig);
+                $this->assertTrue(is_resource($ldif->handle()));
+            }
+        }
+
+        // Test illegal option passing.
+        try {
+            $ldif = new Horde_Ldap_Ldif($this->_outfile, $mode, array('somebad' => 'option'));
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Test passing custom handle.
+        $handle = fopen($this->_outfile, 'r');
+        $ldif = new Horde_Ldap_Ldif($handle, $mode, $this->_defaultConfig);
+        $this->assertTrue(is_resource($ldif->handle()));
+
+        // Reading test with invalid file mode.
+        try {
+            $ldif = new Horde_Ldap_Ldif($this->_outfile, 'y', $this->_defaultConfig);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Reading test with non-existent file.
+        try {
+            $ldif = new Horde_Ldap_Ldif('some/nonexistent/file_for_net_ldap_ldif', 'r', $this->_defaultConfig);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Writing to non-existent file.
+        $ldif = new Horde_Ldap_Ldif('testfile_for_net_ldap_ldif', 'w', $this->_defaultConfig);
+        $this->assertTrue(is_resource($ldif->handle()));
+        @unlink('testfile_for_net_ldap_ldif');
+
+        // Writing to non-existent path.
+        try {
+            $ldif = new Horde_Ldap_Ldif('some/nonexistent/file_for_net_ldap_ldif', 'w', $this->_defaultConfig);
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+
+        // Writing to existing file but without permission. chmod() should
+        // succeed since we test that in setUp().
+        if (chmod($this->_outfile, 0444)) {
+            try {
+                $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $this->_defaultConfig);
+                $this->fail('Horde_Ldap_Exception expected.');
+            } catch (Horde_Ldap_Exception $e) {}
+        } else {
+            $this->markTestSkipped('Could not chmod ' . $this->_outfile . ', write test without permission skipped');
+        }
+    }
+
+    /**
+     * Tests if entries from an LDIF file are correctly constructed.
+     */
+    public function testReadEntry()
+    {
+        /* UNIX line endings. */
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+        $this->assertTrue(is_resource($ldif->handle()));
+
+        $entries = array();
+        do {
+            $entry = $ldif->readEntry();
+            $this->assertType('Horde_Ldap_Entry', $entry);
+            array_push($entries, $entry);
+        } while (!$ldif->eof());
+
+        $this->_compareEntries($this->_testentries, $entries);
+
+        /* Windows line endings. */
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50_WIN.ldif', 'r', $this->_defaultConfig);
+        $this->assertTrue(is_resource($ldif->handle()));
+
+        $entries = array();
+        do {
+            $entry = $ldif->readEntry();
+            $this->assertType('Horde_Ldap_Entry', $entry);
+            array_push($entries, $entry);
+        } while (!$ldif->eof());
+
+        $this->_compareEntries($this->_testentries, $entries);
+    }
+
+    /**
+     * Tests if entries are correctly written.
+     *
+     * This tests converting entries to LDIF lines, wrapping, encoding, etc.
+     */
+    public function testWriteEntry()
+    {
+        $testconf = $this->_defaultConfig;
+
+        /* Test wrapped operation. */
+        $testconf['wrap'] = 50;
+        $testconf['sort'] = 0;
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w50.ldif'));
+
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        // Write LDIF.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($this->_testentries);
+        $ldif->done();
+
+        // Compare files.
+        $this->assertEquals($expected, file($this->_outfile));
+
+        $testconf['wrap'] = 30;
+        $testconf['sort'] = 0;
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w30.ldif'));
+
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        // Write LDIF.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($this->_testentries);
+        $ldif->done();
+
+        // Compare files.
+        $this->assertEquals($expected, file($this->_outfile));
+
+        /* Test unwrapped operation. */
+        $testconf['wrap'] = 40;
+        $testconf['sort'] = 1;
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/sorted_w40.ldif'));
+
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        // Write LDIF.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($this->_testentries);
+        $ldif->done();
+
+        // Compare files.
+        $this->assertEquals($expected, file($this->_outfile));
+
+        $testconf['wrap'] = 50;
+        $testconf['sort'] = 1;
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/sorted_w50.ldif'));
+
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        // Write LDIF.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($this->_testentries);
+        $ldif->done();
+
+        // Compare files.
+        $this->assertEquals($expected, file($this->_outfile));
+
+        /* Test raw option. */
+        $testconf['wrap'] = 50;
+        $testconf['sort'] = 1;
+        $testconf['raw']  = '/attr6/';
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/sorted_w50.ldif'));
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        // Write LDIF.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($this->_testentries);
+        $ldif->done();
+
+        // Compare files, with expected attributes adjusted.
+        $this->assertEquals($expected, file($this->_outfile));
+
+        /* Test writing with non entry as parameter. */
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w');
+        $this->assertTrue(is_resource($ldif->handle()));
+        try {
+            $ldif->writeEntry('malformed_parameter');
+            $this->fail('Horde_Ldap_Exception expected.');
+        } catch (Horde_Ldap_Exception $e) {}
+    }
+
+    /**
+     * Test version writing.
+     */
+    public function testWriteVersion()
+    {
+        $testconf = $this->_defaultConfig;
+
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w50.ldif'));
+
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        // Strip 1 additional line (the "version: 1" line that should not be
+        // written now) and adjust test config.
+        array_shift($expected);
+        unset($testconf['version']);
+
+        // Write LDIF.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($this->_testentries);
+        $ldif->done();
+
+        // Compare files.
+        $this->assertEquals($expected, file($this->_outfile));
+    }
+
+    /**
+     * Round trip test: Read LDIF, parse to entries, write that to LDIF and
+     * compare both files.
+     */
+    public function testReadWriteRead()
+    {
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+        $this->assertTrue(is_resource($ldif->handle()));
+
+        // Read LDIF.
+        $entries = array();
+        do {
+            $entry = $ldif->readEntry();
+            $this->assertType('Horde_Ldap_Entry', $entry);
+            array_push($entries, $entry);
+        } while (!$ldif->eof());
+        $ldif->done();
+
+         // Write LDIF.
+         $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $this->_defaultConfig);
+         $this->assertTrue(is_resource($ldif->handle()));
+         $ldif->writeEntry($entries);
+         $ldif->done();
+
+         // Compare files.
+         $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/unsorted_w50.ldif'));
+
+         // Strip 4 starting lines because of comments in the file header.
+         array_splice($expected, 0, 4);
+
+         $this->assertEquals($expected, file($this->_outfile));
+    }
+
+    /**
+     * Tests if entry changes are correctly written.
+     */
+    public function testWriteEntryChanges()
+    {
+        $testentries = $this->_testentries;
+        $testentries[] = Horde_Ldap_Entry::createFresh('cn=foo,ou=example,dc=cno', array('cn' => 'foo'));
+        $testentries[] = Horde_Ldap_Entry::createFresh('cn=footest,ou=example,dc=cno', array('cn' => 'foo'));
+
+        $testconf = $this->_defaultConfig;
+        $testconf['change'] = 1;
+
+        /* No changes should produce empty file. */
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($testentries);
+        $ldif->done();
+        $this->assertEquals(array(), file($this->_outfile));
+
+        /* Changes test. */
+        // Prepare some changes.
+        $testentries[0]->delete('attr1');
+        $testentries[0]->delete(array('attr2' => 'baz'));
+        $testentries[0]->delete(array('attr4', 'attr3' => 'bar'));
+
+        // Prepare some replaces and adds.
+        $testentries[2]->replace(array('attr1' => 'newvaluefor1'));
+        $testentries[2]->replace(array('attr2' => array('newvalue1for2', 'newvalue2for2')));
+        $testentries[2]->replace(array('attr3' => ''));
+        $testentries[2]->replace(array('newattr' => 'foo'));
+
+        // Delete whole entry.
+        $testentries[3]->delete();
+
+        // Rename and move.
+        $testentries[4]->dn('cn=Bar,ou=example,dc=cno');
+        $testentries[5]->dn('cn=foobartest,ou=newexample,dc=cno');
+
+        // Carry out write.
+        $ldif = new Horde_Ldap_Ldif($this->_outfile, 'w', $testconf);
+        $this->assertTrue(is_resource($ldif->handle()));
+        $ldif->writeEntry($testentries);
+        $ldif->done();
+
+        // Compare results.
+        $expected = array_map(array($this, '_lineend'), file(dirname(__FILE__).'/fixtures/changes.ldif'));
+
+        // Strip 4 starting lines because of comments in the file header.
+        array_splice($expected, 0, 4);
+
+        $this->assertEquals($expected, file($this->_outfile));
+    }
+
+    /**
+     * Tests if syntax errors are detected.
+     *
+     * The used LDIF files have several damaged entries but always one
+     * correct too, to test if Horde_Ldap_Ldif is continue reading as it should
+     * each entry must have 2 correct attributes.
+     */
+    public function testSyntaxerrors()
+    {
+        $this->markTestSkipped('We don\'t continue on syntax errors.');
+        // Test malformed encoding
+        // I think we can ignore this test, because if the LDIF is not encoded properly, we
+        // might be able to successfully fetch the entries data. However, it is possible
+        // that it will be corrupted, but thats not our fault then.
+        // If we should catch that error, we must adjust Horde_Ldap_Ldif::next_lines().
+        /*
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_encoding.ldif', 'r', $this->_defaultConfig);
+        $this->assertFalse((boolean)$ldif->error());
+        $entries = array();
+        do {
+            $entry = $ldif->readEntry();
+            if ($entry) {
+                // the correct attributes need to be parsed
+                $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2));
+                $entries[] = $entry;
+            }
+        } while (!$ldif->eof());
+        $this->assertTrue((boolean)$ldif->error());
+        $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+        $this->assertThat(count($entries), $this->equalTo(1));
+        */
+
+        // Test malformed syntax
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_syntax.ldif', 'r', $this->_defaultConfig);
+        $this->assertFalse((boolean)$ldif->error());
+        $entries = array();
+        do {
+            $entry = $ldif->readEntry();
+            if ($entry) {
+                // the correct attributes need to be parsed
+                $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2));
+                $entries[] = $entry;
+            }
+        } while (!$ldif->eof());
+        $this->assertTrue((boolean)$ldif->error());
+        $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+        $this->assertThat(count($entries), $this->equalTo(2));
+
+        // test bad wrapping
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_wrapping.ldif', 'r', $this->_defaultConfig);
+        $this->assertFalse((boolean)$ldif->error());
+        $entries = array();
+        do {
+           $entry = $ldif->readEntry();
+            if ($entry) {
+                // the correct attributes need to be parsed
+                $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2));
+                $entries[] = $entry;
+            }
+        } while (!$ldif->eof());
+        $this->assertTrue((boolean)$ldif->error());
+        $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+        $this->assertThat(count($entries), $this->equalTo(2));
+    }
+
+    /**
+     * Test error dropping functionality.
+     */
+    public function testError()
+    {
+        $this->markTestSkipped('We use exceptions, not the original error handling.');
+
+        // No error.
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+
+        // Error giving error msg and line number:
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/some_not_existing/path/for/net_ldap_ldif', 'r', $this->_defaultConfig);
+        $this->assertTrue((boolean)$ldif->error());
+        $this->assertType('Net_LDAP2_Error', $ldif->error());
+        $this->assertType('string', $ldif->error(true));
+        $this->assertType('int', $ldif->error_lines());
+        $this->assertThat(strlen($ldif->error(true)), $this->greaterThan(0));
+
+        // Test for line number reporting
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/malformed_syntax.ldif', 'r', $this->_defaultConfig);
+        $this->assertFalse((boolean)$ldif->error());
+        do { $entry = $ldif->readEntry(); } while (!$ldif->eof());
+        $this->assertTrue((boolean)$ldif->error());
+        $this->assertThat($ldif->error_lines(), $this->greaterThan(1));
+    }
+
+    /**
+     * Tests currentLines() and nextLines().
+     *
+     * This should always return the same lines unless forced.
+     */
+    public function testLineMethods()
+    {
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+        $this->assertEquals(array(), $ldif->currentLines(), 'Horde_Ldap_Ldif initialization error!');
+
+        // Read first lines.
+        $lines = $ldif->nextLines();
+
+        // Read the first lines several times and test.
+        for ($i = 0; $i <= 10; $i++) {
+            $r_lines = $ldif->nextLines();
+            $this->assertEquals($lines, $r_lines);
+        }
+
+        // Now force to iterate and see if the content changes.
+        $r_lines = $ldif->nextLines(true);
+        $this->assertNotEquals($lines, $r_lines);
+
+        // It could be confusing to some people, but calling currentEntry()
+        // would not work now, like the description of the method says.
+        $no_entry = $ldif->currentLines();
+        $this->assertEquals(array(), $no_entry);
+    }
+
+    /**
+     * Tests currentEntry(). This should always return the same object.
+     */
+    public function testcurrentEntry()
+    {
+        $ldif = new Horde_Ldap_Ldif(dirname(__FILE__).'/fixtures/unsorted_w50.ldif', 'r', $this->_defaultConfig);
+
+        // Read first entry.
+        $entry = $ldif->readEntry();
+
+        // Test if currentEntry remains the first one.
+        for ($i = 0; $i <= 10; $i++) {
+            $e = $ldif->currentEntry();
+            $this->assertEquals($entry, $e);
+        }
+    }
+
+    /**
+     * Compares two Horde_Ldap_Entries.
+    *
+    * This helper function compares two entries (or array of entries) and
+    * checks if they are equal. They are equal if all DNs from the first crowd
+    * exist in the second AND each attribute is present and equal at the
+    * respective entry. The search is case sensitive.
+    *
+    * @param array|Horde_Ldap_Entry $entry1
+    * @param array|Horde_Ldap_Entry $entry2
+    * @return boolean
+    */
+    protected function _compareEntries($entry1, $entry2)
+    {
+        if (!is_array($entry1)) {
+            $entry1 = array($entry1);
+        }
+        if (!is_array($entry2)) {
+            $entry2 = array($entry2);
+        }
+
+        $entries_data1 = $entries_data2  = array();
+
+        // Step 1: extract and sort data.
+        foreach ($entry1 as $e) {
+            $values = $e->getValues();
+            foreach ($values as $attr_name => $attr_values) {
+                if (!is_array($attr_values)) {
+                    $attr_values = array($attr_values);
+                }
+                $values[$attr_name] = $attr_values;
+            }
+            $entries_data1[$e->dn()] = $values;
+        }
+        foreach ($entry2 as $e) {
+            $values = $e->getValues();
+            foreach ($values as $attr_name => $attr_values) {
+                if (!is_array($attr_values)) {
+                    $attr_values = array($attr_values);
+                }
+                $values[$attr_name] = $attr_values;
+            }
+            $entries_data2[$e->dn()] = $values;
+        }
+
+        // Step 2: compare DNs (entries).
+        $this->assertEquals(array_keys($entries_data1), array_keys($entries_data2), 'Entries DNs not equal! (missing entry or wrong DN)');
+
+        // Step 3: look for attribute existence and compare values.
+        foreach ($entries_data1 as $dn => $attributes) {
+            $this->assertEquals($entries_data1[$dn], $entries_data2[$dn], 'Entries ' . $dn . ' attributes are not equal');
+            foreach ($attributes as $attr_name => $attr_values) {
+                $this->assertEquals(0, count(array_diff($entries_data1[$dn][$attr_name], $entries_data2[$dn][$attr_name])), 'Entries ' . $dn . ' attribute ' . $attr_name . ' values are not equal');
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Create line endings for current OS.
+     *
+     * This is neccessary to make write tests platform indendent.
+     *
+     * @param string $line Line
+     * @return string
+     */
+    protected function _lineend($line)
+    {
+        return rtrim($line) . PHP_EOL;
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/SearchTest.php b/framework/Ldap/test/Horde/Ldap/SearchTest.php
new file mode 100644 (file)
index 0000000..2f71137
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+
+require_once dirname(__FILE__) . '/TestBase.php';
+
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+class Horde_Ldap_SearchTest extends Horde_Ldap_TestBase
+{
+    public static function tearDownAfterClass()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+        $ldap->delete('ou=Horde_Ldap_Test_search1,' . self::$ldapcfg['server']['basedn']);
+        $ldap->delete('ou=Horde_Ldap_Test_search2,' . self::$ldapcfg['server']['basedn']);
+    }
+
+    /**
+     * Tests SPL iterator.
+     */
+    public function testSPLIterator()
+    {
+        $ldap = new Horde_Ldap(self::$ldapcfg['server']);
+
+        // Some testdata, so we have some entries to search for.
+        $base = self::$ldapcfg['server']['basedn'];
+        $ou1 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_search1,' . $base,
+            array(
+                'objectClass' => array('top', 'organizationalUnit'),
+                'ou' => 'Horde_Ldap_Test_search1'));
+        $ou2 = Horde_Ldap_Entry::createFresh(
+            'ou=Horde_Ldap_Test_search2,' . $base,
+            array(
+                'objectClass' => array('top', 'organizationalUnit'),
+                'ou' => 'Horde_Ldap_Test_search2'));
+
+        $ldap->add($ou1);
+        $this->assertTrue($ldap->exists($ou1->dn()));
+        $ldap->add($ou2);
+        $this->assertTrue($ldap->exists($ou2->dn()));
+
+        /* Search and test each method. */
+        $search = $ldap->search(null, '(ou=Horde_Ldap*)');
+        $this->assertType('Horde_Ldap_Search', $search);
+        $this->assertEquals(2, $search->count());
+
+        // current() is supposed to return first valid element.
+        $e1 = $search->current();
+        $this->assertType('Horde_Ldap_Entry', $e1);
+        $this->assertEquals($e1->dn(), $search->key());
+        $this->assertTrue($search->valid());
+
+        // Shift to next entry.
+        $search->next();
+        $e2 = $search->current();
+        $this->assertType('Horde_Ldap_Entry', $e2);
+        $this->assertEquals($e2->dn(), $search->key());
+        $this->assertTrue($search->valid());
+
+        // Shift to non existent third entry.
+        $search->next();
+        $this->assertFalse($search->current());
+        $this->assertFalse($search->key());
+        $this->assertFalse($search->valid());
+
+        // Rewind and test, which should return the first entry a second time.
+        $search->rewind();
+        $e1_1 = $search->current();
+        $this->assertType('Horde_Ldap_Entry', $e1_1);
+        $this->assertEquals($e1_1->dn(), $search->key());
+        $this->assertTrue($search->valid());
+        $this->assertEquals($e1->dn(), $e1_1->dn());
+
+        // Don't rewind but call current, should return first entry again.
+        $e1_2 = $search->current();
+        $this->assertType('Horde_Ldap_Entry', $e1_2);
+        $this->assertEquals($e1_2->dn(), $search->key());
+        $this->assertTrue($search->valid());
+        $this->assertEquals($e1->dn(), $e1_2->dn());
+
+        // Rewind again and test, which should return the first entry a third
+        // time.
+        $search->rewind();
+        $e1_3 = $search->current();
+        $this->assertType('Horde_Ldap_Entry', $e1_3);
+        $this->assertEquals($e1_3->dn(), $search->key());
+        $this->assertTrue($search->valid());
+        $this->assertEquals($e1->dn(), $e1_3->dn());
+
+        /* Try methods on empty search result. */
+        $search = $ldap->search(null, '(ou=Horde_LdapTest_NotExistentEntry)');
+        $this->assertType('Horde_Ldap_Search', $search);
+        $this->assertEquals(0, $search->count());
+        $this->assertFalse($search->current());
+        $this->assertFalse($search->key());
+        $this->assertFalse($search->valid());
+        $search->next();
+        $this->assertFalse($search->current());
+        $this->assertFalse($search->key());
+        $this->assertFalse($search->valid());
+
+        /* Search and simple iterate through the test entries.  Then, rewind
+         * and do it again several times. */
+        $search2 = $ldap->search(null, '(ou=Horde_Ldap*)');
+        $this->assertType('Horde_Ldap_Search', $search2);
+        $this->assertEquals(2, $search2->count());
+        for ($i = 0; $i <= 5; $i++) {
+            $counter = 0;
+            foreach ($search2 as $dn => $entry) {
+                $counter++;
+                // Check on type.
+                $this->assertType('Horde_Ldap_Entry', $entry);
+                // Check on key.
+                $this->assertThat(strlen($dn), $this->greaterThan(1));
+                $this->assertEquals($dn, $entry->dn());
+            }
+            $this->assertEquals($search2->count(), $counter, "Failed at loop $i");
+
+            // Revert to start.
+            $search2->rewind();
+        }
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/TestBase.php b/framework/Ldap/test/Horde/Ldap/TestBase.php
new file mode 100644 (file)
index 0000000..7571e5d
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_TestBase extends PHPUnit_Framework_TestCase
+{
+    protected static $ldapcfg;
+
+    public function setUp()
+    {
+        // Check extension.
+        try {
+            Horde_Ldap::checkLDAPExtension();
+        } catch (Horde_Ldap_Exception $e) {
+            $this->markTestSkipped($e->getMessage());
+        }
+
+        $file = dirname(__FILE__) . '/conf.php';
+        if (!file_exists($file) || !is_readable($file)) {
+            $this->markTestSkipped('conf.php cannot be opened.');
+        }
+        include $file;
+        self::$ldapcfg = $conf;
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/UtilTest.php b/framework/Ldap/test/Horde/Ldap/UtilTest.php
new file mode 100644 (file)
index 0000000..86429ea
--- /dev/null
@@ -0,0 +1,344 @@
+<?php
+/**
+ * @package    Ldap
+ * @subpackage UnitTests
+ * @author     Jan Schneider <jan@horde.org>
+ * @copyright  2010 The Horde Project
+ * @license    http://www.gnu.org/copyleft/lesser.html LGPL
+ */
+
+class Horde_Ldap_UtilTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Test escape_dn_value()
+     */
+    public function testEscape_dn_value()
+    {
+        $dnval    = '  ' . chr(22) . ' t,e+s"t,\\v<a>l;u#e=!    ';
+        $expected = '\20\20\16 t\,e\+s\"t\,\\\\v\<a\>l\;u\#e\=!\20\20\20\20';
+
+        // String call.
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::escape_dn_value($dnval));
+
+        // Array call.
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::escape_dn_value(array($dnval)));
+
+        // Multiple arrays.
+        $this->assertEquals(
+            array($expected, $expected, $expected),
+            Horde_Ldap_Util::escape_dn_value(array($dnval, $dnval, $dnval)));
+    }
+
+    /**
+     * Test unescape_dn_value()
+     */
+    public function testUnescape_dn_value()
+    {
+        $dnval    = '\\20\\20\\16\\20t\\,e\\+s \\"t\\,\\\\v\\<a\\>l\\;u\\#e\\=!\\20\\20\\20\\20';
+        $expected = '  ' . chr(22) . ' t,e+s "t,\\v<a>l;u#e=!    ';
+
+        // String call.
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::unescape_dn_value($dnval));
+
+        // Array call.
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::unescape_dn_value(array($dnval)));
+
+        // Multiple arrays.
+        $this->assertEquals(
+            array($expected, $expected, $expected),
+            Horde_Ldap_Util::unescape_dn_value(array($dnval, $dnval, $dnval)));
+    }
+
+    /**
+     * Test escaping of filter values.
+     */
+    public function testEscape_filter_value()
+    {
+        $expected  = 't\28e,s\29t\2av\5cal\1eue';
+        $filterval = 't(e,s)t*v\\al' . chr(30) . 'ue';
+
+        // String call
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::escape_filter_value($filterval));
+
+        // Array call.
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::escape_filter_value(array($filterval)));
+
+        // Multiple arrays.
+        $this->assertEquals(
+            array($expected, $expected, $expected),
+            Horde_Ldap_Util::escape_filter_value(array($filterval, $filterval, $filterval)));
+    }
+
+    /**
+     * Test unescaping of filter values.
+     */
+    public function testUnescape_filter_value()
+    {
+        $expected  = 't(e,s)t*v\\al' . chr(30) . 'ue';
+        $filterval = 't\28e,s\29t\2av\5cal\1eue';
+
+        // String call
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::unescape_filter_value($filterval));
+
+        // Array call.
+        $this->assertEquals(
+            array($expected),
+            Horde_Ldap_Util::unescape_filter_value(array($filterval)));
+
+        // Multiple arrays.
+        $this->assertEquals(
+            array($expected, $expected, $expected),
+            Horde_Ldap_Util::unescape_filter_value(array($filterval, $filterval, $filterval)));
+    }
+
+    /**
+     * Test asc2hex32()
+     */
+    public function testAsc2hex32()
+    {
+        $expected = '\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
+        $str = '';
+        for ($i = 0; $i < 127; $i++) {
+             $str .= chr($i);
+        }
+        $this->assertEquals($expected, Horde_Ldap_Util::asc2hex32($str));
+    }
+
+    /**
+     * Test HEX unescaping
+     */
+    public function testHex2asc()
+    {
+        $expected = '';
+        for ($i = 0; $i < 127; $i++) {
+             $expected .= chr($i);
+        }
+        $str = '\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
+        $this->assertEquals($expected, Horde_Ldap_Util::hex2asc($str));
+    }
+
+    /**
+     * Tests split_rdn_multival()
+     *
+     * In addition to the above test of the basic split correction, we test
+     * here the functionality of multivalued RDNs.
+     */
+    public function testSplit_rdn_multival()
+    {
+        // One value.
+        $rdn = 'CN=J. Smith';
+        $expected = array('CN=J. Smith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Two values.
+        $rdn = 'OU=Sales+CN=J. Smith';
+        $expected = array('OU=Sales', 'CN=J. Smith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Several multivals.
+        $rdn = 'OU=Sales+CN=J. Smith+L=London+C=England';
+        $expected = array('OU=Sales', 'CN=J. Smith', 'L=London', 'C=England');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Unescaped "+" in value.
+        $rdn = 'OU=Sa+les+CN=J. Smith';
+        $expected = array('OU=Sa+les', 'CN=J. Smith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Unescaped "+" in attr name.
+        $rdn = 'O+U=Sales+CN=J. Smith';
+        $expected = array('O+U=Sales', 'CN=J. Smith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Unescaped "+" in attr name + value.
+        $rdn = 'O+U=Sales+CN=J. Sm+ith';
+        $expected = array('O+U=Sales', 'CN=J. Sm+ith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Unescaped "+" in attribute name, but not first attribute.  This
+        // documents a known bug. However, unfortunately we can't know wether
+        // the "C+" belongs to value "Sales" or attribute "C+N".  To solve
+        // this, we must ask the schema which we do not right now.  The problem
+        // is located in _correct_dn_splitting().
+        $rdn = 'OU=Sales+C+N=J. Smith';
+        // The "C+" is treaten as value of "OU".
+        $expected = array('OU=Sales+C', 'N=J. Smith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+
+        // Escaped "+" in attribute name and value.
+        $rdn = 'O\+U=Sales+CN=J. Sm\+ith';
+        $expected = array('O\+U=Sales', 'CN=J. Sm\+ith');
+        $split = Horde_Ldap_Util::split_rdn_multival($rdn);
+        $this->assertEquals($expected, $split);
+    }
+
+    /**
+     * Tests attribute splitting ('foo=bar' => array('foo', 'bar'))
+     */
+    public function testSplit_attribute_string()
+    {
+        $attr_str = 'foo=bar';
+
+        // Properly.
+        $expected = array('foo', 'bar');
+        $split = Horde_Ldap_Util::split_attribute_string($attr_str);
+        $this->assertEquals($expected, $split);
+
+        // Escaped "=".
+        $attr_str = "fo\=o=b\=ar";
+        $expected = array('fo\=o', 'b\=ar');
+        $split = Horde_Ldap_Util::split_attribute_string($attr_str);
+        $this->assertEquals($expected, $split);
+
+        // Escaped "=" and unescaped = later on.
+        $attr_str = "fo\=o=b=ar";
+        $expected = array('fo\=o', 'b=ar');
+        $split = Horde_Ldap_Util::split_attribute_string($attr_str);
+        $this->assertEquals($expected, $split);
+    }
+
+    /**
+     * Tests Ldap_explode_dn()
+     */
+    public function testLdap_explode_dn()
+    {
+        $dn = 'OU=Sales+CN=J. Smith,dc=example,dc=net';
+        $expected_casefold_none = array(
+            array('CN=J. Smith', 'OU=Sales'),
+            'dc=example',
+            'dc=net'
+        );
+        $expected_casefold_upper = array(
+            array('CN=J. Smith', 'OU=Sales'),
+            'DC=example',
+            'DC=net'
+        );
+        $expected_casefold_lower = array(
+            array('cn=J. Smith', 'ou=Sales'),
+            'dc=example',
+            'dc=net'
+        );
+        $expected_onlyvalues = array(
+            array( 'J. Smith', 'Sales'),
+            'example',
+            'net'
+        );
+        $expected_reverse = array_reverse($expected_casefold_upper);
+
+
+        $dn_exploded_cnone = Horde_Ldap_Util::ldap_explode_dn($dn, array('casefold' => 'none'));
+        $this->assertEquals($expected_casefold_none, $dn_exploded_cnone, 'Option casefold none failed');
+
+        $dn_exploded_cupper = Horde_Ldap_Util::ldap_explode_dn($dn, array('casefold' => 'upper'));
+        $this->assertEquals($expected_casefold_upper, $dn_exploded_cupper, 'Option casefold upper failed');
+
+        $dn_exploded_clower = Horde_Ldap_Util::ldap_explode_dn($dn, array('casefold' => 'lower'));
+        $this->assertEquals($expected_casefold_lower, $dn_exploded_clower, 'Option casefold lower failed');
+
+        $dn_exploded_onlyval = Horde_Ldap_Util::ldap_explode_dn($dn, array('onlyvalues' => true));
+        $this->assertEquals($expected_onlyvalues, $dn_exploded_onlyval, 'Option onlyval failed');
+
+        $dn_exploded_reverse = Horde_Ldap_Util::ldap_explode_dn($dn, array('reverse' => true));
+        $this->assertEquals($expected_reverse, $dn_exploded_reverse, 'Option reverse failed');
+    }
+
+    /**
+     * Tests if canonical_dn() works.
+     *
+     * Note: This tests depend on the default options of canonical_dn().
+     */
+    public function testCanonical_dn()
+    {
+        // Test empty dn (is valid according to RFC).
+        $this->assertEquals('', Horde_Ldap_Util::canonical_dn(''));
+
+        // Default options with common DN.
+        $testdn   = 'cn=beni,DC=php,c=net';
+        $expected = 'CN=beni,DC=php,C=net';
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+
+        // Casefold tests with common DN.
+        $expected_up = 'CN=beni,DC=php,C=net';
+        $expected_lo = 'cn=beni,dc=php,c=net';
+        $expected_no = 'cn=beni,DC=php,c=net';
+        $this->assertEquals($expected_up, Horde_Ldap_Util::canonical_dn($testdn, array('casefold' => 'upper')));
+        $this->assertEquals($expected_lo, Horde_Ldap_Util::canonical_dn($testdn, array('casefold' => 'lower')));
+        $this->assertEquals($expected_no, Horde_Ldap_Util::canonical_dn($testdn, array('casefold' => 'none')));
+
+        // Reverse.
+        $expected_rev = 'C=net,DC=php,CN=beni';
+        $this->assertEquals($expected_rev, Horde_Ldap_Util::canonical_dn($testdn, array('reverse' => true)), 'Option reverse failed');
+
+        // DN as arrays.
+        $dn_index = array('cn=beni', 'dc=php', 'c=net');
+        $dn_assoc = array('cn' => 'beni', 'dc' => 'php', 'c' => 'net');
+        $expected = 'CN=beni,DC=php,C=net';
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($dn_index));
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($dn_assoc));
+
+        // DN with multiple RDN value.
+        $testdn       = 'ou=dev+cn=beni,DC=php,c=net';
+        $testdn_index = array(array('ou=dev', 'cn=beni'), 'DC=php', 'c=net');
+        $testdn_assoc = array(array('ou' => 'dev', 'cn' => 'beni'), 'DC' => 'php', 'c' => 'net');
+        $expected     = 'CN=beni+OU=dev,DC=php,C=net';
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn_assoc));
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($expected));
+
+        // Test DN with OID.
+        $testdn = 'OID.2.5.4.3=beni,dc=php,c=net';
+        $expected = '2.5.4.3=beni,DC=php,C=net';
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+
+        // Test with leading and ending spaces.
+        $testdn   = 'cn=  beni  ,DC=php,c=net';
+        $expected = 'CN=\20\20beni\20\20,DC=php,C=net';
+        $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($testdn));
+
+        // Test with to-be escaped characters in attribute value.
+        $specialchars = array(
+            ',' => '\,',
+            '+' => '\+',
+            '"' => '\"',
+            '\\' => '\\\\',
+            '<' => '\<',
+            '>' => '\>',
+            ';' => '\;',
+            '#' => '\#',
+            '=' => '\=',
+            chr(18) => '\12',
+            '/' => '\/'
+        );
+        foreach ($specialchars as $char => $escape) {
+            $test_string = 'CN=be' . $char . 'ni,DC=ph' . $char . 'p,C=net';
+            $test_index  = array('CN=be' . $char . 'ni', 'DC=ph' . $char . 'p', 'C=net');
+            $test_assoc  = array('CN' => 'be' . $char . 'ni', 'DC' => 'ph' . $char . 'p', 'C' => 'net');
+            $expected    = 'CN=be' . $escape . 'ni,DC=ph' . $escape . 'p,C=net';
+
+            $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($test_string), 'String escaping test (' . $char . ') failed');
+            $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($test_index),  'Indexed array escaping test (' . $char . ') failed');
+            $this->assertEquals($expected, Horde_Ldap_Util::canonical_dn($test_assoc),  'Associative array encoding test (' . $char . ') failed');
+        }
+    }
+}
diff --git a/framework/Ldap/test/Horde/Ldap/conf.php.dist b/framework/Ldap/test/Horde/Ldap/conf.php.dist
new file mode 100644 (file)
index 0000000..2678d31
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+$conf = array(
+    'server' => array(
+        'host'    => 'localhost',
+        'port'    => 389,
+        'basedn'  => 'ou=hordetest,dc=example,dc=com',
+        'writedn' => 'cn=admin,dc=example,dc=com',
+        'writepw' => 'secret'),
+    'capability' => array(
+        'anonymous' => true,
+        'tls'       => true),
+);
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/changes.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/changes.ldif
new file mode 100644 (file)
index 0000000..d67565b
--- /dev/null
@@ -0,0 +1,43 @@
+#
+# This is a LDIF file to test writing changes of entries
+#
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+changetype: modify
+delete: attr1
+-
+delete: attr2
+attr2: baz
+-
+delete: attr4
+-
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+changetype: modify
+add: newattr
+newattr: foo
+-
+delete: attr3
+-
+replace: attr1
+attr1: newvaluefor1
+-
+replace: attr2
+attr2: newvalue1for2
+attr2: newvalue2for2
+-
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+changetype: delete
+
+dn: cn=foo,ou=example,dc=cno
+changetype: modrdn
+newrdn: cn=Bar
+deleteoldrdn: 1
+
+dn: cn=footest,ou=example,dc=cno
+changetype: modrdn
+newrdn: cn=foobartest
+deleteoldrdn: 1
+newsuperior: ou=newexample,dc=cno
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/malformed_encoding.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/malformed_encoding.ldif
new file mode 100644 (file)
index 0000000..469143f
--- /dev/null
@@ -0,0 +1,19 @@
+#
+# This is a LDIF file to test encoding failure
+#
+
+# unencoded DN
+version: 1
+dn: cn=testöäü,ou=example,dc=cno
+objectclass: oc1
+
+# unencoded attr value
+version: 1
+dn: cn=test2,ou=example,dc=cno
+objectclass: testöäü
+cn: test2
+
+# entry ok
+version: 1
+dn: cn=test,ou=example,dc=cno
+objectclass: oc1
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/malformed_syntax.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/malformed_syntax.ldif
new file mode 100644 (file)
index 0000000..f103808
--- /dev/null
@@ -0,0 +1,20 @@
+#
+# This is a LDIF file to test syntax error
+#
+
+# wrong syntax (space too less at val of objectclass)
+dn: cn=test1,ou=example,dc=cno
+objectclass:oc1
+cn: test1
+attr3: foo
+
+# wrong syntax (no DN given)
+objectclass:oc1
+cn: test_invalid
+attr3: foo
+
+# entry ok
+version: 1
+dn: cn=test3,ou=example,dc=cno
+objectclass: oc1
+attr3: foo
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/malformed_wrapping.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/malformed_wrapping.ldif
new file mode 100644 (file)
index 0000000..4b4500b
--- /dev/null
@@ -0,0 +1,24 @@
+#
+# This is a LDIF file to test wrapping failure
+#
+
+# wrong wrapping (entry must fail because DN is damaged):
+# (note, that there must eb an empty line below this comment, otherwise
+# the DN line is treaten as wrapped comment)
+
+ dn: cn=test1,ou=example,dc=cno
+objectclass: oc1
+cn: test1
+
+# wrong syntax (literal line but no wrapped content)
+dn: cn=test2,ou=example,dc=cno
+objectclass:oc1
+cn: test2
+some_wrong_literal_line
+attr3: foo
+
+# entry ok
+version: 1
+dn: cn=test,ou=example,dc=cno
+objectclass: oc1
+cn: test
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/sorted_w40.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/sorted_w40.ldif
new file mode 100644 (file)
index 0000000..5dd0256
--- /dev/null
@@ -0,0 +1,40 @@
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=1, wrap=40
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+objectclass: oc1
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4: brrrzztt
+cn: test1
+
+dn: cn=test blabla,ou=example,dc=cno
+objectclass: oc2
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+cn: test blabla
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+objectclass: oc3
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+cn:: dGVzdCD25Pw=
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/sorted_w50.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/sorted_w50.ldif
new file mode 100644 (file)
index 0000000..60790e8
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=1, wrap=50
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+objectclass: oc1
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4: brrrzztt
+cn: test1
+
+dn: cn=test blabla,ou=example,dc=cno
+objectclass: oc2
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+cn: test blabla
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8
+ h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb
+ 5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+objectclass: oc3
+attr1: 12345
+attr2: 1234
+attr2: baz
+attr3: foo
+attr3: bar
+attr4:: YmxhYmxh9uT8
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+cn:: dGVzdCD25Pw=
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w30.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w30.ldif
new file mode 100644 (file)
index 0000000..0b63baf
--- /dev/null
@@ -0,0 +1,40 @@
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=0, wrap=30
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+cn: test1
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4: brrrzztt
+objectclass: oc1
+attr2: 1234
+attr2: baz
+
+dn: cn=test blabla,ou=example,dc=cno
+cn: test blabla
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc2
+attr2: 1234
+attr2: baz
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+cn:: dGVzdCD25Pw=
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc3
+attr2: 1234
+attr2: baz
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50.ldif
new file mode 100644 (file)
index 0000000..a2d9edb
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# This is a LDIF file to test reading capabilitys
+# It was created using options: sort=0, wrap=50
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+cn: test1
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4: brrrzztt
+objectclass: oc1
+attr2: 1234
+attr2: baz
+
+dn: cn=test blabla,ou=example,dc=cno
+cn: test blabla
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc2
+attr2: 1234
+attr2: baz
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8
+ h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb
+ 5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+cn:: dGVzdCD25Pw=
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc3
+attr2: 1234
+attr2: baz
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace
diff --git a/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50_WIN.ldif b/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50_WIN.ldif
new file mode 100644 (file)
index 0000000..5cfc5ae
--- /dev/null
@@ -0,0 +1,42 @@
+#
+# This is a LDIF file to test reading capabilitys with WINDOWS line endings
+# It was created using options: sort=0, wrap=50
+#
+version: 1
+dn: cn=test1,ou=example,dc=cno
+cn: test1
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4: brrrzztt
+objectclass: oc1
+attr2: 1234
+attr2: baz
+
+dn: cn=test blabla,ou=example,dc=cno
+cn: test blabla
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc2
+attr2: 1234
+attr2: baz
+verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8
+ h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb
+ 5789thvngwr789cghm738
+
+dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8=
+cn:: dGVzdCD25Pw=
+attr3: foo
+attr3: bar
+attr1: 12345
+attr4:: YmxhYmxh9uT8
+objectclass: oc3
+attr2: 1234
+attr2: baz
+attr5:: ZW5kc3BhY2Ug
+attr6:: OmJhZGluaXRjaGFy
+
+dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA=
+cn: endspace