Completed
Push — master ( deca00...5388ff )
by Sam
24s
created

DirectorTest::getExtraRoutes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 0
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control\Tests;
4
5
use SilverStripe\Control\Tests\DirectorTest\TestController;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\Dev\SapphireTest;
9
use SilverStripe\Control\Director;
10
use SilverStripe\Control\RequestProcessor;
11
12
/**
13
 * @todo test Director::alternateBaseFolder()
14
 */
15
class DirectorTest extends SapphireTest
16
{
17
18
    protected static $originalRequestURI;
19
20
    protected $originalProtocolHeaders = array();
21
22
    protected $originalGet = array();
23
24
    protected $originalSession = array();
25
26
    protected $extraControllers = [
27
        TestController::class
28
    ];
29
30
    public function setUp()
0 ignored issues
show
Coding Style introduced by
setUp uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
setUp uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
setUp uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
31
    {
32
        parent::setUp();
33
34
        // Hold the original request URI once so it doesn't get overwritten
35
        if (!self::$originalRequestURI) {
36
            self::$originalRequestURI = $_SERVER['REQUEST_URI'];
37
        }
38
        $_SERVER['REQUEST_URI'] = 'http://www.mysite.com';
39
40
        $this->originalGet = $_GET;
41
        $this->originalSession = $_SESSION;
42
        $_SESSION = array();
43
44
        $headers = array(
45
            'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
46
        );
47
48
        foreach ($headers as $header) {
49
            if (isset($_SERVER[$header])) {
50
                $this->originalProtocolHeaders[$header] = $_SERVER[$header];
51
            }
52
        }
53
54
        Config::modify()->set(Director::class, 'alternate_base_url', '/');
55
    }
56
57
    protected function getExtraRoutes()
58
    {
59
        $rules = parent::getExtraRoutes();
60
61
        $rules['DirectorTestRule/$Action/$ID/$OtherID'] = TestController::class;
62
        $rules['en-nz/$Action/$ID/$OtherID'] = [
63
            'Controller' => TestController::class,
64
            'Locale' => 'en_NZ',
65
        ];
66
        return $rules;
67
    }
68
69
    protected function setUpRoutes()
70
    {
71
        // Don't merge with any existing rules
72
        Director::config()->set('rules', $this->getExtraRoutes());
73
    }
74
75
    public function tearDown()
0 ignored issues
show
Coding Style introduced by
tearDown uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
tearDown uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
tearDown uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
76
    {
77
        $_GET = $this->originalGet;
78
        $_SESSION = $this->originalSession;
79
80
        // Reinstate the original REQUEST_URI after it was modified by some tests
81
        $_SERVER['REQUEST_URI'] = self::$originalRequestURI;
82
83
        if ($this->originalProtocolHeaders) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->originalProtocolHeaders of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
84
            foreach ($this->originalProtocolHeaders as $header => $value) {
85
                $_SERVER[$header] = $value;
86
            }
87
        }
88
89
90
        parent::tearDown();
91
    }
92
93
    public function testFileExists()
94
    {
95
        $tempFileName = 'DirectorTest_testFileExists.tmp';
96
        $tempFilePath = TEMP_FOLDER . '/' . $tempFileName;
97
98
        // create temp file
99
        file_put_contents($tempFilePath, '');
100
101
        $this->assertTrue(
102
            Director::fileExists($tempFilePath),
103
            'File exist check with absolute path'
104
        );
105
106
        $this->assertTrue(
107
            Director::fileExists($tempFilePath . '?queryparams=1&foo[bar]=bar'),
108
            'File exist check with query params ignored'
109
        );
110
111
        unlink($tempFilePath);
112
    }
113
114
    public function testAbsoluteURL()
0 ignored issues
show
Coding Style introduced by
testAbsoluteURL uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
115
    {
116
117
        $rootURL = Director::protocolAndHost();
118
        $_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
119
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/mysite/');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
120
121
        //test empty / local urls
122
        foreach (array('', './', '.') as $url) {
123
            $this->assertEquals("$rootURL/mysite/", Director::absoluteURL($url, Director::BASE));
124
            $this->assertEquals("$rootURL/", Director::absoluteURL($url, Director::ROOT));
125
            $this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL($url, Director::REQUEST));
126
        }
