2020-05-25 19:14:26 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\Tests\Rest\Handler;
|
|
|
|
|
|
2023-10-30 16:04:41 +00:00
|
|
|
use GuzzleHttp\Psr7\Uri;
|
2024-04-05 18:45:21 +00:00
|
|
|
use MediaWiki\ParamValidator\TypeDef\ArrayDef;
|
2020-05-25 19:14:26 +00:00
|
|
|
use MediaWiki\Rest\ConditionalHeaderUtil;
|
|
|
|
|
use MediaWiki\Rest\Handler;
|
2022-05-04 16:29:51 +00:00
|
|
|
use MediaWiki\Rest\HttpException;
|
2020-05-25 19:14:26 +00:00
|
|
|
use MediaWiki\Rest\LocalizedHttpException;
|
2024-04-05 18:45:21 +00:00
|
|
|
use MediaWiki\Rest\Module\Module;
|
2020-05-25 19:14:26 +00:00
|
|
|
use MediaWiki\Rest\RequestData;
|
2024-03-19 14:56:20 +00:00
|
|
|
use MediaWiki\Rest\RequestInterface;
|
2022-07-01 13:21:34 +00:00
|
|
|
use MediaWiki\Rest\Response;
|
2020-05-25 19:14:26 +00:00
|
|
|
use MediaWiki\Rest\ResponseFactory;
|
|
|
|
|
use MediaWiki\Rest\ResponseInterface;
|
|
|
|
|
use MediaWiki\Rest\Router;
|
|
|
|
|
use MediaWiki\Rest\Validator\BodyValidator;
|
2022-11-28 14:22:40 +00:00
|
|
|
use MediaWiki\Session\Session;
|
2024-02-18 21:31:02 +00:00
|
|
|
use MediaWikiUnitTestCase;
|
2022-05-04 16:29:51 +00:00
|
|
|
use PHPUnit\Framework\Assert;
|
2020-05-25 19:14:26 +00:00
|
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
2022-05-04 16:29:51 +00:00
|
|
|
use Wikimedia\Message\MessageValue;
|
2020-05-25 19:14:26 +00:00
|
|
|
use Wikimedia\ParamValidator\ParamValidator;
|
|
|
|
|
use Wikimedia\TestingAccessWrapper;
|
2022-07-01 13:21:34 +00:00
|
|
|
use Wikimedia\Timestamp\ConvertibleTimestamp;
|
2020-05-25 19:14:26 +00:00
|
|
|
|
|
|
|
|
/**
|
2024-05-07 19:00:54 +00:00
|
|
|
* @covers \MediaWiki\Rest\Handler
|
2020-05-25 19:14:26 +00:00
|
|
|
*/
|
2024-02-18 21:31:02 +00:00
|
|
|
class HandlerTest extends MediaWikiUnitTestCase {
|
2020-05-25 19:14:26 +00:00
|
|
|
|
|
|
|
|
use HandlerTestTrait;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string[] $methods
|
|
|
|
|
*
|
2024-05-07 19:00:54 +00:00
|
|
|
* @return Handler&MockObject
|
2020-05-25 19:14:26 +00:00
|
|
|
*/
|
|
|
|
|
private function newHandler( $methods = [] ) {
|
|
|
|
|
$methods = array_merge( $methods, [ 'execute' ] );
|
2024-05-07 19:00:54 +00:00
|
|
|
/** @var Handler&MockObject $handler */
|
2020-05-25 19:14:26 +00:00
|
|
|
$handler = $this->getMockBuilder( Handler::class )
|
|
|
|
|
->onlyMethods( $methods )
|
|
|
|
|
->getMock();
|
|
|
|
|
$handler->method( 'execute' )->willReturn( (object)[] );
|
|
|
|
|
|
|
|
|
|
return $handler;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 16:04:41 +00:00
|
|
|
private function initHandlerPartially( Handler $handler ) {
|
|
|
|
|
$formatter = $this->getDummyTextFormatter( true );
|
|
|
|
|
$responseFactory = new ResponseFactory( [ 'qqx' => $formatter ] );
|
|
|
|
|
|
|
|
|
|
$router = $this->newRouter();
|
|
|
|
|
$module = $this->newModule( [ 'router' => $router ] );
|
|
|
|
|
|
|
|
|
|
$authority = $this->mockAnonUltimateAuthority();
|
|
|
|
|
$hookContainer = $this->createHookContainer();
|
|
|
|
|
|
|
|
|
|
$session = $this->getSession( true );
|
2024-02-21 20:51:29 +00:00
|
|
|
$handler->initContext( $module, 'test', [] );
|
2023-10-30 16:04:41 +00:00
|
|
|
$handler->initServices( $authority, $responseFactory, $hookContainer );
|
|
|
|
|
$handler->initSession( $session );
|
|
|
|
|
|
|
|
|
|
return $handler;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-25 19:14:26 +00:00
|
|
|
public function testGetRouter() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$this->initHandler( $handler, new RequestData() );
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertInstanceOf( Router::class, $handler->getRouter() );
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-23 11:36:19 +00:00
|
|
|
public static function provideGetRouteUrl() {
|
2020-05-25 18:12:33 +00:00
|
|
|
yield 'empty' => [
|
|
|
|
|
'/test',
|
|
|
|
|
[],
|
|
|
|
|
[],
|
|
|
|
|
'/test'
|
|
|
|
|
];
|
|
|
|
|
yield 'path params' => [
|
|
|
|
|
'/test/{foo}/{bar}',
|
|
|
|
|
[ 'foo' => 'Kittens', 'bar' => 'mew' ],
|
|
|
|
|
[],
|
|
|
|
|
'/test/Kittens/mew'
|
|
|
|
|
];
|
|
|
|
|
yield 'missing path params' => [
|
|
|
|
|
'/test/{foo}/{bar}',
|
|
|
|
|
[ 'bar' => 'mew' ],
|
|
|
|
|
[],
|
|
|
|
|
'/test/{foo}/mew'
|
|
|
|
|
];
|
|
|
|
|
yield 'path param encoding' => [
|
|
|
|
|
'/test/{foo}',
|
|
|
|
|
[ 'foo' => 'ä/+/&/?/{}/#/%' ],
|
|
|
|
|
[],
|
|
|
|
|
'/test/%C3%A4%2F%2B%2F%26%2F%3F%2F%7B%7D%2F%23%2F%25'
|
|
|
|
|
];
|
|
|
|
|
yield 'recursive path params' => [
|
|
|
|
|
'/test/{foo}/{bar}',
|
|
|
|
|
[ 'foo' => '{bar}', 'bar' => 'mew' ],
|
|
|
|
|
[],
|
|
|
|
|
'/test/%7Bbar%7D/mew'
|
|
|
|
|
];
|
|
|
|
|
yield 'query params' => [
|
|
|
|
|
'/test',
|
|
|
|
|
[],
|
|
|
|
|
[ 'foo' => 'Kittens', 'bar' => 'mew' ],
|
|
|
|
|
'/test?foo=Kittens&bar=mew'
|
|
|
|
|
];
|
|
|
|
|
yield 'query param encoding' => [
|
|
|
|
|
'/test',
|
|
|
|
|
[],
|
|
|
|
|
[ 'foo' => 'ä/+/&/?/{}/#/%' ],
|
|
|
|
|
'/test?foo=%C3%A4%2F%2B%2F%26%2F%3F%2F%7B%7D%2F%23%2F%25'
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideGetRouteUrl
|
|
|
|
|
*
|
|
|
|
|
* @param string $path
|
|
|
|
|
* @param string[] $pathParams
|
|
|
|
|
* @param string[] $queryParams
|
|
|
|
|
* @param string $expected
|
|
|
|
|
*/
|
|
|
|
|
public function testGetRouteUrl( $path, $pathParams, $queryParams, $expected ) {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$request = new RequestData();
|
|
|
|
|
$this->initHandler( $handler, $request, [ 'path' => $path ] );
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$url = $handler->getRouteUrl( $pathParams, $queryParams );
|
|
|
|
|
$this->assertStringEndsWith( $expected, $url );
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-13 21:00:59 +00:00
|
|
|
public function testGetPath() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$request = new RequestData();
|
|
|
|
|
$this->initHandler( $handler, $request, [ 'path' => 'just/some/path' ] );
|
|
|
|
|
$this->assertSame( 'just/some/path', $handler->getPath() );
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-24 17:54:55 +00:00
|
|
|
public function testSupportedPathparams() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$request = new RequestData();
|
|
|
|
|
$this->initHandler( $handler, $request, [ 'path' => 'some/path/{foo}/{bar}' ] );
|
|
|
|
|
$this->assertSame( [ 'foo', 'bar' ], $handler->getSupportedPathParams() );
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-25 19:14:26 +00:00
|
|
|
public function testGetResponseFactory() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$this->initHandler( $handler, new RequestData() );
|
|
|
|
|
|
|
|
|
|
$this->assertInstanceOf( ResponseFactory::class, $handler->getResponseFactory() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetConditionalHeaderUtil() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$this->initHandler( $handler, new RequestData() );
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertInstanceOf( ConditionalHeaderUtil::class, $handler->getConditionalHeaderUtil() );
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-23 11:36:19 +00:00
|
|
|
public static function provideCheckPreconditions() {
|
2020-05-25 19:14:26 +00:00
|
|
|
yield 'no status' => [ null ];
|
|
|
|
|
yield 'a status' => [ 444 ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideCheckPreconditions
|
|
|
|
|
*/
|
|
|
|
|
public function testCheckPreconditions( $status ) {
|
|
|
|
|
$request = new RequestData();
|
|
|
|
|
|
|
|
|
|
$util = $this->createNoOpMock( ConditionalHeaderUtil::class, [ 'checkPreconditions' ] );
|
|
|
|
|
$util->method( 'checkPreconditions' )->with( $request )->willReturn( $status );
|
|
|
|
|
|
|
|
|
|
$handler = $this->newHandler( [ 'getConditionalHeaderUtil' ] );
|
|
|
|
|
$handler->method( 'getConditionalHeaderUtil' )->willReturn( $util );
|
|
|
|
|
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$resp = $handler->checkPreconditions();
|
|
|
|
|
|
|
|
|
|
$responseStatus = $resp ? $resp->getStatusCode() : null;
|
|
|
|
|
$this->assertSame( $status, $responseStatus );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testApplyConditionalResponseHeaders() {
|
|
|
|
|
$util = $this->createNoOpMock( ConditionalHeaderUtil::class, [ 'applyResponseHeaders' ] );
|
|
|
|
|
$util->method( 'applyResponseHeaders' )->willReturnCallback(
|
2021-02-07 13:10:36 +00:00
|
|
|
static function ( ResponseInterface $response ) {
|
2020-05-25 19:14:26 +00:00
|
|
|
$response->setHeader( 'Testing', 'foo' );
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$handler = $this->newHandler( [ 'getConditionalHeaderUtil' ] );
|
|
|
|
|
$handler->method( 'getConditionalHeaderUtil' )->willReturn( $util );
|
|
|
|
|
|
|
|
|
|
$this->initHandler( $handler, new RequestData() );
|
|
|
|
|
$response = $handler->getResponseFactory()->create();
|
|
|
|
|
$handler->applyConditionalResponseHeaders( $response );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( 'foo', $response->getHeaderLine( 'Testing' ) );
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-23 11:36:19 +00:00
|
|
|
public static function provideValidate() {
|
2024-06-20 13:33:35 +00:00
|
|
|
yield 'empty' => [ [], [], new RequestData(), [], [] ];
|
2020-05-25 19:14:26 +00:00
|
|
|
|
2024-05-07 19:00:54 +00:00
|
|
|
yield 'query parameter' => [
|
2020-05-25 19:14:26 +00:00
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'query',
|
|
|
|
|
]
|
|
|
|
|
],
|
2024-06-20 13:33:35 +00:00
|
|
|
[],
|
2020-05-25 19:14:26 +00:00
|
|
|
new RequestData( [ 'queryParams' => [ 'foo' => 'kittens' ] ] ),
|
2024-05-07 19:00:54 +00:00
|
|
|
[ 'foo' => 'kittens' ],
|
|
|
|
|
[]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'body parameter' => [
|
2024-06-20 13:33:35 +00:00
|
|
|
[],
|
2024-05-07 19:00:54 +00:00
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [ 'parsedBody' => [ 'foo' => 'kittens' ] ] ),
|
|
|
|
|
[],
|
2020-05-25 19:14:26 +00:00
|
|
|
[ 'foo' => 'kittens' ]
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideValidate
|
|
|
|
|
*/
|
2024-06-20 13:33:35 +00:00
|
|
|
public function testValidate( $paramSettings, $bodyParamSettings, $request, $expectedParams, $expectedBody ) {
|
|
|
|
|
$handler = $this->newHandler( [ 'getParamSettings', 'getBodyParamSettings' ] );
|
2024-05-07 19:00:54 +00:00
|
|
|
$handler->method( 'getParamSettings' )->willReturn( $paramSettings );
|
2024-06-20 13:33:35 +00:00
|
|
|
$handler->method( 'getBodyParamSettings' )->willReturn( $bodyParamSettings );
|
2024-05-07 19:00:54 +00:00
|
|
|
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $expectedParams, $handler->getValidatedParams() );
|
|
|
|
|
$this->assertSame( $expectedBody, $handler->getValidatedBody() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testValidate_post() {
|
|
|
|
|
$this->expectDeprecationAndContinue( '/The "post" source is deprecated/' );
|
|
|
|
|
|
|
|
|
|
$paramSettings = [
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'post',
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$request = new RequestData( [ 'postParams' => [ 'foo' => 'kittens' ] ] );
|
|
|
|
|
|
2020-05-25 19:14:26 +00:00
|
|
|
$handler = $this->newHandler( [ 'getParamSettings' ] );
|
|
|
|
|
$handler->method( 'getParamSettings' )->willReturn( $paramSettings );
|
|
|
|
|
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
|
|
|
|
|
$params = $handler->getValidatedParams();
|
2024-05-07 19:00:54 +00:00
|
|
|
$this->assertSame( [ 'foo' => 'kittens' ], $params );
|
2020-05-25 19:14:26 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-26 18:25:16 +00:00
|
|
|
public static function provideValidate_invalid() {
|
|
|
|
|
yield 'missing required' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'query',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
[ 'queryParams' => [ 'bar' => 'kittens' ] ],
|
|
|
|
|
[
|
|
|
|
|
'error' => 'parameter-validation-failed',
|
|
|
|
|
'failureCode' => 'missingparam'
|
2020-05-25 19:14:26 +00:00
|
|
|
]
|
|
|
|
|
];
|
2024-06-26 18:25:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideValidate_invalid
|
|
|
|
|
*/
|
|
|
|
|
public function testValidate_invalid( $paramSettings, $requestData, $expectedError ) {
|
|
|
|
|
$request = new RequestData( $requestData );
|
2020-05-25 19:14:26 +00:00
|
|
|
|
|
|
|
|
$handler = $this->newHandler( [ 'getParamSettings' ] );
|
|
|
|
|
$handler->method( 'getParamSettings' )->willReturn( $paramSettings );
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
$this->fail( 'Expected LocalizedHttpException' );
|
|
|
|
|
} catch ( LocalizedHttpException $ex ) {
|
2024-05-06 11:30:33 +00:00
|
|
|
$data = $ex->getErrorData();
|
2024-06-26 18:25:16 +00:00
|
|
|
|
|
|
|
|
foreach ( $expectedError as $field => $value ) {
|
|
|
|
|
$this->assertSame( $value, $data[$field] ?? null );
|
|
|
|
|
}
|
2020-05-25 19:14:26 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 14:56:20 +00:00
|
|
|
public static function provideValidateBodyParams() {
|
|
|
|
|
yield 'no body parameter' => [
|
|
|
|
|
[
|
|
|
|
|
'pathfoo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [ 'parsedBody' => [ 'foo' => 'kittens' ] ] ),
|
|
|
|
|
[] // extra parameter should be ignored if no body param is defined
|
|
|
|
|
];
|
2020-05-25 19:14:26 +00:00
|
|
|
|
2024-03-19 14:56:20 +00:00
|
|
|
yield 'parameter' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
2024-06-20 13:33:35 +00:00
|
|
|
]
|
2024-03-19 14:56:20 +00:00
|
|
|
],
|
|
|
|
|
new RequestData( [ 'parsedBody' => [ 'foo' => 'kittens' ] ] ),
|
|
|
|
|
[ 'foo' => 'kittens' ]
|
|
|
|
|
];
|
2024-06-20 13:33:35 +00:00
|
|
|
|
|
|
|
|
yield 'body parameter with type coercion' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [
|
|
|
|
|
// Form data automatically enabled type coercion
|
|
|
|
|
'Content-Type' => RequestInterface::FORM_URLENCODED_CONTENT_TYPE
|
|
|
|
|
],
|
|
|
|
|
'parsedBody' => [ 'foo' => '1234' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'foo' => 1234 ]
|
|
|
|
|
];
|
2024-07-12 20:37:08 +00:00
|
|
|
|
|
|
|
|
yield 'multivalue string in form data' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [
|
|
|
|
|
'Content-Type' => RequestInterface::FORM_URLENCODED_CONTENT_TYPE
|
|
|
|
|
],
|
|
|
|
|
'parsedBody' => [ 'foo' => 'x|y|z' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'foo' => [ 'x', 'y', 'z' ] ]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'multivalue string in JSON' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [
|
|
|
|
|
'Content-Type' => RequestInterface::JSON_CONTENT_TYPE
|
|
|
|
|
],
|
|
|
|
|
'parsedBody' => [ 'foo' => [ 'x', 'y', 'z' ] ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'foo' => [ 'x', 'y', 'z' ] ]
|
|
|
|
|
];
|
2024-03-19 14:56:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideValidateBodyParams
|
|
|
|
|
*/
|
|
|
|
|
public function testValidateBodyParams( $paramSettings, $request, $expected ) {
|
2024-06-20 13:33:35 +00:00
|
|
|
$handler = $this->newHandler( [ 'getBodyParamSettings' ] );
|
|
|
|
|
$handler->method( 'getBodyParamSettings' )->willReturn( $paramSettings );
|
2024-03-19 14:56:20 +00:00
|
|
|
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
|
|
|
|
|
$params = $handler->getValidatedBody();
|
|
|
|
|
$this->assertSame( $expected, $params );
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-25 17:39:25 +00:00
|
|
|
public function testGetBodyParamSettings() {
|
2024-06-20 13:33:35 +00:00
|
|
|
$bodyParamSettings = [
|
2024-04-25 17:39:25 +00:00
|
|
|
'bodyfoo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
2024-06-20 13:33:35 +00:00
|
|
|
]
|
2024-04-25 17:39:25 +00:00
|
|
|
];
|
2024-06-20 13:33:35 +00:00
|
|
|
$handler = $this->newHandler( [ 'getBodyParamSettings' ] );
|
|
|
|
|
$handler->method( 'getBodyParamSettings' )->willReturn( $bodyParamSettings );
|
2024-04-25 17:39:25 +00:00
|
|
|
|
2024-06-20 13:33:35 +00:00
|
|
|
$bodyParams = $handler->getBodyParamSettings();
|
2024-04-25 17:39:25 +00:00
|
|
|
|
|
|
|
|
$expected = [
|
2024-06-20 13:33:35 +00:00
|
|
|
'bodyfoo' => $bodyParamSettings['bodyfoo']
|
2024-04-25 17:39:25 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $expected, $bodyParamSettings );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testOverrideGetBodyParamSettings() {
|
|
|
|
|
$paramSettings =
|
|
|
|
|
|
|
|
|
|
$handler = new class() extends Handler {
|
|
|
|
|
|
|
|
|
|
public function execute() {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getBodyParamSettings(): array {
|
|
|
|
|
return [
|
|
|
|
|
'x' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
],
|
|
|
|
|
'y' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getParamSettings(): array {
|
|
|
|
|
return [
|
|
|
|
|
'pathfoo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_DEFAULT => 'foo',
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
],
|
|
|
|
|
'bodyfoo' => [ // should not be used for validation
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_DEFAULT => 'foo',
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
],
|
|
|
|
|
'queryfoo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_DEFAULT => 'foo',
|
|
|
|
|
Handler::PARAM_SOURCE => 'query',
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'parsedBody' => [
|
|
|
|
|
'x' => 'test 1',
|
|
|
|
|
'y' => 'test 2',
|
|
|
|
|
],
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
|
|
|
|
|
// The validated body should contain x and y, but not bodyfoo
|
|
|
|
|
$expectedBody = [
|
|
|
|
|
'x' => 'test 1',
|
|
|
|
|
'y' => 'test 2',
|
|
|
|
|
];
|
|
|
|
|
$body = $handler->getValidatedBody();
|
|
|
|
|
$this->assertSame( $expectedBody, $body );
|
|
|
|
|
|
|
|
|
|
// The validated params should contain pathfoo and queryfoo, but not bodyfoo
|
|
|
|
|
$expectedParams = [
|
|
|
|
|
'pathfoo' => 'foo',
|
|
|
|
|
'queryfoo' => 'foo',
|
|
|
|
|
];
|
|
|
|
|
$params = $handler->getValidatedParams();
|
|
|
|
|
$this->assertSame( $expectedParams, $params );
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 14:56:20 +00:00
|
|
|
public function provideValidateBodyParams_invalid() {
|
2024-05-06 11:30:33 +00:00
|
|
|
$paramDefintions = [
|
2024-03-19 14:56:20 +00:00
|
|
|
'foo' => [
|
2024-06-26 18:25:16 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'timestamp',
|
2024-03-19 14:56:20 +00:00
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
|
2024-05-06 11:30:33 +00:00
|
|
|
yield 'missing param' => [
|
|
|
|
|
$paramDefintions,
|
|
|
|
|
new RequestData( [ 'parsedBody' => [], ] ),
|
|
|
|
|
'missingparam'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'extra param' => [
|
|
|
|
|
$paramDefintions,
|
|
|
|
|
new RequestData(
|
|
|
|
|
[
|
|
|
|
|
'parsedBody' => [
|
|
|
|
|
'foo' => 23,
|
|
|
|
|
'xyzzy' => 'lizzards',
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
'extraneous-body-fields'
|
|
|
|
|
];
|
2024-03-19 14:56:20 +00:00
|
|
|
|
2024-05-06 11:30:33 +00:00
|
|
|
yield 'bad param' => [
|
|
|
|
|
$paramDefintions,
|
|
|
|
|
new RequestData(
|
|
|
|
|
[
|
|
|
|
|
'parsedBody' => [
|
|
|
|
|
'foo' => 'kittens',
|
|
|
|
|
],
|
|
|
|
|
]
|
|
|
|
|
),
|
2024-06-26 18:25:16 +00:00
|
|
|
'badtimestamp'
|
2024-05-06 11:30:33 +00:00
|
|
|
];
|
2024-06-20 13:33:35 +00:00
|
|
|
|
|
|
|
|
yield 'body parameter without type coercion' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [
|
|
|
|
|
// JSON doesn't enable type coercion
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json', ],
|
|
|
|
|
'parsedBody' => [ 'foo' => '1234' ]
|
|
|
|
|
] ),
|
|
|
|
|
'badinteger-type'
|
|
|
|
|
];
|
2024-07-12 20:37:08 +00:00
|
|
|
|
|
|
|
|
yield 'multivalue body parameter as string in JSON' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json', ],
|
|
|
|
|
'parsedBody' => [ 'foo' => 'x|y|z' ]
|
|
|
|
|
] ),
|
|
|
|
|
'multivalue-must-be-array'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'multivalue integer as array of strings in JSON' => [
|
|
|
|
|
[
|
|
|
|
|
'foo' => [
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
ParamValidator::PARAM_ISMULTI => true,
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
],
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json' ],
|
|
|
|
|
'parsedBody' => [ 'foo' => [ '1', '2', '3' ] ]
|
|
|
|
|
] ),
|
|
|
|
|
'badinteger-type'
|
|
|
|
|
];
|
2024-05-06 11:30:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideValidateBodyParams_invalid
|
|
|
|
|
*/
|
|
|
|
|
public function testValidateBodyParams_invalid( $paramSettings, $request, $expectedError ) {
|
2024-06-20 13:33:35 +00:00
|
|
|
$handler = $this->newHandler( [ 'getBodyParamSettings' ] );
|
|
|
|
|
$handler->method( 'getBodyParamSettings' )->willReturn( $paramSettings );
|
2024-03-19 14:56:20 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
$this->fail( 'Expected LocalizedHttpException' );
|
|
|
|
|
} catch ( LocalizedHttpException $ex ) {
|
2024-05-06 11:30:33 +00:00
|
|
|
$data = $ex->getErrorData();
|
|
|
|
|
$this->assertSame( 'parameter-validation-failed', $data['error'] ?? null );
|
|
|
|
|
$this->assertSame( $expectedError, $data['failureCode'] ?? null );
|
2024-03-19 14:56:20 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testBodyValidator() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'headers' => [
|
|
|
|
|
'Content-Type' => 'test/test'
|
|
|
|
|
],
|
|
|
|
|
'bodyContents' => 'test test test'
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$bodyValidator = new class () implements BodyValidator {
|
|
|
|
|
|
|
|
|
|
public function validateBody( RequestInterface $request ) {
|
|
|
|
|
return 'VALIDATED BODY';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-11 07:06:13 +00:00
|
|
|
$this->expectDeprecationAndContinue( '/overrides getBodyValidator/' );
|
|
|
|
|
$this->expectDeprecationAndContinue( '/Validator::validateBody/' );
|
2024-03-19 14:56:20 +00:00
|
|
|
$handler = $this->newHandler( [ 'getBodyValidator' ] );
|
|
|
|
|
$handler->method( 'getBodyValidator' )->willReturn( $bodyValidator );
|
|
|
|
|
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
2020-05-25 19:14:26 +00:00
|
|
|
|
|
|
|
|
$body = $handler->getValidatedBody();
|
2024-03-19 14:56:20 +00:00
|
|
|
$this->assertSame( 'VALIDATED BODY', $body );
|
2020-05-25 19:14:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetRequest() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$request = new RequestData();
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $request, $handler->getRequest() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetConfig() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$config = [ 'foo' => 'bar' ];
|
|
|
|
|
$this->initHandler( $handler, new RequestData(), $config );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $config, $handler->getConfig() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetBodyValidator() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$this->assertInstanceOf(
|
|
|
|
|
BodyValidator::class,
|
|
|
|
|
$handler->getBodyValidator( 'unknown/unknown' )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatGetParamSettingsReturnsNothingPerDefault() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$this->assertSame( [], $handler->getParamSettings() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatGetLastModifiedReturnsNullPerDefault() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertNull( $handler->getLastModified() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatGetETagReturnsNullPerDefault() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertNull( $handler->getETag() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatHasRepresentationReturnsNullPerDefault() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertNull( $handler->hasRepresentation() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatNeedsReadAccessReturnsTruePerDefault() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertTrue( $handler->needsReadAccess() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatNeedsWriteAccessReturnsTruePerDefault() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
|
|
|
|
|
$handler = TestingAccessWrapper::newFromObject( $handler );
|
|
|
|
|
$this->assertTrue( $handler->needsWriteAccess() );
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 11:24:15 +00:00
|
|
|
public static function provideBodyValidationFailure() {
|
|
|
|
|
yield 'extraneous token (bodyContents)' => [
|
|
|
|
|
[
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'pathParams' => [ 'title' => 'Foo' ],
|
|
|
|
|
'headers' => [
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
],
|
|
|
|
|
'bodyContents' => json_encode( [
|
|
|
|
|
'title' => 'Foo',
|
|
|
|
|
'token' => 'TOKEN',
|
|
|
|
|
'comment' => 'Testing',
|
|
|
|
|
'source' => 'Lorem Ipsum',
|
|
|
|
|
'content_model' => 'wikitext'
|
|
|
|
|
] ),
|
|
|
|
|
],
|
|
|
|
|
new MessageValue( 'rest-extraneous-csrf-token' )
|
|
|
|
|
];
|
|
|
|
|
yield 'extraneous token (parsedBody)' => [
|
|
|
|
|
[
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'pathParams' => [ 'title' => 'Foo' ],
|
|
|
|
|
'headers' => [
|
|
|
|
|
'Content-Type' => 'application/json',
|
|
|
|
|
],
|
|
|
|
|
'parsedBody' => [
|
|
|
|
|
'title' => 'Foo',
|
|
|
|
|
'token' => 'TOKEN',
|
|
|
|
|
'comment' => 'Testing',
|
|
|
|
|
'source' => 'Lorem Ipsum',
|
|
|
|
|
'content_model' => 'wikitext'
|
|
|
|
|
],
|
2022-05-04 16:29:51 +00:00
|
|
|
],
|
2024-03-12 11:24:15 +00:00
|
|
|
new MessageValue( 'rest-extraneous-csrf-token' )
|
2022-05-04 16:29:51 +00:00
|
|
|
];
|
2024-03-12 11:24:15 +00:00
|
|
|
}
|
2022-05-04 16:29:51 +00:00
|
|
|
|
2024-03-12 11:24:15 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideBodyValidationFailure
|
|
|
|
|
*/
|
|
|
|
|
public function testBodyValidationFailure( $requestData, $expectedMessage ) {
|
2022-05-04 16:29:51 +00:00
|
|
|
$request = new RequestData( $requestData );
|
|
|
|
|
|
|
|
|
|
$handler = $this->newHandler();
|
Make it possible to override the session in REST API tests
The current signature of the various execute methods only takes a
boolean parameter to determine if the session should be safe against
CSRF, but that does not give callers fine-grained control over the
Session object, including setting a specific token.
Also, do not use createNoOpMock in getSession(), since it implies
strong assertions on what methods are called. This way, getSession
can also be used to get a simple mock session that tests may further
manipulate.
Make $csrfSafe parameter of SessionHelperTestTrait::getSession
mandatory. This way, callers are forced to think what makes sense in
each use case. The various methods in HandlerTestTrait now default to
a session that is safe against CSRF. This assumes that most REST
handlers don't care about the session, and that any handler that does
care about the session and where someone needs to test the behaviour
in case of bad/missing token will explicitly provide a Session that
is NOT safe against CSRF.
Typehint the return value of Session(Backend)::getUser so that PHPUnit
will automatically make it return a mock User object even if the method
is not explicitly mocked. Remove a useless PHPUnit assertion -- setting
the return value to be X and then veryfing that is equal to X is a
tautology, and can only fail if the test itself is flawed (as was the
case, since it was using stdClass as the return type for all
methods). Remove the getUser test case altogether, there's no way to
make it work given the DummySessionBackend, and the test isn't that
helpful anyway. More and more methods will have the same issue as soon
as their return value is typehinted.
Follow-up: I2a9215bf909b83564247ded95ecdb4ead0615150
Change-Id: Ic51dc3e7bf47c81f2ac4705308bb9ecd8275bbaf
2023-02-06 15:18:00 +00:00
|
|
|
$this->initHandler( $handler, $request, [], [], null, $this->getSession( true ) );
|
2022-05-04 16:29:51 +00:00
|
|
|
|
2024-03-12 11:24:15 +00:00
|
|
|
// NOTE: initHandler() should be setting the parsed body if needed!
|
|
|
|
|
$validator = $this->getMockValidator( [], $request->getParsedBody() );
|
2022-05-04 16:29:51 +00:00
|
|
|
$handler->validate( $validator );
|
|
|
|
|
|
2024-03-12 11:24:15 +00:00
|
|
|
// sanity check
|
|
|
|
|
$this->assertTrue( $handler->getSession()->getProvider()->safeAgainstCsrf() );
|
|
|
|
|
|
2022-05-04 16:29:51 +00:00
|
|
|
try {
|
|
|
|
|
$handler->checkSession();
|
|
|
|
|
Assert::fail( 'Expected a LocalizedHttpException to be thrown' );
|
|
|
|
|
} catch ( HttpException $ex ) {
|
2024-03-12 11:24:15 +00:00
|
|
|
$this->assertSame( 400, $ex->getCode(), 'HTTP status' );
|
|
|
|
|
$this->assertInstanceOf( LocalizedHttpException::class, $ex );
|
2022-05-04 16:29:51 +00:00
|
|
|
|
2024-03-12 11:24:15 +00:00
|
|
|
$this->assertEquals( $expectedMessage, $ex->getMessageValue() );
|
|
|
|
|
}
|
2022-05-04 16:29:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testCsrfUnsafeSessionProviderRejection() {
|
|
|
|
|
$handler = $this->newHandler( [ 'requireSafeAgainstCsrf' ] );
|
|
|
|
|
$handler->method( 'requireSafeAgainstCsrf' )->willReturn( true );
|
Make it possible to override the session in REST API tests
The current signature of the various execute methods only takes a
boolean parameter to determine if the session should be safe against
CSRF, but that does not give callers fine-grained control over the
Session object, including setting a specific token.
Also, do not use createNoOpMock in getSession(), since it implies
strong assertions on what methods are called. This way, getSession
can also be used to get a simple mock session that tests may further
manipulate.
Make $csrfSafe parameter of SessionHelperTestTrait::getSession
mandatory. This way, callers are forced to think what makes sense in
each use case. The various methods in HandlerTestTrait now default to
a session that is safe against CSRF. This assumes that most REST
handlers don't care about the session, and that any handler that does
care about the session and where someone needs to test the behaviour
in case of bad/missing token will explicitly provide a Session that
is NOT safe against CSRF.
Typehint the return value of Session(Backend)::getUser so that PHPUnit
will automatically make it return a mock User object even if the method
is not explicitly mocked. Remove a useless PHPUnit assertion -- setting
the return value to be X and then veryfing that is equal to X is a
tautology, and can only fail if the test itself is flawed (as was the
case, since it was using stdClass as the return type for all
methods). Remove the getUser test case altogether, there's no way to
make it work given the DummySessionBackend, and the test isn't that
helpful anyway. More and more methods will have the same issue as soon
as their return value is typehinted.
Follow-up: I2a9215bf909b83564247ded95ecdb4ead0615150
Change-Id: Ic51dc3e7bf47c81f2ac4705308bb9ecd8275bbaf
2023-02-06 15:18:00 +00:00
|
|
|
$this->initHandler( $handler, new RequestData(), [], [], null, $this->getSession( false ) );
|
2022-05-04 16:29:51 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$handler->checkSession();
|
|
|
|
|
Assert::fail( 'Expected a LocalizedHttpException to be thrown' );
|
|
|
|
|
} catch ( HttpException $ex ) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertSame( 400, $ex->getCode(), 'HTTP status' );
|
|
|
|
|
$this->assertInstanceOf( LocalizedHttpException::class, $ex );
|
|
|
|
|
|
|
|
|
|
$expectedMessage = new MessageValue( 'rest-requires-safe-against-csrf' );
|
|
|
|
|
$this->assertEquals( $expectedMessage, $ex->getMessageValue() );
|
|
|
|
|
|
|
|
|
|
$this->assertFalse( $handler->getSession()->getProvider()->safeAgainstCsrf() );
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 13:21:34 +00:00
|
|
|
public function testThatVerifierHeadersAreLoopedThroughForGet() {
|
|
|
|
|
$handler = $this->newHandler( [ 'getETag', 'getLastModified' ] );
|
|
|
|
|
$handler->method( 'getETag' )->willReturn( '"TEST"' );
|
|
|
|
|
$handler->method( 'getLastModified' )->willReturn( '20220101223344' );
|
|
|
|
|
|
|
|
|
|
$params = [ 'method' => 'GET' ];
|
|
|
|
|
$this->initHandler( $handler, new RequestData( $params ) );
|
|
|
|
|
$handler->checkPreconditions();
|
|
|
|
|
|
|
|
|
|
$response = new Response();
|
|
|
|
|
$handler->applyConditionalResponseHeaders( $response );
|
|
|
|
|
$this->assertSame( '"TEST"', $response->getHeaderLine( 'ETag' ) );
|
|
|
|
|
|
|
|
|
|
$lastModified = ConvertibleTimestamp::convert( TS_MW, $response->getHeaderLine( 'Last-Modified' ) );
|
|
|
|
|
$this->assertSame( '20220101223344', $lastModified );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testThatVerifierHeadersAreNotLoopedThroughForPost() {
|
|
|
|
|
$handler = $this->newHandler( [ 'getETag', 'getLastModified' ] );
|
|
|
|
|
$handler->method( 'getETag' )->willReturn( '"TEST"' );
|
|
|
|
|
$handler->method( 'getLastModified' )->willReturn( '20220101223344' );
|
|
|
|
|
|
|
|
|
|
$params = [ 'method' => 'POST' ];
|
|
|
|
|
$this->initHandler( $handler, new RequestData( $params ) );
|
|
|
|
|
$handler->checkPreconditions();
|
|
|
|
|
|
|
|
|
|
$response = new Response();
|
|
|
|
|
$handler->applyConditionalResponseHeaders( $response );
|
2022-11-24 22:22:36 +00:00
|
|
|
$this->assertSame( '', $response->getHeaderLine( 'ETag' ) );
|
|
|
|
|
$this->assertSame( '', $response->getHeaderLine( 'Last-Modified' ) );
|
2022-07-01 13:21:34 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-23 11:36:19 +00:00
|
|
|
public static function provideCacheControl() {
|
2022-11-15 22:24:11 +00:00
|
|
|
yield 'nothing' => [
|
|
|
|
|
'GET',
|
|
|
|
|
[],
|
2022-11-28 14:22:40 +00:00
|
|
|
false, // no persistent session
|
2022-11-15 22:24:11 +00:00
|
|
|
''
|
|
|
|
|
];
|
|
|
|
|
|
2022-11-28 14:22:40 +00:00
|
|
|
yield 'set-cookie in response' => [
|
2022-11-15 22:24:11 +00:00
|
|
|
'GET',
|
|
|
|
|
[
|
2023-01-21 02:45:20 +00:00
|
|
|
'Set-Cookie' => 'foo=bar',
|
2022-11-15 22:24:11 +00:00
|
|
|
'Cache-Control' => 'max-age=123'
|
|
|
|
|
],
|
2022-11-28 14:22:40 +00:00
|
|
|
false, // no persistent session
|
2023-05-19 01:12:51 +00:00
|
|
|
'private,must-revalidate,s-maxage=0'
|
2022-11-15 22:24:11 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'POST with cache control' => [
|
|
|
|
|
'POST',
|
|
|
|
|
[
|
|
|
|
|
'Cache-Control' => 'max-age=123'
|
|
|
|
|
],
|
2022-11-28 14:22:40 +00:00
|
|
|
false, // no persistent session
|
2022-11-15 22:24:11 +00:00
|
|
|
'max-age=123'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'POST use default cache control' => [
|
|
|
|
|
'POST',
|
|
|
|
|
[],
|
2022-11-28 14:22:40 +00:00
|
|
|
false, // no persistent session
|
|
|
|
|
'private,no-cache,s-maxage=0'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'persistent session' => [
|
|
|
|
|
'GET',
|
|
|
|
|
[ 'Cache-Control' => 'max-age=123' ],
|
|
|
|
|
true, // persistent session
|
2023-05-19 01:12:51 +00:00
|
|
|
'private,must-revalidate,s-maxage=0'
|
2022-11-15 22:24:11 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideCacheControl
|
|
|
|
|
*/
|
2022-11-28 14:22:40 +00:00
|
|
|
public function testCacheControl(
|
|
|
|
|
string $method,
|
|
|
|
|
array $headers,
|
|
|
|
|
bool $hasPersistentSession,
|
|
|
|
|
$expected
|
|
|
|
|
) {
|
2022-11-15 22:24:11 +00:00
|
|
|
$response = new Response();
|
|
|
|
|
|
|
|
|
|
foreach ( $headers as $name => $value ) {
|
|
|
|
|
$response->setHeader( $name, $value );
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-28 14:22:40 +00:00
|
|
|
$session = $this->createMock( Session::class );
|
|
|
|
|
$session->method( 'isPersistent' )->willReturn( $hasPersistentSession );
|
|
|
|
|
|
|
|
|
|
$handler = $this->newHandler( [ 'getRequest', 'getSession' ] );
|
2022-11-15 22:24:11 +00:00
|
|
|
$handler->method( 'getRequest' )->willReturn( new RequestData( [ 'method' => $method ] ) );
|
2022-11-28 14:22:40 +00:00
|
|
|
$handler->method( 'getSession' )->willReturn( $session );
|
2022-11-15 22:24:11 +00:00
|
|
|
|
|
|
|
|
$handler->applyCacheControl( $response );
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $expected, $response->getHeaderLine( 'Cache-Control' ) );
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-14 13:02:30 +00:00
|
|
|
public static function provideParseBodyData() {
|
|
|
|
|
return [
|
2024-03-14 13:56:05 +00:00
|
|
|
'no body type with non-empty body' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'bodyContents' => '{"foo":"bar"}',
|
|
|
|
|
] ),
|
2024-02-28 15:49:20 +00:00
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-unsupported-content-type', [ '' ] ),
|
|
|
|
|
415
|
|
|
|
|
)
|
2024-02-14 13:02:30 +00:00
|
|
|
],
|
2024-03-14 13:56:05 +00:00
|
|
|
'no body type with empty body' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [ 'content-length' => '0' ],
|
|
|
|
|
'bodyContent' => '',
|
|
|
|
|
] ),
|
|
|
|
|
null
|
|
|
|
|
],
|
2024-02-14 13:02:30 +00:00
|
|
|
'json body' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'bodyContents' => '{"foo":"bar"}',
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'foo' => 'bar' ]
|
|
|
|
|
],
|
2024-06-26 12:09:19 +00:00
|
|
|
'json patch body' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'bodyContents' => '{"patch":[]}',
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json-patch+json' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'patch' => [] ]
|
|
|
|
|
],
|
2024-05-07 19:00:54 +00:00
|
|
|
'form data' => [
|
2024-02-14 13:02:30 +00:00
|
|
|
new RequestData( [
|
2024-05-07 19:00:54 +00:00
|
|
|
'postParams' => [ 'foo' => 'bar' ],
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ]
|
2024-02-14 13:02:30 +00:00
|
|
|
] ),
|
2024-05-07 19:00:54 +00:00
|
|
|
[ 'foo' => 'bar' ]
|
2024-02-14 13:02:30 +00:00
|
|
|
],
|
2024-05-07 19:00:54 +00:00
|
|
|
'multipart form data' => [
|
2024-05-07 18:48:54 +00:00
|
|
|
new RequestData( [
|
2024-05-07 19:00:54 +00:00
|
|
|
'postParams' => [ 'foo' => 'bar' ],
|
|
|
|
|
'headers' => [ 'Content-Type' => 'multipart/form-data' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'foo' => 'bar' ]
|
|
|
|
|
],
|
|
|
|
|
'unknown body type' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'headers' => [ 'Content-Type' => 'unknown/type' ]
|
2024-05-07 18:48:54 +00:00
|
|
|
] ),
|
|
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-unsupported-content-type', [ '' ] ),
|
|
|
|
|
415
|
|
|
|
|
)
|
|
|
|
|
],
|
2024-03-14 13:56:05 +00:00
|
|
|
'malformed json body' => [
|
2024-02-14 13:02:30 +00:00
|
|
|
new RequestData( [
|
|
|
|
|
'bodyContents' => '{"foo":"bar"',
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json' ]
|
|
|
|
|
] ),
|
|
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-json-body-parse-error', [ '' ] ),
|
|
|
|
|
400
|
|
|
|
|
)
|
|
|
|
|
],
|
2024-03-14 13:56:05 +00:00
|
|
|
'empty json body' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'bodyContents' => '',
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json' ]
|
|
|
|
|
] ),
|
|
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-json-body-parse-error', [ '' ] ),
|
|
|
|
|
400
|
|
|
|
|
)
|
|
|
|
|
],
|
|
|
|
|
'non-array json body' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'bodyContents' => '"foo"',
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json' ]
|
|
|
|
|
] ),
|
|
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-json-body-parse-error', [ '' ] ),
|
|
|
|
|
400
|
|
|
|
|
)
|
|
|
|
|
],
|
2024-07-01 09:20:48 +00:00
|
|
|
'json body with normalization' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'bodyContents' => json_encode( [ 'param' => "L\u{0061}\u{0308}rm" ], JSON_UNESCAPED_UNICODE ),
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/json' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'param' => "L\u{00E4}rm" ]
|
|
|
|
|
],
|
|
|
|
|
'form data with normalization' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'postParams' => [ 'param' => "L\u{0061}\u{0308}rm" ],
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'param' => "L\u{00E4}rm" ]
|
|
|
|
|
],
|
|
|
|
|
'multipart form data with normalization' => [
|
|
|
|
|
new RequestData( [
|
|
|
|
|
'postParams' => [ 'param' => "L\u{0061}\u{0308}rm" ],
|
|
|
|
|
'headers' => [ 'Content-Type' => 'multipart/form-data' ]
|
|
|
|
|
] ),
|
|
|
|
|
[ 'param' => "L\u{00E4}rm" ]
|
|
|
|
|
]
|
2024-02-14 13:02:30 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @dataProvider provideParseBodyData */
|
|
|
|
|
public function testParseBodyData( $requestData, $expectedResult ) {
|
2024-05-07 19:00:54 +00:00
|
|
|
$handler = $this->newHandler( [ 'getSupportedRequestTypes' ] );
|
|
|
|
|
$handler->method( 'getSupportedRequestTypes' )->willReturn( [
|
|
|
|
|
'application/json',
|
2024-06-26 12:09:19 +00:00
|
|
|
'application/json-patch+json',
|
2024-05-07 19:00:54 +00:00
|
|
|
'application/x-www-form-urlencoded',
|
|
|
|
|
'multipart/form-data'
|
|
|
|
|
] );
|
|
|
|
|
|
2024-02-14 13:02:30 +00:00
|
|
|
if ( $expectedResult instanceof LocalizedHttpException ) {
|
|
|
|
|
$this->expectException( LocalizedHttpException::class );
|
2024-02-28 15:49:20 +00:00
|
|
|
$this->expectExceptionCode( $expectedResult->getCode() );
|
|
|
|
|
$this->expectExceptionMessage( $expectedResult->getMessage() );
|
2024-02-14 13:02:30 +00:00
|
|
|
$handler->parseBodyData( $requestData );
|
|
|
|
|
} else {
|
|
|
|
|
$parsedBody = $handler->parseBodyData( $requestData );
|
|
|
|
|
$this->assertEquals( $expectedResult, $parsedBody );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-07 19:00:54 +00:00
|
|
|
public function testPostRequestFailsWithFormData() {
|
|
|
|
|
$handler = $this->newHandler();
|
|
|
|
|
$requestData = new RequestData( [
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
|
|
|
|
|
'postParams' => [ 'test' => 'foo' ]
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$expectedResult = new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-unsupported-content-type', [ '' ] ),
|
|
|
|
|
415
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->expectException( LocalizedHttpException::class );
|
|
|
|
|
$this->expectExceptionCode( $expectedResult->getCode() );
|
|
|
|
|
$this->expectExceptionMessage( $expectedResult->getMessage() );
|
|
|
|
|
$handler->parseBodyData( $requestData );
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-30 16:04:41 +00:00
|
|
|
public function testGetRequestFailsWithBody() {
|
|
|
|
|
$this->markTestSkipped( 'T359509' );
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'GET',
|
|
|
|
|
'bodyContents' => '{"foo":"bar"}',
|
|
|
|
|
'headers' => [ "content-type" => 'application/json' ]
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
|
|
|
|
|
$this->expectExceptionCode( 400 );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetRequestIgnoresEmptyBody() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'GET',
|
|
|
|
|
'bodyContents' => '',
|
|
|
|
|
'headers' => [
|
|
|
|
|
"content-length" => 0,
|
|
|
|
|
"content-type" => 'text/plain'
|
|
|
|
|
]
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
$this->addToAssertionCount( 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testPostRequestFailsWithoutBody() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
|
|
|
|
|
$this->expectExceptionCode( 411 );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testEmptyBodyWithoutContentTypePasses() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'headers' => [ 'content-length' => '0' ],
|
|
|
|
|
'bodyContent' => '',
|
|
|
|
|
// Should pass even without content-type!
|
|
|
|
|
] );
|
|
|
|
|
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
$this->addToAssertionCount( 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRequestBodyWithoutContentTypeFails() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'bodyContents' => '{"foo":"bar"}', // Request body without content-type
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
|
|
|
|
|
$this->expectExceptionCode( 415 );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDeleteRequestWithoutBody() {
|
|
|
|
|
// Test DELETE request without body
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'DELETE',
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
$this->addToAssertionCount( 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDeleteRequestWithBody() {
|
|
|
|
|
// Test DELETE request with body
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'DELETE',
|
|
|
|
|
'bodyContents' => '{"bodyParam":"bar"}',
|
|
|
|
|
'headers' => [ "content-type" => 'application/json' ]
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
$this->addToAssertionCount( 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUnsupportedContentTypeReturns415() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'bodyContents' => '{"foo":"bar"}',
|
|
|
|
|
'headers' => [ "content-type" => 'text/plain' ] // Unsupported content type
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandlerPartially( $handler );
|
|
|
|
|
|
|
|
|
|
$this->expectExceptionCode( 415 );
|
|
|
|
|
$handler->initForExecute( $request );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testHandlerCanAccessParsedBodyForJsonRequest() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'bodyContents' => '{"bodyParam":"bar"}',
|
|
|
|
|
'headers' => [ "content-type" => 'application/json' ]
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
$data = $handler->execute();
|
|
|
|
|
|
|
|
|
|
// Check if the response contains a field called 'parsedBody'
|
|
|
|
|
$this->assertArrayHasKey( 'parsedBody', $data );
|
|
|
|
|
|
|
|
|
|
// Check the value of the 'parsedBody' field
|
|
|
|
|
$parsedBody = $data['parsedBody'];
|
|
|
|
|
$this->assertEquals( [ 'bodyParam' => 'bar' ], $parsedBody );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testHandlerCanAccessValidatedBodyForJsonRequest() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'bodyContents' => '{"bodyParam":"bar"}',
|
|
|
|
|
'headers' => [ "content-type" => 'application/json' ]
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
$data = $handler->execute();
|
|
|
|
|
|
|
|
|
|
// Check if the response contains a field called 'validatedBody'
|
|
|
|
|
$this->assertArrayHasKey( 'validatedBody', $data );
|
|
|
|
|
|
|
|
|
|
// Check the value of the 'validatedBody' field
|
|
|
|
|
$validatedBody = $data['validatedBody'];
|
|
|
|
|
$this->assertEquals( [ 'bodyParam' => 'bar' ], $validatedBody );
|
|
|
|
|
|
|
|
|
|
// Check the value of the 'validatedParams' field.
|
|
|
|
|
// It should not contain bodyParam.
|
|
|
|
|
$validatedParams = $data['validatedParams'];
|
|
|
|
|
$this->assertArrayNotHasKey( 'bodyParam', $validatedParams );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testHandlerCanAccessValidatedParams() {
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo/bar' ),
|
|
|
|
|
'pathParams' => [ 'pathParam' => 'bar' ],
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'headers' => [ "content-type" => 'application/json' ],
|
|
|
|
|
'bodyContents' => '{}'
|
|
|
|
|
] );
|
|
|
|
|
$handler = new EchoHandler();
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
$data = $handler->execute();
|
|
|
|
|
|
|
|
|
|
// Check if the response contains a field called 'pathParams'
|
|
|
|
|
$this->assertArrayHasKey( 'validatedParams', $data );
|
|
|
|
|
|
|
|
|
|
// Check the value of the 'pathParams' field
|
|
|
|
|
$validatedParams = $data['validatedParams'];
|
|
|
|
|
$this->assertEquals( 'bar', $validatedParams[ 'pathParam' ] );
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-07 18:48:54 +00:00
|
|
|
/**
|
|
|
|
|
* Assert that getSupportedRequestTypes() will detect that "post" parameters
|
|
|
|
|
* are declared, so form data is allowed.
|
|
|
|
|
*/
|
|
|
|
|
public function testGetSupportedRequestTypes_post() {
|
2024-05-07 19:00:54 +00:00
|
|
|
$this->expectDeprecationAndContinue( '/The "post" source is deprecated/' );
|
|
|
|
|
|
2024-05-07 18:48:54 +00:00
|
|
|
$paramSettings = [
|
|
|
|
|
'test' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'post',
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
$handler = $this->newHandler( [ 'getParamSettings' ] );
|
|
|
|
|
$handler->method( 'getParamSettings' )->willReturn( $paramSettings );
|
|
|
|
|
|
|
|
|
|
$supportedTypes = $handler->getSupportedRequestTypes();
|
|
|
|
|
|
|
|
|
|
$this->assertContains( 'application/x-www-form-urlencoded', $supportedTypes );
|
|
|
|
|
$this->assertContains( 'multipart/form-data', $supportedTypes );
|
|
|
|
|
$this->assertContains( 'application/json', $supportedTypes );
|
|
|
|
|
|
|
|
|
|
// Should accept form data
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
|
|
|
|
|
'postParams' => [ 'test' => 'foo' ]
|
|
|
|
|
] );
|
|
|
|
|
$handler->parseBodyData( $request );
|
|
|
|
|
|
2024-05-07 19:00:54 +00:00
|
|
|
// The "post" parameter should be processed as "parameter" but not as "body".
|
2024-05-07 18:48:54 +00:00
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey( 'test', $handler->getValidatedParams() );
|
2024-05-07 19:00:54 +00:00
|
|
|
$this->assertArrayNotHasKey( 'test', $handler->getValidatedBody() );
|
2024-05-07 18:48:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Assert that getSupportedRequestTypes() can be overwritten
|
|
|
|
|
* to allow form data.
|
|
|
|
|
*/
|
|
|
|
|
public function testGetSupportedRequestTypes_body() {
|
|
|
|
|
$paramSettings = [
|
|
|
|
|
'test' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
]
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$supportedTypes = [
|
|
|
|
|
'application/x-www-form-urlencoded'
|
|
|
|
|
];
|
|
|
|
|
|
2024-06-20 13:33:35 +00:00
|
|
|
$handler = $this->newHandler( [ 'getBodyParamSettings', 'getSupportedRequestTypes' ] );
|
|
|
|
|
$handler->method( 'getBodyParamSettings' )->willReturn( $paramSettings );
|
2024-05-07 18:48:54 +00:00
|
|
|
$handler->method( 'getSupportedRequestTypes' )->willReturn( $supportedTypes );
|
|
|
|
|
|
|
|
|
|
// Should accept form data
|
|
|
|
|
$request = new RequestData( [
|
|
|
|
|
'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
|
|
|
|
|
'postParams' => [ 'test' => 'foo' ]
|
|
|
|
|
] );
|
|
|
|
|
$handler->parseBodyData( $request );
|
|
|
|
|
|
|
|
|
|
// The "body" parameter should be processed as "body", not as "parameter".
|
|
|
|
|
$this->initHandler( $handler, $request );
|
|
|
|
|
$this->validateHandler( $handler );
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey( 'test', $handler->getValidatedBody() );
|
|
|
|
|
$this->assertArrayNotHasKey( 'test', $handler->getValidatedParams() );
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-05 18:45:21 +00:00
|
|
|
private static function assertWellFormedOAS( array $spec, array $required ) {
|
|
|
|
|
foreach ( $required as $key ) {
|
|
|
|
|
Assert::assertArrayHasKey( $key, $spec );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static function makeMap( array $list, string $key ): array {
|
|
|
|
|
$map = [];
|
|
|
|
|
foreach ( $list as $obj ) {
|
|
|
|
|
$val = $obj[$key];
|
|
|
|
|
$map[$val] = $obj;
|
|
|
|
|
}
|
|
|
|
|
return $map;
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 20:51:29 +00:00
|
|
|
public static function provideGetOpenApiSpec() {
|
2024-04-05 18:45:21 +00:00
|
|
|
yield 'defaults' => [
|
|
|
|
|
'$paramSettings' => [],
|
|
|
|
|
'$bodySettings' => [],
|
|
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test' ],
|
|
|
|
|
'$method' => 'GET',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'responses' ] );
|
|
|
|
|
$resp = $spec['responses'];
|
|
|
|
|
|
|
|
|
|
Assert::assertArrayHasKey( 200, $resp );
|
|
|
|
|
Assert::assertArrayHasKey( 400, $resp );
|
|
|
|
|
Assert::assertArrayHasKey( 500, $resp );
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'path parameters' => [
|
|
|
|
|
'$paramSettings' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
],
|
|
|
|
|
'b' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
ParamValidator::PARAM_TYPE => [ 'x', 'y', 'z' ],
|
2024-02-21 20:51:29 +00:00
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'c' => [
|
2024-02-21 20:51:29 +00:00
|
|
|
Handler::PARAM_SOURCE => 'path',
|
2024-04-05 18:45:21 +00:00
|
|
|
ParamValidator::PARAM_TYPE => 'string',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
2024-02-21 20:51:29 +00:00
|
|
|
],
|
|
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'$bodySettings' => [],
|
|
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test/{a}/{b}' ],
|
|
|
|
|
'$method' => 'GET',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'parameters' ] );
|
|
|
|
|
$params = self::makeMap( $spec['parameters'], 'name' );
|
|
|
|
|
|
|
|
|
|
// The parameter "c" is optional and not in the declared path.
|
|
|
|
|
Assert::assertArrayHasKey( 'a', $params, 'required path param' );
|
|
|
|
|
Assert::assertArrayHasKey( 'b', $params, 'used optional path param' );
|
|
|
|
|
Assert::assertArrayNotHasKey( 'c', $params, 'unused optional path param' );
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'path', $params['a']['in'] );
|
|
|
|
|
Assert::assertSame( 'path', $params['b']['in'] );
|
|
|
|
|
|
|
|
|
|
// All path params must be required in OAS, even if they
|
|
|
|
|
// were declared as optional (T359652) in getParamSettings.
|
|
|
|
|
Assert::assertTrue( $params['a']['required'] );
|
|
|
|
|
Assert::assertTrue( $params['b']['required'] );
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'integer', $params['a']['schema']['type'] );
|
|
|
|
|
Assert::assertSame( 'string', $params['b']['schema']['type'] );
|
|
|
|
|
Assert::assertSame( [ 'x', 'y', 'z' ], $params['b']['schema']['enum'] );
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'query parameters' => [
|
|
|
|
|
'$paramSettings' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'query',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
],
|
|
|
|
|
'b' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'query',
|
|
|
|
|
ParamValidator::PARAM_TYPE => [ 'x', 'y', 'z' ],
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
2024-02-21 20:51:29 +00:00
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'$bodySettings' => [],
|
|
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test' ],
|
|
|
|
|
'$method' => 'GET',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'parameters' ] );
|
|
|
|
|
$params = self::makeMap( $spec['parameters'], 'name' );
|
|
|
|
|
|
|
|
|
|
Assert::assertArrayHasKey( 'a', $params );
|
|
|
|
|
Assert::assertArrayHasKey( 'b', $params );
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'query', $params['a']['in'] );
|
|
|
|
|
Assert::assertSame( 'query', $params['b']['in'] );
|
|
|
|
|
|
|
|
|
|
Assert::assertTrue( $params['a']['required'] );
|
|
|
|
|
Assert::assertFalse( $params['b']['required'] );
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'integer', $params['a']['schema']['type'] );
|
|
|
|
|
Assert::assertSame( 'string', $params['b']['schema']['type'] );
|
|
|
|
|
Assert::assertSame( [ 'x', 'y', 'z' ], $params['b']['schema']['enum'] );
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'request body' => [
|
|
|
|
|
'$paramSettings' => [],
|
|
|
|
|
'$bodySettings' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'array',
|
|
|
|
|
ArrayDef::PARAM_SCHEMA => [
|
|
|
|
|
'type' => 'object',
|
|
|
|
|
'required' => [ 'x', 'y', 'z' ]
|
2024-02-21 20:51:29 +00:00
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
|
|
|
|
],
|
|
|
|
|
'b' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
'p' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'post',
|
2024-02-21 20:51:29 +00:00
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
],
|
|
|
|
|
'$requestTypes' => [ 'application/foo+json', 'application/bar+json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test' ],
|
|
|
|
|
'$method' => 'PUT',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'requestBody' ] );
|
|
|
|
|
Assert::assertTrue( $spec['requestBody']['required'] );
|
|
|
|
|
|
|
|
|
|
Assert::assertArrayHasKey(
|
|
|
|
|
'application/foo+json',
|
|
|
|
|
$spec['requestBody']['content']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Assert::assertSame(
|
|
|
|
|
$spec['requestBody']['content']['application/foo+json'],
|
|
|
|
|
$spec['requestBody']['content']['application/bar+json']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$schema = $spec['requestBody']['content']
|
|
|
|
|
['application/foo+json']['schema'];
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'object', $schema['type'] );
|
|
|
|
|
|
|
|
|
|
// Do not include "post" params if the request type
|
|
|
|
|
// is not application/x-www-form-urlencoded or multipart/form-data.
|
|
|
|
|
Assert::assertArrayHasKey( 'a', $schema['properties'] );
|
|
|
|
|
Assert::assertArrayHasKey( 'b', $schema['properties'] );
|
|
|
|
|
Assert::assertArrayNotHasKey( 'p', $schema['properties'] );
|
|
|
|
|
|
|
|
|
|
Assert::assertContains( 'a', $schema['required'] );
|
|
|
|
|
Assert::assertNotContains( 'b', $schema['required'] );
|
|
|
|
|
|
|
|
|
|
// Nested schema, from ArrayDef
|
|
|
|
|
$aSchema = $schema['properties']['a'];
|
|
|
|
|
Assert::assertSame( 'object', $aSchema['type'] );
|
|
|
|
|
Assert::assertSame( [ 'x', 'y', 'z' ], $aSchema['required'] );
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'form data' => [
|
|
|
|
|
'$paramSettings' => [],
|
|
|
|
|
'$bodySettings' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
],
|
|
|
|
|
'p' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'post',
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
'$requestTypes' => [ 'application/x-www-form-urlencoded' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test' ],
|
|
|
|
|
'$method' => 'POST',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'requestBody' ] );
|
|
|
|
|
Assert::assertTrue( $spec['requestBody']['required'] );
|
|
|
|
|
|
|
|
|
|
Assert::assertArrayHasKey(
|
|
|
|
|
'application/x-www-form-urlencoded',
|
|
|
|
|
$spec['requestBody']['content']
|
|
|
|
|
);
|
|
|
|
|
$schema = $spec['requestBody']['content']
|
|
|
|
|
['application/x-www-form-urlencoded']['schema'];
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'object', $schema['type'] );
|
|
|
|
|
|
|
|
|
|
// Include both "post" and "body" params, because the
|
|
|
|
|
// request type is application/x-www-form-urlencoded.
|
|
|
|
|
Assert::assertArrayHasKey( 'a', $schema['properties'] );
|
|
|
|
|
Assert::assertArrayHasKey( 'p', $schema['properties'] );
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'no request body for GET' => [
|
|
|
|
|
'$paramSettings' => [],
|
|
|
|
|
'$bodySettings' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => true,
|
2024-02-21 20:51:29 +00:00
|
|
|
],
|
|
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test' ],
|
|
|
|
|
'$method' => 'GET',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [] );
|
|
|
|
|
|
|
|
|
|
// When generating a spec for GET, don't include the request body.
|
|
|
|
|
Assert::assertArrayNotHasKey( 'requestBody', $spec );
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'optional body for DELETE' => [
|
|
|
|
|
'$paramSettings' => [],
|
|
|
|
|
'$bodySettings' => [
|
|
|
|
|
'a' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'body',
|
|
|
|
|
ParamValidator::PARAM_TYPE => 'integer',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test' ],
|
|
|
|
|
'$method' => 'DELETE',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'requestBody' ] );
|
|
|
|
|
|
|
|
|
|
// When generating a spec for GET, don't include the request body.
|
|
|
|
|
// FIXME: check if there are required body params!
|
|
|
|
|
Assert::assertFalse( $spec['requestBody']['required'] );
|
|
|
|
|
},
|
2024-02-21 20:51:29 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'optional path params' => [
|
2024-04-05 18:45:21 +00:00
|
|
|
'$paramSettings' => [
|
2024-02-21 20:51:29 +00:00
|
|
|
'p' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
'r' => [ // not in declared path, will be ignored
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
ParamValidator::PARAM_REQUIRED => false,
|
|
|
|
|
],
|
|
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'$bodySettings' => [],
|
|
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [ 'path' => '/test/{p}' ],
|
|
|
|
|
'$method' => 'GET',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'parameters' ] );
|
|
|
|
|
$params = self::makeMap( $spec['parameters'], 'name' );
|
|
|
|
|
|
|
|
|
|
Assert::assertArrayHasKey(
|
|
|
|
|
'p',
|
|
|
|
|
$params,
|
|
|
|
|
'used optional path parameter should be listed'
|
|
|
|
|
);
|
|
|
|
|
Assert::assertArrayNotHasKey(
|
|
|
|
|
'r',
|
|
|
|
|
$params,
|
|
|
|
|
'unused optional path parameter should not be listed'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Assert::assertSame( 'path', $params['p']['in'] );
|
|
|
|
|
Assert::assertTrue(
|
|
|
|
|
$params['p']['required'],
|
|
|
|
|
'used optional path parameter should be marked as required'
|
|
|
|
|
);
|
|
|
|
|
},
|
2024-02-21 20:51:29 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
yield 'OAS info' => [
|
2024-04-05 18:45:21 +00:00
|
|
|
'$paramSettings' => [
|
2024-02-21 20:51:29 +00:00
|
|
|
'p' => [
|
|
|
|
|
Handler::PARAM_SOURCE => 'path',
|
|
|
|
|
],
|
|
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'$bodySettings' => [],
|
|
|
|
|
'$requestTypes' => [ 'application/json' ],
|
|
|
|
|
'$routeConfig' => [
|
2024-02-21 20:51:29 +00:00
|
|
|
'path' => 'test/{p}',
|
|
|
|
|
'OAS' => [
|
|
|
|
|
'title' => 'just a test',
|
|
|
|
|
'parameters' => 'will be ignored',
|
|
|
|
|
]
|
|
|
|
|
],
|
2024-04-05 18:45:21 +00:00
|
|
|
'$method' => 'GET',
|
|
|
|
|
'$assertions' =>
|
|
|
|
|
static function ( array $spec ) {
|
|
|
|
|
self::assertWellFormedOAS( $spec, [ 'title', 'parameters' ] );
|
|
|
|
|
Assert::assertArrayHasKey( 'title', $spec );
|
|
|
|
|
Assert::assertSame( 'just a test', $spec['title'] );
|
|
|
|
|
|
|
|
|
|
$params = self::makeMap( $spec['parameters'], 'name' );
|
|
|
|
|
Assert::assertArrayHasKey( 'p', $params );
|
|
|
|
|
},
|
2024-02-21 20:51:29 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideGetOpenApiSpec
|
|
|
|
|
*/
|
2024-04-05 18:45:21 +00:00
|
|
|
public function testGetOpenApiSpec(
|
|
|
|
|
$paramSettings,
|
|
|
|
|
$bodySettings,
|
|
|
|
|
$requestTypes,
|
|
|
|
|
$routeConfig,
|
|
|
|
|
$method,
|
|
|
|
|
$assertions
|
|
|
|
|
) {
|
|
|
|
|
$handler = $this->newHandler( [
|
2024-02-21 20:51:29 +00:00
|
|
|
'getParamSettings',
|
|
|
|
|
'getBodyParamSettings',
|
|
|
|
|
'getSupportedRequestTypes',
|
2024-04-05 18:45:21 +00:00
|
|
|
] );
|
2024-02-21 20:51:29 +00:00
|
|
|
$handler->method( 'getParamSettings' )->willReturn( $paramSettings );
|
|
|
|
|
$handler->method( 'getBodyParamSettings' )->willReturn( $bodySettings );
|
2024-04-05 18:45:21 +00:00
|
|
|
$handler->method( 'getSupportedRequestTypes' )->willReturn( $requestTypes );
|
2024-02-21 20:51:29 +00:00
|
|
|
|
2024-04-05 18:45:21 +00:00
|
|
|
// The "body" parameter should be processed as "body", not as "parameter".
|
|
|
|
|
$module = $this->createNoOpMock( Module::class );
|
|
|
|
|
$handler->initContext(
|
|
|
|
|
$module,
|
|
|
|
|
$routeConfig['path'],
|
|
|
|
|
$routeConfig
|
|
|
|
|
);
|
|
|
|
|
$spec = $handler->getOpenApiSpec( $method );
|
2024-02-21 20:51:29 +00:00
|
|
|
|
2024-04-05 18:45:21 +00:00
|
|
|
$assertions( $spec );
|
2024-02-21 20:51:29 +00:00
|
|
|
}
|
|
|
|
|
|
2020-05-25 19:14:26 +00:00
|
|
|
}
|