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

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

    /**
     * Sets up the fixture, for example, opens a network connection.
     * This method is called before a test is executed.
     */
    protected function setUp()
    {
        $this->_object = Core_Application::getInstance();
    }

    protected function tearDown()
    {
        Core_Application::resetInstance();
        putenv('APPLICATION_ENV');
    }

    /**
     * @covers Core_Application::getInstance
     */
    public function testCanGetInstance()
    {
        $object = Core_Application::getInstance();
        $this->assertInstanceOf('Core_Application', $object);
    }

    /**
     * @covers Core_Application::getInstance
     * @depends testCanGetInstance
     */
    public function testWillUseInstacePassedToGetInstanceIfCreatingAnInstance()
    {
        Core_Application::resetInstance();
        $application = new Core_Application;

        $this->assertEquals($application, Core_Application::getInstance($application));
    }

    /**
     * @covers Core_Application::getInstance
     * @depends testWillUseInstacePassedToGetInstanceIfCreatingAnInstance
     */
    public function testWillIgnoreInstacePassedToGetInstanceIfInstanceAlreadyExists()
    {
        $this->markTestIncomplete('TODO: fix broken');

        $application = new Core_Application;

        $this->assertNotEquals($application, Core_Application::getInstance($application));
    }

    /**
     * @covers Core_Application::getInstance
     */
    public function testWillGetTheSameInstanceEveryTime()
    {
        $object1 = Core_Application::getInstance();
        $object1->setDocumentRoot('/some/path');
        $object2 = Core_Application::getInstance();

        $this->assertEquals('/some/path', $object2->getDocumentRoot());
    }

    /**
     * @covers Core_Application::resetInstance
     * @depends testWillGetTheSameInstanceEveryTime
     */
    public function testCanResetInstance()
    {
        $object1 = Core_Application::getInstance();
        $object1->setDocumentRoot('/some/path');
        Core_Application::resetInstance();

        $this->setExpectedException('RuntimeException');
        $object2 = Core_Application::getInstance();
        $object2->getDocumentRoot();
    }

    /**
     * @covers Core_Application::has
     */
    public function testHasWillProxyToContainer()
    {
        $container = $this->getMock('Core_Yadif_Container');
        $container
            ->expects($this->once())
            ->method('hasComponent')
            ->with($this->equalTo('Bla'))
            ->will($this->returnValue(true));
        $this->_object->setContainer($container);

        $this->assertTrue($this->_object->has('Bla'));
    }

    /**
     * @covers Core_Application::get
     */
    public function testGetWillProxyToContainer()
    {
        $component = new StdClass;
        $container = $this->_mockContainer('Bla', $component);
        $this->_object->setContainer($container);

        $this->assertEquals($component, $this->_object->get('Bla'));
    }

    /**
     * @covers Core_Application::get
     */
    public function testGetWillBindPassedParamsToContainerParams()
    {
        $container = $this->_mockContainer('Bla', null);
        $container
            ->expects($this->once())
            ->method('bindParam')
            ->with($this->equalTo(':paramName'), $this->equalTo('paramValue'));
        $this->_object->setContainer($container);

        $this->_object->get('Bla', array('paramName' => 'paramValue'));
    }

    /**
     * @covers Core_Application::getRuntime
     */
    public function testCanGetRuntime()
    {
        $this->assertEquals('cli', $this->_object->getRuntime());
    }

    /**
     * @covers Core_Application::getRuntime
     * @group runkit
     * @group pecl
     * @runInSeparateProcess
     */
    public function testIfNotCLIRuntimeWillBeWeb()
    {
        if (!function_exists('runkit_function_add')) {
            $this->markTestSkipped('Requires the runkit_function_add() provided by "runkit" PECL');
        } else if (!ini_get('runkit.internal_override')) {
            $this->markTestSkipped('Requires PHP INI setting runkit.internal_override = true');
        }
        runkit_function_rename('php_sapi_name', 'php_sapi_name_old');
        runkit_function_add('php_sapi_name', '', 'return "apache2handler";');

        $this->assertEquals('web', $this->_object->getRuntime());

        runkit_function_remove('php_sapi_name');
        runkit_function_rename('php_sapi_name_old', 'php_sapi_name');
    }

    /**
     * @covers Core_Application::setContext
     * @covers Core_Application::getContext
     */
    public function testCanSetAndGetContext()
    {
        $this->_object->setContext('bla');

        $this->assertEquals('bla', $this->_object->getContext());
    }

    /**
     * @covers Core_Application::getContext
     */
    public function testDefaultContextIsCurrentRuntime()
    {
        $this->assertEquals($this->_object->getRuntime(), $this->_object->getContext());
    }

    /**
     * @covers Core_Application::setRoot
     * @covers Core_Application::getRoot
     */
    public function testCanSetAndGetRoot()
    {
        $this->_object->setRoot(dirname(__FILE__));
        $this->assertEquals(dirname(__FILE__), $this->_object->getRoot());
    }

    /**
     * @covers Core_Application::getRoot
     */
    public function testIfRootNotDefinedGetRootWillThrowAnExceptionIfAPPLICATION_ROOTNotDefined()
    {
        $this->setExpectedException('RuntimeException');
        $this->_object->getRoot();
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::getRoot
     * @depends testIfRootNotDefinedGetRootWillThrowAnExceptionIfAPPLICATION_ROOTNotDefined
     */
    public function testGetRootWillUseAPPLICATION_ROOTConstant()
    {
        define('APPLICATION_ROOT', '/bla');
        $this->assertEquals('/bla', $this->_object->getRoot());
    }

    /**
     * @covers Core_Application::setDocumentRoot
     * @covers Core_Application::getDocumentRoot
     */
    public function testCanSetAndGetDocumentRoot()
    {
        $this->_object->setDocumentRoot(dirname(__FILE__));
        $this->assertEquals(dirname(__FILE__), $this->_object->getDocumentRoot());
    }

    /**
     * @covers Core_Application::getDocumentRoot
     */
    public function testIfDocumentRootNotDefinedGetDocumentRootWillThrowAnRuntimeException()
    {
        $this->setExpectedException('RuntimeException');
        $this->_object->getDocumentRoot();
    }

    /**
     * @covers Core_Application::setOptions
     */
    public function testCanSetOptions()
    {
        $options = array('documentRoot' => '/foo');
        $this->_object->setOptions($options);
        $this->assertEquals('/foo', $this->_object->getDocumentRoot());
    }

    /**
     * @covers Core_Application::setConfig
     * @covers Core_Application::getConfig
     */
    public function testCanSetAndGetConfig()
    {
        $config = $this->getMock('Zend_Config', null, array(array('documentRoot' => '/foo')));
        $this->_object->setConfig($config);
        $this->assertEquals($config, $this->_object->getConfig());
    }

    /**
     * @covers Core_Application::setConfig
     */
    public function testPassingAnUnknownOptionThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->setOptions(array('no-such' => 'option'));
    }

    /**
     * @covers Core_Application::setConfig
     */
    public function testWillSkipForbiddenOptions()
    {
        $this->_object->setDocumentRoot('/foo');
        $this->_object->setOptions(array('options' => array('documentRoot' => 'bar')));
        $this->assertEquals('/foo', $this->_object->getDocumentRoot());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::setConfig
     * @covers Core_Application::<protected>
     */
    public function testCanSetConfigAsConfigFile()
    {
        $config = TESTS_ROOT .'/resources/fixtures/Core/Application/with-sections.ini';
        $this->_object
            ->setEnvironment('testing')
            ->setConfig($config);

        $this->assertEquals('/foo', $this->_object->getDocumentRoot());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::setConfig
     * @covers Core_Application::<protected>
     */
    public function testWillReadStringsAsPathsToConfigFiles()
    {
        $file   = TESTS_ROOT .'/resources/fixtures/Core/Application/without-sections.ini';
        $config = $this->getMock('Zend_Config', null, array(array('classmap' => $file)));
        $loader = $this->getMock('Core_Loader');
        $loader
            ->staticExpects($this->once())
            ->method('setClassmap')
            ->with($this->equalTo(array('foo' => 'bar')));

        $this->_object
            ->setLoader($loader)
            ->setConfig($config);
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::setConfig
     * @covers Core_Application::<protected>
     */
    public function testCanConfigurePhpIniValuesViaOptions()
    {
        $precision = ini_get('precision');
        $this->_object->setOptions(array('php' => array('precision' => $precision + 2)));

        $this->assertEquals($precision + 2, ini_get('precision'));
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::setConfig
     * @covers Core_Application::<protected>
     */
    public function testCanConfigureLoaderClassmapViaOptions()
    {
        $classmap = array('Foo' => '/path/to/Foo.php');
        $loader   = $this->getMock('Core_Loader');
        $loader
            ->staticExpects($this->once())
            ->method('setClassMap')
            ->with($this->equalTo($classmap));
        $this->_object->setLoader($loader);

        $this->_object->setOptions(array('classmap' => $classmap));
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::setConfig
     * @covers Core_Application::<protected>
     */
    public function testCanConfigureContainerViaOptions()
    {
        $components = array('some' => array('class' => 'SomeClass'));
        $options    = array('container' => $components);
        $config     = $this->getMock('Zend_Config', null, array($options));

        $container  = $this->getMock('Core_Yadif_Container');
        $container
            ->expects($this->once())
            ->method('addComponents')
            ->with($this->equalTo($components))
            ->will($this->returnValue($container));
        $this->_object->setContainer($container);

        $this->_object->setConfig($config);
    }

    /**
     * @covers Core_Application::getConfig
     */
    public function testGetConfigWillThrowAnRuntimeExceptionIfNoConfigWasGiven()
    {
        $this->setExpectedException('RuntimeException');
        $this->_object->getConfig();
    }

    /**
     * @covers Core_Application::setEnvironment
     * @covers Core_Application::getEnvironment
     */
    public function testCanSetAndGetEnvironment()
    {
        $this->_object->setEnvironment('development');
        $this->assertEquals('development', $this->_object->getEnvironment());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::getEnvironment
     * @depends testCanSetAndGetEnvironment
     */
    public function testGetEnviromentWillThrowAnInvalidArgumentExceptionIfNoEnvironmentAvailable()
    {
        $this->setExpectedException('RuntimeException');
        $this->_object->getEnvironment();
    }


    /**
     * @runInSeparateProcess
     * @covers Core_Application::getEnvironment
     */
    public function testGetEnviromentWillUseAPLICATION_ENVEnvironmentVariableAsEnvironment()
    {
        putenv('APPLICATION_ENV=production');
        $this->assertEquals('production', $this->_object->getEnvironment());
    }

    /**
     * @runInSeparateProcess
     * @covers Core_Application::getEnvironment
     */
    public function testGetEnviromentWillUseAPLICATION_ENVConstantAsEnvironment()
    {
        define('APPLICATION_ENV', 'staging');
        $this->assertEquals('staging', $this->_object->getEnvironment());
    }

    /**
     * @covers Core_Application::setLoader
     * @covers Core_Application::getLoader
     */
    public function testCanSetAndGetLoader()
    {
        $loader = $this->getMock('Core_Loader');
        $this->_object->setLoader($loader);
        $this->assertEquals($loader, $this->_object->getLoader());
    }

    /**
     * @covers Core_Application::setContainer
     * @covers Core_Application::getContainer
     */
    public function testCanSetAndGetContainer()
    {
        $container = $this->getMock('Core_Yadif_Container');
        $this->_object->setContainer($container);
        $this->assertEquals($container, $this->_object->getContainer());
    }

    /**
     * @covers Core_Application::getContainer
     */
    public function testIfNoContainerGivenWillCreateANewInstanceOfCore_Yadif_Container()
    {
        $this->assertInstanceOf('Core_Yadif_Container', $this->_object->getContainer());
    }

    /**
     * @covers Core_Application::setPluginLoader
     * @covers Core_Application::getPluginLoader
     */
    public function testCanSetAndGetPluginLoader()
    {
        $pluginLoader = $this->getMock('Core_Loader_PluginLoader');
        $this->_object->setPluginLoader($pluginLoader);
        $this->assertEquals($pluginLoader, $this->_object->getPluginLoader());
    }

    /**
     * @covers Core_Application::getPluginLoader
     */
    public function testIfNoPluginLoaderSetWillFetchOneFromContainerAsPluginLoader()
    {
        $pluginLoader = $this->getMock('Core_Loader_PluginLoader');
        $container    = $this->_mockContainer('PluginLoader', $pluginLoader);
        $this->_object->setContainer($container);

        $this->assertEquals($pluginLoader, $this->_object->getPluginLoader());
    }

    /**
     * @covers Core_Application::setEventDispatcher
     * @covers Core_Application::getEventDispatcher
     */
    public function testCanSetAndGetEventDispatcher()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher');
        $this->_object->setEventDispatcher($eventDispatcher);
        $this->assertEquals($eventDispatcher, $this->_object->getEventDispatcher());
    }

    /**
     * @covers Core_Application::getEventDispatcher
     */
    public function testIfNoEventDispatcherSetWillFetchOneFromContainerAsEventDispatcher()
    {
        $eventDispatcher = $this->getMock('Core_Event_Dispatcher');
        $container       = $this->_mockContainer('EventDispatcher', $eventDispatcher);
        $this->_object->setContainer($container);

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

    /**
     * @covers Core_Application::addPlugin
     * @covers Core_Application::hasPlugin
     */
    public function testCanAddPlugin()
    {
        $plugin = $this->_mockPlugin('somePlugin');
        $this->_object->addPlugin($plugin);
        $this->assertTrue($this->_object->hasPlugin('somePlugin'));
    }

    /**
     * @covers Core_Application::getPlugins
     */
    public function testCanGetPlugins()
    {
        $plugin = $this->_mockPlugin('pluginName');
        $this->_object->addPlugin($plugin);
        $this->assertContains($plugin, $this->_object->getPlugins());
    }

    /**
     * @covers Core_Application::getPlugin
     */
    public function testCanGetPluginByName()
    {
        $plugin = $this->_mockPlugin('pluginName');
        $this->_object->addPlugin($plugin);
        $this->assertEquals($plugin, $this->_object->getPlugin('pluginname'));
    }

    /**
     * @covers Core_Application::getPlugin
     */
    public function testGettingUnknownPluginThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->getPlugin('noSuchPlugin');
    }

    /**
     * @covers Core_Application::createPlugin
     */
    public function testCanCreatePlugin()
    {
        $plugin = $this->_mockPlugin('somePlugin');
        $this->_object->setPluginLoader($this->_mockPluginLoader('Abstract', null, $plugin));

        $this->_object->createPlugin('Abstract', 'somePlugin', array('opt' => 'ions'));
        $this->assertTrue($this->_object->hasPlugin('somePlugin'));
    }

    /**
     * @covers Core_Application::createPlugin
     */
    public function testWhenCreatingPluginWillTryToFindItInContainerViaTypeWithAppPrefix()
    {
        $type      = 'somePluginType';
        $ctype     = 'app' .$type;
        $plugin    = $this->_mockPlugin('somePlugin');
        $container = $this->_mockContainer($ctype, $plugin);
        $container
            ->expects($this->once())
            ->method('hasComponent')
            ->with($this->equalTo($ctype))
            ->will($this->returnValue(true));
        $this->_object->setContainer($container);

        $this->assertEquals($plugin, $this->_object->createPlugin($type, 'somePlugin'));
    }

    /**
     * @covers Core_Application::addPlugins
     * @depends testCanAddPlugin
     */
    public function testCanAddArrayOfPluginInstancesAndSpecs()
    {
        $plugin       = $this->_mockPlugin('somePlugin2');
        $pluginLoader = $this->_mockPluginLoader(
            'Abstract', array('name' => 'somePlugin2', 'options' => array('opt' => 'ions')), $plugin
        );
        $this->_object->setPluginLoader($pluginLoader);

        $plugins = array(
            $this->_mockPlugin('somePlugin1'),
            'somePlugin2' => array(
                'type'    => 'Abstract',
                'options' => array(
                    'opt'     => 'ions',
                )
            )
        );
        $this->_object->addPlugins($plugins);

        $this->assertTrue($this->_object->hasPlugin('somePlugin1'));
        $this->assertTrue($this->_object->hasPlugin('somePlugin2'));
    }

    /**
     * @covers Core_Application::addPlugins
     * @depends testCanAddPlugin
     */
    public function testAddingPluginViaSpecWithoutTypeThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $plugins = array(
            'somePlugin2' => array(
                'options' => array(
                    'opt'     => 'ions',
                )
            )
        );
        $this->_object->addPlugins($plugins);
    }

    /**
     * @covers Core_Application::setConfig
     */
    public function testCanAddPluginsViaOptions()
    {
        $plugin = $this->_mockPlugin('somePlugin');
        $this->_object->setPluginLoader($this->_mockPluginLoader('Abstract', null, $plugin));

        $options = array(
            'plugins' => array(
                'somePlugin' => array(
                    'type' => 'Abstract',
                )
            )
        );
        $this->_object->setOptions($options);
        $this->assertTrue($this->_object->hasPlugin('somePlugin'));
    }

    /**
     * @covers Core_Application::removePlugin
     * @depends testCanAddPlugin
     */
    public function testCanRemovePlugin()
    {
        $this->_object->addPlugin($this->_mockPlugin('foo'));
        $this->_object->removePlugin('foo');

        $this->assertFalse($this->_object->hasPlugin('foo'));
    }

    /**
     * @covers Core_Application::removePlugin
     */
    public function testRemovingUnknownPluginThrowsAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->removePlugin('noSuchPlugin');
    }

    /**
     * @covers Core_Application::__call
     */
    public function testCallsToNonexistantMethodsPrefixedWithGetWillLookupNameInContainer()
    {
        $component = new StdClass;
        $container = $this->_mockContainer('something', $component);
        $this->_object->setContainer($container);

        $this->assertEquals($component, $this->_object->getSomething());
    }

    /**
     * @covers Core_Application::__call
     */
    public function testCallsToNonexistantMethodsNotPrefixedWithGetWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');
        $this->_object->fooSomething();
    }

    /**
     * @covers Core_Application::run
     */
    public function testWillDispatchApplicationStartupEventOnRunStart()
    {
        // we did not set the router
        $this->setExpectedException('Yadif_Exception');

        $eventDispatcher = new Invalid_Event_Dispatcher;
        $this->_object->setEventDispatcher($eventDispatcher);
        $this->_object->run();

        $this->assertEquals(Core_Application::EVENT_STARTUP, $eventDispatcher->event->getName());
        $this->assertEquals($this->_object, $eventDispatcher->event->getSubject());
    }

    /**
     * @covers Core_Application::run
     */
    public function testCanRunApplication()
    {
        $route  = $this->getMock('Core_Router_Route_Interface');
        $route
            ->expects($this->once())
            ->method('getValues')
            ->will($this->returnValue(array('module' => 'foo', 'controller' => 'bar', 'action' => 'bat')));
        $router = $this->getMock('Core_Router');
        $router
            ->expects($this->once())
            ->method('route')
            ->with($this->equalTo('/some/path'))
            ->will($this->returnValue($route));
        $container = $this->getMock('Core_Yadif_Container');
        $container
            ->expects($this->at(0))
            ->method('getComponent')
            ->with($this->equalTo('EventDispatcher'))
            ->will($this->returnValue($this->getMock('Core_Event_Dispatcher_Interface')));
        $container
            ->expects($this->at(1))
            ->method('getComponent')
            ->with($this->equalTo('Router'))
            ->will($this->returnValue($router));
        $dispatcher = new Invalid_Dispatcher;
        $container
            ->expects($this->at(2))
            ->method('getComponent')
            ->with($this->equalTo('Dispatcher'))
            ->will($this->returnValue($dispatcher));
        $this->_object->setContainer($container);

        $this->assertEquals('response', $this->_object->run('/some/path'));

        // verify proper location setup
        $location = $dispatcher->location;
        $this->assertEquals('foo', $location->getModule());
        $this->assertEquals('bar', $location->getController());
        $this->assertEquals('bat', $location->getAction());
    }

    /**
     * @covers Core_Application::run
     */
    public function testWillPassRequestUriToRouteIfNoPathSpecifiedForRun()
    {
        $route  = $this->getMock('Core_Router_Route_Interface');
        $route
            ->expects($this->once())
            ->method('getValues')
            ->will($this->returnValue(array('module' => 'foo', 'controller' => 'bar', 'action' => 'bat')));
        $router = $this->getMock('Core_Router');
        $router
            ->expects($this->once())
            ->method('route')
            ->with($this->equalTo('/request/uri'))
            ->will($this->returnValue($route));
        $container = $this->getMock('Core_Yadif_Container');
        $container
            ->expects($this->at(0))
            ->method('getComponent')
            ->with($this->equalTo('EventDispatcher'))
            ->will($this->returnValue($this->getMock('Core_Event_Dispatcher_Interface')));
        $container
            ->expects($this->at(1))
            ->method('getComponent')
            ->with($this->equalTo('Router'))
            ->will($this->returnValue($router));
        $request = $this->getMock('Core_Request');
        $request
            ->expects($this->once())
            ->method('getUri')
            ->will($this->returnValue('/request/uri'));
        $container
            ->expects($this->at(2))
            ->method('getComponent')
            ->with($this->equalTo('Request'))
            ->will($this->returnValue($request));
        $dispatcher = new Invalid_Dispatcher;
        $container
            ->expects($this->at(3))
            ->method('getComponent')
            ->with($this->equalTo('Dispatcher'))
            ->will($this->returnValue($dispatcher));
        $this->_object->setContainer($container);

        $this->assertEquals('response', $this->_object->run());

        // verify proper location setup
        $location = $dispatcher->location;
        $this->assertEquals('foo', $location->getModule());
        $this->assertEquals('bar', $location->getController());
        $this->assertEquals('bat', $location->getAction());
    }

    /**
     * @covers Core_Application::run
     */
    public function testWillRegisterShutdownFunctionOnRunEnd()
    {
        $route  = $this->getMock('Core_Router_Route_Interface');
        $route
            ->expects($this->once())
            ->method('getValues')
            ->will($this->returnValue(array('module' => 'foo', 'controller' => 'bar', 'action' => 'bat')));
        $router = $this->getMock('Core_Router');
        $router
            ->expects($this->once())
            ->method('route')
            ->with($this->equalTo('/some/path'))
            ->will($this->returnValue($route));
        $container = $this->getMock('Core_Yadif_Container');
        $eventDispatcher = new Invalid_Event_Dispatcher;
        $eventDispatcher->subscribe(Core_Application::EVENT_SHUTDOWN, 'printf');
        $container
            ->expects($this->at(0))
            ->method('getComponent')
            ->with($this->equalTo('EventDispatcher'))
            ->will($this->returnValue($eventDispatcher));
        $container
            ->expects($this->at(1))
            ->method('getComponent')
            ->with($this->equalTo('Router'))
            ->will($this->returnValue($router));
        $dispatcher = new Invalid_Dispatcher;
        $container
            ->expects($this->at(2))
            ->method('getComponent')
            ->with($this->equalTo('Dispatcher'))
            ->will($this->returnValue($dispatcher));
        $this->_object->setContainer($container);

        $this->_object->run('/some/path');
    }

    /**
     * @covers Core_Application::shutdown
     */
    public function testWillDispatchApplicationShutdownEventFromShutdownMethod()
    {
        $event     = $this->getMock('Core_Event_Interface');
        $container = $this->getMock('Core_Yadif_Container');
        $eventDispatcher = new Invalid_Event_Dispatcher;
        $eventDispatcher->subscribe(Core_Application::EVENT_SHUTDOWN, 'printf');
        $container
            ->expects($this->at(0))
            ->method('getComponent')
            ->with($this->equalTo('EventDispatcher'))
            ->will($this->returnValue($eventDispatcher));
        $this->_object->setContainer($container);
        $this->_object->shutdown($event);

        $this->assertEquals($event, $eventDispatcher->event);
    }

    /**
     * @covers Core_Application::run
     */
    public function testExceptionWhileRoutingWillTriggerARouterExceptionEvent()
    {
        $this->setExpectedException('RuntimeException');

        $router = $this->getMock('Core_Router');
        $router
            ->expects($this->once())
            ->method('route')
            ->with($this->equalTo('/some/path'))
            ->will($this->throwException(new RuntimeException('Routing exception!')));
        $eventDispatcher = new Invalid_Event_Dispatcher;
        $this->_object
            ->setContainer($this->_mockContainer('Router', $router))
            ->setEventDispatcher($eventDispatcher);
        $this->assertEquals('event dispatcher response', $this->_object->run('/some/path'));

        $event = $eventDispatcher->event;
        $this->assertEquals(Core_Application::EVENT_ROUTER_EXCEPTION, $event->getName());
        $this->assertEquals('/some/path', $event->getSubject());
    }

    /**
     * @covers Core_Application::run
     */
    public function testExceptionWhileDispatchingWillTriggerADispatcherExceptionEvent()
    {
        $this->setExpectedException('RuntimeException');

        $container       = $this->getMock('Core_Yadif_Container');

        $route           = $this->getMock('Core_Router_Route_Interface');
        $route
            ->expects($this->once())
            ->method('getValues')
            ->will($this->returnValue(array('module' => 'foo', 'controller' => 'bar', 'action' => 'bat')));
        $router          = $this->getMock('Core_Router');
        $router
            ->expects($this->once())
            ->method('route')
            ->will($this->returnValue($route));
        $container
            ->expects($this->at(0))
            ->method('getComponent')
            ->with($this->equalTo('Router'))
            ->will($this->returnValue($router));

        $dispatcher      = $this->getMock('Core_Dispatcher');
        $dispatcher
            ->expects($this->once())
            ->method('dispatch')
            ->will($this->throwException(new RuntimeException('Dispatch exception!')));
        $container
            ->expects($this->at(1))
            ->method('getComponent')
            ->with($this->equalTo('Dispatcher'))
            ->will($this->returnValue($dispatcher));

        $eventDispatcher = new Invalid_Event_Dispatcher;
        $this->_object
            ->setEventDispatcher($eventDispatcher)
            ->setContainer($container);
        $this->assertEquals('event dispatcher response', $this->_object->run('/some/path'));

        $event = $eventDispatcher->event;
        $this->assertEquals(Core_Application::EVENT_DISPATCHER_EXCEPTION, $event->getName());

        $location = $event->getSubject();
        $this->assertEquals('foo', $location->getModule());
        $this->assertEquals('bar', $location->getController());
        $this->assertEquals('bat', $location->getAction());
    }

    protected function _mockPlugin($name)
    {
        $plugin = $this->getMock('Core_Application_Plugin_Interface');
        $plugin
            ->expects($this->once())
            ->method('getName')
            ->will($this->returnValue($name));

        return $plugin;
    }

    protected function _mockPluginLoader($type = null, $params = null, $plugin = null)
    {
        $options = array('initializeApplicationPluginPlugin');
        $pluginLoader = $this->getMock('Core_Loader_PluginLoader', $options);
        if (null !== $type) {
            $mocker = $pluginLoader
                ->expects($this->once())
                ->method(current($options));

            if (null !== $params) {
                if (is_string($params)) {
                    // $params is the name alone
                    $mocker->with($this->equalTo($type), $this->equalTo($params));
                } else if (is_array($params)) {
                    // params should atleast have a name
                    if (!array_key_exists('name', $params)) {
                        // but it does not
                        throw new InvalidArgumentException('Mocking plugin loader without "name" specified in params');
                    }

                    // options are... well, optional
                    if (!array_key_exists('options', $params)) {
                        $mocker
                            ->with($this->equalTo($type), $this->equalTo($params['name']));
                    } else {
                        $name    = $params['name'];
                        $options = $params['options'];
                        $mocker
                            ->with($this->equalTo($type), $this->equalTo($name), $this->equalTo($options));
                    }
                }
            }

            if (null !== $plugin) {
                $mocker
                    ->will($this->returnValue($plugin));
            }
        }
        return $pluginLoader;
    }
}

class Invalid_Dispatcher extends Core_Dispatcher
{
    public $location;

    public function dispatch(Core_Dispatcher_Location_Interface $location)
    {
        $this->location = $location;
        return 'response';
    }
}

class Invalid_Event_Dispatcher extends Core_Event_Dispatcher
{
    public $event;

    public function dispatch(Core_Event_Interface $event)
    {
        $this->event = $event;
        return 'event dispatcher response';
    }
}