<?php

/*
 * @copyright NetMonsters <team@netmonsters.ru>
 * @link http://netmonsters.ru
 * @package Majestic
 * @subpackage UnitTests
 * @since 2011-11-4
 * 
 * Unit tests for MySQLiStatement class
 */

require_once dirname(__FILE__) . '/../../Registry.php';
require_once dirname(__FILE__) . '/../../Config.php';
require_once dirname(__FILE__) . '/../../util/profiler/CommandProfiler.php';
require_once dirname(__FILE__) . '/../../util/profiler/Profiler.php';
require_once dirname(__FILE__) . '/../../model/Db.php';
require_once dirname(__FILE__) . '/../../model/DbDriver.php';
require_once dirname(__FILE__) . '/../../model/DbStatement.php';
require_once dirname(__FILE__) . '/../../model/MySQLiStatement.php';
require_once dirname(__FILE__) . '/../../exception/GeneralException.php';

class MySQLiStatementTest extends PHPUnit_Framework_TestCase
{


    private $driver;

    private $stmt;

    private $sql;

    private $testData = array(
        array('one' => 11, 'two' => 12),
        array('one' => 21, 'two' => 22),
        array('one' => 31, 'two' => 32),
    );


    public function run(PHPUnit_Framework_TestResult $result = NULL)
    {
        $this->setPreserveGlobalState(false);
        return parent::run($result);
    }

    public function setUp()
    {
        if (!isset($this->stmt)) {
            $this->driver = $this->getMockBuilder('DbDriverMock')
                    ->disableOriginalConstructor()
                    ->setMethods(array('quote', 'getConnection'))
                    ->getMock();
            $this->sql = 'SELECT * :place FROM :place AND :new';
            $this->stmt = new MySQLiStatement($this->driver, $this->sql);
        }
    }

    public function testBindParam()
    {
        $val = $this->getMockBuilder('DbExpr')
                ->disableOriginalConstructor()
                ->getMock();
        $this->assertFalse($this->stmt->bindParam('var', $val));
        $this->assertTrue($this->stmt->bindParam('place', $val));
    }

    public function testBindParamExceptionParam()
    {
        $val = 1;
        $this->setExpectedException('GeneralException', 'Placeholder must be an integer or string');
        $this->stmt->bindParam(array(), $val);
    }

    public function testBindParamExceptionWrongObject()
    {
        $val = $this->getMock('NotDbExpr');
        $this->setExpectedException('GeneralException', 'Objects excepts DbExpr not allowed.');
        $this->stmt->bindParam('paa', $val);
    }

    /**
     * @runInSeparateProcess
     */
    public function testExecute()
    {
        Config::set('PROFILER_DETAILS', false);

        $this->driver
                ->expects($this->any())
                ->method('quote')
                ->with($this->anything())
                ->will($this->returnCallback(array($this, 'driverQuote')));

        $this->setDriverGetConnectionMethod();

        $result = $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertTrue($result);
    }

    /**
     * @runInSeparateProcess
     */
    public function testExecuteNoPlaceholders()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();

        $this->sql = 'PLAIN SQL';
        $result = $this->stmt->execute(array());
        $this->assertTrue($result);
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testFetchNoResult()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->assertFalse($this->stmt->fetch());
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testDriverExecuteNoResult()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionWrongResultMethod();
        $this->setExpectedException('GeneralException', 'ERROR');
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testFetch()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertSame('OBJECT', $this->stmt->fetch());
        $this->assertSame('ARRAY', $this->stmt->fetch(Db::FETCH_NUM));
        $this->assertSame('ASSOC', $this->stmt->fetch(Db::FETCH_ASSOC));
        $this->assertSame('ARRAY', $this->stmt->fetch(Db::FETCH_BOTH));
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testFetchObject()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertSame('OBJECT', $this->stmt->fetchObject());
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testFetchPairs()
    {
        Config::set('PROFILER_DETAILS', false);

        $resultMock = $this->getMockBuilder('mysqli_result')
                ->disableOriginalConstructor()
                ->setMethods(array('fetch_array', 'close'))
                ->setMockClassName('Mysqli_Result_Mock')
                ->getMock();
        $resultMock
                ->expects($this->at(0))
                ->method('fetch_array')
                ->will($this->returnValue(array('pair', 'value')));
        $resultMock
                ->expects($this->at(1))
                ->method('fetch_array')
                ->will($this->returnValue(false));
        $resultMock
                ->expects($this->any())
                ->method('close')
                ->will($this->returnValue(TRUE));

        $mysqliMock = $this->getMock('MysqliDrvr', array('query'));
        $mysqliMock
                ->expects($this->any())
                ->method('query')
                ->with($this->anything())
                ->will($this->returnValue($resultMock));

        $this->driver
                ->expects($this->any())
                ->method('getConnection')
                ->will($this->returnValue($mysqliMock));

        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertSame(array('pair' => 'value'), $this->stmt->fetchPairs());
    }

