<?php

use PHPOnCouch\CouchClient;
use PHPOnCouch\CouchAdmin;
use PHPOnCouch\CouchDocument;
use PHPOnCouch\Exceptions;

require_once join(DIRECTORY_SEPARATOR, [__DIR__, '_config', 'config.php']);

/**
 * Generated by PHPUnit_SkeletonGenerator on 2017-04-25 at 03:07:19.
 * @backupGlobals disabled
 * @backupStaticAttributes disabled
 */
class CouchDocumentTest extends \PHPUnit_Framework_TestCase
{

	private $host = 'localhost';
	private $port = '5984';
	private $dbname = 'couchclienttest';

	/**
	 *
	 * @var PHPOnCouch\CouchClient
	 */
	private $client;

	/**
	 *
	 * @var PHPOnCouch\CouchClient
	 */
	private $aclient;

	/**
	 * Sets up the fixture, for example, opens a network connection.
	 * This method is called before a test is executed.
	 */
	protected function setUp()
	{
		$config = \config::getInstance();
		$this->host = $config->getHost();
		$this->port = $config->getPort();
		$this->url = $config->getUrl($this->host, $this->port, null);
		$this->aUrl = $config->getUrl($this->host, $this->port, $config->getFirstAdmin());
		$this->couch_server = 'http://' . $this->host . ':' . $this->port . '/';

		$this->client = new CouchClient($this->url, $this->dbname);
		$this->aclient = new CouchClient($this->aUrl, $this->dbname);
		try {
			$this->aclient->deleteDatabase();
		} catch (Exceptions\CouchNotFoundException $e) {
			
		}
		$this->aclient->createDatabase();
	}

	protected function getReplicateTargetClient($createTarget = false)
	{
		$target = new CouchClient($this->aUrl, 'replicationtarget');
		try {
			$target->deleteDatabase();
		} catch (\Exception $e) {
			
		}
		if ($createTarget)
			$target->createDatabase();
		return $target;
	}

	/**
	 * Tears down the fixture, for example, closes a network connection.
	 * This method is called after a test is executed.
	 */
	protected function tearDown()
	{
		try {
			$this->aclient->deleteDatabase();
		} catch (Exceptions\CouchNotFoundException $e) {
			
		}
		$this->client = null;
		$this->aclient = null;
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::load
	 */
	public function testLoad()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$prop = $reflectedClass->getProperty('_couchData');
		$prop->setAccessible(true);

		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$expectedFields = $this->aclient->getDoc($tmpDoc->_id);

		$cd = new CouchDocument($this->aclient);
		$cd->load($tmpDoc->_id);
		$actualData = $prop->getValue($cd);

		$this->assertEquals($expectedFields, $actualData->fields);

		//Parameter validation
		$this->expectException(InvalidArgumentException::class);
		$cd->load('');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::setAutocommit
	 */
	public function testSetAutocommit()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$prop = $reflectedClass->getProperty('_couchData');
		$prop->setAccessible(true);
		$cd = new CouchDocument($this->aclient);

		//Test with boolean
		$this->assertEquals($cd, $cd->setAutocommit(true));
		$this->assertEquals(true, $prop->getValue($cd)->autocommit);

		//Test with number
		$this->assertEquals($cd, $cd->setAutocommit(0));
		$this->assertEquals(false, $prop->getValue($cd)->autocommit);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::getAutocommit
	 * @depends testSetAutocommit
	 */
	public function testGetAutocommit()
	{
		$cd = new CouchDocument($this->aclient);

		//Test with boolean
		$this->assertEquals($cd, $cd->setAutocommit(true));
		$this->assertEquals(true, $cd->getAutocommit());

		//Test with number
		$this->assertEquals($cd, $cd->setAutocommit(0));
		$this->assertEquals(false, $cd->getAutocommit());
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::loadFromObject
	 */
	public function testLoadFromObject()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$prop = $reflectedClass->getProperty('_couchData');
		$prop->setAccessible(true);

		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$expectedFields = $this->aclient->getDoc($tmpDoc->_id);

		$cd = new CouchDocument($this->aclient);
		$this->assertEquals($cd, $cd->loadFromObject($expectedFields));
		$actualData = $prop->getValue($cd);

		$this->assertEquals((array) $expectedFields, (array) $actualData->fields);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::getInstance
	 * @depends testLoad
	 */
	public function testGetInstance()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$prop = $reflectedClass->getProperty('_couchData');
		$prop->setAccessible(true);

		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$expectedFields = $this->aclient->getDoc($tmpDoc->_id);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$actualData = $prop->getValue($cd);

		$this->assertEquals($expectedFields, $actualData->fields);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::getKeys
	 * @depends testGetInstance
	 */
	public function testGetKeys()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$prop = $reflectedClass->getProperty('_couchData');
		$prop->setAccessible(true);

		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$actualData = $prop->getValue($cd);

		$this->assertEquals(array_keys(get_object_vars(($actualData->fields))), $cd->getKeys());
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::getFields
	 * @depends testGetInstance
	 */
	public function testGetFields()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$expectedDoc = $this->aclient->getDoc($tmpDoc->_id);

		$this->assertEquals($expectedDoc, $cd->getFields());
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::getUri
	 * @depends testGetInstance
	 */
	public function testGetUri()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);

		$this->assertEquals($this->aclient->getDatabaseUri() . '/' . $tmpDoc->_id, $cd->getUri());
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::get
	 */
	public function testGet()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$expectedFields = $this->aclient->getDoc($tmpDoc->_id);

		foreach (get_object_vars($expectedFields) as $key => $value) {
			$this->assertEquals($value, $cd->get($key));
		}

		//If not existing, returns null
		$this->assertNull($cd->get(time()));

		$this->expectException(InvalidArgumentException::class);
		$cd->get('');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::id
	 * @depends testGet
	 */
	public function testId()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);
		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$this->assertEquals($tmpDoc->_id, $cd->id());
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::__get
	 */
	public function test__get()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$expectedFields = $this->aclient->getDoc($tmpDoc->_id);

