* * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ namespace Silex\Tests; use Silex\Application; use Silex\ControllerCollection; use Silex\Api\ControllerProviderInterface; use Silex\Route; use Silex\Provider\MonologServiceProvider; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\Routing\RouteCollection; /** * Application test cases. * * @author Igor Wiedler */ class ApplicationTest extends \PHPUnit_Framework_TestCase { public function testMatchReturnValue() { $app = new Application(); $returnValue = $app->match('/foo', function () {}); $this->assertInstanceOf('Silex\Controller', $returnValue); $returnValue = $app->get('/foo', function () {}); $this->assertInstanceOf('Silex\Controller', $returnValue); $returnValue = $app->post('/foo', function () {}); $this->assertInstanceOf('Silex\Controller', $returnValue); $returnValue = $app->put('/foo', function () {}); $this->assertInstanceOf('Silex\Controller', $returnValue); $returnValue = $app->patch('/foo', function () {}); $this->assertInstanceOf('Silex\Controller', $returnValue); $returnValue = $app->delete('/foo', function () {}); $this->assertInstanceOf('Silex\Controller', $returnValue); } public function testConstructorInjection() { // inject a custom parameter $params = array('param' => 'value'); $app = new Application($params); $this->assertSame($params['param'], $app['param']); // inject an existing parameter $params = array('locale' => 'value'); $app = new Application($params); $this->assertSame($params['locale'], $app['locale']); } public function testGetRequest() { $request = Request::create('/'); $app = new Application(); $app->get('/', function (Request $req) use ($request) { return $request === $req ? 'ok' : 'ko'; }); $this->assertEquals('ok', $app->handle($request)->getContent()); } public function testGetRoutesWithNoRoutes() { $app = new Application(); $routes = $app['routes']; $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); $this->assertEquals(0, count($routes->all())); } public function testGetRoutesWithRoutes() { $app = new Application(); $app->get('/foo', function () { return 'foo'; }); $app->get('/bar')->run(function () { return 'bar'; }); $routes = $app['routes']; $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $routes); $this->assertEquals(0, count($routes->all())); $app->flush(); $this->assertEquals(2, count($routes->all())); } public function testOnCoreController() { $app = new Application(); $app->get('/foo/{foo}', function (\ArrayObject $foo) { return $foo['foo']; })->convert('foo', function ($foo) { return new \ArrayObject(array('foo' => $foo)); }); $response = $app->handle(Request::create('/foo/bar')); $this->assertEquals('bar', $response->getContent()); $app->get('/foo/{foo}/{bar}', function (\ArrayObject $foo) { return $foo['foo']; })->convert('foo', function ($foo, Request $request) { return new \ArrayObject(array('foo' => $foo.$request->attributes->get('bar'))); }); $response = $app->handle(Request::create('/foo/foo/bar')); $this->assertEquals('foobar', $response->getContent()); } public function testOn() { $app = new Application(); $app['pass'] = false; $app->on('test', function (Event $e) use ($app) { $app['pass'] = true; }); $app['dispatcher']->dispatch('test'); $this->assertTrue($app['pass']); } public function testAbort() { $app = new Application(); try { $app->abort(404); $this->fail(); } catch (HttpException $e) { $this->assertEquals(404, $e->getStatusCode()); } } /** * @dataProvider escapeProvider */ public function testEscape($expected, $text) { $app = new Application(); $this->assertEquals($expected, $app->escape($text)); } public function escapeProvider() { return array( array('<', '<'), array('>', '>'), array('"', '"'), array("'", "'"), array('abc', 'abc'), ); } public function testControllersAsMethods() { $app = new Application(); $app->get('/{name}', 'Silex\Tests\FooController::barAction'); $this->assertEquals('Hello Fabien', $app->handle(Request::create('/Fabien'))->getContent()); } public function testApplicationTypeHintWorks() { $app = new SpecialApplication(); $app->get('/{name}', 'Silex\Tests\FooController::barSpecialAction'); $this->assertEquals('Hello Fabien in Silex\Tests\SpecialApplication', $app->handle(Request::create('/Fabien'))->getContent()); } public function testHttpSpec() { $app = new Application(); $app['charset'] = 'ISO-8859-1'; $app->get('/', function () { return 'hello'; }); // content is empty for HEAD requests $response = $app->handle(Request::create('/', 'HEAD')); $this->assertEquals('', $response->getContent()); // charset is appended to Content-Type $response = $app->handle(Request::create('/')); $this->assertEquals('text/html; charset=ISO-8859-1', $response->headers->get('Content-Type')); } public function testRoutesMiddlewares() { $app = new Application(); $test = $this; $middlewareTarget = array(); $beforeMiddleware1 = function (Request $request) use (&$middlewareTarget, $test) { $test->assertEquals('/reached', $request->getRequestUri()); $middlewareTarget[] = 'before_middleware1_triggered'; }; $beforeMiddleware2 = function (Request $request) use (&$middlewareTarget, $test) { $test->assertEquals('/reached', $request->getRequestUri()); $middlewareTarget[] = 'before_middleware2_triggered'; }; $beforeMiddleware3 = function (Request $request) use (&$middlewareTarget, $test) { throw new \Exception('This middleware shouldn\'t run!'); }; $afterMiddleware1 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { $test->assertEquals('/reached', $request->getRequestUri()); $middlewareTarget[] = 'after_middleware1_triggered'; }; $afterMiddleware2 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { $test->assertEquals('/reached', $request->getRequestUri()); $middlewareTarget[] = 'after_middleware2_triggered'; }; $afterMiddleware3 = function (Request $request, Response $response) use (&$middlewareTarget, $test) { throw new \Exception('This middleware shouldn\'t run!'); }; $app->get('/reached', function () use (&$middlewareTarget) { $middlewareTarget[] = 'route_triggered'; return 'hello'; }) ->before($beforeMiddleware1) ->before($beforeMiddleware2) ->after($afterMiddleware1) ->after($afterMiddleware2); $app->get('/never-reached', function () use (&$middlewareTarget) { throw new \Exception('This route shouldn\'t run!'); }) ->before($beforeMiddleware3) ->after($afterMiddleware3); $result = $app->handle(Request::create('/reached')); $this->assertSame(array('before_middleware1_triggered', 'before_middleware2_triggered', 'route_triggered', 'after_middleware1_triggered', 'after_middleware2_triggered'), $middlewareTarget); $this->assertEquals('hello', $result->getContent()); } public function testRoutesBeforeMiddlewaresWithResponseObject() { $app = new Application(); $app->get('/foo', function () { throw new \Exception('This route shouldn\'t run!'); }) ->before(function () { return new Response('foo'); }); $request = Request::create('/foo'); $result = $app->handle($request); $this->assertEquals('foo', $result->getContent()); } public function testRoutesAfterMiddlewaresWithResponseObject() { $app = new Application(); $app->get('/foo', function () { return new Response('foo'); }) ->after(function () { return new Response('bar'); }); $request = Request::create('/foo'); $result = $app->handle($request); $this->assertEquals('bar', $result->getContent()); } public function testRoutesBeforeMiddlewaresWithRedirectResponseObject() { $app = new Application(); $app->get('/foo', function () { throw new \Exception('This route shouldn\'t run!'); }) ->before(function () use ($app) { return $app->redirect('/bar'); }); $request = Request::create('/foo'); $result = $app->handle($request); $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $result); $this->assertEquals('/bar', $result->getTargetUrl()); } public function testRoutesBeforeMiddlewaresTriggeredAfterSilexBeforeFilters() { $app = new Application(); $middlewareTarget = array(); $middleware = function (Request $request) use (&$middlewareTarget) { $middlewareTarget[] = 'middleware_triggered'; }; $app->get('/foo', function () use (&$middlewareTarget) { $middlewareTarget[] = 'route_triggered'; }) ->before($middleware); $app->before(function () use (&$middlewareTarget) { $middlewareTarget[] = 'before_triggered'; }); $app->handle(Request::create('/foo')); $this->assertSame(array('before_triggered', 'middleware_triggered', 'route_triggered'), $middlewareTarget); } public function testRoutesAfterMiddlewaresTriggeredBeforeSilexAfterFilters() { $app = new Application(); $middlewareTarget = array(); $middleware = function (Request $request) use (&$middlewareTarget) { $middlewareTarget[] = 'middleware_triggered'; }; $app->get('/foo', function () use (&$middlewareTarget) { $middlewareTarget[] = 'route_triggered'; }) ->after($middleware); $app->after(function () use (&$middlewareTarget) { $middlewareTarget[] = 'after_triggered'; }); $app->handle(Request::create('/foo')); $this->assertSame(array('route_triggered', 'middleware_triggered', 'after_triggered'), $middlewareTarget); } public function testFinishFilter() { $containerTarget = array(); $app = new Application(); $app->finish(function () use (&$containerTarget) { $containerTarget[] = '4_filterFinish'; }); $app->get('/foo', function () use (&$containerTarget) { $containerTarget[] = '1_routeTriggered'; return new StreamedResponse(function () use (&$containerTarget) { $containerTarget[] = '3_responseSent'; }); }); $app->after(function () use (&$containerTarget) { $containerTarget[] = '2_filterAfter'; }); $app->run(Request::create('/foo')); $this->assertSame(array('1_routeTriggered', '2_filterAfter', '3_responseSent', '4_filterFinish'), $containerTarget); } /** * @expectedException \RuntimeException */ public function testNonResponseAndNonNullReturnFromRouteBeforeMiddlewareShouldThrowRuntimeException() { $app = new Application(); $middleware = function (Request $request) { return 'string return'; }; $app->get('/', function () { return 'hello'; }) ->before($middleware); $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); } /** * @expectedException \RuntimeException */ public function testNonResponseAndNonNullReturnFromRouteAfterMiddlewareShouldThrowRuntimeException() { $app = new Application(); $middleware = function (Request $request) { return 'string return'; }; $app->get('/', function () { return 'hello'; }) ->after($middleware); $app->handle(Request::create('/'), HttpKernelInterface::MASTER_REQUEST, false); } public function testSubRequest() { $app = new Application(); $app->get('/sub', function (Request $request) { return new Response('foo'); }); $app->get('/', function (Request $request) use ($app) { return $app->handle(Request::create('/sub'), HttpKernelInterface::SUB_REQUEST); }); $this->assertEquals('foo', $app->handle(Request::create('/'))->getContent()); } public function testRegisterShouldReturnSelf() { $app = new Application(); $provider = $this->getMock('Pimple\ServiceProviderInterface'); $this->assertSame($app, $app->register($provider)); } public function testMountShouldReturnSelf() { $app = new Application(); $mounted = new ControllerCollection(new Route()); $mounted->get('/{name}', function ($name) { return new Response($name); }); $this->assertSame($app, $app->mount('/hello', $mounted)); } public function testMountPreservesOrder() { $app = new Application(); $mounted = new ControllerCollection(new Route()); $mounted->get('/mounted')->bind('second'); $app->get('/before')->bind('first'); $app->mount('/', $mounted); $app->get('/after')->bind('third'); $app->flush(); $this->assertEquals(array('first', 'second', 'third'), array_keys(iterator_to_array($app['routes']))); } /** * @expectedException \LogicException * @expectedExceptionMessage The "mount" method takes either a "ControllerCollection" instance, "ControllerProviderInterface" instance, or a callable. */ public function testMountNullException() { $app = new Application(); $app->mount('/exception', null); } /** * @expectedException \LogicException * @expectedExceptionMessage The method "Silex\Tests\IncorrectControllerCollection::connect" must return a "ControllerCollection" instance. Got: "NULL" */ public function testMountWrongConnectReturnValueException() { $app = new Application(); $app->mount('/exception', new IncorrectControllerCollection()); } public function testMountCallable() { $app = new Application(); $app->mount('/prefix', function (ControllerCollection $coll) { $coll->get('/path'); }); $app->flush(); $this->assertEquals(1, $app['routes']->count()); } public function testSendFile() { $app = new Application(); $response = $app->sendFile(__FILE__, 200, array('Content-Type: application/php')); $this->assertInstanceOf('Symfony\Component\HttpFoundation\BinaryFileResponse', $response); $this->assertEquals(__FILE__, (string) $response->getFile()); } /** * @expectedException \LogicException * @expectedExceptionMessage The "homepage" route must have code to run when it matches. */ public function testGetRouteCollectionWithRouteWithoutController() { $app = new Application(); unset($app['exception_handler']); $app->match('/')->bind('homepage'); $app->handle(Request::create('/')); } public function testRedirectDoesNotRaisePHPNoticesWhenMonologIsRegistered() { $app = new Application(); ErrorHandler::register(null, false); $app['monolog.logfile'] = 'php://memory'; $app->register(new MonologServiceProvider()); $app->get('/foo/', function () { return 'ok'; }); $response = $app->handle(Request::create('/foo')); $this->assertEquals(301, $response->getStatusCode()); } public function testBeforeFilterOnMountedControllerGroupIsolatedToGroup() { $app = new Application(); $app->match('/', function () { return new Response('ok'); }); $mounted = $app['controllers_factory']; $mounted->before(function () { return new Response('not ok'); }); $app->mount('/group', $mounted); $response = $app->handle(Request::create('/')); $this->assertEquals('ok', $response->getContent()); } public function testViewListenerWithPrimitive() { $app = new Application(); $app->get('/foo', function () { return 123; }); $app->view(function ($view, Request $request) { return new Response($view); }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('123', $response->getContent()); } public function testViewListenerWithArrayTypeHint() { $app = new Application(); $app->get('/foo', function () { return array('ok'); }); $app->view(function (array $view) { return new Response($view[0]); }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('ok', $response->getContent()); } public function testViewListenerWithObjectTypeHint() { $app = new Application(); $app->get('/foo', function () { return (object) array('name' => 'world'); }); $app->view(function (\stdClass $view) { return new Response('Hello '.$view->name); }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('Hello world', $response->getContent()); } public function testViewListenerWithCallableTypeHint() { $app = new Application(); $app->get('/foo', function () { return function () { return 'world'; }; }); $app->view(function (callable $view) { return new Response('Hello '.$view()); }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('Hello world', $response->getContent()); } public function testViewListenersCanBeChained() { $app = new Application(); $app->get('/foo', function () { return (object) array('name' => 'world'); }); $app->view(function (\stdClass $view) { return array('msg' => 'Hello '.$view->name); }); $app->view(function (array $view) { return $view['msg']; }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('Hello world', $response->getContent()); } public function testViewListenersAreIgnoredIfNotSuitable() { $app = new Application(); $app->get('/foo', function () { return 'Hello world'; }); $app->view(function (\stdClass $view) { throw new \Exception('View listener was called'); }); $app->view(function (array $view) { throw new \Exception('View listener was called'); }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('Hello world', $response->getContent()); } public function testViewListenersResponsesAreNotUsedIfNull() { $app = new Application(); $app->get('/foo', function () { return 'Hello world'; }); $app->view(function ($view) { return 'Hello view listener'; }); $app->view(function ($view) { return; }); $response = $app->handle(Request::create('/foo')); $this->assertEquals('Hello view listener', $response->getContent()); } public function testDefaultRoutesFactory() { $app = new Application(); $this->assertInstanceOf('Symfony\Component\Routing\RouteCollection', $app['routes']); } public function testOverriddenRoutesFactory() { $app = new Application(); $app['routes_factory'] = $app->factory(function () { return new RouteCollectionSubClass(); }); $this->assertInstanceOf('Silex\Tests\RouteCollectionSubClass', $app['routes']); } } class FooController { public function barAction(Application $app, $name) { return 'Hello '.$app->escape($name); } public function barSpecialAction(SpecialApplication $app, $name) { return 'Hello '.$app->escape($name).' in '.get_class($app); } } class IncorrectControllerCollection implements ControllerProviderInterface { public function connect(Application $app) { return; } } class RouteCollectionSubClass extends RouteCollection { } class SpecialApplication extends Application { }