    /**
     * @runInSeparateProcess
     */
    public function testFetchWithDebug()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod();
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertSame('OBJECT', $this->stmt->fetch());
        $this->assertContains('Queries: 1', Profiler::getInstance()->getCli());
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testClose()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', false);
        $this->assertAttributeEquals(null, 'result', $this->stmt);
        $this->setDriverGetConnectionMethod();
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertAttributeNotEquals(null, 'result', $this->stmt);
        $this->stmt->close();
        $this->assertAttributeEquals(null, 'result', $this->stmt);
        $this->assertContains('Queries not counted.', Profiler::getInstance()->getCli());
    }

    /**
     *  @group MySQL
     */
    public function testAffectedRows()
    {
        $mysqliMock = $this->getMockBuilder('MysqliDrvr');
        $mysqliMock->affected_rows = 'AFFECTED_ROWS';

        $this->driver
                ->expects($this->any())
                ->method('getConnection')
                ->will($this->returnValue($mysqliMock));

        $this->assertSame('AFFECTED_ROWS', $this->stmt->affectedRows());
    }

    /**
     * @group MySQL
     */
    public function testNumRowsNoResult()
    {
        $this->assertFalse($this->stmt->numRows());
    }

    /**
     * @runInSeparateProcess
     * @TODO: exception just for code coverage - could not mock mysqli properties
     * @group MySQL
     */
    public function testNumRows()
    {
        $this->markTestSkipped('Unable to execute a method or access a property of an incomplete object.');
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));
        $this->assertNull($this->stmt->numRows());
    }

    /**
     * @runInSeparateProcess
     * @group MySQL
     */
    public function testFetchInvalidMode()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();
        $this->stmt->execute(array('place' => 'place_val', 'new' => 'new_val'));

        $this->setExpectedException('GeneralException');

        $this->stmt->fetch(324);
    }

    private function setDriverGetConnectionMethod()
    {
        $resultMock = $this->getMockBuilder('mysqli_result')
                ->disableOriginalConstructor()
                ->setMethods(array('fetch_object', 'fetch_array', 'fetch_assoc', 'close'))
                ->setMockClassName('Mysqli_Result_Mock')
                ->getMock();
        $resultMock
                ->expects($this->any())
                ->method('fetch_object')
                ->will($this->returnValue('OBJECT'));
        $resultMock
                ->expects($this->any())
                ->method('fetch_array')
                ->will($this->returnValue('ARRAY'));
        $resultMock
                ->expects($this->any())
                ->method('fetch_assoc')
                ->will($this->returnValue('ASSOC'));
        $resultMock
                ->expects($this->any())
                ->method('close')
                ->will($this->returnValue(TRUE));

        $mysqliMock = $this->getMock('MysqliDrvr', array('query'));
        $mysqliMock
                ->expects($this->any())
                ->method('query')
                ->with($this->anything())
                ->will($this->returnValue($resultMock));

        $this->driver
                ->expects($this->any())
                ->method('getConnection')
                ->will($this->returnValue($mysqliMock));

        return $this;
    }

    private function setDriverGetConnectionWrongResultMethod()
    {
        $mysqliMock = $this->getMock('MysqliDrvr', array('query'));
        $mysqliMock
                ->expects($this->any())
                ->method('query')
                ->with($this->anything())
                ->will($this->returnValue(false));
        $mysqliMock->error = 'ERROR';
        $mysqliMock->errno = 0;

        $this->driver
                ->expects($this->any())
                ->method('getConnection')
                ->will($this->returnValue($mysqliMock));

        return $this;
    }

    public function driverQuote($val)
    {
        return $val;
    }

    public function dbStatementAssemble($val)
    {
        return $val;
    }

}