From: Jan Schneider Date: Fri, 17 Sep 2010 11:18:01 +0000 (+0200) Subject: Add test suite. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=c48e1a2dda9cb1e587fc7f21bb6c58e3fb71cb96;p=horde.git Add test suite. --- diff --git a/.gitignore b/.gitignore index 2aa932f79..baeedfa97 100644 --- a/.gitignore +++ b/.gitignore @@ -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 index 000000000..c8805e0a9 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/AllTests.php @@ -0,0 +1,36 @@ + + * @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 index 000000000..44bc73cb2 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/EntryTest.php @@ -0,0 +1,27 @@ + + * @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 index 000000000..4d48761f5 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/ExampleTest.php @@ -0,0 +1,23 @@ + + * @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 index 000000000..1d341b784 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/FilterTest.php @@ -0,0 +1,236 @@ + + * @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 index 000000000..e15a05217 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/LdapTest.php @@ -0,0 +1,599 @@ + + * @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 index 000000000..eb1f34054 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/LdifTest.php @@ -0,0 +1,632 @@ + + * @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 index 000000000..2f711378a --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/SearchTest.php @@ -0,0 +1,127 @@ + + * @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 index 000000000..7571e5df6 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/TestBase.php @@ -0,0 +1,30 @@ + + * @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 index 000000000..86429ea03 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/UtilTest.php @@ -0,0 +1,344 @@ + + * @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,\\vl;u#e=! '; + $expected = '\20\20\16 t\,e\+s\"t\,\\\\v\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\\l\\;u\\#e\\=!\\20\\20\\20\\20'; + $expected = ' ' . chr(22) . ' t,e+s "t,\\vl;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 index 000000000..2678d3122 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/conf.php.dist @@ -0,0 +1,12 @@ + 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 index 000000000..d67565b9b --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/changes.ldif @@ -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 index 000000000..469143f8b --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/malformed_encoding.ldif @@ -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 index 000000000..f10380845 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/malformed_syntax.ldif @@ -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 index 000000000..4b4500b94 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/malformed_wrapping.ldif @@ -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 index 000000000..5dd02561a --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/sorted_w40.ldif @@ -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 index 000000000..60790e870 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/sorted_w50.ldif @@ -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 index 000000000..0b63baf1c --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w30.ldif @@ -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 index 000000000..a2d9edb3b --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50.ldif @@ -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 index 000000000..5cfc5aec6 --- /dev/null +++ b/framework/Ldap/test/Horde/Ldap/fixtures/unsorted_w50_WIN.ldif @@ -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