127
128
        // Test site root url
129
        $this->assertEquals("$rootURL/", Director::absoluteURL('/'));
130
131
        // Test Director::BASE
132
        $this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::BASE));
0 ignored issues
show
Bug introduced by
It seems like $rootURL defined by \SilverStripe\Control\Director::protocolAndHost() on line 117 can also be of type boolean; however, SilverStripe\Control\Director::absoluteURL() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
133
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::BASE));
134
        $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::BASE));
135
        $this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::BASE));
136
        $this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::BASE));
137
138
        // Test Director::ROOT
139
        $this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::ROOT));
0 ignored issues
show
Bug introduced by
It seems like $rootURL defined by \SilverStripe\Control\Director::protocolAndHost() on line 117 can also be of type boolean; however, SilverStripe\Control\Director::absoluteURL() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
140
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::ROOT));
141
        $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::ROOT));
142
        $this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::ROOT));
143
        $this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::ROOT));
144
145
        // Test Director::REQUEST
146
        $this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::REQUEST));
0 ignored issues
show
Bug introduced by
It seems like $rootURL defined by \SilverStripe\Control\Director::protocolAndHost() on line 117 can also be of type boolean; however, SilverStripe\Control\Director::absoluteURL() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
147
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::REQUEST));
148
        $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::REQUEST));
149
        $this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::REQUEST));
150
        $this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::REQUEST));
151
152
        // Test evaluating relative urls relative to base (default)
153
        $this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test"));
154
        $this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url"));
155
        $this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test", Director::BASE));
156
        $this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url", Director::BASE));
157
158
        // Test evaluting relative urls relative to root
159
        $this->assertEquals("$rootURL/test", Director::absoluteURL("test", Director::ROOT));
160
        $this->assertEquals("$rootURL/test/url", Director::absoluteURL("test/url", Director::ROOT));
161
162
        // Test relative to requested page
