IntegrationTestCase   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 343
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 115
c 5
b 0
f 0
dl 0
loc 343
rs 9.84
wmc 32

20 Methods

Rating   Name   Duplication   Size   Complexity  
A _unsetJson() 0 4 1
A _setJson() 0 4 1
A _setUserAgent() 0 6 2
A _unsetAjax() 0 3 1
A assertResponseContainsTags() 0 5 1
A skipOnDataSource() 0 9 2
A assertRedirectLogin() 0 13 1
A _loginUser() 0 10 1
B assertRouteForRole() 0 41 9
A _logoutUser() 0 10 3
A _setAjax() 0 4 1
A _sendRequest() 0 6 1
A tearDown() 0 10 1
A loginJwt() 0 10 1
A getMockOnController() 0 8 1
A setUp() 0 6 1
A markUpdated() 0 4 1
A getMockForTable() 0 6 1
A _unsetUserAgent() 0 3 1
A attachToController() 0 7 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace Saito\Test;
14
15
use App\Test\Fixture\UserFixture;
16
use Cake\Core\Configure;
17
use Cake\Event\Event;
18
use Cake\Event\EventManager;
19
use Cake\Http\Response;
20
use Cake\ORM\TableRegistry;
21
use Cake\Routing\Router;
22
use Cake\TestSuite\IntegrationTestTrait;
23
use Cake\TestSuite\TestCase;
24
use Saito\Exception\SaitoForbiddenException;
25
26
/**
27
 * Setup/teardown, helper and assumptions for Saito integration tests
28
 */
