<?php

require __DIR__ . '/../../vendor/autoload.php';

use PHPUnit\Framework\Attributes\RunInSeparateProcess;

final class DirectoryTest extends \Mockery\Adapter\Phpunit\MockeryTestCase
{

    public function test_openldap_islocked_locked_forever(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => "000001010000Z"
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_enabled' => true));
        $this->assertTrue($isLocked, "Account should be locked forever");
    }

    public function test_openldap_islocked_not_locked(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => null
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_enabled' => true));
        $this->assertFalse($isLocked, "Account should not be locked");
    }

    public function test_openldap_islocked_still_locked(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => (new DateTime)->format("Ymdhis\Z")
                    ]
                ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_duration' => 86400, 'lockout_enabled' => true));
        $this->assertTrue($isLocked, "Account should still be locked");
    }

    public function test_openldap_islocked_no_more_locked(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => (new DateTime)->modify("-10 days")->format("Ymdhis\Z")
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_duration' => 86400, 'lockout_enabled' => true));
        $this->assertFalse($isLocked, "Account should no more be locked");
    }

    public function test_openldap_islocked_lockout_duration_zero(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => (new DateTime)->modify("-10 days")->format("Ymdhis\Z")
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_duration' => 0, 'lockout_enabled' => true));
        $this->assertTrue($isLocked, "Account should be locked");
    }

    public function test_openldap_islocked_no_lockout_duration(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => (new DateTime)->modify("-10 days")->format("Ymdhis\Z")
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_duration' => null, 'lockout_enabled' => true));
        $this->assertTrue($isLocked, "Account should be locked");
    }

    public function test_openldap_islocked_lockout_disabled(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => (new DateTime)->format("Ymdhis\Z")
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_duration' => 86400, 'lockout_enabled' => false));
        $this->assertFalse($isLocked, "Account should not be locked");
    }

    public function test_openldap_islocked_lockdate_in_future(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => (new DateTime)->modify("+10 days")->format("Ymdhis\Z")
                    ]
                 ];

        $isLocked = (new Ltb\Directory\OpenLDAP)->isLocked($entry, array('lockout_duration' => 86400, 'lockout_enabled' => true));
        $this->assertFalse($isLocked, "Account should not be locked");
    }

    public function test_openldap_getlockdate_empty(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => null
                    ]
                 ];

        $getLockDate = (new Ltb\Directory\OpenLDAP)->getLockDate($entry, null);
        $this->assertNull($getLockDate, "Lock date should be null");
    }

    public function test_openldap_getlockdate_lock_forever(): void
    {
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => "000001010000Z"
                    ]
                 ];

        $getLockDate = (new Ltb\Directory\OpenLDAP)->getLockDate($entry, null);
        $this->assertNull($getLockDate, "Lock date should be null if user is locked forever");
    }

    public function test_openldap_getlockdate_notempty(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => $dt->format("Ymdhis\Z")
                    ]
                 ];

        $getLockDate = (new Ltb\Directory\OpenLDAP)->getLockDate($entry, null);
        $this->assertInstanceOf("DateTime", $getLockDate, "Lock date should be a PHP DateTime object");
        $this->assertEquals($dt->format("Y/m/d - h:i:s"), $getLockDate->format("Y/m/d - h:i:s"), "Lock date is correct");
    }

    public function test_openldap_getunlockdate_noduration(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => $dt->format("Ymdhis\Z")
                    ]
                 ];

        $unlockDate = (new Ltb\Directory\OpenLDAP)->getUnlockDate($entry, array('lockout_duration' => 0));
        $this->assertNull($unlockDate, "Unkock date should be null");
    }

    public function test_openldap_getunlockdate_duration(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdaccountlockedtime' => [
                        'count' => 1,
                        0 => $dt->format("Ymdhis\Z")
                    ]
                 ];

        $unlockDate = (new Ltb\Directory\OpenLDAP)->getUnlockDate($entry, array('lockout_duration' => 86400));
        $this->assertInstanceOf("DateTime", $unlockDate, "Unlock date should be a PHP DateTime object");
        $this->assertEquals($dt->modify("+1 day")->format("Y/m/d - h:i:s"), $unlockDate->format("Y/m/d - h:i:s"), "Unlock date is correct");
    }

    public function test_openldap_isexpired_expired(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdchangedtime' => [
                        'count' => 1,
                        0 => $dt->modify("-1 month")->format("Ymdhis\Z")
                    ]
                 ];

        $isPasswordExpired = (new Ltb\Directory\OpenLDAP)->isPasswordExpired($entry, array('password_max_age' => 86400));
        $this->assertTrue($isPasswordExpired, "Password should be expired");
    }

    public function test_openldap_isexpired_not_expired(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdchangedtime' => [
                        'count' => 1,
                        0 => $dt->modify("-1 hour")->format("Ymdhis\Z")
                    ]
                 ];

        $isPasswordExpired = (new Ltb\Directory\OpenLDAP)->isPasswordExpired($entry, array('password_max_age' => 86400));
        $this->assertFalse($isPasswordExpired, "Password should not be expired");
    }

    public function test_openldap_getpasswordexpirationdate_empty(): void
    {
        $entry = [
                    'pwdchangedtime' => [
                        'count' => 1,
                        0 => null,
                    ]
                 ];

        $passwordExpirationDate = (new Ltb\Directory\OpenLDAP)->getPasswordExpirationDate($entry, null);
        $this->assertNull($passwordExpirationDate, "Password expiration date should be null");
    }

    public function test_openldap_getpasswordexpirationdate_notempty(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdchangedtime' => [
                        'count' => 1,
                        0 => $dt->format("Ymdhis\Z"),
                    ]
                 ];

        $passwordExpirationDate = (new Ltb\Directory\OpenLDAP)->getPasswordExpirationDate($entry, array('password_max_age' => 86400));
        $this->assertInstanceOf("DateTime", $passwordExpirationDate, "Password expiration date should be a PHP DateTime object");
        $this->assertEquals($dt->modify("+1 day")->format("Y/m/d - h:i:s"), $passwordExpirationDate->format("Y/m/d - h:i:s"), "Password expiration date is correct");
    }

    public function test_openldap_reset_true(): void
    {
        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');
        $phpLDAPMock->shouldreceive([
            'ldap_read' => null,
            'ldap_errno' => 0,
            'ldap_get_entries' => [
                'count' => 1,
                0 => [
                    'pwdreset' => [
                        'count' => 1,
                        0 => "TRUE",
                    ]
                ]
            ]
        ]);

        $reset = (new Ltb\Directory\OpenLDAP)->resetAtNextConnection(null, null);
        $this->assertTrue($reset, "Reset should be true");
    }

    public function test_openldap_reset_false(): void
    {
        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');
        $phpLDAPMock->shouldreceive([
            'ldap_read' => null,
            'ldap_errno' => 0,
            'ldap_get_entries' => [
                'count' => 1,
                0 => [
                    'pwdreset' => [
                        'count' => 1,
                        0 => "FALSE",
                    ]
                ]
            ]
        ]);

        $reset = (new Ltb\Directory\OpenLDAP)->resetAtNextConnection(null, null);
        $this->assertFalse($reset, "Reset should be false");
    }

    public function test_openldap_reset_false_empty(): void
    {
        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');
        $phpLDAPMock->shouldreceive([
            'ldap_read' => null,
            'ldap_errno' => 0,
            'ldap_get_entries' => [
                'count' => 1,
                0 => [
                    'pwdreset' => [
                        'count' => 1,
                        0 => null,
                    ]
                ]
            ]
        ]);

        $reset = (new Ltb\Directory\OpenLDAP)->resetAtNextConnection(null, null);
        $this->assertFalse($reset, "Reset should be false");
    }

    public function test_openldap_isvalid_nodate(): void
    {

        $isAccountValid = (new Ltb\Directory\OpenLDAP)->isAccountValid(null, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_openldap_isvalid_startdate_before(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdstarttime' => [
                        'count' => 1,
                        0 => $dt->modify("-1 week")->format("Ymdhis\Z")
                    ]
                ];

        $isAccountValid = (new Ltb\Directory\OpenLDAP)->isAccountValid($entry, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_openldap_isvalid_startdate_after(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdstarttime' => [
                        'count' => 1,
                        0 => $dt->modify("+1 week")->format("Ymdhis\Z")
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\OpenLDAP)->isAccountValid($entry, null);
        $this->assertFalse($isAccountValid, "Account should not be valid");
    }

    public function test_openldap_isvalid_enddate_before(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdendtime' => [
                        'count' => 1,
                        0 => $dt->modify("-1 week")->format("Ymdhis\Z")
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\OpenLDAP)->isAccountValid($entry, null);
        $this->assertFalse($isAccountValid, "Account should not be valid");
    }

    public function test_openldap_isvalid_enddate_after(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdendtime' => [
                        'count' => 1,
                        0 => $dt->modify("+1 week")->format("Ymdhis\Z")
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\OpenLDAP)->isAccountValid($entry, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_openldap_isvalid_bothdate(): void
    {
        $dt = new DateTime;
        $entry = [
                    'pwdstarttime' => [
                        'count' => 1,
                        0 => $dt->modify("-1 week")->format("Ymdhis\Z")
                    ],
                    'pwdendtime' => [
                        'count' => 1,
                        0 => $dt->modify("+2 week")->format("Ymdhis\Z")
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\OpenLDAP)->isAccountValid($entry, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_openldap_compute_password(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $password = "secret";
        $hash = "auto";
        $hash_options=[];
        $use_exop_passwd = false;
        $old_password_hash = "{SHA512}PJkJr+wlNU1VHa4hWQuybjjVPyFzuNPcPu5MBH56scHri4UQPjvnumE7MbtcnDYhTcnxSkL9ei/bhIVrylxEwg=="; # 123

        $ldap_connection = "ldap_connection";
        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");
        $ldapInstanceMock->shouldreceive('get_password_value')
                         ->with($dn, "userPassword")
                         ->andReturn($old_password_hash);

        $password = (new Ltb\Directory\OpenLDAP)->computePassword(
                        $ldapInstanceMock,
                        $dn,
                        $password,
                        $hash,
                        $hash_options,
                        $use_exop_passwd);
        $this->assertEquals(
            "{SHA512}vSsar3708Jvp9Szi2NWZZ02Bqp1qRCFpbcTZPdBhnWgs5WtNZKnvCXdhztmeD2cmW192CF5bDufKRpayrW/isg==",
            $password,
            "invalid OpenLDAP computePassword value");
    }

    public function test_openldap_changePasswordData_use_exop_passwd(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ), "description" => array( "desc1" ) );
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "user";
        $use_exop_passwd = true;
        $use_ppolicy_control = false;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[];

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $ldapInstanceMock->shouldreceive('change_password_with_exop')
                         ->with($dn, $oldpassword, $password, $use_ppolicy_control)
                         ->andReturn( [ 0, "change_password_with_exop_OK", null ] );

        $ldapInstanceMock->shouldreceive('modify_attributes')
                         ->with($dn, $userdata)
                         ->andReturn( [ 0, "modify_attributes_OK" ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\OpenLDAP)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by OpenLDAP changePasswordData with exop_passwd");
        $this->assertEquals( "modify_attributes_OK", $error_msg, "bad error message sent by OpenLDAP changePasswordData with exop_passwd");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by OpenLDAP changePasswordData with exop_passwd");
    }

    public function test_openldap_changePasswordData_no_exop_passwd_with_ppolicy_control(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ), "description" => array( "desc1" ) );
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "user";
        $use_exop_passwd = false;
        $use_ppolicy_control = true;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[];

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $expectedUserData = $userdata;
        $expectedUserData['userPassword'] = $password;
        $ldapInstanceMock->shouldreceive('modify_attributes_using_ppolicy')
                         ->with($dn, $expectedUserData)
                         ->andReturn( [ 0, "modify_attributes_using_ppolicy_OK", null ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\OpenLDAP)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by OpenLDAP changePasswordData without (no exop_passwd, ppolicy control)");
        $this->assertEquals( "modify_attributes_using_ppolicy_OK", $error_msg, "bad error message sent by OpenLDAP changePasswordData (no exop_passwd, ppolicy control)");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by OpenLDAP changePasswordData without exop_passwd (no exop_passwd, ppolicy control)");
    }

    public function test_openldap_changePasswordData_no_exop_passwd_no_ppolicy_control(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ), "description" => array( "desc1" ) );
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "user";
        $use_exop_passwd = false;
        $use_ppolicy_control = false;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[];

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $expectedUserData = $userdata;
        $expectedUserData['userPassword'] = $password;
        $ldapInstanceMock->shouldreceive('modify_attributes')
                         ->with($dn, $expectedUserData)
                         ->andReturn( [ 0, "modify_attributes_without_ppolicy_OK" ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\OpenLDAP)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by OpenLDAP changePasswordData without (no exop_passwd, ppolicy control)");
        $this->assertEquals( "modify_attributes_without_ppolicy_OK", $error_msg, "bad error message sent by OpenLDAP changePasswordData (no exop_passwd, ppolicy control)");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by OpenLDAP changePasswordData without exop_passwd (no exop_passwd, ppolicy control)");
    }

    public function test_activedirectory_islocked_locked_forever(): void
    {
        $ad_date = ((int)time() + 11644473600) * 10000000;
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                 ];

        $isLocked = (new Ltb\Directory\ActiveDirectory)->isLocked($entry, array('lockout_duration' => 0));
        $this->assertTrue($isLocked, "Account should be locked forever");
    }

    public function test_activedirectory_islocked_not_locked(): void
    {
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => null
                    ]
                 ];

        $isLocked = (new Ltb\Directory\ActiveDirectory)->isLocked($entry, array('lockout_duration' => 0));
        $this->assertFalse($isLocked, "Account should not be locked");
    }

    public function test_activedirectory_islocked_still_locked(): void
    {
        $ad_date = ((int)time() + 11644473600) * 10000000;
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                ];

        $isLocked = (new Ltb\Directory\ActiveDirectory)->isLocked($entry, array('lockout_duration' => 86400));
        $this->assertTrue($isLocked, "Account should be still locked");
    }

    public function test_activedirectory_islocked_no_more_locked(): void
    {
        $ad_date = ((int)time() - 864000 + 11644473600) * 10000000;
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                 ];

        $isLocked = (new Ltb\Directory\ActiveDirectory)->isLocked($entry, array('lockout_duration' => 86400));
        $this->assertFalse($isLocked, "Account should no more be locked");
    }

    public function test_activedirectory_getlockdate_empty(): void
    {
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => null,
                    ]
                 ];

        $getLockDate = (new Ltb\Directory\ActiveDirectory)->getLockDate($entry, null);
        $this->assertNull($getLockDate, "Lock date should be null");
    }

    public function test_activedirectory_getlockdate_notempty(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => $ad_date,
                    ]
                 ];


        $getLockDate = (new Ltb\Directory\ActiveDirectory)->getLockDate($entry, null);
        $this->assertInstanceOf("DateTime", $getLockDate, "Lock date should be a PHP DateTime object");
        $this->assertEquals($dt->format("Y/m/d - h:i:s"), $getLockDate->format("Y/m/d - h:i:s"), "Lock date is correct");
    }

    public function test_activedirectory_getunlockdate_noduration(): void
    {
        $ad_date = ((int)time() + 11644473600) * 10000000;
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => $ad_date,
                    ]
                 ];


        $unlockDate = (new Ltb\Directory\ActiveDirectory)->getUnlockDate($entry, array('lockout_duration' => 0));
        $this->assertNull($unlockDate, "Unock date should be null");
    }

    public function test_activedirectory_getunlockdate_duration(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'lockouttime' => [
                        'count' => 1,
                        0 => $ad_date,
                    ]
                 ];

        $unlockDate = (new Ltb\Directory\ActiveDirectory)->getUnlockDate($entry, array('lockout_duration' => 86400));
        $this->assertInstanceOf("DateTime", $unlockDate, "Unlock date should be a PHP DateTime object");
        $this->assertEquals($dt->modify("+1 day")->format("Y/m/d - h:i:s"), $unlockDate->format("Y/m/d - h:i:s"), "Unlock date is correct");
    }

    public function test_activedirectory_isexpired_expired(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->modify("-1 month")->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                 ];

        $isPasswordExpired = (new Ltb\Directory\ActiveDirectory)->isPasswordExpired($entry, array('password_max_age' => 86400));
        $this->assertTrue($isPasswordExpired, "Password should be expired");
    }

    public function test_activedirectory_isexpired_not_expired(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->modify("-1 hour")->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                 ];

        $isPasswordExpired = (new Ltb\Directory\ActiveDirectory)->isPasswordExpired($entry, array('password_max_age' => 86400));
        $this->assertFalse($isPasswordExpired, "Password should not be expired");
    }

    public function test_activedirectory_getpasswordexpirationdate_empty(): void
    {
        $entry = [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => null,
                    ]
                 ];

        $getPasswordExpirationDate = (new Ltb\Directory\ActiveDirectory)->getPasswordExpirationDate($entry, null);
        $this->assertNull($getPasswordExpirationDate, "Password expiration date should be null");
    }

    public function test_activedirectory_getpasswordexpirationdate_notempty(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => $ad_date,
                    ]
                 ];

        $passwordExpirationDate = (new Ltb\Directory\ActiveDirectory)->getPasswordExpirationDate($entry, array('password_max_age' => 86400));
        $this->assertInstanceOf("DateTime", $passwordExpirationDate, "Password expiration date should be a PHP DateTime object");
        $this->assertEquals($dt->modify("+1 day")->format("Y/m/d - h:i:s"), $passwordExpirationDate->format("Y/m/d - h:i:s"), "Password expiration date is correct");
    }

    public function test_activedirectory_reset_true(): void
    {
        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');
        $phpLDAPMock->shouldreceive([
            'ldap_read' => null,
            'ldap_errno' => 0,
            'ldap_get_entries' => [
                'count' => 1,
                0 => [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => 0,
                    ]
                ]
            ]
        ]);

        $reset = (new Ltb\Directory\ActiveDirectory)->resetAtNextConnection(null, null);
        $this->assertTrue($reset, "Reset should be true");
    }

    public function test_activedirectory_reset_false(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->getTimestamp() + 11644473600) * 10000000;
        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');
        $phpLDAPMock->shouldreceive([
            'ldap_read' => null,
            'ldap_errno' => 0,
            'ldap_get_entries' => [
                'count' => 1,
                0 => [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => $ad_date,
                    ]
                ]
            ]
        ]);

        $reset = (new Ltb\Directory\ActiveDirectory)->resetAtNextConnection(null, null);
        $this->assertFalse($reset, "Reset should be false");
    }

    public function test_activedirectory_reset_false_empty(): void
    {
        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');
        $phpLDAPMock->shouldreceive([
            'ldap_read' => null,
            'ldap_errno' => 0,
            'ldap_get_entries' => [
                'count' => 1,
                0 => [
                    'pwdlastset' => [
                        'count' => 1,
                        0 => null,
                    ]
                ]
            ]
        ]);

        $reset = (new Ltb\Directory\ActiveDirectory)->resetAtNextConnection(null, null);
        $this->assertFalse($reset, "Reset should be false");
    }

    public function test_activedirectory_isenabled_true(): void
    {
        $entry = [
                    'useraccountcontrol' => [
                        'count' => 1,
                        0 => 512
                    ]
                 ];

        $accountEnabled = (new Ltb\Directory\ActiveDirectory)->isAccountEnabled($entry);
        $this->assertTrue($accountEnabled, "Account should be enabled");
    }

    public function test_activedirectory_isenabled_false(): void
    {
        $entry = [
                    'useraccountcontrol' => [
                        'count' => 1,
                        0 => 514,
                    ]
                 ];

        $accountEnabled = (new Ltb\Directory\ActiveDirectory)->isAccountEnabled($entry);
        $this->assertFalse($accountEnabled, "Account should be disabled");
    }

    public function test_openldap_isenabled_true(): void
    {
        $entry = [
                     'count' => 0,
                     'dn' => 'uid=test,ou=people,dc=my-domain,dc=com'
                 ];

        $accountEnabled = (new Ltb\Directory\OpenLDAP)->isAccountEnabled($entry);
        $this->assertTrue($accountEnabled, "OpenLDAP account should be enabled");
    }

    public function test_openldap_isenabled_false(): void
    {

        $entry = [
                     'pwdaccountdisabled' =>
                     [
                         'count' => 1,
                         0 => '00000101000000Z',
                     ],
                     0 => 'pwdaccountdisabled',
                     'count' => 1,
                     'dn' => 'uid=test,ou=people,dc=my-domain,dc=com'
                 ];

        $accountEnabled = (new Ltb\Directory\OpenLDAP)->isAccountEnabled($entry);
        $this->assertFalse($accountEnabled, "OpenLDAP account should be disabled");
    }

    public function test_openldap_enable_account_ok(): void
    {
        $ldap = "ldap_connection";
        $dn = "cn=dummy,dc=my-domain,dc=com";
        $update_result = "update_result";

        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');

        $phpLDAPMock->shouldreceive('ldap_mod_replace')
                    ->with($ldap, $dn, [ 'pwdAccountDisabled' => [] ])
                    ->andReturn($update_result);

        $phpLDAPMock->shouldreceive('ldap_errno')
                    ->with($ldap)
                    ->andReturn(0);

        $enableAccountResult = (new Ltb\Directory\OpenLDAP)->enableAccount($ldap, $dn);
        $this->assertTrue($enableAccountResult, "Error while enabling OpenLDAP account");
    }

    public function test_openldap_enable_account_ko(): void
    {
        $ldap = "ldap_connection";
        $dn = "cn=dummy,dc=my-domain,dc=com";
        $update_result = "update_result";

        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');

        $phpLDAPMock->shouldreceive('ldap_mod_replace')
                    ->with($ldap, $dn, [ 'pwdAccountDisabled' => [] ])
                    ->andReturn($update_result);

        $phpLDAPMock->shouldreceive('ldap_errno')
                    ->with($ldap)
                    ->andReturn(50);

        $phpLDAPMock->shouldreceive('ldap_error')
                    ->with($ldap)
                    ->andReturn("Insufficient rights");

        $enableAccountResult = (new Ltb\Directory\OpenLDAP)->enableAccount($ldap, $dn);
        $this->assertFalse($enableAccountResult, "Should have encountered error while enabling OpenLDAP account");
    }

    public function test_openldap_disable_account_ok(): void
    {
        $ldap = "ldap_connection";
        $dn = "cn=dummy,dc=my-domain,dc=com";
        $update_result = "update_result";

        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');

        $phpLDAPMock->shouldreceive('ldap_mod_replace')
                    ->with(
                              $ldap,
                              $dn,
                              \Mockery::on(function ($mod) {
                                  if( preg_match('/^[0-9]{14}Z$/', $mod['pwdAccountDisabled'][0]) )
                                      return true;
                                  else
                                      return false;
                              })
                          )
                    ->andReturn($update_result);

        $phpLDAPMock->shouldreceive('ldap_errno')
                    ->with($ldap)
                    ->andReturn(0);

        $disableAccountResult = (new Ltb\Directory\OpenLDAP)->disableAccount($ldap, $dn);
        $this->assertTrue($disableAccountResult, "Error while disabling OpenLDAP account");
    }

    public function test_openldap_disable_account_ko(): void
    {
        $ldap = "ldap_connection";
        $dn = "cn=dummy,dc=my-domain,dc=com";
        $update_result = "update_result";

        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');

        $phpLDAPMock->shouldreceive('ldap_mod_replace')
                    ->with(
                              $ldap,
                              $dn,
                              \Mockery::on(function ($mod) {
                                  if( preg_match('/^[0-9]{14}Z$/', $mod['pwdAccountDisabled'][0]) )
                                      return true;
                                  else
                                      return false;
                              })
                          )
                    ->andReturn($update_result);

        $phpLDAPMock->shouldreceive('ldap_errno')
                    ->with($ldap)
                    ->andReturn(50);

        $phpLDAPMock->shouldreceive('ldap_error')
                    ->with($ldap)
                    ->andReturn("Insufficient rights");

        $disableAccountResult = (new Ltb\Directory\OpenLDAP)->disableAccount($ldap, $dn);
        $this->assertFalse($disableAccountResult, "Should have encountered error while disabling OpenLDAP account");
    }

    public function test_activedirectory_isvalid_nodate(): void
    {
        $isAccountValid = (new Ltb\Directory\ActiveDirectory)->isAccountValid(null, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_activedirectory_isvalid_enddate_before(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->modify("-1 week")->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'accountexpires' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\ActiveDirectory)->isAccountValid($entry, null);
        $this->assertFalse($isAccountValid, "Account should not be valid");
    }

    public function test_activedirectory_isvalid_enddate_after(): void
    {
        $dt = new DateTime;
        $ad_date = ((int)$dt->modify("+1 week")->getTimestamp() + 11644473600) * 10000000;
        $entry = [
                    'accountexpires' => [
                        'count' => 1,
                        0 => $ad_date
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\ActiveDirectory)->isAccountValid($entry, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_activedirectory_isvalid_enddate_zero(): void
    {
        $entry = [
                    'accountexpires' => [
                        'count' => 1,
                        0 => 0,
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\ActiveDirectory)->isAccountValid($entry, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_activedirectory_isvalid_enddate_full(): void
    {
        $entry = [
                    'accountexpires' => [
                        'count' => 1,
                        0 => 9223372036854775807
                    ]
                 ];

        $isAccountValid = (new Ltb\Directory\ActiveDirectory)->isAccountValid($entry, null);
        $this->assertTrue($isAccountValid, "Account should be valid");
    }

    public function test_activedirectory_compute_password(): void
    {
        $ldapInstance = null;
        $dn = "";
        $password = "secret";
        $hash = null;
        $hash_options = null;
        $use_exop_passwd = null;

        $password = (new Ltb\Directory\ActiveDirectory)->computePassword(
                        $ldapInstance,
                        $dn,
                        $password,
                        $hash,
                        $hash_options,
                        $use_exop_passwd);
        $this->assertEquals(
            "\x22\x00\x73\x00\x65\x00\x63\x00\x72\x00\x65\x00\x74\x00\x22\x00",
            $password,
            "invalid Active Directory computePassword value");
    }

    public function test_activedirectory_changePasswordData_change_as_user(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ), "description" => array( "desc1" ) );
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "user";
        $use_exop_passwd = true;
        $use_ppolicy_control = false;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[];

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $ldapInstanceMock->shouldreceive('change_ad_password_as_user')
                         ->with($dn, $oldpassword, $password)
                         ->andReturn( [ 0, "change_password_OK", null ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\ActiveDirectory)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by ActiveDirectory changePasswordData as user");
        $this->assertEquals( "change_password_OK", $error_msg, "bad error message sent by ActiveDirectory changePasswordData as user");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by ActiveDirectory changePasswordData as user");
    }

    #[RunInSeparateProcess]
    public function test_activedirectory_changePasswordData_use_exop_passwd(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ),
                                           "description" => array( "desc1" ) );
        $expectedUserData = $userdata;
        $expectedUserData["lockoutTime"] = 0;
        $expectedUserData["pwdLastSet"] = 0;
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "manager";
        $use_exop_passwd = true;
        $use_ppolicy_control = false;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[ "force_unlock" => 1, "force_pwd_change" => 1 ];

        $ltbPasswordMock = Mockery::mock('overload:\Ltb\Password');

        $ltbPasswordMock->shouldreceive('set_ad_data')
                    ->with($userdata, $ldap_options, $password)
                    ->andReturn($expectedUserData);

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $ldapInstanceMock->shouldreceive('change_password_with_exop')
                         ->with($dn, $oldpassword, $password, $use_ppolicy_control)
                         ->andReturn( [ 0, "change_password_OK", null ] );

        $ldapInstanceMock->shouldreceive('modify_attributes')
                         ->with($dn, $expectedUserData)
                         ->andReturn( [ 0, "change_password_OK" ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\ActiveDirectory)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by ActiveDirectory changePasswordData with use_exop_passwd");
        $this->assertEquals( "change_password_OK", $error_msg, "bad error message sent by ActiveDirectory changePasswordData with use_exop_passwd");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by ActiveDirectory changePasswordData with use_exop_passwd");
    }

    #[RunInSeparateProcess]
    public function test_activedirectory_changePasswordData_no_exop_passwd_with_ppolicy(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ),
                                           "description" => array( "desc1" ) );
        $expectedUserData = $userdata;
        $expectedUserData["lockoutTime"] = 0;
        $expectedUserData["pwdLastSet"] = 0;
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "manager";
        $use_exop_passwd = false;
        $use_ppolicy_control = true;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[ "force_unlock" => 1, "force_pwd_change" => 1 ];

        $ltbPasswordMock = Mockery::mock('overload:\Ltb\Password');

        $ltbPasswordMock->shouldreceive('set_ad_data')
                    ->with($userdata, $ldap_options, $password)
                    ->andReturn($expectedUserData);

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $ldapInstanceMock->shouldreceive('modify_attributes_using_ppolicy')
                         ->with($dn, $expectedUserData)
                         ->andReturn( [ 0, "change_password_OK", null ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\ActiveDirectory)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by ActiveDirectory changePasswordData (no exop_passwd, with ppolicy)");
        $this->assertEquals( "change_password_OK", $error_msg, "bad error message sent by ActiveDirectory changePasswordData (no exop_passwd, with ppolicy)");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by ActiveDirectory changePasswordData (no exop_passwd, with ppolicy)");
    }

    #[RunInSeparateProcess]
    public function test_activedirectory_changePasswordData_no_exop_passwd_no_ppolicy(): void
    {
        $dn = "uid=user,ou=people,dc=my-domain,dc=com";
        $userdata = array( "mail"=> array( "user@domain.com" ),
                                           "description" => array( "desc1" ) );
        $expectedUserData = $userdata;
        $expectedUserData["lockoutTime"] = 0;
        $expectedUserData["pwdLastSet"] = 0;
        $password = "newsecret";
        $oldpassword = "oldsecret";
        $who_change_password = "manager";
        $use_exop_passwd = false;
        $use_ppolicy_control = false;
        $custom_pwd_field_mode = false;
        $custom_pwd_attribute = "";
        $ldap_options=[ "force_unlock" => 1, "force_pwd_change" => 1 ];

        $ltbPasswordMock = Mockery::mock('overload:\Ltb\Password');

        $ltbPasswordMock->shouldreceive('set_ad_data')
                    ->with($userdata, $ldap_options, $password)
                    ->andReturn($expectedUserData);

        $ldapInstanceMock = Mockery::mock("Ltb\Ldap");

        $ldapInstanceMock->shouldreceive('modify_attributes')
                         ->with($dn, $expectedUserData)
                         ->andReturn( [ 0, "change_password_OK" ] );

        list($error_code, $error_msg, $ppolicy_error_code) = (new Ltb\Directory\ActiveDirectory)->changePasswordData(
                        $ldapInstanceMock,
                        $dn,
                        $userdata,
                        $password,
                        $oldpassword,
                        $who_change_password,
                        $use_exop_passwd,
                        $use_ppolicy_control,
                        $custom_pwd_field_mode,
                        $custom_pwd_attribute,
                        $ldap_options
                    );
        $this->assertEquals( 0, $error_code, "bad error code sent by ActiveDirectory changePasswordData (no exop_passwd, no ppolicy)");
        $this->assertEquals( "change_password_OK", $error_msg, "bad error message sent by ActiveDirectory changePasswordData (no exop_passwd, no ppolicy)");
        $this->assertEquals( null, $ppolicy_error_code, "bad ppolicy error code sent by ActiveDirectory changePasswordData (no exop_passwd, no ppolicy)");
    }

    #[RunInSeparateProcess]
    public function test_activedirectory_getAccountStatus_password_must_change(): void
    {
        $ldap = "ldap_connection";
        $errno = 49;
        $extended_error = "dummy, dummy, Data: 773";
        $ldap_error = "ldap_error";

        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');

        $phpLDAPMock->shouldreceive('ldap_get_option')
                    ->with($ldap, 0x0032,  $extended_error )
                    ->andReturn(true);

        $phpLDAPMock->shouldreceive('ldap_error')
                    ->with($ldap)
                    ->andReturn($ldap_error);

        #$this->expectErrorLog();

        $accountStatus = (new Ltb\Directory\ActiveDirectory)->getAccountStatus(
            $ldap,
            $errno,
            $extended_error
        );
        $this->assertEquals( true, $accountStatus['PASSWORD_MUST_CHANGE'],
                             "bad account status (PASSWORD_MUST_CHANGE should be set)");
        $this->assertEquals( false, isset($accountStatus['PASSWORD_EXPIRED']),
                             "bad account status (PASSWORD_EXPIRED should be unset)");
        $this->assertEquals( $extended_error, $accountStatus['EXTENDED_ERROR'],
                             "bad extended_error");
        $this->assertEquals( $ldap_error, $accountStatus['LDAP_ERROR'],
                             "bad ldap_error");
    }

    #[RunInSeparateProcess]
    public function test_activedirectory_getAccountStatus_password_expired(): void
    {
        $ldap = "ldap_connection";
        $errno = 49;
        $extended_error = "dummy, dummy, Data: 532";
        $ldap_error = "ldap_error";

        $phpLDAPMock = Mockery::mock('overload:Ltb\PhpLDAP');

        $phpLDAPMock->shouldreceive('ldap_get_option')
                    ->with($ldap, 0x0032,  $extended_error )
                    ->andReturn(true);

        $phpLDAPMock->shouldreceive('ldap_error')
                    ->with($ldap)
                    ->andReturn($ldap_error);

        #$this->expectErrorLog();

        $accountStatus = (new Ltb\Directory\ActiveDirectory)->getAccountStatus(
            $ldap,
            $errno,
            $extended_error
        );
        $this->assertEquals( false, isset($accountStatus['PASSWORD_MUST_CHANGE']),
                             "bad account status (PASSWORD_MUST_CHANGE should be unset)");
        $this->assertEquals( true, $accountStatus['PASSWORD_EXPIRED'],
                             "bad account status (PASSWORD_EXPIRED should be set)");
        $this->assertEquals( $extended_error, $accountStatus['EXTENDED_ERROR'],
                             "bad extended_error");
        $this->assertEquals( $ldap_error, $accountStatus['LDAP_ERROR'],
                             "bad ldap_error");
    }

}