163
        $this->assertEquals("$rootURL/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
164
        $this->assertEquals("$rootURL/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
165
166
        // Test that javascript links are not left intact
167
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
168
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")'));
169
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")'));
170
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")'));
171
    }
172
173
    public function testAlternativeBaseURL()
0 ignored issues
show
Coding Style introduced by
testAlternativeBaseURL uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
174
    {
175
        // Get original protocol and hostname
176
        $rootURL = Director::protocolAndHost();
177
178
        // relative base URLs - you should end them in a /
179
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/relativebase/');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
180
        $_SERVER['REQUEST_URI'] = "$rootURL/relativebase/sub-page/";
181
182
        $this->assertEquals('/relativebase/', Director::baseURL());
183
        $this->assertEquals($rootURL . '/relativebase/', Director::absoluteBaseURL());
184
        $this->assertEquals(
185
            $rootURL . '/relativebase/subfolder/test',
186
            Director::absoluteURL('subfolder/test')
187
        );
188
189
        // absolute base URLs - you should end them in a /
190
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', 'http://www.example.org/');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
191
        $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
192
        $this->assertEquals('http://www.example.org/', Director::baseURL());
193
        $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
194
        $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
195
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
196
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
197
        $this->assertEquals(
198
            'http://www.example.org/sub-page/subfolder/test',
199
            Director::absoluteURL('subfolder/test', Director::REQUEST)
200
        );
201
        $this->assertEquals(
202
            'http://www.example.org/subfolder/test',
203
            Director::absoluteURL('subfolder/test', Director::ROOT)
204
        );
205
        $this->assertEquals(
206
            'http://www.example.org/subfolder/test',
207
            Director::absoluteURL('subfolder/test', Director::BASE)
208
        );
209
    }
210
211
    /**
212
     * Tests that {@link Director::is_absolute()} works under different environment types
213
     */
214
    public function testIsAbsolute()
215
    {
216
        $expected = array (
217
            'C:/something' => true,
218
            'd:\\'         => true,
219
            'e/'           => false,
220
            's:/directory' => true,
221
            '/var/www'     => true,
222
            '\\Something'  => true,
223
            'something/c:' => false,
224
            'folder'       => false,
225
            'a/c:/'        => false
226
        );
227
228
        foreach ($expected as $path => $result) {
229
            $this->assertEquals(Director::is_absolute($path), $result, "Test result for $path");
230
        }
231
    }
232
233
    public function testIsAbsoluteUrl()
234
    {
235
        $this->assertTrue(Director::is_absolute_url('http://test.com/testpage'));
236
        $this->assertTrue(Director::is_absolute_url('ftp://test.com'));
237
        $this->assertFalse(Director::is_absolute_url('test.com/testpage'));
238
        $this->assertFalse(Director::is_absolute_url('/relative'));
239
        $this->assertFalse(Director::is_absolute_url('relative'));
240
        $this->assertFalse(Director::is_absolute_url("/relative/?url=http://foo.com"));
241
        $this->assertFalse(Director::is_absolute_url("/relative/#http://foo.com"));
242
        $this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com"));
243
        $this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com"));
244
        $this->assertTrue(Director::is_absolute_url('//test.com'));
245
        $this->assertTrue(Director::is_absolute_url('/////test.com'));
246
        $this->assertTrue(Director::is_absolute_url('  ///test.com'));
247
        $this->assertTrue(Director::is_absolute_url('http:test.com'));
248
        $this->assertTrue(Director::is_absolute_url('//http://test.com'));
249
    }
250
251
    public function testIsRelativeUrl()
252
    {
253
        $siteUrl = Director::absoluteBaseURL();
254
        $this->assertFalse(Director::is_relative_url('http://test.com'));
255
        $this->assertFalse(Director::is_relative_url('https://test.com'));
256
        $this->assertFalse(Director::is_relative_url('   https://test.com/testpage   '));
257
        $this->assertTrue(Director::is_relative_url('test.com/testpage'));
258
        $this->assertFalse(Director::is_relative_url('ftp://test.com'));
259
        $this->assertTrue(Director::is_relative_url('/relative'));
260
        $this->assertTrue(Director::is_relative_url('relative'));
261
        $this->assertTrue(Director::is_relative_url('/relative/?url=http://test.com'));
262
        $this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
263
    }
264
265
    public function testMakeRelative()
266
    {
267
        $siteUrl = Director::absoluteBaseURL();
268
        $siteUrlNoProtocol = preg_replace('/https?:\/\//', '', $siteUrl);
269
270
        $this->assertEquals(Director::makeRelative("$siteUrl"), '');
271
        $this->assertEquals(Director::makeRelative("https://$siteUrlNoProtocol"), '');
272
        $this->assertEquals(Director::makeRelative("http://$siteUrlNoProtocol"), '');
273
274
        $this->assertEquals(Director::makeRelative("   $siteUrl/testpage   "), 'testpage');
275
        $this->assertEquals(Director::makeRelative("$siteUrlNoProtocol/testpage"), 'testpage');
276
277
        $this->assertEquals(Director::makeRelative('ftp://test.com'), 'ftp://test.com');
278
        $this->assertEquals(Director::makeRelative('http://test.com'), 'http://test.com');
279
280
        $this->assertEquals(Director::makeRelative('relative'), 'relative');
281
        $this->assertEquals(Director::makeRelative("$siteUrl/?url=http://test.com"), '?url=http://test.com');
282
283
        $this->assertEquals("test", Director::makeRelative("https://".$siteUrlNoProtocol."/test"));
284
        $this->assertEquals("test", Director::makeRelative("http://".$siteUrlNoProtocol."/test"));
285
    }
286
287
    /**
288
     * Mostly tested by {@link testIsRelativeUrl()},
289
     * just adding the host name matching aspect here.
290
     */
291
    public function testIsSiteUrl()
292
    {
293
        $this->assertFalse(Director::is_site_url("http://test.com"));
294
        $this->assertTrue(Director::is_site_url(Director::absoluteBaseURL()));
0 ignored issues
show
Security Bug introduced by
It seems like \SilverStripe\Control\Director::absoluteBaseURL() targeting SilverStripe\Control\Director::absoluteBaseURL() can also be of type false; however, SilverStripe\Control\Director::is_site_url() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
295
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL()));
296
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL())));
297
        $this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL()));