29
abstract class IntegrationTestCase extends TestCase
30
{
31
    use AssertTrait;
0 ignored issues
show
Bug introduced by
The trait Saito\Test\AssertTrait requires the property $length which is not provided by Saito\Test\IntegrationTestCase.
Loading history...
32
    use IntegrationTestTrait {
0 ignored issues
show
Bug introduced by
The trait Cake\TestSuite\IntegrationTestTrait requires the property $viewVars which is not provided by Saito\Test\IntegrationTestCase.
Loading history...
33
        _sendRequest as _sendRequestParent;
34
    }
35
    use SecurityMockTrait;
36
    use TestCaseTrait {
0 ignored issues
show
Bug introduced by
The trait Saito\Test\TestCaseTrait requires the property $path which is not provided by Saito\Test\IntegrationTestCase.
Loading history...
37
        getMockForTable as getMockForTableParent;
38
    }
39
40
    /**
41
     * @var array cache environment variables
42
     */
43
    protected $_env = [];
44
45
    /**
46
     * {@inheritDoc}
47
     */
48
    public function setUp()
49
    {
50
        $this->disableErrorHandlerMiddleware();
51
        $this->setUpSaito();
52
        $this->markUpdated();
53
        parent::setUp();
54
    }
55
56
    /**
57
     * {@inheritDoc}
58
     */
59
    public function tearDown()
60
    {
61
        parent::tearDown();
62
        // This will restore the Config. Leave it after parent::tearDown() or
63
        // Cake's Configure restore will overwrite it and mess things up.
64
        $this->tearDownSaito();
65
        $this->_unsetAjax();
66
        $this->_unsetJson();
67
        $this->_unsetUserAgent();
68
        $this->_clearCaches();
69
    }
70
71
    /**
72
     * set request ajax
73
     *
74
     * @return void
75
     */
76
    protected function _setAjax()
77
    {
78
        $this->disableCsrf();
79
        $_ENV['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';
80
    }
81
82
    /**
83
     * unset request ajax
84
     *
85
     * @return void
86
     */
87
    protected function _unsetAjax()
88
    {
89
        unset($_ENV['HTTP_X_REQUESTED_WITH']);
90
    }
91
92
    /**
93
     * set request json
94
     *
95
     * @return void
96
     */
97
    protected function _setJson()
98
    {
99
        $this->configRequest([
100
            'headers' => ['Accept' => 'application/json'],
101
        ]);
102
    }
103
104
    /**
105
     * unset request json
106
     *
107
     * @return void
108
     */
109
    protected function _unsetJson()
110
    {
111
        $this->configRequest([
112
            'headers' => ['Accept' => 'text/html,application/xhtml+xml,application/xml'],
113
        ]);
114
    }
115
116
    /**
117
     * Set user agent
118
     *
119
     * @param string $agent agent
120
     * @return void
121
     */
122
    protected function _setUserAgent($agent)
123
    {
124
        if (isset($this->_env['HTTP_USER_AGENT'])) {
125
            $this->_env['HTTP_USER_AGENT'] = $_ENV['HTTP_USER_AGENT'];
126
        }
127
        $_ENV['HTTP_USER_AGENT'] = $agent;
128
    }
129
130
    /**
131
     * Resets user agent
132
     *
133
     * @return void
134
     */
135
    protected function _unsetUserAgent()
136
    {
137
        unset($_ENV['HTTP_USER_AGENT']);
138
    }
139
140
    /**
141
     * Mocks a table with methods
142
     *
143
     * @param string $table table-name
144
     * @param array $methods methods to mock
145
     * @return mixed
146
     */
147
    protected function getMockForTable($table, array $methods = [])
148
    {
149
        $mock = $this->getMockForTableParent($table, $methods);
150
        $this->attachToController($table, $mock);
151
152
        return $mock;
153
    }
154
155
    /**
156
     * Mocks a property on a controller (e.g. empty Component).
157
     *
158
     * @param string $name Property to mock
159
     * @param array $methods Methods to mock
160
     * @return mixed
161
     */
162
    protected function getMockOnController(string $name, array $methods = [])
163
    {
164
        $mock = $this->getMockBuilder('stdClass')
165
            ->setMethods($methods)
166
            ->getMock();
167
        $this->attachToController($name, $mock);
168
169
        return $mock;
170
    }
171
172
    /**
173
     * Attaches an property to the controller
174
     *
175
     * @param string $name Property name
176
     * @param mixed $item Item
177
     * @return void
178
     */
179
    private function attachToController(string $name, $item): void
180
    {
181
        EventManager::instance()->on(
182
            'Controller.initialize',
183
            function (Event $event) use ($name, $item) {
184
                $Controller = $event->getSubject();
185
                $Controller->{$name} = $item;
186
            }
187
        );
188
    }
189
190
    /**
191
     * Configure next request as user authenticated with JWT-Token
192
     *
193
     * @param int $userId user
194
     * @return void
195
     */
196
    protected function loginJwt(int $userId)
197
    {
198
        $jwtKey = Configure::read('Security.cookieSalt');
199
        $jwtPayload = ['sub' => $userId];
200
        $jwtToken = \Firebase\JWT\JWT::encode($jwtPayload, $jwtKey);
201
202
        $this->configRequest([
203
            'headers' => [
204
                'Accept' => 'application/json',
205
                'Authorization' => 'bearer ' . $jwtToken,
206
            ],
207
        ]);
208
    }
209
210
    /**
211
     * Login user
212
     *
213
     * @param int $id user-ID
214
     * @return mixed
215
     */
216
    protected function _loginUser($id)
217
    {
218
        // see: http://stackoverflow.com/a/10411128/1372085
219
        $this->_logoutUser();
220
        $userFixture = new UserFixture();
221
        $users = $userFixture->records;
222
        $user = $users[$id - 1];
223
        $this->session(['Auth' => $user]);
224
225
        return $user;
226
    }
227
228
    /**
229
     * Logout user
230
     *
231
     * @return void
232
     */
233
    protected function _logoutUser()
234
    {
235
        // if user is logged-in it should interfere with test runs
236
        if (isset($_COOKIE['Saito-AU'])) :
237
            unset($_COOKIE['Saito-AU']);
238
        endif;
239
        if (isset($_COOKIE['Saito'])) :
240
            unset($_COOKIE['Saito']);
241
        endif;
242
        unset($this->_session['Auth']);
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248
    protected function _sendRequest($url, $method, $data = [])
249
    {
250
        // Workaround for Cake 3.6 Router on test bug with named routes in plugins
251
        // "A route named "<foo>" has already been connected to "<bar>".
252
        Router::reload();
253
        $this->_sendRequestParent($url, $method, $data);
254
    }
255
256
    /**
257
     * Skip test on particular datasource
258
     *
259
     * @param string $datasource MySQL|Postgres
260
     * @return void
261
     */
262
    protected function skipOnDataSource(string $datasource): void
263
    {
264
        $datasource = strtolower($datasource);
265
266
        $driver = TableRegistry::get('Entries')->getConnection()->getDriver();
0 ignored issues
show
Deprecated Code introduced by
The function Cake\ORM\TableRegistry::get() has been deprecated: 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

266
        $driver = /** @scrutinizer ignore-deprecated */ TableRegistry::get('Entries')->getConnection()->getDriver();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
267
        $class = strtolower(get_class($driver));
268
269
        if (strpos($class, $datasource)) {
270
            $this->markTestSkipped("Skipped on datasource '$datasource'");
271
        }
272
    }
273
274
    /**
275
     * Marks the Saito installation installed and updated (don't run installer or updater)
276
     *
277
     * @return void
278
     */
279
    private function markUpdated()
280
    {
281
        Configure::write('Saito.installed', true);
282
        Configure::write('Saito.updated', true);
283
    }
284
285
    /**
286
     * Check if only specific roles is allowed on action
287
     *
288
     * @param string $route URL
289
     * @param string $role role
290
     * @param true|string|null $referer true: same as $url, null: none, string: URL
291
     * @param string $method HTTP-method
292
     * @return void
293
     */
294
    public function assertRouteForRole($route, $role, $referer = true, $method = 'GET')
295
    {
296
        if ($referer === true) {
297
            $referer = $route;
0 ignored issues
show
Unused Code introduced by
The assignment to $referer is dead and can be removed.
Loading history...
298
        }
299
        $method = strtolower($method);
300
        $types = ['admin' => 3, 'mod' => 2, 'user' => 1, 'anon' => 0];
301
302
        foreach ($types as $title => $type) {
303
            switch ($title) {
304
                case 'anon':
305
                    break;
306
                case 'user':
307
                    $this->_loginUser(3);
308
                    break;
309
                case 'mod':
310
                    $this->_loginUser(2);
311
                    break;
312
                case 'admin':
313
                    $this->_loginUser(1);
314
                    break;
315
            }
316
317
            if ($type < $types[$role]) {
318
                $cought = false;
319
                try {
320
                    $this->{$method}($route);
321
                } catch (SaitoForbiddenException $e) {
322
                    $cought = true;
323
                }
324
325
                $method = strtoupper($method);
326
327
                $this->assertTrue(
328
                    $cought,
329
                    sprintf('Route "%s %s" should not be accessible to role "%s"', $method, $title, $route)
330
                );
331
            } else {
332
                $this->{$method}($route);
333
                $method = strtoupper($method);
334
                $this->assertNoRedirect("Redirect wasn't expected for user-role '$role' on $method $route");
335
            }
336
        }
337
    }
338
339
    /**
340
     * Check that an redirect to the login is performed
341
     *
342
     * @param string $redirectUrl redirect URL '/where/I/come/from'
343
     * @param string $msg Message
344
     * @return void
345
     */
346
    public function assertRedirectLogin($redirectUrl = null, string $msg = '')
347
    {
348
        /** @var Response $response */
349
        $response = $this->_response;
350
        $expected = Router::url([
351
            '_name' => 'login',
352
            'plugin' => false,
353
            '?' => ['redirect' => $redirectUrl],
354
        ], true);
355
        $redirectHeader = $response->getHeader('Location')[0];
356
        $this->assertEquals($expected, $redirectHeader, $msg);
357
        $this->assertResponseEmpty();
358
        $this->assertResponseCode(302);
359
    }
360
361
    /**
362
     * assert contains tags
363
     *
364
     * @param array $expected expected
365
     * @return void
366
     */
367
    public function assertResponseContainsTags($expected)
368
    {
369
        $this->assertContainsTag(
370
            $expected,
371
            (string)$this->_controller->response->getBody()
372
        );
373
    }
374
}
375