mirror of
https://github.com/codeigniter4/CodeIgniter4.git
synced 2025-02-20 11:44:28 +08:00
Merge remote-tracking branch 'upstream/develop' into 4.4
This commit is contained in:
commit
37449d3d3d
@ -1326,6 +1326,11 @@ class RouteCollection implements RouteCollectionInterface
|
||||
// Build our resulting string, inserting the $params in
|
||||
// the appropriate places.
|
||||
foreach ($matches[0] as $index => $pattern) {
|
||||
if (! isset($params[$index])) {
|
||||
throw new InvalidArgumentException(
|
||||
'Missing argument for "' . $pattern . '" in route "' . $from . '".'
|
||||
);
|
||||
}
|
||||
if (! preg_match('#^' . $pattern . '$#u', $params[$index])) {
|
||||
throw RouterException::forInvalidParameterType();
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ trait FeatureTestTrait
|
||||
/**
|
||||
* Set the raw body for the request
|
||||
*
|
||||
* @param mixed $body
|
||||
* @param string $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
@ -136,6 +136,8 @@ trait FeatureTestTrait
|
||||
* Calls a single URI, executes it, and returns a TestResponse
|
||||
* instance that can be used to run many assertions against.
|
||||
*
|
||||
* @param string $method HTTP verb
|
||||
*
|
||||
* @return TestResponse
|
||||
*/
|
||||
public function call(string $method, string $path, ?array $params = null)
|
||||
@ -147,7 +149,7 @@ trait FeatureTestTrait
|
||||
$request = $this->setupRequest($method, $path);
|
||||
$request = $this->setupHeaders($request);
|
||||
$request = $this->populateGlobals($method, $request, $params);
|
||||
$request = $this->setRequestBody($request);
|
||||
$request = $this->setRequestBody($request, $params);
|
||||
|
||||
// Initialize the RouteCollection
|
||||
if (! $routes = $this->routes) {
|
||||
@ -258,6 +260,8 @@ trait FeatureTestTrait
|
||||
/**
|
||||
* Setup a Request object to use so that CodeIgniter
|
||||
* won't try to auto-populate some of the items.
|
||||
*
|
||||
* @param string $method HTTP verb
|
||||
*/
|
||||
protected function setupRequest(string $method, ?string $path = null): IncomingRequest
|
||||
{
|
||||
@ -302,6 +306,8 @@ trait FeatureTestTrait
|
||||
*
|
||||
* Always populate the GET vars based on the URI.
|
||||
*
|
||||
* @param string $method HTTP verb
|
||||
*
|
||||
* @return Request
|
||||
*
|
||||
* @throws ReflectionException
|
||||
@ -310,16 +316,23 @@ trait FeatureTestTrait
|
||||
{
|
||||
// $params should set the query vars if present,
|
||||
// otherwise set it from the URL.
|
||||
$get = ! empty($params) && $method === 'get'
|
||||
$get = (! empty($params) && $method === 'get')
|
||||
? $params
|
||||
: $this->getPrivateProperty($request->getUri(), 'query');
|
||||
|
||||
$request->setGlobal('get', $get);
|
||||
if ($method !== 'get') {
|
||||
$request->setGlobal($method, $params);
|
||||
|
||||
if ($method === 'get') {
|
||||
$request->setGlobal('request', $request->fetchGlobal('get'));
|
||||
}
|
||||
|
||||
$request->setGlobal('request', $params);
|
||||
if ($method === 'post') {
|
||||
$request->setGlobal($method, $params);
|
||||
$request->setGlobal(
|
||||
'request',
|
||||
$request->fetchGlobal('post') + $request->fetchGlobal('get')
|
||||
);
|
||||
}
|
||||
|
||||
$_SESSION = $this->session ?? [];
|
||||
|
||||
@ -331,32 +344,31 @@ trait FeatureTestTrait
|
||||
* This allows the body to be formatted in a way that the controller is going to
|
||||
* expect as in the case of testing a JSON or XML API.
|
||||
*
|
||||
* @param array|null $params The parameters to be formatted and put in the body. If this is empty, it will get the
|
||||
* what has been loaded into the request global of the request class.
|
||||
* @param array|null $params The parameters to be formatted and put in the body.
|
||||
*/
|
||||
protected function setRequestBody(Request $request, ?array $params = null): Request
|
||||
{
|
||||
if (isset($this->requestBody) && $this->requestBody !== '') {
|
||||
if ($this->requestBody !== '') {
|
||||
$request->setBody($this->requestBody);
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
if (isset($this->bodyFormat) && $this->bodyFormat !== '') {
|
||||
if (empty($params)) {
|
||||
$params = $request->fetchGlobal('request');
|
||||
}
|
||||
if ($this->bodyFormat !== '') {
|
||||
$formatMime = '';
|
||||
if ($this->bodyFormat === 'json') {
|
||||
$formatMime = 'application/json';
|
||||
} elseif ($this->bodyFormat === 'xml') {
|
||||
$formatMime = 'application/xml';
|
||||
}
|
||||
if (! empty($formatMime) && ! empty($params)) {
|
||||
$formatted = Services::format()->getFormatter($formatMime)->format($params);
|
||||
$request->setBody($formatted);
|
||||
|
||||
if ($formatMime !== '') {
|
||||
$request->setHeader('Content-Type', $formatMime);
|
||||
}
|
||||
|
||||
if ($params !== null && $formatMime !== '') {
|
||||
$formatted = Services::format()->getFormatter($formatMime)->format($params);
|
||||
// "withBodyFormat() and $params of call()" has higher priority than withBody().
|
||||
$request->setBody($formatted);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
|
@ -17,6 +17,7 @@ use CodeIgniter\HTTP\URI;
|
||||
use CodeIgniter\Router\Exceptions\RouterException;
|
||||
use CodeIgniter\Test\CIUnitTestCase;
|
||||
use Config\App;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @backupGlobals enabled
|
||||
@ -900,4 +901,20 @@ final class MiscUrlTest extends CIUnitTestCase
|
||||
url_to('path-to', 'string', 13, 'en')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/codeigniter4/CodeIgniter4/issues/7651
|
||||
*/
|
||||
public function testUrlToMissingArgument()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Missing argument for "([a-zA-Z]+)" in route "([a-zA-Z]+)/login".');
|
||||
|
||||
$routes = Services::routes();
|
||||
$routes->group('(:alpha)', static function ($routes) {
|
||||
$routes->match(['get'], 'login', 'Common\LoginController::loginView', ['as' => 'loginURL']);
|
||||
});
|
||||
|
||||
url_to('loginURL');
|
||||
}
|
||||
}
|
||||
|
@ -376,6 +376,162 @@ final class FeatureTestTraitTest extends CIUnitTestCase
|
||||
$this->assertTrue($response->isOK());
|
||||
}
|
||||
|
||||
public function testCallGetWithParams()
|
||||
{
|
||||
$this->withRoutes([
|
||||
[
|
||||
'get',
|
||||
'home',
|
||||
static fn () => json_encode(Services::request()->getGet()),
|
||||
],
|
||||
]);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->get('home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$this->assertStringContainsString(
|
||||
// All GET values will be strings.
|
||||
'{"true":"1","false":"","int":"2","null":"","float":"1.23","string":"foo"}',
|
||||
$response->getBody()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallGetWithParamsAndREQUEST()
|
||||
{
|
||||
$this->withRoutes([
|
||||
[
|
||||
'get',
|
||||
'home',
|
||||
static fn () => json_encode(Services::request()->fetchGlobal('request')),
|
||||
],
|
||||
]);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->get('home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$this->assertStringContainsString(
|
||||
// All GET values will be strings.
|
||||
'{"true":"1","false":"","int":"2","null":"","float":"1.23","string":"foo"}',
|
||||
$response->getBody()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallPostWithParams()
|
||||
{
|
||||
$this->withRoutes([
|
||||
[
|
||||
'post',
|
||||
'home',
|
||||
static fn () => json_encode(Services::request()->getPost()),
|
||||
],
|
||||
]);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->post('home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$this->assertStringContainsString(
|
||||
// All POST values will be strings.
|
||||
'{"true":"1","false":"","int":"2","null":"","float":"1.23","string":"foo"}',
|
||||
$response->getBody()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallPostWithParamsAndREQUEST()
|
||||
{
|
||||
$this->withRoutes([
|
||||
[
|
||||
'post',
|
||||
'home',
|
||||
static fn () => json_encode(Services::request()->fetchGlobal('request')),
|
||||
],
|
||||
]);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->post('home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$this->assertStringContainsString(
|
||||
// All POST values will be strings.
|
||||
'{"true":"1","false":"","int":"2","null":"","float":"1.23","string":"foo"}',
|
||||
$response->getBody()
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallPutWithJsonRequest()
|
||||
{
|
||||
$this->withRoutes([
|
||||
[
|
||||
'put',
|
||||
'home',
|
||||
'\Tests\Support\Controllers\Popcorn::echoJson',
|
||||
],
|
||||
]);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->withBodyFormat('json')
|
||||
->call('put', 'home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$response->assertJSONExact($data);
|
||||
}
|
||||
|
||||
public function testCallPutWithJsonRequestAndREQUEST()
|
||||
{
|
||||
$this->withRoutes([
|
||||
[
|
||||
'put',
|
||||
'home',
|
||||
static fn () => json_encode(Services::request()->fetchGlobal('request')),
|
||||
],
|
||||
]);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->withBodyFormat('json')
|
||||
->call('put', 'home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$this->assertStringContainsString('[]', $response->getBody());
|
||||
}
|
||||
|
||||
public function testCallWithJsonRequest()
|
||||
{
|
||||
$this->withRoutes([
|
||||
@ -385,9 +541,19 @@ final class FeatureTestTraitTest extends CIUnitTestCase
|
||||
'\Tests\Support\Controllers\Popcorn::echoJson',
|
||||
],
|
||||
]);
|
||||
$response = $this->withBodyFormat('json')->call('post', 'home', ['foo' => 'bar']);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$response = $this->withBodyFormat('json')
|
||||
->call('post', 'home', $data);
|
||||
|
||||
$response->assertOK();
|
||||
$response->assertJSONExact(['foo' => 'bar']);
|
||||
$response->assertJSONExact($data);
|
||||
}
|
||||
|
||||
public function testSetupRequestBodyWithParams()
|
||||
@ -400,14 +566,39 @@ final class FeatureTestTraitTest extends CIUnitTestCase
|
||||
$this->assertSame('application/json', $request->header('Content-Type')->getValue());
|
||||
}
|
||||
|
||||
public function testSetupJSONRequestBodyWithBody()
|
||||
{
|
||||
$request = $this->setupRequest('post', 'home');
|
||||
$request = $this->withBodyFormat('json')
|
||||
->withBody(json_encode(['foo1' => 'bar1']))
|
||||
->setRequestBody($request);
|
||||
|
||||
$this->assertJsonStringEqualsJsonString(
|
||||
json_encode(['foo1' => 'bar1']),
|
||||
$request->getBody()
|
||||
);
|
||||
$this->assertSame(
|
||||
'application/json',
|
||||
$request->header('Content-Type')->getValue()
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetupRequestBodyWithXml()
|
||||
{
|
||||
$request = $this->setupRequest('post', 'home');
|
||||
|
||||
$request = $this->withBodyFormat('xml')->setRequestBody($request, ['foo' => 'bar']);
|
||||
$data = [
|
||||
'true' => true,
|
||||
'false' => false,
|
||||
'int' => 2,
|
||||
'null' => null,
|
||||
'float' => 1.23,
|
||||
'string' => 'foo',
|
||||
];
|
||||
$request = $this->withBodyFormat('xml')->setRequestBody($request, $data);
|
||||
|
||||
$expectedXml = '<?xml version="1.0"?>
|
||||
<response><foo>bar</foo></response>
|
||||
<response><true>1</true><false/><int>2</int><null/><float>1.23</float><string>foo</string></response>
|
||||
';
|
||||
|
||||
$this->assertSame($expectedXml, $request->getBody());
|
||||
|
@ -12,6 +12,10 @@ Release Date: Unreleased
|
||||
BREAKING
|
||||
********
|
||||
|
||||
- **FeatureTestTrait:** When using :ref:`withBodyFormat() <feature-formatting-the-request>`,
|
||||
the priority of the request body has been changed.
|
||||
See :ref:`Upgrading Guide <upgrade-437-feature-testing>` for details.
|
||||
|
||||
Message Changes
|
||||
***************
|
||||
|
||||
|
@ -18,6 +18,27 @@ Mandatory File Changes
|
||||
Breaking Changes
|
||||
****************
|
||||
|
||||
.. _upgrade-437-feature-testing:
|
||||
|
||||
Feature Testing Request Body
|
||||
============================
|
||||
|
||||
If you call:
|
||||
|
||||
1. :ref:`withBody() <feature-setting-the-body>`
|
||||
2. and :ref:`withBodyFormat() <feature-formatting-the-request>`
|
||||
3. and pass the ``$params`` to :ref:`call() <feature-requesting-a-page>` (or shorthand methods)
|
||||
|
||||
the priority for a Request body has been changed. In the unlikely event that you
|
||||
have test code affected by this change, modify it.
|
||||
|
||||
For example, now the ``$params`` is used to build the request body, and the ``$body``
|
||||
is not used::
|
||||
|
||||
$this->withBody($body)->withBodyFormat('json')->call('post', $params)
|
||||
|
||||
Previously, the ``$body`` was used for the request body.
|
||||
|
||||
Breaking Enhancements
|
||||
*********************
|
||||
|
||||
|
@ -21,23 +21,34 @@ are called if you implement your own methods.
|
||||
|
||||
.. literalinclude:: feature/001.php
|
||||
|
||||
Requesting A Page
|
||||
.. _feature-requesting-a-page:
|
||||
|
||||
Requesting a Page
|
||||
=================
|
||||
|
||||
Essentially, feature tests simply allows you to call an endpoint on your application and get the results back.
|
||||
to do this, you use the ``call()`` method. The first parameter is the HTTP method to use (most frequently either GET or POST).
|
||||
The second parameter is the path on your site to test. The third parameter accepts an array that is used to populate the
|
||||
superglobal variables for the HTTP verb you are using. So, a method of **GET** would have the **$_GET** variable
|
||||
populated, while a **post** request would have the **$_POST** array populated.
|
||||
To do this, you use the ``call()`` method.
|
||||
|
||||
1. The first parameter is the HTTP method to use (most frequently either GET or POST).
|
||||
2. The second parameter is the URI path on your site to test.
|
||||
3. The third parameter ``$params`` accepts an array that is used to populate the
|
||||
superglobal variables for the HTTP verb you are using. So, a method of **GET**
|
||||
would have the **$_GET** variable populated, while a **POST** request would
|
||||
have the **$_POST** array populated. The ``$params`` is also used in
|
||||
:ref:`feature-formatting-the-request`.
|
||||
|
||||
.. note:: The ``$params`` array does not make sense for every HTTP verb, but is
|
||||
included for consistency.
|
||||
|
||||
.. literalinclude:: feature/002.php
|
||||
|
||||
Shorthand Methods
|
||||
-----------------
|
||||
|
||||
Shorthand methods for each of the HTTP verbs exist to ease typing and make things clearer:
|
||||
|
||||
.. literalinclude:: feature/003.php
|
||||
|
||||
.. note:: The ``$params`` array does not make sense for every HTTP verb, but is included for consistency.
|
||||
|
||||
Setting Different Routes
|
||||
------------------------
|
||||
|
||||
@ -74,22 +85,32 @@ to send out emails. You can tell the system to skip any event handling with the
|
||||
|
||||
.. literalinclude:: feature/007.php
|
||||
|
||||
Formatting The Request
|
||||
.. _feature-formatting-the-request:
|
||||
|
||||
Formatting the Request
|
||||
-----------------------
|
||||
|
||||
You can set the format of your request's body using the ``withBodyFormat()`` method. Currently this supports either
|
||||
`json` or `xml`. This will take the parameters passed into ``call()``, ``post()``, ``get()``... and assign them to the
|
||||
body of the request in the given format. This will also set the `Content-Type` header for your request accordingly.
|
||||
``json`` or ``xml``.
|
||||
This is useful when testing JSON or XML APIs so that you can set the request in the form that the controller will expect.
|
||||
|
||||
This will take the parameters passed into ``call()``, ``post()``, ``get()``... and assign them to the
|
||||
body of the request in the given format.
|
||||
|
||||
This will also set the `Content-Type` header for your request accordingly.
|
||||
|
||||
.. literalinclude:: feature/008.php
|
||||
|
||||
.. _feature-setting-the-body:
|
||||
|
||||
Setting the Body
|
||||
----------------
|
||||
|
||||
You can set the body of your request with the ``withBody()`` method. This allows you to format the body how you want
|
||||
to format it. It is recommended that you use this if you have more complicated XMLs to test. This will also not set
|
||||
the Content-Type header for you so if you need that, you can set it with the ``withHeaders()`` method.
|
||||
to format it. It is recommended that you use this if you have more complicated XMLs to test.
|
||||
|
||||
This will not set
|
||||
the `Content-Type` header for you. If you need that, you can set it with the ``withHeaders()`` method.
|
||||
|
||||
Checking the Response
|
||||
=====================
|
||||
|
Loading…
x
Reference in New Issue
Block a user