<?php


/**
 * This test ensures that the SimpleSAML_Database class can properly
 * query a database.
 *
 * It currently uses sqlite to test, but an alternate config.php file
 * should be created for test cases to ensure that it will work
 * in an environment.
 *
 * @author Tyler Antonio, University of Alberta. <tantonio@ualberta.ca>
 * @package SimpleSAMLphp
 */
class SimpleSAML_DatabaseTest extends PHPUnit_Framework_TestCase
{

    /**
     * @var SimpleSAML_Configuration
     */
    protected $config;

    /**
     * @var SimpleSAML\Database
     */
    protected $db;


    /**
     * Make protected functions available for testing
     *
     * @param string $getMethod The method to get.
     * @requires PHP 5.3.2
     *
     * @return mixed The method itself.
     */
    protected static function getMethod($getMethod)
    {
        $class = new ReflectionClass('SimpleSAML\Database');
        $method = $class->getMethod($getMethod);
        $method->setAccessible(true);
        return $method;
    }


    /**
     * @covers SimpleSAML\Database::getInstance
     * @covers SimpleSAML\Database::generateInstanceId
     * @covers SimpleSAML\Database::__construct
     * @covers SimpleSAML\Database::connect
     */
    public function setUp()
    {
        $config = array(
            'database.dsn'        => 'sqlite::memory:',
            'database.username'   => null,
            'database.password'   => null,
            'database.prefix'     => 'phpunit_',
            'database.persistent' => true,
            'database.slaves'     => array(),
        );

        $this->config = new SimpleSAML_Configuration($config, "test/SimpleSAML/DatabaseTest.php");

        // Ensure that we have a functional configuration class
        $this->assertInstanceOf('SimpleSAML_Configuration', $this->config);
        $this->assertEquals($config['database.dsn'], $this->config->getString('database.dsn'));

        $this->db = SimpleSAML\Database::getInstance($this->config);

        // Ensure that we have a functional database class.
        $this->assertInstanceOf('SimpleSAML\Database', $this->db);
    }


    /**
     * @covers SimpleSAML\Database::getInstance
     * @covers SimpleSAML\Database::generateInstanceId
     * @covers SimpleSAML\Database::__construct
     * @covers SimpleSAML\Database::connect
     * @expectedException Exception
     * @test
     */
    public function connectionFailure()
    {
        $config = array(
            'database.dsn'        => 'mysql:host=localhost;dbname=saml',
            'database.username'   => 'notauser',
            'database.password'   => 'notausersinvalidpassword',
            'database.prefix'     => 'phpunit_',
            'database.persistent' => true,
            'database.slaves'     => array(),
        );

        $this->config = new SimpleSAML_Configuration($config, "test/SimpleSAML/DatabaseTest.php");
        $db = SimpleSAML\Database::getInstance($this->config);
    }


    /**
     * @covers SimpleSAML\Database::getInstance
     * @covers SimpleSAML\Database::generateInstanceId
     * @covers SimpleSAML\Database::__construct
     * @covers SimpleSAML\Database::connect
     * @test
     */
    public function instances()
    {
        $config = array(
            'database.dsn'        => 'sqlite::memory:',
            'database.username'   => null,
            'database.password'   => null,
            'database.prefix'     => 'phpunit_',
            'database.persistent' => true,
            'database.slaves'     => array(),
        );
        $config2 = array(
            'database.dsn'        => 'sqlite::memory:',
            'database.username'   => null,
            'database.password'   => null,
            'database.prefix'     => 'phpunit2_',
            'database.persistent' => true,
            'database.slaves'     => array(),
        );

        $config1 = new SimpleSAML_Configuration($config, "test/SimpleSAML/DatabaseTest.php");
        $config2 = new SimpleSAML_Configuration($config2, "test/SimpleSAML/DatabaseTest.php");
        $config3 = new SimpleSAML_Configuration($config, "test/SimpleSAML/DatabaseTest.php");

        $db1 = SimpleSAML\Database::getInstance($config1);
        $db2 = SimpleSAML\Database::getInstance($config2);
        $db3 = SimpleSAML\Database::getInstance($config3);

        $generateInstanceId = self::getMethod('generateInstanceId');

        $instance1 = $generateInstanceId->invokeArgs($db1, array($config1));
        $instance2 = $generateInstanceId->invokeArgs($db2, array($config2));
        $instance3 = $generateInstanceId->invokeArgs($db3, array($config3));

        // Assert that $instance1 and $instance2 have different instance ids
        $this->assertNotEquals(
            $instance1,
            $instance2,
            "Database instances should be different, but returned the same id"
        );
        // Assert that $instance1 and $instance3 have identical instance ids
        $this->assertEquals(
            $instance1,
            $instance3,
            "Database instances should have the same id, but returned different id"
        );

        // Assert that $db1 and $db2 are different instances
        $this->assertNotEquals(
            spl_object_hash($db1),
            spl_object_hash($db2),
            "Database instances should be different, but returned the same spl_object_hash"
        );
        // Assert that $db1 and $db3 are identical instances
        $this->assertEquals(
            spl_object_hash($db1),
            spl_object_hash($db3),
            "Database instances should be the same, but returned different spl_object_hash"
        );
    }


