Introduce FrontController as the replacement for RootController.
[hoplite.git] / testing / tests / http / front_controller_test.php
1 <?php
2 // Hoplite
3 // Copyright (c) 2015 Blue Static
4 //
5 // This program is free software: you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or any later version.
8 //
9 // This program is distributed in the hope that it will be useful, but WITHOUT
10 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 // more details.
13 //
14 // You should have received a copy of the GNU General Public License along with
15 // this program. If not, see <http://www.gnu.org/licenses/>.
16
17 namespace hoplite\test;
18 use hoplite\http as http;
19
20 require_once HOPLITE_ROOT . '/http/action.php';
21 require_once HOPLITE_ROOT . '/http/front_controller.php';
22 require_once HOPLITE_ROOT . '/http/interceptor.php';
23 require_once HOPLITE_ROOT . '/http/output_filter.php';
24 require_once HOPLITE_ROOT . '/http/url_map.php';
25
26 class ActionReporter extends http\Action
27 {
28 public $did_invoke = FALSE;
29
30 public function Invoke(http\Request $q, http\Response $s)
31 {
32 $this->did_invoke = TRUE;
33 }
34 }
35
36 class ClosureInterceptor implements http\Interceptor
37 {
38 private $interceptor;
39 public $did_intercept = FALSE;
40
41 public function __construct($interceptor)
42 {
43 $this->interceptor = $interceptor;
44 }
45
46 public function DoIntercept(http\FrontController $controller,
47 http\Action $action = NULL,
48 http\Request $request,
49 http\Response $response)
50 {
51 $this->did_intercept = TRUE;
52 if ($this->interceptor) {
53 $interceptor = $this->interceptor;
54 $interceptor($controller, $action, $request, $response);
55 }
56 }
57 }
58
59 class FrontControllerTest extends \PHPUnit_Framework_TestCase
60 {
61 /*!
62 Configures a mock FrontControler.
63 @param array|NULL Array of methods to mock
64 @param varargs Constructor parameters.
65 @return Mock FrontControler
66 */
67 public function ConfigureMock()
68 {
69 $args = func_get_args();
70 return $this->getMock('hoplite\http\FrontController', $args[0], array_slice($args, 1));
71 }
72
73 public function testProcessRequest()
74 {
75 $globals = array('_SERVER' => array(
76 'REQUEST_METHOD' => 'GET',
77 'PATH_INFO' => '/some/action/42'
78 ));
79 $mock = $this->ConfigureMock(array('RouteRequest', 'SendResponse'), $globals);
80
81 $mock->request()->url = 'some/action/42';
82
83 $mock->expects($this->once())
84 ->method('RouteRequest')
85 ->with($this->equalTo($mock->request()));
86
87 $mock->expects($this->once())
88 ->method('SendResponse');
89
90 $mock->ProcessRequest();
91 }
92
93 public function testRouteRequest()
94 {
95 $globals = array();
96 $mock = $this->ConfigureMock(array('SendResponse', 'InvokeAction'), $globals);
97
98 $mock->request()->url = 'some/action/42';
99 $map_value = 'ActionReporter';
100 $action = new ActionReporter($mock);
101
102 $url_map = $this->getMock('hoplite\http\UrlMap', array(), array($mock));
103 $url_map->expects($this->once())
104 ->method('Evaluate')
105 ->with($this->equalTo($mock->request()))
106 ->will($this->returnValue($map_value));
107 $url_map->expects($this->once())
108 ->method('LookupAction')
109 ->with($this->equalTo($map_value))
110 ->will($this->returnValue($action));
111
112 $mock->set_url_map($url_map);
113 $mock->RouteRequest($mock->request());
114
115 $this->assertTrue($action->did_invoke);
116 }
117
118 public function testRouteRequestInvalid()
119 {
120 $globals = array();
121 $mock = $this->ConfigureMock(array('SendResponse'), $globals);
122
123 $mock->request()->url = 'another/action';
124
125 $mock->expects($this->never())
126 ->method('SendResponse');
127
128 $url_map = $this->getMock('hoplite\http\UrlMap', array(), array($mock));
129 $url_map->expects($this->once())
130 ->method('Evaluate')
131 ->with($this->equalTo($mock->request()));
132
133 $mock->set_url_map($url_map);
134 $mock->RouteRequest($mock->request());
135 // Nothing should happen for a non-routed request.
136 }
137
138 public function testSendResponse()
139 {
140 $globals = array();
141 $mock = $this->ConfigureMock(array('_Exit'), $globals);
142
143 $mock->expects($this->once())
144 ->method('_Exit');
145
146 $output_filter = $this->getMock('hoplite\http\OutputFilter', array(), array($mock));
147 $output_filter->expects($this->once())
148 ->method('FilterOutput')
149 ->with($this->isInstanceOf('hoplite\http\Request'),
150 $this->isInstanceOf('hoplite\http\Response'));
151
152 $mock->set_output_filter($output_filter);
153 $mock->SendResponse();
154 }
155
156 public function testStopWithRedirect()
157 {
158 $globals = array();
159 $mock = $this->ConfigureMock(array('_Exit'), $globals);
160 $mock->expects($this->once())
161 ->method('_Exit');
162
163 $output_filter = $this->getMock('hoplite\http\OutputFilter', array(), array($mock));
164 $output_filter->expects($this->once())
165 ->method('FilterOutput')
166 ->with($this->isInstanceOf('hoplite\http\Request'),
167 $this->isInstanceOf('hoplite\http\Response'));
168
169 $mock->set_output_filter($output_filter);
170
171 $mock->SendResponseRedirect('/foo/bar');
172
173 $this->assertEquals('/foo/bar', $mock->response()->headers['Location']);
174 $this->assertEquals(http\ResponseCode::FOUND, $mock->response()->response_code);
175 }
176
177 public function testAbsolutify()
178 {
179 $globals = array(
180 '_SERVER' => array(
181 'HTTP_HOST' => 'www.bluestatic.org',
182 'REQUEST_URI' => '/hoplite/webapp/test/path',
183 'PATH_INFO' => '/test/path',
184 'SERVER_PORT' => 80,
185 ),
186 );
187 $mock = new \hoplite\http\FrontController($globals);
188
189 $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/');
190 $this->assertEquals($mock->MakeURL('/', TRUE), 'http://www.bluestatic.org/hoplite/webapp/');
191
192 $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2');
193 $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'http://www.bluestatic.org/hoplite/webapp/path/3');
194
195 $globals['_SERVER']['HTTPS'] = 'on';
196 $globals['_SERVER']['SERVER_PORT'] = 443;
197 $mock = new \hoplite\http\FrontController($globals);
198 $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/');
199 $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org/hoplite/webapp/');
200
201 $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2');
202 $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org/hoplite/webapp/path/3');
203
204 $globals['_SERVER']['SERVER_PORT'] = 8080;
205 $mock = new \hoplite\http\FrontController($globals);
206 $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2');
207 $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/');
208 $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/path/3');
209 }
210
211 public function testAbsolutifyFront()
212 {
213 $globals = array(
214 '_SERVER' => array(
215 'HTTP_HOST' => 'www.bluestatic.org',
216 'REQUEST_URI' => '/hoplite/webapp/',
217 'PATH_INFO' => NULL,
218 'SERVER_PORT' => 80,
219 ),
220 );
221 $mock = new \hoplite\http\FrontController($globals);
222
223 $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/');
224 $this->assertEquals($mock->MakeURL('/', TRUE), 'http://www.bluestatic.org/hoplite/webapp/');
225
226 $globals['_SERVER']['HTTPS'] = 'on';
227 $globals['_SERVER']['SERVER_PORT'] = 443;
228 $mock = new \hoplite\http\FrontController($globals);
229 $this->assertEquals($mock->MakeURL('/'), '/hoplite/webapp/');
230 $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org/hoplite/webapp/');
231
232 $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2');
233 $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org/hoplite/webapp/path/3');
234
235 $globals['_SERVER']['SERVER_PORT'] = 8080;
236 $mock = new \hoplite\http\FrontController($globals);
237 $this->assertEquals($mock->MakeURL('/path/2'), '/hoplite/webapp/path/2');
238 $this->assertEquals($mock->MakeURL('/', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/');
239 $this->assertEquals($mock->MakeURL('/path/3', TRUE), 'https://www.bluestatic.org:8080/hoplite/webapp/path/3');
240 }
241
242 public function testThreeInterceptors()
243 {
244 $interceptors = [
245 new ClosureInterceptor(NULL),
246 new ClosureInterceptor(NULL),
247 new ClosureInterceptor(NULL),
248 ];
249
250 $mock = new \hoplite\http\FrontController([]);
251 $mock->set_url_map(new http\UrlMap($mock));
252
253 foreach ($interceptors as $interceptor) {
254 $mock->AddInterceptor($interceptor);
255 }
256
257 $mock->RouteRequest($mock->request());
258
259 foreach ($interceptors as $interceptor) {
260 $this->assertTrue($interceptor->did_intercept);
261 }
262 }
263
264 public function testInterruptInterceptor()
265 {
266 $i1 = new ClosureInterceptor(NULL);
267 $i2 = new ClosureInterceptor(function($controller, $action, $request, $response) {
268 $controller->SendResponseCode(400);
269 });
270 $test = $this;
271 $i3 = new ClosureInterceptor(function($controller, $action, $request, $response) use ($test) {
272 $test->assertEquals(400, $response->response_code);
273 });
274
275 $mock = $this->ConfigureMock(['SendResponse'], []);
276 $mock->set_url_map(new http\UrlMap($mock));
277 $mock->AddInterceptor($i1);
278 $mock->AddInterceptor($i2);
279 $mock->AddInterceptor($i3);
280
281 $mock->expects($this->once())
282 ->method('SendResponse');
283
284 $mock->RouteRequest($mock->request());
285
286 $this->assertEquals(400, $mock->response()->response_code);
287 $this->assertTrue($i1->did_intercept);
288 $this->assertTrue($i2->did_intercept);
289 // i3 would not normally run but because _Exit is mocked, the script does
290 // not finish.
291 }
292 }