298
    }
299
300
    /**
301
     * Tests isDev, isTest, isLive set from querystring
302
     */
303
    public function testQueryIsEnvironment()
0 ignored issues
show
Coding Style introduced by
testQueryIsEnvironment uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
testQueryIsEnvironment uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
304
    {
305
        // Reset
306
        unset($_SESSION['isDev']);
307
        unset($_SESSION['isLive']);
308
        unset($_GET['isTest']);
309
        unset($_GET['isDev']);
310
        $_SESSION = $_SESSION ?: array();
311
312
        // Test isDev=1
313
        $_GET['isDev'] = '1';
314
        $this->assertTrue(Director::isDev());
315
        $this->assertFalse(Director::isTest());
316
        $this->assertFalse(Director::isLive());
317
318
        // Test persistence
319
        unset($_GET['isDev']);
320
        $this->assertTrue(Director::isDev());
321
        $this->assertFalse(Director::isTest());
322
        $this->assertFalse(Director::isLive());
323
324
        // Test change to isTest
325
        $_GET['isTest'] = '1';
326
        $this->assertFalse(Director::isDev());
327
        $this->assertTrue(Director::isTest());
328
        $this->assertFalse(Director::isLive());
329
330
        // Test persistence
331
        unset($_GET['isTest']);
332
        $this->assertFalse(Director::isDev());
333
        $this->assertTrue(Director::isTest());
334
        $this->assertFalse(Director::isLive());
335
    }
336
337
    public function testResetGlobalsAfterTestRequest()
0 ignored issues
show
Coding Style introduced by
testResetGlobalsAfterTestRequest uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
testResetGlobalsAfterTestRequest uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
testResetGlobalsAfterTestRequest uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
338
    {
339
        $_GET = array('somekey' => 'getvalue');
340
        $_POST = array('somekey' => 'postvalue');
341
        $_COOKIE = array('somekey' => 'cookievalue');
342
343
        $cookies = Injector::inst()->createWithArgs(
344
            'SilverStripe\\Control\\Cookie_Backend',
345
            array(array('somekey' => 'sometestcookievalue'))
346
        );
347
348
        $getresponse = Director::test(
349
            'errorpage?somekey=sometestgetvalue',
350
            array('somekey' => 'sometestpostvalue'),
351
            null,
352
            null,
353
            null,
354
            null,
355
            $cookies
356
        );
357
358
        $this->assertEquals(
359
            'getvalue',
360
            $_GET['somekey'],
361
            '$_GET reset to original value after Director::test()'
362
        );
363
        $this->assertEquals(
364
            'postvalue',
365
            $_POST['somekey'],
366
            '$_POST reset to original value after Director::test()'
367
        );
368
        $this->assertEquals(
369
            'cookievalue',
370
            $_COOKIE['somekey'],
371
            '$_COOKIE reset to original value after Director::test()'
372
        );
373
    }
374
375
    public function testTestRequestCarriesGlobals()
376
    {
377
        $fixture = array('somekey' => 'sometestvalue');
378
        foreach (array('get', 'post') as $method) {
379
            foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
380
                $url = 'TestController/' . sprintf($testfunction, ucfirst($method))
381
                    . '?' . http_build_query($fixture);
382
383
                $getresponse = Director::test(
384
                    $url,
385
                    $fixture,
386
                    null,
387
                    strtoupper($method),
388
                    null,
389
                    null,
390
                    Injector::inst()->createWithArgs('SilverStripe\\Control\\Cookie_Backend', array($fixture))
391
                );
392
393
                $this->assertInstanceOf('SilverStripe\\Control\\HTTPResponse', $getresponse, 'Director::test() returns HTTPResponse');
394
                $this->assertEquals($fixture['somekey'], $getresponse->getBody(), 'Director::test() ' . $testfunction);
395
            }
396
        }
397
    }
