Completed
Push — master ( e849bf...44b152 )
by Schlaefer
11:58 queued 03:40
created

IntegrationTestCase::getMockOnController()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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