<?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/Db.php';
require_once dirname(__FILE__) . '/../../model/DbDriver.php';
require_once dirname(__FILE__) . '/../../model/NoSqlDbDriver.php';
require_once dirname(__FILE__) . '/../../model/MongoDbCommand.php';
require_once dirname(__FILE__) . '/../../model/DbStatement.php';
require_once dirname(__FILE__) . '/../../model/MongoStatement.php';
require_once dirname(__FILE__) . '/../../model/MongoDriver.php';
require_once dirname(__FILE__) . '/../../exception/GeneralException.php';

class MongoDriverTest extends PHPUnit_Framework_TestCase
{

    private $conf = array();

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

        $data = array(
            array(
                'name' => 'bread',
                'price' => 3.2,
                'quantity' => 10
            ),
            array(
                'name' => 'eggs',
                'price' => 2.1,
                'quantity' => 20
            ),
            array(
                'name' => 'fish',
                'price' => 13.2,
                'quantity' => 2
            ),
            array(
                'name' => 'milk',
                'price' => 3.8,
                'quantity' => 1
            ),
            array(
                'name' => 'eggs',
                'price' => 2.3,
                'quantity' => 5
            )
        );
        $connection = new Mongo('mongodb://' . $this->conf['hostname'] . ':' . $this->conf['port']);
        $db = $connection->selectDB($this->conf['database']);
        $db->authenticate($this->conf['username'], $this->conf['password']);
        $collection = 'items';
        $db->dropCollection($collection);
        $collection = $db->selectCollection($collection);
        foreach ($data as $document) {
            $collection->insert($document);
        }
    }

    /**
     * @group Mongo
     */
    public function testGetConnectionNoHostname()
    {
        unset($this->conf['hostname']);
        $this->setExpectedException('GeneralException', 'Configuration must have a "hostname"');
        $mongo = new MongoDriver($this->conf);
    }

    /**
     * @group Mongo
     */
    public function testGetConnectionWrongPassword()
    {
        $this->conf['password'] = 'nopass';
        $mongo = new MongoDriver($this->conf);
        $this->setExpectedException('MongoConnectionException', 'Couldn\'t authenticate with database');
        $this->assertInstanceOf('MongoDB', $mongo->getConnection());
    }

    /**
     * @group Mongo
     */
    public function testGetConnection()
    {
        $mongo = new MongoDriver($this->conf);

        $this->assertFalse($mongo->isConnected());
        $this->assertInstanceOf('Mongo', $mongo->getConnection());
        $this->assertTrue($mongo->isConnected());
        $mongo->getConnection();
        $mongo->disconnect();
        $this->assertFalse($mongo->isConnected());
    }

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

        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(2, $mongo->find('items', array('name' => 'eggs'))->numRows());
        $eggs = $mongo->find('items', array('name' => 'eggs'));
        $egg = $eggs->fetch();
        $this->assertEquals(20, $egg->quantity);
        $egg = $eggs->fetchObject();
        $this->assertEquals('eggs', $egg->name);
        $this->assertFalse($eggs->fetchObject());

        $this->assertEquals(3, $mongo->find('items', array('price' => array('$lt' => 3.5)))->numRows());
        $data = $mongo->find('items', array('price' => array('$lt' => 3.5)));
        $count = 0;
        while ($row = $data->fetch(Db::FETCH_ASSOC)) {
            $count++;
            $this->assertLessThan(3.5, $row['price']);
        }
        $this->assertEquals(3, $count);
        $this->assertEquals(5, $mongo->find('items', array())->numRows());
    }

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

        $mongo = new MongoDriver($this->conf);

        $count = $mongo->find('items', array())->numRows();

        $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(2, $mongo->find('items', array('name' => 'eggs'))->numRows());
        $mongo->insert('items', array('name' => 'fdsbssc'));
        $mongo->insert('items', array('name' => 'boc'));
        $mongo->insert('items', array('name' => 'abc'));
        $mongo->insert('items', array('name' => 'vcxxc'));
        $mongo->insert('items', array('name' => 'abbc'));
        $mongo->insert('items', array('name' => 'dsbssc'));
        $mongo->insert('items', array('name' => 'bssc'));

        $data = $mongo->find('items', array());
        $this->assertEquals($count + 7, $data->numRows());
        $data->order(array('name' => 1));
        $this->assertEquals('abbc', $data->fetch()->name);
        $this->assertEquals('abc', $data->fetch()->name);
        $this->assertEquals('boc', $data->fetch()->name);
        $data = $mongo->find('items', array());
        $data->order(array('name' => -1));
        $data->skip(3);
        $data->limit(1);
        while ($row = $data->fetch()) {
            $this->assertEquals('fdsbssc', $row->name);
        }
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testCount()
    {
        Config::set('DEBUG', false);
        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(5, $mongo->count('items'));
        $this->assertEquals(2, $mongo->count('items', array('name' => 'eggs')));
        $this->assertEquals(1, $mongo->count('items', array('name' => 'eggs'), 1));
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testCursorCount()
    {
        Config::set('DEBUG', false);
        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(5, $mongo->find('items')->count(5));
        $this->assertCount(3, $mongo->find('items')->limit(3)->fetchAll());
        $this->assertEquals(5, $mongo->count('items'));
    }

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

        $mongo = new MongoDriver($this->conf);
        $eggs = $mongo->get('items', array('name' => 'eggs'))->fetchObject();
        $this->assertEquals(20, $eggs->quantity);
        $eggs = $mongo->get('items', array('name' => 'eggs'))->fetch();
        $this->assertEquals('eggs', $eggs->name);
        $this->assertInstanceOf('MongoId', $eggs->_id);
    }

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

        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(0, $mongo->delete('items', array('name' => 'esggs')));
        $this->assertEquals(2, $mongo->delete('items', array('name' => 'eggs')));
        $this->assertEquals(1, $mongo->delete('items', array('name' => 'bread')));
        $this->assertEquals(0, $mongo->find('items', array('name' => 'bread'))->numRows());
    }

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

        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(0, $mongo->insert('items', array('name' => 'bread')));
        $this->assertNotEmpty($mongo->getInsertId());
        $this->assertEquals(2, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(0, $mongo->insert('items', array('name' => 'meat', 'weight' => 230)));
        $this->assertEquals(230, $mongo->get('items', array('name' => 'meat'))->fetch()->weight);
    }

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

        $data = array(
            array('name' => 'first object'),
            array('name' => 'second object'),
            array('name' => 'equal object'),
            array('name' => 'equal object')
        );
        $mongo = new MongoDriver($this->conf);
        $mongo->insert('items', $data, true);
        $this->assertEquals(1, $mongo->find('items', array('name' => 'first object'))->numRows());
        $this->assertEquals(1, $mongo->find('items', array('name' => 'second object'))->numRows());
        $this->assertEquals(2, $mongo->find('items', array('name' => 'equal object'))->numRows());
    }

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

        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(0, $mongo->getInsertId());
        $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(0, $mongo->insert('items', array('name' => 'bread')));
        $this->assertEquals(2, $mongo->find('items', array('name' => 'bread'))->numRows());
        $id1 = $mongo->getInsertId();
        $this->assertNotEmpty($id1);
        $this->assertEquals(0, $mongo->insert('items', array('name' => 'bread')));
        $id2 = $mongo->getInsertId();
        $this->assertNotEmpty($id2);
        $this->assertNotEquals($id1, $id2);
        $this->assertEquals(3, $mongo->find('items', array('name' => 'bread'))->numRows());
    }

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

        $mongo = new MongoDriver($this->conf);
        $this->assertEquals(1, $mongo->find('items', array('name' => 'bread'))->numRows());
        $this->assertEquals(1, $mongo->update('items', array('$set' => array('price' => 200, 'date' => 'today')), array('name' => 'fish')));
        $this->assertEquals(2, $mongo->update('items', array('$set' => array('price' => 1)), array('name' => 'eggs')));
        $fish = $mongo->get('items', array('name' => 'fish'))->fetch();
        $this->assertEquals(200, $fish->price);
        $this->assertEquals('today', $fish->date);
        $this->assertEquals(0, $mongo->update('items', array('$set' => array('price' => 200, 'date' => 'today')), array('name' => 'ball')));
    }

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

        $mongo = new MongoDriver($this->conf);

        $mongo->insert('items', array('name' => 'bread'));
        $this->assertEquals(1, $mongo->update('items', array('$set' => array('price' => 200, 'date' => 'today')), array('name' => 'ball'), true, true));
        $this->assertEquals('today', $mongo->get('items', array('name' => 'ball'))->fetch()->date);
    }

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

        $mongo = new MongoDriver($this->conf);

        $this->assertEquals(10, $mongo->get('items', array('name' => 'bread'))->fetch()->quantity);
        $result = $mongo->findAndModify('items', array('name' => 'bread'), array('$set' => array('quantity' => 20)));
        $this->assertEquals(10, $result->fetch()->quantity);
        $this->assertEquals(20, $mongo->get('items', array('name' => 'bread'))->fetch()->quantity);
    }

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

        $mongo = new MongoDriver($this->conf);

        $this->assertEquals(10, $mongo->get('items', array('name' => 'bread'))->fetch()->quantity);
        $result = $mongo->findAndModify('items', array('name' => 'breading'), array('$set' => array('quantity' => 20)))->fetch();
        $this->assertFalse($result);
        $this->assertEquals(10, $mongo->get('items', array('name' => 'bread'))->fetch()->quantity);
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testEvalCommand()
    {
        Config::set('DEBUG', false);
        $mongo = new MongoDriver($this->conf);
        $result = $mongo->command('items', array('$eval' => 'function() { return db.items.count();}'));
        $this->assertEquals(5, $result->fetch());
        $this->assertEquals(5, $mongo->count('items'));
        $result = $mongo->command('items', array('$eval' => 'function() { return "HELLO!";}'));
        $this->assertEquals("HELLO!", $result->fetch());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testEval()
    {
        Config::set('DEBUG', false);
        $mongo = new MongoDriver($this->conf);
        $result = $mongo->command('items', array('$eval' => 'function() {return true; }'));
        $this->assertTrue($result->fetch());
        $result = $mongo->command('items', array('$eval' => 'function() {return "Hello"; }'));
        $this->assertSame('Hello', $result->fetch());
        $result = $mongo->command('items', array('$eval' => 'function() {return db.items.count(); }'));
        $this->assertEquals(5, $result->fetch());
    }

    /**
     * @runInSeparateProcess
     * @group Mongo
     */
    public function testCommand()
    {
        Config::set('DEBUG', false);
        $mongo = new MongoDriver($this->conf);
        $result = $mongo->command('items', array('distinct' => 'items', 'key' => 'name'));
        $this->assertEquals(4, count($result->fetch(DB::FETCH_ASSOC)));
    }
}