398
399
    /**
400
     * Tests that additional parameters specified in the routing table are
401
     * saved in the request
402
     */
403
    public function testRouteParams()
404
    {
405
        Director::test('en-nz/myaction/myid/myotherid', null, null, null, null, null, null, $request);
406
407
        $this->assertEquals(
408
            array(
409
                'Controller' => TestController::class,
410
                'Action' => 'myaction',
411
                'ID' => 'myid',
412
                'OtherID' => 'myotherid',
413
                'Locale' => 'en_NZ'
414
            ),
415
            $request->params()
416
        );
417
    }
418
419
    public function testForceSSLProtectsEntireSite()
0 ignored issues
show
Coding Style introduced by
testForceSSLProtectsEntireSite uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
420
    {
421
        $_SERVER['REQUEST_URI'] = '/admin';
422
        $output = Director::forceSSL();
423
        $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
424
425
        $_SERVER['REQUEST_URI'] = Director::baseURL() . 'some-url';
426
        $output = Director::forceSSL();
427
        $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
428
    }
429
430
    public function testForceSSLOnTopLevelPagePattern()
0 ignored issues
show
Coding Style introduced by
testForceSSLOnTopLevelPagePattern uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
431
    {
432
        $_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin';
433
        $output = Director::forceSSL(array('/^admin/'));
434
        $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
435
    }
436
437
    public function testForceSSLOnSubPagesPattern()
0 ignored issues
show
Coding Style introduced by
testForceSSLOnSubPagesPattern uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
438
    {
439
        $_SERVER['REQUEST_URI'] = Director::baseURL() . Config::inst()->get('SilverStripe\\Security\\Security', 'login_url');
440
        $output = Director::forceSSL(array('/^Security/'));
441
        $this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
442
    }
443
444
    public function testForceSSLWithPatternDoesNotMatchOtherPages()
0 ignored issues
show
Coding Style introduced by
testForceSSLWithPatternDoesNotMatchOtherPages uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
445
    {
446
        $_SERVER['REQUEST_URI'] = Director::baseURL() . 'normal-page';
447
        $output = Director::forceSSL(array('/^admin/'));
448
        $this->assertFalse($output);
449
450
        $_SERVER['REQUEST_URI'] = Director::baseURL() . 'just-another-page/sub-url';
451
        $output = Director::forceSSL(array('/^admin/', '/^Security/'));
452
        $this->assertFalse($output);
453
    }
454
455
    public function testForceSSLAlternateDomain()
0 ignored issues
show
Coding Style introduced by
testForceSSLAlternateDomain uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
456
    {
457
        Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', '/');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
458
        $_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin';
459
        $output = Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
460
        $this->assertEquals($output, 'https://secure.mysite.com/admin');
461
    }
462
463
    /**
464
     * @covers \SilverStripe\Control\Director::extract_request_headers()
465
     */
466
    public function testExtractRequestHeaders()
467
    {
468
        $request = array(
469
            'REDIRECT_STATUS'      => '200',
470
            'HTTP_HOST'            => 'host',
471
            'HTTP_USER_AGENT'      => 'User Agent',
472
            'HTTP_ACCEPT'          => 'text/html',
473
            'HTTP_ACCEPT_LANGUAGE' => 'en-us',
474
            'HTTP_COOKIE'          => 'MyCookie=1',
475
            'SERVER_PROTOCOL'      => 'HTTP/1.1',
476
            'REQUEST_METHOD'       => 'GET',
477
            'REQUEST_URI'          => '/',
478
            'SCRIPT_NAME'          => FRAMEWORK_DIR . '/main.php',
479
            'CONTENT_TYPE'         => 'text/xml',
480
            'CONTENT_LENGTH'       => 10
481
        );
482
483
        $headers = array(
484
            'Host'            => 'host',
485
            'User-Agent'      => 'User Agent',
486
            'Accept'          => 'text/html',
487
            'Accept-Language' => 'en-us',
488
            'Cookie'          => 'MyCookie=1',
489
            'Content-Type'    => 'text/xml',
490
            'Content-Length'  => '10'
491
        );
492
493
        $this->assertEquals($headers, Director::extract_request_headers($request));
494
    }
