From 34e7cded83dcb399912b881b09ee913f532083fe Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 12 Jun 2011 10:30:22 -0400 Subject: [PATCH] * Write a unittest for RootControler * Fix a bunch of syntax errors --- http/action.php | 2 +- http/output_filter.php | 2 +- http/response.php | 4 +- http/response_code.php | 2 +- http/root_controller.php | 103 ++++++++++++- http/url_map.php | 2 +- testing/tests/base/functions_test.php | 2 +- testing/tests/http/root_controller_test.php | 161 ++++++++++++++++++++ 8 files changed, 264 insertions(+), 14 deletions(-) create mode 100644 testing/tests/http/root_controller_test.php diff --git a/http/action.php b/http/action.php index 1e73a2e..46f1859 100644 --- a/http/action.php +++ b/http/action.php @@ -45,7 +45,7 @@ abstract class Action /*! Performs the action and fills out the response's data model. */ - public function Invoke(Request $request, Response $response); + public abstract function Invoke(Request $request, Response $response); /*! Called after this has been Invoked(). diff --git a/http/output_filter.php b/http/output_filter.php index 7ffde90..2eeb0e5 100644 --- a/http/output_filter.php +++ b/http/output_filter.php @@ -16,7 +16,7 @@ namespace hoplite\http; -require_once HOPLITE_ROOT . 'http/response_code.php'; +require_once HOPLITE_ROOT . '/http/response_code.php'; /*! The OutputFilter is executed after all Actions have been processed. The diff --git a/http/response.php b/http/response.php index 86f2d02..aa90ef3 100644 --- a/http/response.php +++ b/http/response.php @@ -16,8 +16,8 @@ namespace hoplite\http; -require_once HOPLITE_ROOT . 'base/strict_object.php'; -require_once HOPLITE_ROOT . 'http/response_code.php'; +require_once HOPLITE_ROOT . '/base/strict_object.php'; +require_once HOPLITE_ROOT . '/http/response_code.php'; /*! A Response holds data processed by Action objects. When the RootController is diff --git a/http/response_code.php b/http/response_code.php index ab8cd54..f837079 100644 --- a/http/response_code.php +++ b/http/response_code.php @@ -23,7 +23,7 @@ namespace hoplite\http; */ class ResponseCode { - const CONTINUE = 100; + const HTTP_CONTINUE = 100; // CONTINUE is a keyword. const SWITCHING_PROTOCOLS = 101; const OK = 200; const CREATED = 201; diff --git a/http/root_controller.php b/http/root_controller.php index f63cda9..e2e90ae 100644 --- a/http/root_controller.php +++ b/http/root_controller.php @@ -16,33 +16,104 @@ namespace hoplite\http; +require_once HOPLITE_ROOT . '/http/request.php'; +require_once HOPLITE_ROOT . '/http/response.php'; +require_once HOPLITE_ROOT . '/http/response_code.php'; + /*! The RootController is meant to be invoked from the index.php of the application. */ class RootController { + /*! @var Request */ + private $request = NULL; + + /*! @var Response */ + private $response = NULL; + + /*! @var UrlMap */ + private $url_map = NULL; + + /*! @var OutputFilter */ + private $output_filter = NULL; + /*! Creates the controller with the request context information, typicallhy from the global scope ($GLOBALS), but can be injected for testing. - @param globals + @param UrlMap The routing map + @param OutputFilter The object responsible for decorating output. + @param array& PHP globals array */ - public function __construct($globals) - {} + public function __construct(array $globals) + { + $this->response = new Response(); + $this->request = new Request(); + $this->request->data = array( + '_GET' => &$globals['_GET'], + '_POST' => &$globals['_POST'], + '_COOKIE' => &$globals['_COOKIE'], + '_SERVER' => &$globals['_SERVER'] + ); + } + + /*! Accessors */ + public function request() { return $this->request; } + public function response() { return $this->response; } + + /*! Sets the UrlMap. */ + public function set_urL_map(UrlMap $url_map) { $this->url_map = $url_map; } + + /*! Sest the Output Filter. */ + public function set_output_filter(OutputFilter $output_filter) + { + $this->output_filter = $output_filter; + } /*! Createst the Request and Response that are used throughout the duration of the execution. */ public function Run() - {} + { + // The query rewriter module of the webserver rewrites a request from: + // http://example.com/webapp/user/view/42 + // to: + // http://example.com/webapp/index.php/user/view/42 + // ... which then becomes accessible from PATH_INFO. + $url = $this->request->data['_SERVER']['PATH_INFO']; + if ($url[0] == '/') + $url = substr($url, 1); + + // Set the final pieces of the request. + $this->request->url = $url; + $this->request->http_method = $this->request->data['_SERVER']['REQUEST_METHOD']; + + // Dispatch the request to an Action. + $this->RouteRequest($url); + + // When control returns here, all actions have been invoked and it's time + // to start the output filter and exit. + $this->Stop(); + } /*! Prevents any other Actions from executing. This starts the OutputFilter and then exits. */ public function Stop() - {} + { + $this->output_filter->FilterOutput($this->request, $this->response); + $this->_Exit(); + } + + /*! + Wrapper around PHP exit(). + */ + protected function _Exit() + { + exit; + } /*! Invoked by Run() and can be invoked by others to evaluate and perform the @@ -50,14 +121,32 @@ class RootController @param string The URL fragment to look up in the */ public function RouteRequest($url_fragment) - {} + { + $url_map_value = $this->url_map->Evaluate($url_fragment); + + $action = NULL; + if ($url_map_value) + $action = $this->url_map->LookupAction($url_map_value); + + if (!$action) { + $this->response->response_code = ResponseCode::NOT_FOUND; + $this->Stop(); + return; + } + + $this->InvokeAction($action); + } /*! Used to run an Action and drive it through its states. @param Action */ public function InvokeAction(Action $action) - {} + { + $action->FilterRequest($this->request, $this->response); + $action->Invoke($this->request, $this->response); + $action->FilterResponse($this->request, $this->response); + } /*! Performs a reverse-lookup in the UrlMap for the pattern/fragment for the diff --git a/http/url_map.php b/http/url_map.php index d3590be..b7d3fa4 100644 --- a/http/url_map.php +++ b/http/url_map.php @@ -90,7 +90,7 @@ class UrlMap @return string|NULL A matched value in the ::map() or NULL if no match. */ - public function Evaluate(Request $request) + public function Evaluate($request_url) {} /*! @brief Takes a value from the map and returns an Action object. diff --git a/testing/tests/base/functions_test.php b/testing/tests/base/functions_test.php index a58b965..395b800 100644 --- a/testing/tests/base/functions_test.php +++ b/testing/tests/base/functions_test.php @@ -15,7 +15,7 @@ // this program. If not, see . namespace hoplite\test; -use \hoplite\base as base; +use hoplite\base as base; require_once HOPLITE_ROOT . '/base/functions.php'; diff --git a/testing/tests/http/root_controller_test.php b/testing/tests/http/root_controller_test.php new file mode 100644 index 0000000..c499fe1 --- /dev/null +++ b/testing/tests/http/root_controller_test.php @@ -0,0 +1,161 @@ +. + +namespace hoplite\test; +use hoplite\http as http; + +require_once HOPLITE_ROOT . '/http/action.php'; +require_once HOPLITE_ROOT . '/http/output_filter.php'; +require_once HOPLITE_ROOT . '/http/root_controller.php'; +require_once HOPLITE_ROOT . '/http/url_map.php'; + +class ActionReporter extends http\Action +{ + public $did_filter_request = FALSE; + public $did_invoke = FALSE; + public $did_filter_response = FALSE; + + public function FilterRequest(http\Request $q, http\Response $s) + { + $this->did_filter_request = TRUE; + } + + public function Invoke(http\Request $q, http\Response $s) + { + $this->did_invoke = TRUE; + } + + public function FilterResponse(http\Request $q, http\Response $s) + { + $this->did_filter_response = TRUE; + } +} + +class RootControllerTest extends \PHPUnit_Framework_TestCase +{ + /*! + Configures a mock RootControler. + @param array|NULL Array of methods to mock + @param varargs Constructor parameters. + @return Mock RootControler + */ + public function ConfigureMock() + { + $args = func_get_args(); + return $this->getMock('hoplite\http\RootController', $args[0], array_slice($args, 1)); + } + + public function testRun() + { + $globals = array('_SERVER' => array( + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => '/some/action/42' + )); + $mock = $this->ConfigureMock(array('RouteRequest', 'Stop'), $globals); + + $mock->expects($this->once()) + ->method('RouteRequest') + ->with($this->equalTo('some/action/42')); + + $mock->expects($this->once()) + ->method('Stop'); + + $mock->Run(); + } + + public function testInvokeAction() + { + $globals = array(); + $fixture = new http\RootController($globals); + + $action = new ActionReporter($fixture); + + $this->assertFalse($action->did_filter_request); + $this->assertFalse($action->did_invoke); + $this->assertFalse($action->did_filter_response); + + $fixture->InvokeAction($action); + + $this->assertTrue($action->did_filter_request); + $this->assertTrue($action->did_invoke); + $this->assertTrue($action->did_filter_response); + } + + public function testRouteRequest() + { + $globals = array(); + $mock = $this->ConfigureMock(array('Stop', 'InvokeAction'), $globals); + + $fragment = 'some/action/42'; + $map_value = 'ActionReporter'; + $action = new ActionReporter($mock); + + $mock->expects($this->once()) + ->method('InvokeAction') + ->with($this->isInstanceOf('hoplite\test\ActionReporter')); + + $url_map = $this->getMock('hoplite\http\UrlMap', array(), array($mock)); + $url_map->expects($this->once()) + ->method('Evaluate') + ->with($this->equalTo($fragment)) + ->will($this->returnValue($map_value)); + $url_map->expects($this->once()) + ->method('LookupAction') + ->with($this->equalTo($map_value)) + ->will($this->returnValue($action)); + + $mock->set_url_map($url_map); + $mock->RouteRequest($fragment); + } + + public function testRouteRequestInvalid() + { + $globals = array(); + $mock = $this->ConfigureMock(array('Stop'), $globals); + + $fragment = 'another/action'; + + $mock->expects($this->once()) + ->method('Stop'); + + $url_map = $this->getMock('hoplite\http\UrlMap', array(), array($mock)); + $url_map->expects($this->once()) + ->method('Evaluate') + ->with($this->equalTo($fragment)); + + $mock->set_url_map($url_map); + $mock->RouteRequest($fragment); + $this->assertEquals(http\ResponseCode::NOT_FOUND, $mock->response()->response_code); + } + + public function testStop() + { + $globals = array(); + $mock = $this->ConfigureMock(array('_Exit'), $globals); + + $mock->expects($this->once()) + ->method('_Exit'); + + $output_filter = $this->getMock('hoplite\http\OutputFilter', array(), array($mock)); + $output_filter->expects($this->once()) + ->method('FilterOutput') + ->with($this->isInstanceOf('hoplite\http\Request'), + $this->isInstanceOf('hoplite\http\Response')); + + $mock->set_output_filter($output_filter); + $mock->Stop(); + } +} -- 2.22.5