Completed
Push — master ( 3c1969...5b5792 )
by Schlaefer
03:16 queued 10s
created

IntegrationTestCase   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
dl 0
loc 293
rs 10
c 0
b 0
f 0
wmc 28
lcom 1
cbo 13

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 8 1
A tearDown() 0 8 1
A _setAjax() 0 5 1
A _unsetAjax() 0 4 1
A _setJson() 0 6 1
A _unsetJson() 0 6 1
A _setUserAgent() 0 7 2
A getMockForTable() 0 13 1
A loginJwt() 0 13 1
A _loginUser() 0 11 1
A _logoutUser() 0 11 3
A _sendRequest() 0 7 1
A skipOnDataSource() 0 11 2
A markUpdated() 0 5 1
B assertRouteForRole() 0 34 8
A assertRedirectLogin() 0 12 1
A assertResponseContainsTags() 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 2012-2018
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
17
use Cake\Core\Configure;
18
use Cake\Event\Event;
19
use Cake\Event\EventManager;
20
use Cake\ORM\TableRegistry;
21
use Cake\Routing\Router;
22
use Cake\TestSuite\IntegrationTestTrait;
23
use Cake\TestSuite\TestCase;
24
25
/**
26
 * Setup/teardown, helper and assumptions for Saito integration tests
27
 */
