<?php

/*
 * @copyright NetMonsters <team@netmonsters.ru>
 * @link http://netmonsters.ru
 * @package Majestic
 * @subpackage UnitTests
 * @since 2011-11-15
 * 
 * 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/MongoStatement.php';
require_once dirname(__FILE__) . '/../../exception/GeneralException.php';

class MongoStatementTest extends PHPUnit_Framework_TestCase
{


    private $driver;

    private $stmt;

    private $request;

    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('getConnection'))
                    ->getMock();
            $this->request = $this->getMockBuilder('MongoDbCommandMock')
                    ->disableOriginalConstructor()
                    ->setMethods(array('execute', 'bindParam', 'getInsertId', '__toString'))
                    ->getMock();
            $this->stmt = new MongoStatement($this->driver, $this->request);
        }
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testAffectedNumRowsNoResult()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->assertFalse($this->stmt->affectedRows());
        $this->assertFalse($this->stmt->numRows());

        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->any())
                ->method('execute')
                ->will($this->returnValue(array()));
        $this->stmt->execute();
        $this->assertFalse($this->stmt->affectedRows());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testAffectedNumRows()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('n' => 20, 'ok' => 1)));
        $this->stmt->execute();
        $this->assertEquals(20, $this->stmt->affectedRows());
    }

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

        $this->request = $this->getMockBuilder('InsertMongoCommand')
                ->disableOriginalConstructor()
                ->setMethods(array('execute', 'bindParam', 'getInsertId'))
                ->getMock();

        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('n' => 20, 'ok' => 1)));
        $this->request
                ->expects($this->once())
                ->method('getInsertId')
                ->will($this->returnValue('4b0rrs'));

        $this->stmt = new MongoStatement($this->driver, $this->request);

        $this->stmt->execute();
        $this->assertEquals('4b0rrs', $this->stmt->getInsertId());
    }

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

        $this->setDriverGetConnectionMethod()->setRequestExecuteMethod();
        $this->assertTrue($this->stmt->execute());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testExecuteNoResult()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->any())
                ->method('execute')
                ->will($this->returnValue(false));
        $this->setExpectedException('GeneralException', 'MongoDB request error.');
        $this->stmt->execute();
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testExecuteNoConnection()
    {
        Config::set('PROFILER_DETAILS', false);
        $this->driver
                ->expects($this->any())
                ->method('getConnection')
                ->will($this->returnValue(false));
        $this->setExpectedException('GeneralException', 'No connection to MongoDB server.');
        $this->stmt->execute();
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testExecuteWithDebug()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod()->setRequestExecuteMethod();
        $this->assertTrue($this->stmt->execute());
        $this->assertEquals(10, $this->stmt->numRows());
        $this->assertContains('Queries: 1', Profiler::getInstance()->getCli());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testBindParam()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);

        $this->request
                ->expects($this->once())
                ->method('bindParam')
                ->will($this->returnValue(true));

        $this->setDriverGetConnectionMethod()->setRequestExecuteMethod();
        $this->assertTrue($this->stmt->execute(array('one' => 'two')));

        $this->assertAttributeNotEquals(null, 'result', $this->stmt);
        $this->stmt->close();
        $this->assertAttributeEquals(null, 'result', $this->stmt);
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testFetch()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', false);
        $this->assertFalse($this->stmt->fetch());

        $this->setDriverGetConnectionMethod()->setRequestForFetch();

        $this->stmt->execute();
        $result = $this->stmt->fetch();
        $this->assertEquals('prev', $result->next);
        $this->assertFalse($this->stmt->fetch());
        $this->assertContains('Queries not counted.', Profiler::getInstance()->getCli());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testFetchWithInitialArray()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->assertFalse($this->stmt->fetch());

        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('retval' => 'val')));

        $this->stmt->execute();
        $this->assertEquals('val', $this->stmt->fetch());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testFetchAssocFromCursor()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->assertFalse($this->stmt->fetch());

        $this->setDriverGetConnectionMethod()->setRequestForFetch();

        $this->stmt->execute();
        $result = $this->stmt->fetch(Db::FETCH_ASSOC);
        $this->assertEquals('prev', $result['next']);
        $this->assertEquals(array(), $this->stmt->fetch(Db::FETCH_ASSOC));
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testFetchAssocFromArray()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->assertFalse($this->stmt->fetch());

        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('some' => 'val')));

        $this->stmt->execute();
        $result = $this->stmt->fetch(Db::FETCH_ASSOC);
        $this->assertEquals('val', $result['some']);
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testFetchWrongMode()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->assertFalse($this->stmt->fetch());

        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('some' => 'val')));

        $this->stmt->execute();
        $this->setExpectedException('GeneralException', 'Invalid fetch mode "222" specified');
        $this->stmt->fetch(222);
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testSkipOrderLimit()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod()->setRequestForFetch();

        $this->stmt->execute();
        $this->assertInstanceOf('MongoStatement', $this->stmt->order(array('id' => 1)));
        $this->assertInstanceOf('MongoStatement', $this->stmt->limit(10));
        $this->assertInstanceOf('MongoStatement', $this->stmt->skip(1));

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

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testCount()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod()->setRequestForFetch();

        $this->stmt->execute();
        $this->assertSame(10, $this->stmt->count());

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

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testCountException()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('some' => 'val')));

        $this->stmt->execute();
        $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible count result of opened cursor');
        $this->stmt->count();
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testOrderException()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('some' => 'val')));

        $this->stmt->execute();
        $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible order results of opened cursor');
        $this->stmt->order(array('id' => 1));
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testSkipException()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('some' => 'val')));

        $this->stmt->execute();
        $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible skip results of opened cursor');
        $this->stmt->skip(array('id' => 1));
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testLimitException()
    {
        Config::set('PROFILER', true);
        Config::set('PROFILER_DETAILS', true);
        $this->setDriverGetConnectionMethod();
        $this->request
                ->expects($this->once())
                ->method('execute')
                ->will($this->returnValue(array('some' => 'val')));

        $this->stmt->execute();
        $this->setExpectedException('GeneralException', 'MongoStatement error. Impossible limit results of opened cursor');
        $this->stmt->limit(array('id' => 1));
    }

    private function setDriverGetConnectionMethod()
    {
        $mongoMock = $this->getMock('Mongo');

        $this->driver
                ->expects($this->any())
                ->method('getConnection')
                ->will($this->returnValue($mongoMock));
        return $this;
    }

    public function setRequestExecuteMethod()
    {
        $resultMock = $this->getMockBuilder('MongoCursor')
                ->setMethods(array('count'))
                ->disableOriginalConstructor()
                ->getMock();

        $resultMock
                ->expects($this->any())
                ->method('count')
                ->will($this->returnValue(10));

        $this->request
                ->expects($this->any())
                ->method('execute')
                ->will($this->returnValue($resultMock));

        return $this;
    }

    public function setRequestForFetch()
    {
        $resultMock = $this->getMockBuilder('MongoCursor')
                ->setMethods(array('count', 'getNext', 'limit', 'sort', 'skip'))
                ->disableOriginalConstructor()
                ->getMock();

        $resultMock
                ->expects($this->any())
                ->method('limit');
        $resultMock
                ->expects($this->any())
                ->method('sort');
        $resultMock
                ->expects($this->any())
                ->method('skip');
        $resultMock
                ->expects($this->any())
                ->method('count')
                ->will($this->returnValue(10));
        $resultMock
                ->expects($this->at(0))
                ->method('getNext')
                ->will($this->returnValue(array('next' => 'prev', '_id' => 10)));
        $resultMock
                ->expects($this->at(1))
                ->method('getNext')
                ->will($this->returnValue(array()));

        $this->request
                ->expects($this->any())
                ->method('execute')
                ->will($this->returnValue($resultMock));

        return $this;
    }
}