ci4

Форк
0
/
FeatureTestTrait.php 
429 строк · 11.2 Кб
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Test;
15

16
use CodeIgniter\Events\Events;
17
use CodeIgniter\HTTP\Exceptions\RedirectException;
18
use CodeIgniter\HTTP\IncomingRequest;
19
use CodeIgniter\HTTP\Method;
20
use CodeIgniter\HTTP\Request;
21
use CodeIgniter\HTTP\SiteURI;
22
use CodeIgniter\HTTP\URI;
23
use Config\App;
24
use Config\Services;
25
use Exception;
26
use ReflectionException;
27

28
/**
29
 * Trait FeatureTestTrait
30
 *
31
 * Provides additional utilities for doing full HTTP testing
32
 * against your application in trait format.
33
 */
34
trait FeatureTestTrait
35
{
36
    /**
37
     * Sets a RouteCollection that will override
38
     * the application's route collection.
39
     *
40
     * Example routes:
41
     * [
42
     *    ['GET', 'home', 'Home::index'],
43
     * ]
44
     *
45
     * @param array|null $routes Array to set routes
46
     *
47
     * @return $this
48
     */
49
    protected function withRoutes(?array $routes = null)
50
    {
51
        $collection = service('routes');
52

53
        if ($routes !== null) {
54
            $collection->resetRoutes();
55

56
            foreach ($routes as $route) {
57
                if ($route[0] === strtolower($route[0])) {
58
                    @trigger_error(
59
                        'Passing lowercase HTTP method "' . $route[0] . '" is deprecated.'
60
                        . ' Use uppercase HTTP method like "' . strtoupper($route[0]) . '".',
61
                        E_USER_DEPRECATED
62
                    );
63
                }
64

65
                /**
66
                 * @TODO For backward compatibility. Remove strtolower() in the future.
67
                 * @deprecated 4.5.0
68
                 */
69
                $method = strtolower($route[0]);
70

71
                if (isset($route[3])) {
72
                    $collection->{$method}($route[1], $route[2], $route[3]);
73
                } else {
74
                    $collection->{$method}($route[1], $route[2]);
75
                }
76
            }
77
        }
78

79
        $this->routes = $collection;
80

81
        return $this;
82
    }
83

84
    /**
85
     * Sets any values that should exist during this session.
86
     *
87
     * @param array|null $values Array of values, or null to use the current $_SESSION
88
     *
89
     * @return $this
90
     */
91
    public function withSession(?array $values = null)
92
    {
93
        $this->session = $values ?? $_SESSION;
94

95
        return $this;
96
    }
97

98
    /**
99
     * Set request's headers
100
     *
101
     * Example of use
102
     * withHeaders([
103
     *  'Authorization' => 'Token'
104
     * ])
105
     *
106
     * @param array $headers Array of headers
107
     *
108
     * @return $this
109
     */
110
    public function withHeaders(array $headers = [])
111
    {
112
        $this->headers = $headers;
113

114
        return $this;
115
    }
116

117
    /**
118
     * Set the format the request's body should have.
119
     *
120
     * @param string $format The desired format. Currently supported formats: xml, json
121
     *
122
     * @return $this
123
     */
124
    public function withBodyFormat(string $format)
125
    {
126
        $this->bodyFormat = $format;
127

128
        return $this;
129
    }
130

131
    /**
132
     * Set the raw body for the request
133
     *
134
     * @param string $body
135
     *
136
     * @return $this
137
     */
138
    public function withBody($body)
139
    {
140
        $this->requestBody = $body;
141

142
        return $this;
143
    }
144

145
    /**
146
     * Don't run any events while running this test.
147
     *
148
     * @return $this
149
     */
150
    public function skipEvents()
151
    {
152
        Events::simulate(true);
153

154
        return $this;
155
    }
156

157
    /**
158
     * Calls a single URI, executes it, and returns a TestResponse
159
     * instance that can be used to run many assertions against.
160
     *
161
     * @param string $method HTTP verb
162
     *
163
     * @return TestResponse
164
     */
165
    public function call(string $method, string $path, ?array $params = null)
166
    {
167
        if ($method === strtolower($method)) {
168
            @trigger_error(
169
                'Passing lowercase HTTP method "' . $method . '" is deprecated.'
170
                . ' Use uppercase HTTP method like "' . strtoupper($method) . '".',
171
                E_USER_DEPRECATED
172
            );
173
        }
174

175
        /**
176
         * @deprecated 4.5.0
177
         * @TODO remove this in the future.
178
         */
179
        $method = strtoupper($method);
180

181
        // Simulate having a blank session
182
        $_SESSION                  = [];
183
        $_SERVER['REQUEST_METHOD'] = $method;
184

185
        $request = $this->setupRequest($method, $path);
186
        $request = $this->setupHeaders($request);
187
        $name    = strtolower($method);
188
        $request = $this->populateGlobals($name, $request, $params);
189
        $request = $this->setRequestBody($request, $params);
190

191
        // Initialize the RouteCollection
192
        if (! $routes = $this->routes) {
193
            $routes = service('routes')->loadRoutes();
194
        }
195

196
        $routes->setHTTPVerb($method);
197

198
        // Make sure any other classes that might call the request
199
        // instance get the right one.
200
        Services::injectMock('request', $request);
201

202
        // Make sure filters are reset between tests
203
        Services::injectMock('filters', Services::filters(null, false));
204

205
        // Make sure validation is reset between tests
206
        Services::injectMock('validation', Services::validation(null, false));
207

208
        $response = $this->app
209
            ->setContext('web')
210
            ->setRequest($request)
211
            ->run($routes, true);
212

213
        // Reset directory if it has been set
214
        service('router')->setDirectory(null);
215

216
        return new TestResponse($response);
217
    }
218

219
    /**
220
     * Performs a GET request.
221
     *
222
     * @param string $path URI path relative to baseURL. May include query.
223
     *
224
     * @return TestResponse
225
     *
226
     * @throws RedirectException
227
     * @throws Exception
228
     */
229
    public function get(string $path, ?array $params = null)
230
    {
231
        return $this->call(Method::GET, $path, $params);
232
    }
233

234
    /**
235
     * Performs a POST request.
236
     *
237
     * @return TestResponse
238
     *
239
     * @throws RedirectException
240
     * @throws Exception
241
     */
242
    public function post(string $path, ?array $params = null)
243
    {
244
        return $this->call(Method::POST, $path, $params);
245
    }
246

247
    /**
248
     * Performs a PUT request
249
     *
250
     * @return TestResponse
251
     *
252
     * @throws RedirectException
253
     * @throws Exception
254
     */
255
    public function put(string $path, ?array $params = null)
256
    {
257
        return $this->call(Method::PUT, $path, $params);
258
    }
259

260
    /**
261
     * Performss a PATCH request
262
     *
263
     * @return TestResponse
264
     *
265
     * @throws RedirectException
266
     * @throws Exception
267
     */
268
    public function patch(string $path, ?array $params = null)
269
    {
270
        return $this->call(Method::PATCH, $path, $params);
271
    }
272

273
    /**
274
     * Performs a DELETE request.
275
     *
276
     * @return TestResponse
277
     *
278
     * @throws RedirectException
279
     * @throws Exception
280
     */
281
    public function delete(string $path, ?array $params = null)
282
    {
283
        return $this->call(Method::DELETE, $path, $params);
284
    }
285

286
    /**
287
     * Performs an OPTIONS request.
288
     *
289
     * @return TestResponse
290
     *
291
     * @throws RedirectException
292
     * @throws Exception
293
     */
294
    public function options(string $path, ?array $params = null)
295
    {
296
        return $this->call(Method::OPTIONS, $path, $params);
297
    }
298

299
    /**
300
     * Setup a Request object to use so that CodeIgniter
301
     * won't try to auto-populate some of the items.
302
     *
303
     * @param string $method HTTP verb
304
     */
305
    protected function setupRequest(string $method, ?string $path = null): IncomingRequest
306
    {
307
        $config = config(App::class);
308
        $uri    = new SiteURI($config);
309

310
        // $path may have a query in it
311
        $path  = URI::removeDotSegments($path);
312
        $parts = explode('?', $path);
313
        $path  = $parts[0];
314
        $query = $parts[1] ?? '';
315

316
        $superglobals = service('superglobals');
317
        $superglobals->setServer('QUERY_STRING', $query);
318

319
        $uri->setPath($path);
320
        $uri->setQuery($query);
321

322
        Services::injectMock('uri', $uri);
323

324
        $request = Services::incomingrequest($config, false);
325

326
        $request->setMethod($method);
327
        $request->setProtocolVersion('1.1');
328

329
        if ($config->forceGlobalSecureRequests) {
330
            $_SERVER['HTTPS'] = 'test';
331
            $server           = $request->getServer();
332
            $server['HTTPS']  = 'test';
333
            $request->setGlobal('server', $server);
334
        }
335

336
        return $request;
337
    }
338

339
    /**
340
     * Setup the custom request's headers
341
     *
342
     * @return IncomingRequest
343
     */
344
    protected function setupHeaders(IncomingRequest $request)
345
    {
346
        if (! empty($this->headers)) {
347
            foreach ($this->headers as $name => $value) {
348
                $request->setHeader($name, $value);
349
            }
350
        }
351

352
        return $request;
353
    }
354

355
    /**
356
     * Populates the data of our Request with "global" data
357
     * relevant to the request, like $_POST data.
358
     *
359
     * Always populate the GET vars based on the URI.
360
     *
361
     * @param string               $name   Superglobal name (lowercase)
362
     * @param non-empty-array|null $params
363
     *
364
     * @return Request
365
     *
366
     * @throws ReflectionException
367
     */
368
    protected function populateGlobals(string $name, Request $request, ?array $params = null)
369
    {
370
        // $params should set the query vars if present,
371
        // otherwise set it from the URL.
372
        $get = ($params !== null && $params !== [] && $name === 'get')
373
            ? $params
374
            : $this->getPrivateProperty($request->getUri(), 'query');
375

376
        $request->setGlobal('get', $get);
377

378
        if ($name === 'get') {
379
            $request->setGlobal('request', $request->fetchGlobal('get'));
380
        }
381

382
        if ($name === 'post') {
383
            $request->setGlobal($name, $params);
384
            $request->setGlobal(
385
                'request',
386
                $request->fetchGlobal('post') + $request->fetchGlobal('get')
387
            );
388
        }
389

390
        $_SESSION = $this->session ?? [];
391

392
        return $request;
393
    }
394

395
    /**
396
     * Set the request's body formatted according to the value in $this->bodyFormat.
397
     * This allows the body to be formatted in a way that the controller is going to
398
     * expect as in the case of testing a JSON or XML API.
399
     *
400
     * @param array|null $params The parameters to be formatted and put in the body.
401
     */
402
    protected function setRequestBody(Request $request, ?array $params = null): Request
403
    {
404
        if ($this->requestBody !== '') {
405
            $request->setBody($this->requestBody);
406
        }
407

408
        if ($this->bodyFormat !== '') {
409
            $formatMime = '';
410
            if ($this->bodyFormat === 'json') {
411
                $formatMime = 'application/json';
412
            } elseif ($this->bodyFormat === 'xml') {
413
                $formatMime = 'application/xml';
414
            }
415

416
            if ($formatMime !== '') {
417
                $request->setHeader('Content-Type', $formatMime);
418
            }
419

420
            if ($params !== null && $formatMime !== '') {
421
                $formatted = service('format')->getFormatter($formatMime)->format($params);
422
                // "withBodyFormat() and $params of call()" has higher priority than withBody().
423
                $request->setBody($formatted);
424
            }
425
        }
426

427
        return $request;
428
    }
429
}
430

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.