28
abstract class IntegrationTestCase extends TestCase
29
{
30
    use AssertTrait;
31
    use IntegrationTestTrait {
32
        _sendRequest as _sendRequestParent;
33
    }
34
    use SecurityMockTrait;
35
    use TestCaseTrait {
36
        getMockForTable as getMockForTableParent;
37
    }
38
39
    /**
40
     * @var array cache environment variables
41
     */
42
    protected $_env = [];
43
44
    /**
45
     * {@inheritDoc}
46
     */
47
    public function setUp()
48
    {
49
        parent::setUp();
50
        $this->disableErrorHandlerMiddleware();
51
        $this->setUpSaito();
52
        $this->_clearCaches();
53
        $this->markUpdated();
54
    }
55
56
    /**
57
     * {@inheritDoc}
58
     */
59
    public function tearDown()
60
    {
61
        $this->tearDownSaito();
62
        $this->_unsetAjax();
63
        $this->_unsetJson();
64
        parent::tearDown();
65
        $this->_clearCaches();
66
    }
67
68
    /**
69
     * set request ajax
70
     *
71
     * @return void
72
     */
73
    protected function _setAjax()
74
    {
75
        $this->disableCsrf();
76
        $_ENV['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';
77
    }
78
79
    /**
80
     * unset request ajax
81
     *
82
     * @return void
83
     */
84
    protected function _unsetAjax()
85
    {
86
        unset($_ENV['HTTP_X_REQUESTED_WITH']);
87
    }
88
89
    /**
90
     * set request json
91
     *
92
     * @return void
93
     */
94
    protected function _setJson()
95
    {
96
        $this->configRequest([
97
            'headers' => ['Accept' => 'application/json']
98
        ]);
99
    }
100
101
    /**
102
     * unset request json
103
     *
104
     * @return void
105
     */
106
    protected function _unsetJson()
107
    {
108
        $this->configRequest([
109
            'headers' => ['Accept' => 'text/html,application/xhtml+xml,application/xml']
110
        ]);
111
    }
112
113
    /**
114
     * Set user agent
115
     *
116
     * @param string $agent agent
117
     * @return void
118
     */
119
    protected function _setUserAgent($agent)
120
    {
121
        if (isset($this->_env['HTTP_USER_AGENT'])) {
122
            $this->_env['HTTP_USER_AGENT'] = $_ENV['HTTP_USER_AGENT'];
123
        }
124
        $_ENV['HTTP_USER_AGENT'] = $agent;
125
    }
126
127
    /**
128
     * Mocks a table with methods
129
     *
130
     * @param string $table table-name
131
     * @param array $methods methods to mock
132
     * @return mixed
133
     */
134
    public function getMockForTable($table, array $methods = [])
135
    {
136
        $Mock = $this->getMockForTableParent($table, $methods);
137
        EventManager::instance()->on(
138
            'Controller.initialize',
139
            function (Event $event) use ($table, $Mock) {
0 ignored issues
show
Documentation introduced by
function (\Cake\Event\Ev...er->{$table} = $Mock; } is of type object<Closure>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
140
                $Controller = $event->getSubject();
141
                $Controller->{$table} = $Mock;
142
            }
143
        );
144
145
        return $Mock;
146
    }
147
148
    /**
149
     * Configure next request as user authenticated with JWT-Token
150
     *
151
     * @param int $userId user
152
     * @return void
153
     */
154
    protected function loginJwt(int $userId)
155
    {
156
        $jwtKey = Configure::read('Security.cookieSalt');
157
        $jwtPayload = ['sub' => $userId];
158
        $jwtToken = \Firebase\JWT\JWT::encode($jwtPayload, $jwtKey);
159
160
        $this->configRequest([
161
            'headers' => [
162
                'Accept' => 'application/json',
163
                'Authorization' => 'bearer ' . $jwtToken,
164
            ]
165
        ]);
166
    }
167
168
    /**
169
     * Login user
170
     *
171
     * @param int $id user-ID
172
     * @return mixed
173
     */
174
    protected function _loginUser($id)
175
    {
176
        // see: http://stackoverflow.com/a/10411128/1372085
177
        $this->_logoutUser();
178
        $userFixture = new UserFixture();
179
        $users = $userFixture->records;
180
        $user = $users[$id - 1];
181
        $this->session(['Auth.User' => $user]);
182
183
        return $user;
184
    }
185
186
    /**
187
     * Logout user
188
     *
189
     * @return void
190
     */
191
    protected function _logoutUser()
192
    {
193
        // if user is logged-in it should interfere with test runs
194
        if (isset($_COOKIE['Saito-AU'])) :
195
            unset($_COOKIE['Saito-AU']);
196
        endif;
197
        if (isset($_COOKIE['Saito'])) :
198
            unset($_COOKIE['Saito']);
199
        endif;
200
        unset($this->_session['Auth.User']);
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    protected function _sendRequest($url, $method, $data = [])
207
    {
208
        // Workaround for Cake 3.6 Router on test bug with named routes in plugins
209
        // "A route named "<foo>" has already been connected to "<bar>".
210
        Router::reload();
211
        $this->_sendRequestParent($url, $method, $data);
212
    }
213
214
    /**
215
     * Skip test on particular datasource
216
     *
217
     * @param string $datasource MySQL|Postgres
218
     * @return void
219
     */
220
    protected function skipOnDataSource(string $datasource): void
221
    {
222
        $datasource = strtolower($datasource);
223
224
        $driver = TableRegistry::get('Entries')->getConnection()->getDriver();
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\TableRegistry::get() has been deprecated with message: 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead.

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

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

Loading history...
225
        $class = strtolower(get_class($driver));
226
227
        if (strpos($class, $datasource)) {
228
            $this->markTestSkipped("Skipped on datasource '$datasource'");
229
        }
230
    }
231
232
    /**
233
     * Marks the Saito installation installed and updated (don't run installer or updater)
234
     *
235
     * @return void
236
     */
237
    private function markUpdated()
238
    {
239
        Configure::write('Saito.installed', true);
240
        Configure::write('Saito.updated', true);
241
    }
242
243
    /**
244
     * Check if only specific roles is allowed on action
245
     *
246
     * @param string $route URL
247
     * @param string $role role
248
     * @param true|string|null $referer true: same as $url, null: none, string: URL
249
     * @param string $method HTTP-method
250
     * @return void
251
     */
252
    public function assertRouteForRole($route, $role, $referer = true, $method = 'GET')
253
    {
254
        if ($referer === true) {
255
            $referer = $route;
256
        }
257
        $method = strtolower($method);
258
        $types = ['admin' => 3, 'mod' => 2, 'user' => 1, 'anon' => 0];
259
260
        foreach ($types as $title => $type) {
261
            switch ($title) {
262
                case 'anon':
263
                    break;
264
                case 'user':
265
                    $this->_loginUser(3);
266
                    break;
267
                case 'mod':
268
                    $this->_loginUser(2);
269
                    break;
270
                case 'admin':
271
                    $this->_loginUser(1);
272
                    break;
273
            }
274
275
            if ($type < $types[$role]) {
276
                $this->{$method}($route);
277
                $method = strtoupper($method);
278
                $this->assertRedirectLogin($referer, "No login redirect for $role on $method $route");
279
            } else {
280
                $this->{$method}($route);
281
                $method = strtoupper($method);
282
                $this->assertNoRedirect("Redirect wasn't expected for user-role '$role' on $method $route");
283
            }
284
        }
285
    }
286
287
    /**
288
     * Check that an redirect to the login is performed
289
     *
290
     * @param string $redirectUrl redirect URL '/where/I/come/from'
291
     * @param string $msg Message
292
     * @return void
293
     */
294
    public function assertRedirectLogin($redirectUrl = null, string $msg = '')
295
    {
296
        /** @var Response $response */
297
        $response = $this->_controller->response;
298
        $expected = Router::url([
299
            '_name' => 'login',
300
            'plugin' => false,
301
            '?' => ['redirect' => $redirectUrl]
302
        ], true);
303
        $redirectHeader = $response->getHeader('Location')[0];
304
        $this->assertEquals($expected, $redirectHeader, $msg);
305
    }
306
307
    /**
308
     * assert contains tags
309
     *
310
     * @param array $expected expected
311
     * @return void
312
     */
313
    public function assertResponseContainsTags($expected)
314
    {
315
        $this->assertContainsTag(
316
            $expected,
317
            (string)$this->_controller->response->getBody()
318
        );
319
    }
320
}
321