<?php

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

require_once dirname(__FILE__) . '/../../model/DbDriver.php';
require_once dirname(__FILE__) . '/../../model/NoSqlDbDriver.php';
require_once dirname(__FILE__) . '/../../model/MongoDriver.php';
require_once dirname(__FILE__) . '/../../model/MongoDbCommand.php';
require_once dirname(__FILE__) . '/../../exception/GeneralException.php';

class MongoDbCommandTest extends PHPUnit_Framework_TestCase
{

    private $conf = array();

    private $driver;

    private $collection;

    public function setUp()
    {
        $this->conf = array(
            'hostname' => 'localhost',
            'database' => 'test',
            'username' => 'test',
            'password' => '1234',
            'port' => 27017
        );
        $this->driver = new MongoDriver($this->conf);
        $connection = $this->driver->getConnection();

        $db = $connection->selectDB($this->conf['database']);
        $db->authenticate($this->conf['username'], $this->conf['password']);
        $collection = 'items';
        $db->dropCollection($collection);
        $this->collection = $db->selectCollection($collection);
    }

    /**
     * @group Mongo
     */
    public function testCommandFactory()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND);
        $this->assertInstanceOf('MongoDbCommand', $cmd);
        $this->assertInstanceOf('FindMongoCommand', $cmd);
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT);
        $this->assertInstanceOf('MongoDbCommand', $cmd);
        $this->assertInstanceOf('InsertMongoCommand', $cmd);
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE);
        $this->assertInstanceOf('MongoDbCommand', $cmd);
        $this->assertInstanceOf('UpdateMongoCommand', $cmd);
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE);
        $this->assertInstanceOf('MongoDbCommand', $cmd);
        $this->assertInstanceOf('RemoveMongoCommand', $cmd);
    }

    /**
     * @group Mongo
     */
    public function testFindCommand()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'bread'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(0, $result->count());
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $cmd->execute();
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $cmd->execute();

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array());
        $this->assertEquals(2, $cmd->execute()->count());

        $cmd
                ->bindParam('condition', array('name' => 'insert'))
                ->bindParam('fields', array())
                ->bindParam('multiple', false);

        $result = $cmd->execute();
        $this->assertEquals('insert', $result['name']);
    }

    /**
     * @group Mongo
     */
    public function testCountCommand()
    {
        $count_cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COUNT, $this->collection);
        $count_cmd->bindParam('condition', array('name' => 'bread'));
        $count_result = $count_cmd->execute();
        $find_cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $find_cmd->bindParam('condition', array('name' => 'bread'))->bindParam('fields', array());
        $find_result = $find_cmd->execute();
        $this->assertEquals(0, $count_result);
        $this->assertEquals($count_result, $find_result->count());


        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $cmd->execute();
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $cmd->execute();

        $count_cmd->bindParam('condition', array('name' => 'insert'));
        $this->assertEquals(2, $count_cmd->execute());
        $find_cmd->bindParam('condition', array('name' => 'insert'));
        $this->assertEquals($find_cmd->execute()->count(), $count_cmd->execute());

    }

    /**
     * @group Mongo
     */
    public function testInsertCommand()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);

        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);

        $this->assertFalse($cmd->getInsertId());

        $this->assertArrayHasKey('n', $cmd->execute());
        $this->assertNotEmpty($cmd->getInsertId());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(1, $result->count());
    }

    /**
     * @group Mongo
     */
    public function testInsertCommandMultipleObjects()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);

        $data = array(
            array('name' => 'first object'),
            array('name' => 'second object'),
            array('name' => 'equal object'),
            array('name' => 'equal object')
        );
        $cmd
                ->bindParam('data', $data)
                ->bindParam('multiple', true)
                ->bindParam('safe', true);

        $this->assertFalse($cmd->getInsertId());

        $this->assertArrayHasKey('n', $cmd->execute());

        $cmd->bindParam('data', array());
        $cmd->execute();

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'first object'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(1, $result->count());
        $cmd->bindParam('condition', array('name' => 'second object'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(1, $result->count());
        $cmd->bindParam('condition', array('name' => 'equal object'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(2, $result->count());


    }

    /**
     * @group Mongo
     */
    public function testInsertCommandNotAllParamsBinded()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $this->setExpectedException('GeneralException', 'InsertMongoCommand error. Bind all required params first');
        $cmd->execute();
    }

    /**
     * @group Mongo
     */
    public function testUpdateCommand()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $this->assertArrayHasKey('n', $cmd->execute());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->collection);
        $cmd
                ->bindParam('condition', array('name' => 'insert'))
                ->bindParam('data', array('$set' => array('name' => 'update')))
                ->bindParam('upsert', false)
                ->bindParam('safe', true);
        $this->assertArrayHasKey('n', $cmd->execute());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(0, $result->count());
        $cmd->bindParam('condition', array('name' => 'update'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(1, $result->count());
    }

    /**
     * @group Mongo
     */
    public function testUpdateCommandNotAllParamsBinded()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->collection);
        $cmd->bindParam('data', array('name' => 'bread'));
        $this->setExpectedException('GeneralException', 'UpdateMongoCommand error. Bind all required params first');
        $cmd->execute();
    }

    /**
     * @group Mongo
     */
    public function testRemoveCommand()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $this->assertArrayHasKey('n', $cmd->execute());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(1, $result->count());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->collection);
        $cmd
                ->bindParam('condition', array('name' => 'insert'))
                ->bindParam('safe', true);
        $this->assertArrayHasKey('n', $cmd->execute());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, $this->collection);
        $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array());
        $result = $cmd->execute();
        $this->assertEquals(0, $result->count());
    }

    /**
     * @group Mongo
     */
    public function testRemoveCommandNotAllParamsBinded()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->collection);
        $this->setExpectedException('GeneralException', 'RemoveMongoCommand error. Bind all required params first.');
        $cmd->execute();
    }

    /**
     * @group Mongo
     */
    public function testCommandCommandNotAllParamsBinded()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, $this->collection);
        $this->setExpectedException('GeneralException', 'CommandMongoCommand error. Bind all required params first');
        $cmd->execute();
    }

    /**
     * @group Mongo
     */
    public function testCommandCommandNotMongoDb()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, new CollectionMock());
        $cmd->bindParam('command', array());
        $this->assertFalse($cmd->execute());
    }

    /**
     * @group Mongo
     */
    public function testCommandCommand()
    {
        $col = new CollectionMock();
        $col->db = $this->getMock('MongoDb', array('command'), array(), 'SomeMongoMock', false);
        $col->db
                ->expects($this->once())
                ->method('command')
                ->will($this->returnValue(true));
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, $col);
        $cmd->bindParam('command', array());
        $this->assertTrue($cmd->execute());
    }

    /**
     * @group Mongo
     */
    public function testToStringParamsNotSet()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, new CollectionMock());
        $this->assertSame('Command properties not set', $cmd->__toString());
    }

    /**
     * @group Mongo
     */
    public function testToString()
    {
        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COUNT, new CollectionMock());
        $cmd->bindParam('condition', array());
        $this->assertStringStartsWith(PHP_EOL . 'Collection: CollectionMock', $cmd->__toString());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::FIND, new CollectionMock());
        $cmd->bindParam('condition', array());
        $this->assertStringStartsWith(PHP_EOL . 'Collection: CollectionMock', $cmd->__toString());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::COMMAND, new CollectionMock());
        $this->assertSame('Command properties not set', $cmd->__toString());
        $cmd->bindParam('command', array());
        $this->assertStringStartsWith(PHP_EOL . 'Collection: CollectionMock', $cmd->__toString());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $this->assertSame('Command properties not set', $cmd->__toString());
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('safe', true);
        $this->assertStringStartsWith(PHP_EOL . 'Collection: ', $cmd->__toString());
        $this->assertContains('Bulk insert: FALSE', $cmd->__toString());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::INSERT, $this->collection);
        $this->assertSame('Command properties not set', $cmd->__toString());
        $cmd
                ->bindParam('data', array('name' => 'insert'))
                ->bindParam('multiple', true)
                ->bindParam('safe', true);
        $this->assertContains('Bulk insert: TRUE', $cmd->__toString());

        $cmd->bindParam('condition', array('name' => 'insert'))->bindParam('fields', array());
        $this->assertStringStartsWith(PHP_EOL . 'Collection: ', $cmd->__toString());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::REMOVE, $this->collection);
        $this->assertSame('Command properties not set', $cmd->__toString());
        $cmd
                ->bindParam('condition', array('name' => 'insert'))
                ->bindParam('safe', true);
        $this->assertStringStartsWith(PHP_EOL . 'Collection: ', $cmd->__toString());

        $cmd = MongoCommandBuilder::factory(MongoCommandBuilder::UPDATE, $this->collection);
        $this->assertSame('Command properties not set', $cmd->__toString());
        $cmd
                ->bindParam('condition', array('name' => 'insert'))
                ->bindParam('data', array('$set' => array('name' => 'update')))
                ->bindParam('upsert', false)
                ->bindParam('safe', true);
        $this->assertStringStartsWith(PHP_EOL . 'Collection: ', $cmd->__toString());
    }
}

class CollectionMock
{
    public $db = array();

    public function __toString()
    {
        return PHP_EOL . 'CollectionMock';
    }
}