    /**
     * @covers SimpleSAML\Database::getInstance
     * @covers SimpleSAML\Database::generateInstanceId
     * @covers SimpleSAML\Database::__construct
     * @covers SimpleSAML\Database::connect
     * @covers SimpleSAML\Database::getSlave
     * @test
     */
    public function slaves()
    {
        $getSlave = self::getMethod('getSlave');

        $master = spl_object_hash(PHPUnit_Framework_Assert::readAttribute($this->db, 'dbMaster'));
        $slave = spl_object_hash($getSlave->invokeArgs($this->db, array()));

        $this->assertTrue(($master == $slave), "getSlave should have returned the master database object");

        $config = array(
            'database.dsn'        => 'sqlite::memory:',
            'database.username'   => null,
            'database.password'   => null,
            'database.prefix'     => 'phpunit_',
            'database.persistent' => true,
            'database.slaves'     => array(
                array(
                    'dsn'      => 'sqlite::memory:',
                    'username' => null,
                    'password' => null,
                ),
            ),
        );

        $sspConfiguration = new SimpleSAML_Configuration($config, "test/SimpleSAML/DatabaseTest.php");
        $msdb = SimpleSAML\Database::getInstance($sspConfiguration);

        $slaves = PHPUnit_Framework_Assert::readAttribute($msdb, 'dbSlaves');
        $gotSlave = spl_object_hash($getSlave->invokeArgs($msdb, array()));

        $this->assertEquals(
            spl_object_hash($slaves[0]),
            $gotSlave,
            "getSlave should have returned a slave database object"
        );
    }


    /**
     * @covers SimpleSAML\Database::applyPrefix
     * @test
     */
    public function prefix()
    {
        $prefix = $this->config->getString('database.prefix');
        $table = "saml20_idp_hosted";
        $pftable = $this->db->applyPrefix($table);

        $this->assertEquals($prefix.$table, $pftable, "Did not properly apply the table prefix");
    }


    /**
     * @covers SimpleSAML\Database::write
     * @covers SimpleSAML\Database::read
     * @covers SimpleSAML\Database::exec
     * @covers SimpleSAML\Database::query
     * @test
     */
    public function querying()
    {
        $table = $this->db->applyPrefix("sspdbt");
        $this->assertEquals($this->config->getString('database.prefix')."sspdbt", $table);

        $this->db->write(
            "CREATE TABLE IF NOT EXISTS $table (ssp_key INT(16) NOT NULL, ssp_value TEXT NOT NULL)",
            false
        );

        $query1 = $this->db->read("SELECT * FROM $table");
        $this->assertEquals(0, $query1->fetch(), "Table $table is not empty when it should be.");

        $ssp_key = time();
        $ssp_value = md5(rand(0, 10000));
        $stmt = $this->db->write(
            "INSERT INTO $table (ssp_key, ssp_value) VALUES (:ssp_key, :ssp_value)",
            array('ssp_key' => array($ssp_key, PDO::PARAM_INT), 'ssp_value' => $ssp_value)
        );
        $this->assertEquals(1, $stmt, "Could not insert data into $table.");

        $query2 = $this->db->read("SELECT * FROM $table WHERE ssp_key = :ssp_key", array('ssp_key' => $ssp_key));
        $data = $query2->fetch();
        $this->assertEquals($data['ssp_value'], $ssp_value, "Inserted data doesn't match what is in the database");
    }


    /**
     * @covers SimpleSAML\Database::read
     * @covers SimpleSAML\Database::query
     * @expectedException Exception
     * @test
     */
    public function readFailure()
    {
        $table = $this->db->applyPrefix("sspdbt");
        $this->assertEquals($this->config->getString('database.prefix')."sspdbt", $table);

        $this->db->read("SELECT * FROM $table");
    }


    /**
     * @covers SimpleSAML\Database::write
     * @covers SimpleSAML\Database::exec
     * @expectedException Exception
     * @test
     */
    public function noSuchTable()
    {
        $this->db->write("DROP TABLE phpunit_nonexistent", false);
    }


    public function tearDown()
    {
        $table = $this->db->applyPrefix("sspdbt");
        $this->db->write("DROP TABLE IF EXISTS $table", false);

        unset($this->config);
        unset($this->db);
    }
}