<?php
/**
 * core.framework
 *
 * @category   Core
 * @package    Core_Application
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2013. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 */

/**
 * @category   Core
 * @package    Core_Application
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2013. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 * @group      Core_Application
 * @group      Core_Application_Plugin
 */
class Core_Application_Plugin_EventHandler_RestTest extends CoreTest_Container_TestCase
{
    /**
     * @var Core_Application_Plugin_EventHandler_Rest
     */
    protected $_object;

    protected function setUp()
    {
        $this->_object = new Core_Application_Plugin_EventHandler_Rest('rest');
    }

    protected function tearDown()
    {
        Core_Application::resetInstance();
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::setFilter
     * @covers Core_Application_Plugin_EventHandler_Rest::getFilter
     */
    public function testCanSetAndGetFilter()
    {
        $this->_object->setFilter('some.boolean.expression && true');
        $this->assertEquals('some.boolean.expression && true', $this->_object->getFilter());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::getFilter
     */
    public function testDefaultFilterIsSet()
    {
        $filter = 'params.location.module == "api" && params.location.controller != "oauth"';
        $this->assertEquals($filter, $this->_object->getFilter());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::setInjectIdentity
     * @covers Core_Application_Plugin_EventHandler_Rest::isInjectIdentity
     */
    public function testCanSetAndGetInjectIdentity()
    {
        $this->_object->setInjectIdentity(false);
        $this->assertFalse($this->_object->isInjectIdentity());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::isInjectIdentity
     */
    public function testWeInjectIdentityByDefault()
    {
        $this->assertTrue($this->_object->isInjectIdentity());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::setResponse
     * @covers Core_Application_Plugin_EventHandler_Rest::getResponse
     */
    public function testCanSetAndGetResponse()
    {
        $response = $this->getMock('Core_Response');
        $this->_object->setResponse($response);
        $this->assertEquals($response, $this->_object->getResponse());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::getResponse
     */
    public function testIfViewNotSetItWillFetchItFromApplicationAsResponse()
    {
        $response   = $this->getMock('Core_Response');
        $container  = $this->_mockContainer('Response', $response);
        Core_Application::getInstance()->setContainer($container);

        $this->assertEquals($response, $this->_object->getResponse());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::setOauth2Server
     * @covers Core_Application_Plugin_EventHandler_Rest::getOauth2Server
     */
    public function testCanSetAndGetOauth2Server()
    {
        $server = $this->getMock('Oauth2_Server');
        $this->_object->setOauth2Server($server);
        $this->assertEquals($server, $this->_object->getOauth2server());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::getOauth2Server
     */
    public function testIfOauth2ServerNotSetItWillFetchItFromApplicationAsOauth2Server()
    {
        $server    = $this->getMock('Oauth2_Server');
        $container = $this->_mockContainer('Oauth2Server', $server);
        Core_Application::getInstance()->setContainer($container);

        $this->assertEquals($server, $this->_object->getOauth2server());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::setUser
     * @covers Core_Application_Plugin_EventHandler_Rest::getUser
     */
    public function testCanSetAndGetUser()
    {
        $user = $this->getMock('Core_User');
        $this->_object->setUser($user);
        $this->assertEquals($user, $this->_object->getUser());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::getUser
     */
    public function testIfUserNotSetItWillFetchItFromApplicationAsUser()
    {
        $user      = $this->getMock('Core_User');
        $container = $this->_mockContainer('User', $user);
        Core_Application::getInstance()->setContainer($container);

        $this->assertEquals($user, $this->_object->getUser());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::init
     */
    public function testWillIncludeFilterToEventSubscriptions()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher_Interface');
        $eventDispatcher
            ->expects($this->at(1))
            ->method('subscribe')
            ->with(
                $this->equalTo(Core_Dispatcher::EVENT_PREDISPATCH),
                $this->equalTo(array($this->_object, 'oauthTokenHandler')),
                $this->equalTo(-500),
                $this->equalTo('some.boolean.expression') // this param is dynamic
            );
        $this->_object
            ->setFilter('some.boolean.expression')
            ->setEventDispatcher($eventDispatcher)
            ->init();
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::init
     */
    public function testWillSubscribePluginToPostDispatchInRestContext()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher_Interface');
        $eventDispatcher
            ->expects($this->at(2))
            ->method('subscribe')
            ->with(
                $this->equalTo(Core_Dispatcher::EVENT_POSTDISPATCH),
                $this->equalTo(array($this->_object, 'postDispatchHandler')),
                $this->equalTo(999),
                $this->equalTo('params.application.context == "rest"')
            );
        $this->_object
            ->setEventDispatcher($eventDispatcher)
            ->init();
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::init
     */
    public function testWillSubscribePluginToExceptionsInRestContext()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher_Interface');
        $eventDispatcher
            ->expects($this->at(3))
            ->method('subscribe')
            ->with(
                $this->equalTo(Core_Application::EVENT_EXCEPTION),
                $this->equalTo(array($this->_object, 'exceptionHandler')),
                $this->equalTo(-100),
                $this->equalTo('params.application.context == "rest"')
            );
        $this->_object
            ->setEventDispatcher($eventDispatcher)
            ->init();
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::oauthTokenHandler
     */
    public function testWillStopDispatchIfOauthTokenIsNotValid()
    {
        $event = $this->getMock('Core_Event', array('setIsProcessed'), array($this, 'someEvent'));
        $event
            ->expects($this->once())
            ->method('setIsProcessed')
            ->with($this->equalTo(true));

        $server = $this->getMock('Oauth2_Server', array('verifyResourceRequest'));
        $server
            ->expects($this->once())
            ->method('verifyResourceRequest')

            // request is invalid!
            ->will($this->returnValue(false));

        // adding fluent interface to mock
        $response = $this->getMock('Core_Response', array('setType', 'setCode', 'setHeaders', 'setBody'));
        $response->expects($this->once())->method('setType')->will($this->returnValue($response));
        $response->expects($this->once())->method('setCode')->will($this->returnValue($response));
        $response->expects($this->once())->method('setHeaders')->will($this->returnValue($response));
        $response->expects($this->once())->method('setBody')->will($this->returnValue($response));

        $this->_object
            ->setOauth2server($server)
            ->setResponse($response);

        $this->_object->oauthTokenHandler($event);
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::oauthTokenHandler
     */
    public function testWillInjectIdentityFromTokenIfValid()
    {
        $event    = $this->getMockForAbstractClass('Core_Event_Interface');
        $response = $this->getMock('Core_Response', array('setType', 'setCode', 'setHeaders', 'setBody'));
        $user     = $this->getMock('Core_User', array('setIdentity', 'setPersist'));
        $user
            ->expects($this->once())
            ->method('setIdentity')
            ->with($this->equalTo(23))
            ->will($this->returnValue($user));
        $user
            ->expects($this->once())
            ->method('setPersist')
            ->with($this->equalTo(false))
            ->will($this->returnValue($user));

        $server   = $this->getMock('Oauth2_Server', array('verifyResourceRequest', 'getAccessTokenData'));
        $server
            ->expects($this->once())
            ->method('verifyResourceRequest')
            // request is valid!
            ->will($this->returnValue(true));
        $server
            ->expects($this->once())
            ->method('getAccessTokenData')
            ->will($this->returnValue(array('user_id' => 23)));

        $this->_object
            ->setOauth2server($server)
            ->setUser($user)
            ->setResponse($response);

        $this->_object->oauthTokenHandler($event);
    }
    
    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::oauthTokenHandler
     */
    public function testWillInjectAnonymousIdentityFromTokenIfNoUserIDValid()
    {
        $event    = $this->getMockForAbstractClass('Core_Event_Interface');
        $user     = $this->getMock('Core_User', array('setIdentity', 'setPersist'));
        $user
            ->expects($this->once())
            ->method('setIdentity')
            ->with($this->equalTo(false))
            ->will($this->returnValue($user));
        $user
            ->expects($this->once())
            ->method('setPersist')
            ->with($this->equalTo(false))
            ->will($this->returnValue($user));

        $server   = $this->getMock('Oauth2_Server', array('verifyResourceRequest', 'getAccessTokenData'));
        $server
            ->expects($this->once())
            ->method('verifyResourceRequest')
            // request is valid!
            ->will($this->returnValue(true));
        $server
            ->expects($this->once())
            ->method('getAccessTokenData')
            ->will($this->returnValue(array('user_id' => null)));

        $this->_object
            ->setOauth2server($server)
            ->setUser($user);

        $this->_object->oauthTokenHandler($event);
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::contextHandler
     */
    public function testWillChangeApplicationContextToRest()
    {
        $this->_object->setResponse($this->getMock('Core_Response'));

        $event = $this->getMockForAbstractClass('Core_Event_Interface');
        $this->_object->contextHandler($event);

        $this->assertEquals('rest', Core_Application::getInstance()->getContext());
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::contextHandler
     */
    public function testWillAddResponseHeaderForCORS()
    {
        $response = $this->getMock('Core_Response', array('setHeader'));
        $response
            ->expects($this->once())
            ->method('setHeader')
            ->with($this->equalTo('Access-Control-Allow-Origin'), $this->equalTo('*'));
        $this->_object->setResponse($response);

        $event = $this->getMockForAbstractClass('Core_Event_Interface');
        $this->_object->contextHandler($event);
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::exceptionHandler
     * @covers Core_Application_Plugin_EventHandler_Rest::<protected>
     *
     * @runInSeparateProcess
     */
    public function testWillHandleExceptionsInRestContext()
    {
        $this->markTestIncomplete('TODO: fix broken');

        $response = $this->getMock('Core_Response', array('setBody', 'setType', 'setCode', 'render'));
        $response
            ->expects($this->once())
            ->method('setBody')
            ->with($this->equalTo(array(array('message' => 'Err'))))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setType')
            ->with($this->equalTo('application/vnd.error'))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setCode')
            ->with($this->equalTo(500))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('render')
            ->will($this->returnValue('<rendered/>'));

        $event    = $this->getMock('Core_Event', array('getParam', 'setIsProcessed'), array($this, 'someName'));
        $event
            ->expects($this->once())
            ->method('getParam')
            ->with($this->equalTo('exception'))
            ->will($this->returnValue(new Exception('Err')));
        $event
            ->expects($this->once())
            ->method('setIsProcessed')
            ->with($this->equalTo(true));
        $this->expectOutputString('<rendered/>');

        $this->_object
            ->setResponse($response)
            ->exceptionHandler($event);
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::postDispatchHandler
     */
    public function testWillAutoSerializeReturnDataIfArray()
    {
        $response = $this->getMock('Core_Response', array('setBody', 'setType', 'setFormat', 'render'));
        $response
            ->expects($this->once())
            ->method('setBody')
            ->with($this->equalTo(array('a', 'b', 'c')))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setType')
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setFormat')
            ->will($this->returnValue($response));
        $this->_object->setResponse($response);
        $event    = $this->getMock('Core_Event_Interface');

        $this->assertEquals($response, $this->_object->postDispatchHandler($event, array('a', 'b', 'c')));
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::postDispatchHandler
     */
    public function testWillReturnDataFromPostDispatchHandlerIfNotArray()
    {
        $event = $this->getMock('Core_Event_Interface');

        $this->assertEquals('abc', $this->_object->postDispatchHandler($event, 'abc'));
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::exceptionHandler
     * @covers Core_Application_Plugin_EventHandler_Rest::<protected>
     * 
     * @runInSeparateProcess
     */
    public function testWillSetCode422AndExtractErrorsForCore_Validate_Exception()
    {
        $this->markTestIncomplete('TODO: fix broken');

        $exception = $this->getMock('Core_Validate_Exception', array('getErrors'));
        $exception
            ->expects($this->once())
            ->method('getErrors')
            ->will($this->returnValue(array('a', 'b', 'c')));

        $response = $this->getMock('Core_Response', array('setBody', 'setType', 'setCode', 'render'));
        $response
            ->expects($this->once())
            ->method('setBody')
            ->with($this->equalTo(array('a', 'b', 'c')))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setType')
            ->with($this->equalTo('application/vnd.error.tree'))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setCode')
            ->with($this->equalTo(422))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('render')
            ->will($this->returnValue('<rendered/>'));

        $event    = $this->getMock('Core_Event', array('getParam', 'setIsProcessed'), array($this, 'someName'));
        $event
            ->expects($this->once())
            ->method('getParam')
            ->with($this->equalTo('exception'))
            ->will($this->returnValue($exception));
        $event
            ->expects($this->once())
            ->method('setIsProcessed')
            ->with($this->equalTo(true));
        $this->setExpectedException('Exception');

        $this->_object
            ->setResponse($response)
            ->exceptionHandler($event);
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::exceptionHandler
     * @covers Core_Application_Plugin_EventHandler_Rest::<protected>
     * 
     * @runInSeparateProcess
     */
    public function testWillSetCode422ForLogicException()
    {
        $this->markTestIncomplete('TODO: fix broken');

        $response = $this->getMock('Core_Response', array('setBody', 'setType', 'setCode', 'render'));
        $response
            ->expects($this->once())
            ->method('setBody')
            ->with($this->equalTo(array(array('message' => 'Err'))))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setType')
            ->with($this->equalTo('application/vnd.error'))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setCode')
            ->with($this->equalTo(422))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('render')
            ->will($this->returnValue('<rendered/>'));

        $event    = $this->getMock('Core_Event', array('getParam', 'setIsProcessed'), array($this, 'someName'));
        $event
            ->expects($this->once())
            ->method('getParam')
            ->with($this->equalTo('exception'))
            ->will($this->returnValue(new LogicException('Err')));
        $event
            ->expects($this->once())
            ->method('setIsProcessed')
            ->with($this->equalTo(true));
        $this->setExpectedException('Exception');

        $this->_object
            ->setResponse($response)
            ->exceptionHandler($event);
    }

    /**
     * @covers Core_Application_Plugin_EventHandler_Rest::exceptionHandler
     * @covers Core_Application_Plugin_EventHandler_Rest::<protected>
     * 
     * @runInSeparateProcess
     */
    public function testWillExtractStatusCodeFromExceptionIfGiven()
    {
        $this->markTestIncomplete('TODO: fix broken');

        $response = $this->getMock('Core_Response', array('setBody', 'setCode', 'render'));
        $response
            ->expects($this->once())
            ->method('setBody')
            ->with($this->equalTo(array(array('message' => 'Err'))))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('setCode')
            ->with($this->equalTo(999))
            ->will($this->returnValue($response));
        $response
            ->expects($this->once())
            ->method('render')
            ->will($this->returnValue('<rendered/>'));

        $event    = $this->getMock('Core_Event', array('getParam', 'setIsProcessed'), array($this, 'someName'));
        $event
            ->expects($this->once())
            ->method('getParam')
            ->with($this->equalTo('exception'))
            ->will($this->returnValue(new Invalid_RestException('Err')));
        $event
            ->expects($this->once())
            ->method('setIsProcessed')
            ->with($this->equalTo(true));
        $this->setExpectedException('Exception');

        $this->_object
            ->setResponse($response)
            ->exceptionHandler($event);
    }
}

class Invalid_RestException extends Exception
{
    const STATUS_CODE = 999;
}