		foreach (get_object_vars($expectedFields) as $key => $value) {
			$this->assertEquals($value, $cd->$key);
		}

		//If not existing, returns null
		$time = time();
		$this->assertNull($cd->$time);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::setOne
	 */
	public function testSetOneNoProp()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$method = $reflectedClass->getMethod('setOne');
		$method->setAccessible(true);
		$this->expectException(InvalidArgumentException::class);
		$cd = new CouchDocument($this->aclient);
		$method->invoke($cd, '', 'value');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::setOne
	 */
	public function testSetOneNoUnderscore()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$method = $reflectedClass->getMethod('setOne');
		$method->setAccessible(true);
		$this->expectException(InvalidArgumentException::class);
		$cd = new CouchDocument($this->aclient);
		$method->invoke($cd, '_underscore', 'value');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::setOne
	 */
	public function testSetOneSetRev()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$method = $reflectedClass->getMethod('setOne');
		$method->setAccessible(true);
		$this->expectException(InvalidArgumentException::class);
		$cd = new CouchDocument($this->aclient);
		$method->invoke($cd, '_rev', 'value');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::setOne
	 */
	public function testSetOneSetId()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$method = $reflectedClass->getMethod('setOne');
		$method->setAccessible(true);
		$this->expectException(InvalidArgumentException::class);
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);
		$cd = new CouchDocument($this->aclient);
		$cd->load($tmpDoc->_id);
		$method->invoke($cd, '_id', 'value');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::record
	 */
	public function testRecord()
	{
		$cd = new CouchDocument($this->aclient);
		$cd->setAutocommit(false);

		$cd->_id = 'test_record';
		$cd->name = 'alexis';
		//We set the doc
		$this->aclient->storeDoc((object) $cd->getFields());

		//if we update the document, it should not auto commit
		$cd->name = 'john';

		$updatedDoc = CouchDocument::getInstance($this->aclient, $cd->_id);
		$this->assertEquals($updatedDoc->name, 'alexis');
		$updatedDoc->setAutocommit(false);
		$updatedDoc->name = 'john';
		$updatedDoc->record();
		$updated2 = $this->aclient->getDoc($cd->_id);
		$this->assertEquals($updated2->name, 'john');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::set
	 */
	public function testSet()
	{
		$cd = new CouchDocument($this->aclient);
		$this->expectException(InvalidArgumentException::class);
		$cd->set('oops');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::__set
	 */
	public function test__set()
	{
		$cd = new CouchDocument($this->aclient);
		$name = "john";
		$this->assertEmpty($cd->name);
		$cd->name = $name;
		$this->assertEquals($cd->name, $name);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::__isset
	 */
	public function test__isset()
	{
		$cd = new CouchDocument($this->aclient);
		$this->assertFalse(isset($cd->_id));
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::remove
	 */
	public function testRemove()
	{
		$reflectedClass = new \ReflectionClass(CouchDocument::class);
		$prop = $reflectedClass->getProperty('_couchData');
		$prop->setAccessible(true);

		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);

		$shouldReturnFalse = ['_id', '_rev'];

		foreach (get_object_vars($cd->getFields()) as $key => $value) {
			$this->assertEquals(!in_array($key, $shouldReturnFalse), $cd->remove($key));
		}

		$this->expectException(InvalidArgumentException::class);
		$cd->remove('');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::__unset
	 * @depends testRemove
	 */
	public function test__unset()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);
		$this->assertNotEmpty($cd->name);
		unset($cd->name);
		$this->assertEmpty($cd->name);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::replicateTo
	 */
	public function testReplicateTo()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);

		$cd = CouchDocument::getInstance($this->aclient, $tmpDoc->_id);

		//Load the replicator to avoid an error
		require_once join(DIRECTORY_SEPARATOR, [dirname(__DIR__), 'src', 'CouchReplicator.php']);

		$target = $this->getReplicateTargetClient();

		$this->assertTrue($cd->replicateTo($target->getDatabaseUri(), true));

		//The target should have the document and should be created
		$this->assertNotEmpty($target->getDoc($tmpDoc->_id));

		//We try to copy to a wrong URL
		$this->assertFalse($cd->replicateTo('wow_whatsthat'));
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::replicateTo
	 */
	public function testReplicateToWrongParameter()
	{
		$cd = new CouchDocument($this->aclient);
		$this->expectException(InvalidArgumentException::class);
		$cd->replicateTo('we_dont_care_about_the_url');
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::replicateFrom
	 * @depends testReplicateTo
	 */
	public function testReplicateFrom()
	{
		$tmpDoc = (object) ['_id' => 'test_load_tmpdoc', 'name' => 'test_doc'];
		$this->aclient->storeDoc($tmpDoc);
		$target = $this->getReplicateTargetClient();
		$cd = new CouchDocument($target);

		$this->assertEmpty($cd->_id);
		$this->assertTrue($cd->replicateFrom($tmpDoc->_id, $this->aclient->getDatabaseUri(), true));

		$this->assertEquals($tmpDoc->_id, $cd->_id);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::storeAttachment
	 */
	public function testStoreAttachment()
	{
		$cd = new CouchDocument($this->aclient);
		$cd->set(array(
			'_id' => 'somedoc'
		));
		$back = $cd->storeAsAttachment("This is the content", "file.txt", "text/plain");
		$fields = $cd->getFields();

		$this->assertInternalType("object", $back);
		$this->assertObjectHasAttribute("ok", $back);
		$this->assertEquals($back->ok, true);
		$this->assertObjectHasAttribute("_attachments", $fields);
		$this->assertObjectHasAttribute("file.txt", $fields->_attachments);

		$cd = new CouchDocument($this->aclient);
		$cd->set(array(
			'_id' => 'somedoc2'
		));
		$back = $cd->storeAttachment(join(DIRECTORY_SEPARATOR, [__DIR__, '_config', 'test.txt']), "text/plain", "file.txt");
		$fields = $cd->getFields();

		$this->assertInternalType("object", $back);
		$this->assertObjectHasAttribute("ok", $back);
		$this->assertEquals($back->ok, true);
		$this->assertObjectHasAttribute("_attachments", $fields);
		$this->assertObjectHasAttribute("file.txt", $fields->_attachments);

		$back = $cd->deleteAttachment("file.txt");
		$fields = $cd->getFields();
		$this->assertInternalType("object", $back);
		$this->assertObjectHasAttribute("ok", $back);
		$this->assertEquals($back->ok, true);
		$test = property_exists($fields, '_attachments');
		$this->assertEquals($test, false);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::storeAsAttachment
	 */
	public function testStoreAsAttachment()
	{
		$cd = new CouchDocument($this->aclient);
		$cd->set(array(
			'_id' => 'somedoc'
		));
		$back = $cd->storeAsAttachment("This is the content", "file.txt", "text/plain");
		$fields = $cd->getFields();

		$this->assertInternalType("object", $back);
		$this->assertObjectHasAttribute("ok", $back);
		$this->assertEquals($back->ok, true);
		$this->assertObjectHasAttribute("_attachments", $fields);
		$this->assertObjectHasAttribute("file.txt", $fields->_attachments);

		$cd = new CouchDocument($this->aclient);
		$cd->set(array(
			'_id' => 'somedoc2'
		));
		$back = $cd->storeAttachment(join(DIRECTORY_SEPARATOR, [__DIR__, '_config', 'test.txt']), "text/plain", "file.txt");
		$fields = $cd->getFields();

		$this->assertInternalType("object", $back);
		$this->assertObjectHasAttribute("ok", $back);
		$this->assertEquals($back->ok, true);
		$this->assertObjectHasAttribute("_attachments", $fields);
		$this->assertObjectHasAttribute("file.txt", $fields->_attachments);

		$back = $cd->deleteAttachment("file.txt");
		$fields = $cd->getFields();
		$this->assertInternalType("object", $back);
		$this->assertObjectHasAttribute("ok", $back);
		$this->assertEquals($back->ok, true);
		$test = property_exists($fields, '_attachments');
		$this->assertEquals($test, false);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::deleteAttachment
	 */
	public function testDeleteAttachment()
	{
		$cd = new CouchDocument($this->aclient);
		$id = 'deleteAttachment';
		$cd->set(array(
			'_id' => $id
		));
		$cd->storeAttachment(join(DIRECTORY_SEPARATOR, [__DIR__, '_config', 'test.txt']), "text/plain", "file.txt");
		$updatedDoc = $this->aclient->getDoc($id);
		$this->assertEquals($updatedDoc->_attachments, $cd->_attachments);
		//Delete it
		$deleteResp = $cd->deleteAttachment('file.txt');
		$this->assertInternalType("object", $deleteResp);
		$this->assertTrue($deleteResp->ok);
		$this->assertEmpty($cd->_attachments);
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::getAttachmentUri
	 */
	public function testGetAttachmentUri()
	{
		$cd = new CouchDocument($this->aclient);
		$id = 'deleteAttachment';
		$cd->set(array(
			'_id' => $id
		));

		$attachmentName = 'smth';
		$expected = $cd->getUri() . '/' . $attachmentName;
		$this->assertEquals($expected, $cd->getAttachmentUri($attachmentName));
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::show
	 */
	public function testShow()
	{
		$doc = new CouchDocument($this->aclient);
		$doc->_id = "_design/test";
		$show = array(
			"simple" => "function (doc, ctx) {
				ro = {body: ''};
				if ( ! doc ) {
					ro.body = 'no document';
				} else {
					ro.body = 'document: '+doc._id;
				}
				ro.body += ' ';
				var len = 0;
				for ( var k in ctx.query ) {
					len++;
				}
				ro.body += len;
				return ro;
			}",
			"json" => "function (doc, ctx) {
				ro = {body: ''};
				back = {doc: null};
				if ( doc ) {
					back.doc = doc._id;
				}
				var len = 0;
				for ( var k in ctx.query ) {
					len++;
				}
				back.query_length = len;
				ro.body = JSON.stringify(back);
				ro.headers = { \"content-type\": 'application/json' };
				return ro;
			}"
		);
		$doc->shows = $show;
		$test = $doc->show("test", "simple");
		$this->assertEquals($test, "document: _design/test 0");
	}

	/**
	 * @covers PHPOnCouch\CouchDocument::update
	 */
	public function testUpdate()
	{
		$cd = new CouchDocument($this->aclient);

		//No document loaded, no update :)
		$this->assertFalse($cd->update('', ''));


		//Should update successfully
		$updateFn = <<<EOT
function(doc,req) {
	var resp = {query:null,form:null};
	if ( "query" in req ) {
		resp.query = req.query;
	}
	if ( "form" in req ) {
		resp.form = req.form;
	}
	return [doc,{
			headers: {"Content-Type": "application/json"},
			body: JSON.stringify(resp)
		}];
}
EOT;
		$ddoc = new stdClass();
		$ddoc->_id = "_design/test";
		$ddoc->updates = array("test" => $updateFn);
		$this->aclient->storeDoc($ddoc);
		$doc = new stdClass();
		$doc->_id = "foo";
		$this->aclient->storeDoc($doc);

		$cd = CouchDocument::getInstance($this->aclient, $doc->_id);
		$update = $cd->update("test", "test", array());
		$this->assertInternalType("object", $update);
		$this->assertObjectHasAttribute("query", $update);
		$this->assertInternalType("object", $update->query);
		$this->assertEquals(0, count((array) $update->query));
		$this->assertObjectHasAttribute("form", $update);
		$this->assertInternalType("object", $update->form);
		$this->assertEquals(0, count((array) $update->form));
	}

}
