<?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_ExceptionEventTest extends CoreTest_Container_TestCase
{
    /**
     * @var Core_Application_Plugin_ExceptionEvent
     */
    protected $_object;

    public function setup()
    {
        $this->_object = new Core_Application_Plugin_ExceptionEvent('ee');
    }

    protected function tearDown()
    {
        Core_Application::resetInstance();
    }
    
    /**
     * @covers Core_Application_Plugin_ExceptionEvent::setDebug
     * @covers Core_Application_Plugin_ExceptionEvent::isDebug
     */
    public function testCanGetAndSetDebug()
    {
        $this->_object->setDebug(true);

        $this->assertTrue($this->_object->isDebug());
    }
    
    /**
     * @covers Core_Application_Plugin_ExceptionEvent::isDebug
     */
    public function testDebugIsFalseByDefault()
    {
        $this->assertFalse($this->_object->isDebug());
    }
    
    /**
     * @covers Core_Application_Plugin_ExceptionEvent::setThrowErrorExceptions
     * @covers Core_Application_Plugin_ExceptionEvent::isThrowErrorExceptions
     */
    public function testCanSetAndGetThrowErrorExceptions()
    {
        $this->_object->setThrowErrorExceptions(true);
        $this->assertTrue($this->_object->isThrowErrorExceptions());
    }
    
    /**
     * @covers Core_Application_Plugin_ExceptionEvent::isThrowErrorExceptions
     */
    public function testThrowErrorExceptionsIsFalseByDefault()
    {
        $this->assertFalse($this->_object->isThrowErrorExceptions());
    }

    /**
     * @covers Core_Application_Plugin_ExceptionEvent::setEventDispatcher
     * @covers Core_Application_Plugin_ExceptionEvent::getEventDispatcher
     */
    public function testCanGetAndSetEventDispatcher()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher');
        $this->_object->setEventDispatcher($eventDispatcher);

        $this->assertEquals($eventDispatcher, $this->_object->getEventDispatcher());
    }

    /**
     * @covers Core_Application_Plugin_ExceptionEvent::getEventDispatcher
     * @depends testCanGetAndSetEventDispatcher
     */
    public function testIfDispatcherNotSetWillPullFromApplicationAsEventDispatcher()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher');
        $container       = $this->_mockContainer('EventDispatcher', $eventDispatcher);
        Core_Application::getInstance()->setContainer($container);

        $this->assertEquals($eventDispatcher, $this->_object->getEventDispatcher());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application_Plugin_ExceptionEvent::init
     */
    public function testWillPlaceErrorHandlerFromInit()
    {
        $this->_object
            ->setEventDispatcher($this->getMock('Core_Event_Dispatcher_Interface'))
            ->init();

        $handler       ='Invalid_Event_Dispatcher_Processor::dummy_error_exception_handler_function';
        $errorHandler = set_error_handler($handler);
        $this->assertEquals(array($this->_object, 'errorHandler'), $errorHandler);

        $exceptionHandler = set_exception_handler($handler);
        $this->assertEquals(array($this->_object, 'exceptionHandler'), $exceptionHandler);
    }

    /**
     * @covers Core_Application_Plugin_ExceptionEvent::exceptionHandler
     * @covers Core_Application_Plugin_ExceptionEvent::<protected>
     */
    public function testWillHandleExceptions()
    {
        $exception       = $this->getMock('Core_Application_NotFoundException');
        $eventDispatcher = new Invalid_Event_Dispatcher_Processor;
        $this->_object
            ->setEventDispatcher($eventDispatcher)
            ->exceptionHandler($exception);

        $this->assertEquals(Core_Application_NotFoundException::EVENT_EXCEPTION, $eventDispatcher->event->getName());
        $this->assertEquals($exception, $eventDispatcher->event->getParam('exception'));
    }

    /**
     * @covers Core_Application_Plugin_ExceptionEvent::exceptionHandler
     * @covers Core_Application_Plugin_ExceptionEvent::<protected>
     */
    public function testWillHandleExceptionsThrownWhileHandlingExceptions()
    {
        $exception              = $this->getMock('Exception');
        $eventDispatcher        = new Invalid_Event_Dispatcher_Processor;
        $eventDispatcher->throw = true;
        $this->expectOutputRegex('/^Exception "Exception" raised within exception handler/');
        $this->_object
            ->setDebug(true)
            ->setEventDispatcher($eventDispatcher);

        // will mark the end of execution here
        $this->assertTrue($this->_object->exceptionHandler($exception));
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application_Plugin_ExceptionEvent::errorHandler
     * @covers Core_Application_Plugin_ExceptionEvent::<protected>
     */
    public function testWillNotInvokeErrorHandlerIfErrorTypeIsNotOfDesiredType()
    {
        // report everything except notices
        error_reporting(E_ALL ^ E_NOTICE);

        // not invoking the error handler (ignoring the notice completely)
        $this->assertFalse($this->_object->errorHandler(E_NOTICE, 'Skip this notice!', __FILE__, __LINE__));
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application_Plugin_ExceptionEvent::errorHandler
     * @covers Core_Application_Plugin_ExceptionEvent::<protected>
     */
    public function testWillInvokeErrorHandlerIfErrorTypeIsOfDesiredType()
    {
        $eventDispatcher          = new Invalid_Event_Dispatcher_Processor;
        $eventDispatcher->process = true;
        $this->_object
            ->setThrowErrorExceptions(true)
            ->setEventDispatcher($eventDispatcher);

        // report EVERYTHING
        error_reporting(E_ALL);

        $this->setExpectedException('ErrorException');

        // invoking the error handler and dispatching the ExceptionEvent (event dispatcher processed the event)
        $this->assertTrue($this->_object->errorHandler(E_NOTICE, 'Do not skip this!', __FILE__, __LINE__));
    }
    
    /**
     * @runInSeparateProcess
     * @covers Core_Application_Plugin_ExceptionEvent::errorHandler
     * @covers Core_Application_Plugin_ExceptionEvent::<protected>
     */
    public function testWillNotThrowTheErrorExceptionByDefault()
    {
        $eventDispatcher          = new Invalid_Event_Dispatcher_Processor;
        $eventDispatcher->process = true;
        $this->_object
            ->setThrowErrorExceptions(false)
            ->setEventDispatcher($eventDispatcher);

        // report EVERYTHING
        error_reporting(E_ALL);

        // invoking the error handler and dispatching the ExceptionEvent (event dispatcher processed the event)
        $this->_object->errorHandler(E_NOTICE, 'Do not skip this!', __FILE__, __LINE__);
        $this->assertTrue($eventDispatcher->event->isProcessed());
    }

    /**
     * @covers Core_Application_Plugin_ExceptionEvent::exitHandler
     */
    public function testWillNotInvokeExitHandlerIfNoErrorOccured()
    {
        $this->assertFalse($this->_object->exitHandler());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application_Plugin_ExceptionEvent::exitHandler
     */
    public function testWillInvokeExitHandlerIfAnErrorOccured()
    {
        $eventDispatcher          = new Invalid_Event_Dispatcher_Processor;
        $eventDispatcher->process = true;
        $this->_object
            ->setEventDispatcher($eventDispatcher);

        // we trigger a notice (but suppress it from PHPUnit)
        @trigger_error('Hello from an user-generated PHP error.', E_USER_NOTICE);

        // error was processed (so our handler was triggered)
        $this->_object->exitHandler();
        $this->assertTrue($eventDispatcher->event->isProcessed());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application_Plugin_ExceptionEvent::exitHandler
     */
    public function testExitHandlerWillForceNotThrowingTheErrorException()
    {
        $eventDispatcher          = new Invalid_Event_Dispatcher_Processor;
        $eventDispatcher->process = true;
        $this->_object
            // this will not be acted upon
            ->setThrowErrorExceptions(true)
            ->setEventDispatcher($eventDispatcher);

        // we trigger a notice (but suppress it from PHPUnit)
        @trigger_error('Hello from an user-generated PHP error.', E_USER_NOTICE);

        // error was processed (so our handler was triggered)
        $this->_object->exitHandler();
        $this->assertTrue($eventDispatcher->event->isProcessed());
    }
}

class Invalid_Event_Dispatcher_Processor extends Core_Event_Dispatcher
{
    public $event;
    public $process = false;
    public $throw   = false;

    public function dispatchUntil(Core_Event_Interface $event)
    {
        if ($this->throw) {
            throw new Exception('OMG!');
        } elseif ($this->process) {
            $event->setIsProcessed(true);
        }

        $this->event = $event;
    }

    static function dummy_error_exception_handler_function($e)
    {
    }
}