495
496
    public function testUnmatchedRequestReturns404()
497
    {
498
        // Remove non-tested rules
499
        $this->assertEquals(404, Director::test('no-route')->getStatusCode());
500
    }
501
502
    public function testIsHttps()
0 ignored issues
show
Coding Style introduced by
testIsHttps uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
503
    {
504
        if (!TRUSTED_PROXY) {
505
            $this->markTestSkipped('Test cannot be run without trusted proxy');
506
        }
507
        // nothing available
508
        $headers = array(
509
            'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
510
        );
511
512
        $origServer = $_SERVER;
513
514
        foreach ($headers as $header) {
515
            if (isset($_SERVER[$header])) {
516
                unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
517
            }
518
        }
519
520
        $this->assertFalse(Director::is_https());
521
522
        $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
523
        $this->assertTrue(Director::is_https());
524
525
        $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
526
        $this->assertFalse(Director::is_https());
527
528
        $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'ftp';
529
        $this->assertFalse(Director::is_https());
530
531
        $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
532
        $this->assertTrue(Director::is_https());
533
534
        $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
535
        $this->assertFalse(Director::is_https());
536
537
        $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'ftp';
538
        $this->assertFalse(Director::is_https());
539
540
        $_SERVER['HTTP_FRONT_END_HTTPS'] = 'On';
541
        $this->assertTrue(Director::is_https());
542
543
        $_SERVER['HTTP_FRONT_END_HTTPS'] = 'Off';
544
        $this->assertFalse(Director::is_https());
545
546
        // https via HTTPS
547
        $_SERVER['HTTPS'] = 'true';
548
        $this->assertTrue(Director::is_https());
549
550
        $_SERVER['HTTPS'] = '1';
551
        $this->assertTrue(Director::is_https());
552
553
        $_SERVER['HTTPS'] = 'off';
554
        $this->assertFalse(Director::is_https());
555
556
        // https via SSL
557
        $_SERVER['SSL'] = '';
558
        $this->assertTrue(Director::is_https());
559
560
        $_SERVER = $origServer;
561
    }
562
563
    public function testTestIgnoresHashes()
564
    {
565
        //test that hashes are ignored
566
        $url = "TestController/returnGetValue?somekey=key";
567
        $hash = "#test";
568
        $response = Director::test($url . $hash, null, null, null, null, null, null, $request);
569
        $this->assertFalse($response->isError());
570
        $this->assertEquals('key', $response->getBody());
571
        $this->assertEquals($request->getURL(true), $url);
572
573
        //test encoded hashes are accepted
574
        $url = "TestController/returnGetValue?somekey=test%23key";
575
        $response = Director::test($url, null, null, null, null, null, null, $request);
576
        $this->assertFalse($response->isError());
577
        $this->assertEquals('test#key', $response->getBody());
578
        $this->assertEquals($request->getURL(true), $url);
579
    }
580
581
    public function testRequestFilterInDirectorTest()
582
    {
583
        $filter = new DirectorTest\TestRequestFilter;
584
585
        $processor = new RequestProcessor(array($filter));
586
587
        Injector::inst()->registerService($processor, 'SilverStripe\\Control\\RequestProcessor');
588
589
        $response = Director::test('some-dummy-url');
590
591
        $this->assertEquals(1, $filter->preCalls);
592
        $this->assertEquals(1, $filter->postCalls);
593
594
        $filter->failPost = true;
595
596
        $this->setExpectedException('SilverStripe\\Control\\HTTPResponse_Exception');
597
598
        $response = Director::test('some-dummy-url');
599
600
        $this->assertEquals(2, $filter->preCalls);
601
        $this->assertEquals(2, $filter->postCalls);
602
603
        $filter->failPre = true;
604
605
        $response = Director::test('some-dummy-url');
606
607
        $this->assertEquals(3, $filter->preCalls);
608
609
        // preCall 'false' will trigger an exception and prevent post call execution
610
        $this->assertEquals(2, $filter->postCalls);
611
    }
612
}
613