Completed
Push — master ( 644ae6...bba86b )
by Daniel
10:38
created

ControllerTest::testAllowedActions()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 191
Code Lines 143

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 143
nc 1
nop 0
dl 0
loc 191
rs 8.2857
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Control\Tests;
4
5
use InvalidArgumentException;
6
use PHPUnit_Framework_Error;
7
use SilverStripe\Control\RequestHandler;
8
use SilverStripe\Control\Tests\ControllerTest\AccessBaseController;
9
use SilverStripe\Control\Tests\ControllerTest\AccessSecuredController;
10
use SilverStripe\Control\Tests\ControllerTest\AccessWildcardSecuredController;
11
use SilverStripe\Control\Tests\ControllerTest\ContainerController;
12
use SilverStripe\Control\Tests\ControllerTest\HasAction;
13
use SilverStripe\Control\Tests\ControllerTest\HasAction_Unsecured;
14
use SilverStripe\Control\Tests\ControllerTest\IndexSecuredController;
15
use SilverStripe\Control\Tests\ControllerTest\SubController;
16
use SilverStripe\Control\Tests\ControllerTest\TestController;
17
use SilverStripe\Control\Tests\ControllerTest\UnsecuredController;
18
use SilverStripe\Core\Config\Config;
19
use SilverStripe\Control\Controller;
20
use SilverStripe\Control\Director;
21
use SilverStripe\Control\HTTPRequest;
22
use SilverStripe\Dev\Deprecation;
23
use SilverStripe\Dev\FunctionalTest;
24
use SilverStripe\ORM\DataModel;
25
use SilverStripe\Security\Member;
26
use SilverStripe\View\SSViewer;
27
28
class ControllerTest extends FunctionalTest
29
{
30
31
    protected static $fixture_file = 'ControllerTest.yml';
32
33
    protected $autoFollowRedirection = false;
34
35
    protected $depSettings = null;
36
37
    protected $extraControllers = [
38
        AccessBaseController::class,
39
        AccessSecuredController::class,
40
        AccessWildcardSecuredController::class,
41
        ContainerController::class,
42
        HasAction::class,
43
        HasAction_Unsecured::class,
44
        IndexSecuredController::class,
45
        SubController::class,
46
        TestController::class,
47
        UnsecuredController::class,
48
    ];
49
50
    public function setUp()
51
    {
52
        parent::setUp();
53
        Director::config()->update('alternate_base_url', '/');
54
        $this->depSettings = Deprecation::dump_settings();
55
56
        // Add test theme
57
        $themeDir = substr(__DIR__, strlen(FRAMEWORK_DIR)) . '/ControllerTest/';
58
        $themes = [
59
            "silverstripe/framework:{$themeDir}",
60
            SSViewer::DEFAULT_THEME
61
        ];
62
        SSViewer::set_themes($themes);
63
    }
64
65
    public function tearDown()
66
    {
67
        Deprecation::restore_settings($this->depSettings);
68
        parent::tearDown();
69
    }
70
71
    public function testDefaultAction()
72
    {
73
        /* For a controller with a template, the default action will simple run that template. */
74
        $response = $this->get("TestController/");
75
        $this->assertRegExp("/This is the main template. Content is 'default content'/", $response->getBody());
76
    }
77
78
    public function testMethodActions()
79
    {
80
        /* The Action can refer to a method that is called on the object.  If a method returns an array, then it
81
        * will be used to customise the template data */
82
        $response = $this->get("TestController/methodaction");
83
        $this->assertRegExp("/This is the main template. Content is 'methodaction content'./", $response->getBody());
84
85
        /* If the method just returns a string, then that will be used as the response */
86
        $response = $this->get("TestController/stringaction");
87
        $this->assertRegExp("/stringaction was called./", $response->getBody());
88
    }
89
90
    public function testTemplateActions()
91
    {
92
        /* If there is no method, it can be used to point to an alternative template. */
93
        $response = $this->get("TestController/templateaction");
94
        $this->assertRegExp(
95
            "/This is the template for templateaction. Content is 'default content'./",
96
            $response->getBody()
97
        );
98
    }
99
100
    public function testUndefinedActions()
101
    {
102
        $response = $this->get('IndexSecuredController/undefinedaction');
103
        $this->assertInstanceOf('SilverStripe\\Control\\HTTPResponse', $response);
104
        $this->assertEquals(404, $response->getStatusCode(), 'Undefined actions return a not found response.');
105
    }
106
107
    public function testAllowedActions()
108
    {
109
        $adminUser = $this->objFromFixture(Member::class, 'admin');
110
111
        $response = $this->get("UnsecuredController/");
112
        $this->assertEquals(
113
            200,
114
            $response->getStatusCode(),
115
            'Access granted on index action without $allowed_actions on defining controller, ' .
116
            'when called without an action in the URL'
117
        );
118
119
        $response = $this->get("UnsecuredController/index");
120
        $this->assertEquals(
121
            200,
122
            $response->getStatusCode(),
123
            'Access denied on index action without $allowed_actions on defining controller, ' .
124
            'when called with an action in the URL'
125
        );
126
127
        $response = $this->get("UnsecuredController/method1");
128
        $this->assertEquals(
129
            403,
130
            $response->getStatusCode(),
131
            'Access denied on action without $allowed_actions on defining controller, ' .
132
            'when called without an action in the URL'
133
        );
134
135
        $response = $this->get("AccessBaseController/");
136
        $this->assertEquals(
137
            200,
138
            $response->getStatusCode(),
139
            'Access granted on index with empty $allowed_actions on defining controller, ' .
140
            'when called without an action in the URL'
141
        );
142
143
        $response = $this->get("AccessBaseController/index");
144
        $this->assertEquals(
145
            200,
146
            $response->getStatusCode(),
147
            'Access granted on index with empty $allowed_actions on defining controller, ' .
148
            'when called with an action in the URL'
149
        );
150
151
        $response = $this->get("AccessBaseController/method1");
152
        $this->assertEquals(
153
            403,
154
            $response->getStatusCode(),
155
            'Access denied on action with empty $allowed_actions on defining controller'
156
        );
157
158
        $response = $this->get("AccessBaseController/method2");
159
        $this->assertEquals(
160
            403,
161
            $response->getStatusCode(),
162
            'Access denied on action with empty $allowed_actions on defining controller, ' .
163
            'even when action is allowed in subclasses (allowed_actions don\'t inherit)'
164
        );
165
166
        $response = $this->get("AccessSecuredController/");
167
        $this->assertEquals(
168
            200,
169
            $response->getStatusCode(),
170
            'Access granted on index with non-empty $allowed_actions on defining controller, ' .
171
            'even when index isn\'t specifically mentioned in there'
172
        );
173
174
        $response = $this->get("AccessSecuredController/method1");
175
        $this->assertEquals(
176
            403,
177
            $response->getStatusCode(),
178
            'Access denied on action which is only defined in parent controller, ' .
179
            'even when action is allowed in currently called class (allowed_actions don\'t inherit)'
180
        );
181
182
        $response = $this->get("AccessSecuredController/method2");
183
        $this->assertEquals(
184
            200,
185
            $response->getStatusCode(),
186
            'Access granted on action originally defined with empty $allowed_actions on parent controller, ' .
187
            'because it has been redefined in the subclass'
188
        );
189
190
        $response = $this->get("AccessSecuredController/templateaction");
191
        $this->assertEquals(
192
            403,
193
            $response->getStatusCode(),
194
            'Access denied on action with $allowed_actions on defining controller, ' .
195
            'if action is not a method but rather a template discovered by naming convention'
196
        );
197
198
        $response = $this->get("AccessSecuredController/templateaction");
199
        $this->assertEquals(
200
            403,
201
            $response->getStatusCode(),
202
            'Access denied on action with $allowed_actions on defining controller, ' .
203
            'if action is not a method but rather a template discovered by naming convention'
204
        );
205
206
        $this->session()->inst_set('loggedInAs', $adminUser->ID);
207
        $response = $this->get("AccessSecuredController/templateaction");
208
        $this->assertEquals(
209
            200,
210
            $response->getStatusCode(),
211
            'Access granted for logged in admin on action with $allowed_actions on defining controller, ' .
212
            'if action is not a method but rather a template discovered by naming convention'
213
        );
214
        $this->session()->inst_set('loggedInAs', null);
215
216
        $response = $this->get("AccessSecuredController/adminonly");
217
        $this->assertEquals(
218
            403,
219
            $response->getStatusCode(),
220
            'Access denied on action with $allowed_actions on defining controller, ' .
221
            'when restricted by unmatched permission code'
222
        );
223
224
        $response = $this->get("AccessSecuredController/aDmiNOnlY");
225
        $this->assertEquals(
226
            403,
227
            $response->getStatusCode(),
228
            'Access denied on action with $allowed_actions on defining controller, ' .
229
            'regardless of capitalization'
230
        );
231
232
        $response = $this->get('AccessSecuredController/protectedmethod');
233
        $this->assertEquals(
234
            404,
235
            $response->getStatusCode(),
236
            "Access denied to protected method even if its listed in allowed_actions"
237
        );
238
239
        $this->session()->inst_set('loggedInAs', $adminUser->ID);
240
        $response = $this->get("AccessSecuredController/adminonly");
241
        $this->assertEquals(
242
            200,
243
            $response->getStatusCode(),
244
            "Permission codes are respected when set in \$allowed_actions"
245
        );
246
        $this->session()->inst_set('loggedInAs', null);
247
248
        $response = $this->get('AccessBaseController/extensionmethod1');
249
        $this->assertEquals(
250
            200,
251
            $response->getStatusCode(),
252
            "Access granted to method defined in allowed_actions on extension, " .
253
            "where method is also defined on extension"
254
        );
255
256
        $response = $this->get('AccessSecuredController/extensionmethod1');
257
        $this->assertEquals(
258
            200,
259
            $response->getStatusCode(),
260
            "Access granted to method defined in allowed_actions on extension, " .
261
            "where method is also defined on extension, even when called in a subclass"
262
        );
263
264
        $response = $this->get('AccessBaseController/extensionmethod2');
265
        $this->assertEquals(
266
            404,
267
            $response->getStatusCode(),
268
            "Access denied to method not defined in allowed_actions on extension, " .
269
            "where method is also defined on extension"
270
        );
271
272
        $response = $this->get('IndexSecuredController/');
273
        $this->assertEquals(
274
            403,
275
            $response->getStatusCode(),
276
            "Access denied when index action is limited through allowed_actions, " .
277
            "and doesn't satisfy checks, and action is empty"
278
        );
279
280
        $response = $this->get('IndexSecuredController/index');
281
        $this->assertEquals(
282
            403,
283
            $response->getStatusCode(),
284
            "Access denied when index action is limited through allowed_actions, " .
285
            "and doesn't satisfy checks"
286
        );
287
288
        $this->session()->inst_set('loggedInAs', $adminUser->ID);
289
        $response = $this->get('IndexSecuredController/');
290
        $this->assertEquals(
291
            200,
292
            $response->getStatusCode(),
293
            "Access granted when index action is limited through allowed_actions, " .
294
            "and does satisfy checks"
295
        );
296
        $this->session()->inst_set('loggedInAs', null);
297
    }
298
299
    public function testWildcardAllowedActions()
300
    {
301
        $this->setExpectedException(InvalidArgumentException::class, "Invalid allowed_action '*'");
302
        $this->get('AccessWildcardSecuredController');
303
    }
304
305
    /**
306
     * Test Controller::join_links()
307
     */
308
    public function testJoinLinks()
309
    {
310
        /* Controller::join_links() will reliably join two URL-segments together so that they will be
311
        * appropriately parsed by the URL parser */
312
        $this->assertEquals("admin/crm/MyForm", Controller::join_links("admin/crm", "MyForm"));
313
        $this->assertEquals("admin/crm/MyForm", Controller::join_links("admin/crm/", "MyForm"));
314
315
        /* It will also handle appropriate combination of querystring variables */
316
        $this->assertEquals("admin/crm/MyForm?flush=1", Controller::join_links("admin/crm/?flush=1", "MyForm"));
317
        $this->assertEquals("admin/crm/MyForm?flush=1", Controller::join_links("admin/crm/", "MyForm?flush=1"));
318
        $this->assertEquals(
319
            "admin/crm/MyForm?field=1&other=1",
320
            Controller::join_links("admin/crm/?field=1", "MyForm?other=1")
321
        );
322
323
        /* It can handle arbitrary numbers of components, and will ignore empty ones */
324
        $this->assertEquals("admin/crm/MyForm/", Controller::join_links("admin/", "crm", "", "MyForm/"));
325
        $this->assertEquals(
326
            "admin/crm/MyForm/?a=1&b=2",
327
            Controller::join_links("admin/?a=1", "crm", "", "MyForm/?b=2")
328
        );
329
330
        /* It can also be used to attach additional get variables to a link */
331
        $this->assertEquals("admin/crm?flush=1", Controller::join_links("admin/crm", "?flush=1"));
332
        $this->assertEquals("admin/crm?existing=1&flush=1", Controller::join_links("admin/crm?existing=1", "?flush=1"));
333
        $this->assertEquals(
334
            "admin/crm/MyForm?a=1&b=2&c=3",
335
            Controller::join_links("?a=1", "admin/crm", "?b=2", "MyForm?c=3")
336
        );
337
338
        // And duplicates are handled nicely
339
        $this->assertEquals(
340
            "admin/crm?foo=2&bar=3&baz=1",
341
            Controller::join_links("admin/crm?foo=1&bar=1&baz=1", "?foo=2&bar=3")
342
        );
343
344
        $this->assertEquals(
345
            'admin/action',
346
            Controller::join_links('admin/', '/', '/action'),
347
            'Test that multiple slashes are trimmed.'
348
        );
349
350
        $this->assertEquals('/admin/action', Controller::join_links('/admin', 'action'));
351
352
        /* One fragment identifier is handled as you would expect */
353
        $this->assertEquals("my-page?arg=var#subsection", Controller::join_links("my-page#subsection", "?arg=var"));
354
355
        /* If there are multiple, it takes the last one */
356
        $this->assertEquals(
357
            "my-page?arg=var#second-section",
358
            Controller::join_links("my-page#subsection", "?arg=var", "#second-section")
359
        );
360
361
        /* Does type-safe checks for zero value */
362
        $this->assertEquals("my-page/0", Controller::join_links("my-page", 0));
363
364
        // Test array args
365
        $this->assertEquals(
366
            "admin/crm/MyForm?a=1&b=2&c=3",
367
            Controller::join_links(["?a=1", "admin/crm", "?b=2", "MyForm?c=3"])
368
        );
369
    }
370
371
    public function testLink()
372
    {
373
        $controller = new HasAction();
374
        $this->assertEquals('HasAction/', $controller->Link());
375
        $this->assertEquals('HasAction/', $controller->Link(null));
376
        $this->assertEquals('HasAction/', $controller->Link(false));
377
        $this->assertEquals('HasAction/allowed-action/', $controller->Link('allowed-action'));
378
    }
379
380
    /**
381
     * @covers \SilverStripe\Control\Controller::hasAction
382
     */
383
    public function testHasAction()
384
    {
385
        $controller = new HasAction();
386
        $unsecuredController = new HasAction_Unsecured();
387
        $securedController = new AccessSecuredController();
388
389
        $this->assertFalse(
390
            $controller->hasAction('1'),
391
            'Numeric actions do not slip through.'
392
        );
393
        //$this->assertFalse(
394
        //	$controller->hasAction('lowercase_permission'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
395
        //	'Lowercase permission does not slip through.'
396
        //);
397
        $this->assertFalse(
398
            $controller->hasAction('undefined'),
399
            'undefined actions do not exist'
400
        );
401
        $this->assertTrue(
402
            $controller->hasAction('allowed_action'),
403
            'allowed actions are recognised'
404
        );
405
        $this->assertTrue(
406
            $controller->hasAction('template_action'),
407
            'action-specific templates are recognised'
408
        );
409
410
        $this->assertTrue(
411
            $unsecuredController->hasAction('defined_action'),
412
            'Without an allowed_actions, any defined methods are recognised as actions'
413
        );
414
415
        $this->assertTrue(
416
            $securedController->hasAction('adminonly'),
417
            'Method is generally visible even if its denied via allowed_actions'
418
        );
419
420
        $this->assertFalse(
421
            $securedController->hasAction('protectedmethod'),
422
            'Method is not visible when protected, even if its defined in allowed_actions'
423
        );
424
425
        $this->assertTrue(
426
            $securedController->hasAction('extensionmethod1'),
427
            'Method is visible when defined on an extension and part of allowed_actions'
428
        );
429
430
        $this->assertFalse(
431
            $securedController->hasAction('internalextensionmethod'),
432
            'Method is not visible when defined on an extension, but not part of allowed_actions'
433
        );
434
435
        $this->assertFalse(
436
            $securedController->hasAction('protectedextensionmethod'),
437
            'Method is not visible when defined on an extension, part of allowed_actions, ' .
438
            'but with protected visibility'
439
        );
440
    }
441
442
    /* Controller::BaseURL no longer exists, but was just a direct call to Director::BaseURL, so not sure what this
443
    * code was supposed to test
444
    public function testBaseURL() {
445
    Config::modify()->merge('Director', 'alternate_base_url', '/baseurl/');
446
    $this->assertEquals(Controller::BaseURL(), Director::BaseURL());
447
    }
448
    */
449
450
    public function testRedirectBackByReferer()
451
    {
452
        $internalRelativeUrl = Controller::join_links(Director::baseURL(), '/some-url');
453
        $internalAbsoluteUrl = Controller::join_links(Director::absoluteBaseURL(), '/some-url');
454
455
        $response = $this->get(
456
            'TestController/redirectbacktest',
457
            null,
458
            array('Referer' => $internalRelativeUrl)
459
        );
460
        $this->assertEquals(302, $response->getStatusCode());
461
        $this->assertEquals(
462
            $internalAbsoluteUrl,
463
            $response->getHeader('Location'),
464
            "Redirects on internal relative URLs"
465
        );
466
467
        $response = $this->get(
468
            'TestController/redirectbacktest',
469
            null,
470
            array('Referer' => $internalAbsoluteUrl)
471
        );
472
        $this->assertEquals(302, $response->getStatusCode());
473
        $this->assertEquals(
474
            $internalAbsoluteUrl,
475
            $response->getHeader('Location'),
476
            "Redirects on internal absolute URLs"
477
        );
478
479
        $externalAbsoluteUrl = 'http://myhost.com/some-url';
480
        $response = $this->get(
481
            'TestController/redirectbacktest',
482
            null,
483
            array('Referer' => $externalAbsoluteUrl)
484
        );
485
        $this->assertEquals(
486
            Director::absoluteBaseURL(),
487
            $response->getHeader('Location'),
488
            "Redirects back to home page on external url"
489
        );
490
    }
491
492
    public function testRedirectBackByBackUrl()
493
    {
494
        $internalRelativeUrl = Controller::join_links(Director::baseURL(), '/some-url');
495
        $internalAbsoluteUrl = Controller::join_links(Director::absoluteBaseURL(), '/some-url');
496
497
        $response = $this->get('TestController/redirectbacktest?BackURL=' . urlencode($internalRelativeUrl));
498
        $this->assertEquals(302, $response->getStatusCode());
499
        $this->assertEquals(
500
            $internalAbsoluteUrl,
501
            $response->getHeader('Location'),
502
            "Redirects on internal relative URLs"
503
        );
504
505
        // BackURL is internal link
506
        $internalAbsoluteUrl = Director::absoluteBaseURL() . '/some-url';
507
        $link = 'TestController/redirectbacktest?BackURL=' . urlencode($internalAbsoluteUrl);
508
        $response = $this->get($link);
509
        $this->assertEquals($internalAbsoluteUrl, $response->getHeader('Location'));
510
        $this->assertEquals(
511
            302,
512
            $response->getStatusCode(),
513
            "Redirects on internal absolute URLs"
514
        );
515
516
        // Note that this test is affected by the prior ->get()
517
        $externalAbsoluteUrl = 'http://myhost.com/some-url';
518
        $response = $this->get('TestController/redirectbacktest?BackURL=' . urlencode($externalAbsoluteUrl));
519
        $this->assertEquals(
520
            Director::absoluteURL($link),
521
            $response->getHeader('Location'),
522
            "If BackURL Is external link, fall back to last url (Referer)"
523
        );
524
    }
525
526
    public function testSubActions()
527
    {
528
        /* If a controller action returns another controller, ensure that the $action variable is correctly forwarded */
529
        $response = $this->get("ContainerController/subcontroller/subaction");
530
        $this->assertEquals('subaction', $response->getBody());
531
532
        $request = new HTTPRequest(
533
            'GET',
534
            'ContainerController/subcontroller/substring/subvieweraction'
535
        );
536
        /* Shift to emulate the director selecting the controller */
537
        $request->shift();
538
        /* Handle the request to create conditions where improperly passing the action to the viewer might fail */
539
        $controller = new ControllerTest\ContainerController();
540
        try {
541
            $controller->handleRequest($request, DataModel::inst());
542
        } catch (ControllerTest\SubController_Exception $e) {
543
            $this->fail($e->getMessage());
544
        }
545
    }
546
}
547