wiki.techinc.nl/tests/phpunit/unit/includes/Rest/RouterTest.php
Translation updater bot e93d7f0705 Hard-deprecate getBodyValidator
This patch emits deprecation warnings when extensions override
Handler::getBodyValidator() or otherwise use JsonBodyValidator.
Request body validation should be performed based on body parameter
declarations returned from getBodyParamSettings().

Bug: T358560
Depends-On: I8b12c9c9d5a73c620e3c5d035f815d7c951c7b30
Change-Id: I44b3395b8d2489c5e7ec8de0077fd7a7c9f49bfe
2024-07-16 13:06:01 +00:00

866 lines
28 KiB
PHP

<?php
namespace MediaWiki\Tests\Rest;
use GuzzleHttp\Psr7\Uri;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\MainConfigNames;
use MediaWiki\Rest\BasicAccess\StaticBasicAuthorizer;
use MediaWiki\Rest\CorsUtils;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\HttpException;
use MediaWiki\Rest\RedirectException;
use MediaWiki\Rest\Reporter\ErrorReporter;
use MediaWiki\Rest\RequestData;
use MediaWiki\Rest\RequestInterface;
use MediaWiki\Rest\ResponseException;
use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\Router;
use MediaWiki\Rest\StringStream;
use MediaWiki\Rest\Validator\JsonBodyValidator;
use MediaWiki\Tests\Rest\Handler\HelloHandler;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\NullLogger;
use RuntimeException;
use Throwable;
use UDPTransport;
use Wikimedia\ObjectCache\HashBagOStuff;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\Stats\OutputFormats;
use Wikimedia\Stats\StatsCache;
use Wikimedia\Stats\StatsFactory;
use Wikimedia\TestingAccessWrapper;
/**
* @covers \MediaWiki\Rest\Router
* @covers \MediaWiki\Rest\Handler
*/
class RouterTest extends MediaWikiUnitTestCase {
use RestTestTrait;
private const CANONICAL_SERVER = 'https://wiki.example.com';
private const INTERNAL_SERVER = 'http://api.local:8080';
/** @var Throwable[] */
private $reportedErrors = [];
private $cacheBag;
protected function setUp(): void {
parent::setUp();
$this->cacheBag = new HashBagOStuff();
}
private function createRouter(
RequestInterface $request,
?string $authError = null,
array $routeFiles = [ __DIR__ . '/testRoutes.json' ]
): Router {
/** @var MockObject|ErrorReporter $mockErrorReporter */
$mockErrorReporter = $this->createNoOpMock( ErrorReporter::class, [ 'reportError' ] );
$mockErrorReporter->method( 'reportError' )
->willReturnCallback( function ( $e ) {
$this->reportedErrors[] = $e;
} );
$config = [
MainConfigNames::CanonicalServer => self::CANONICAL_SERVER,
MainConfigNames::InternalServer => self::INTERNAL_SERVER,
MainConfigNames::RestPath => '/rest',
MainConfigNames::ScriptPath => '/w'
];
$extraRoutes = [
[ 'path' => '/', 'class' => HelloHandler::class ]
];
return $this->newRouter( [
'routeFiles' => $routeFiles,
'extraRoutes' => $extraRoutes,
'request' => $request,
'config' => $config,
'cacheBag' => $this->cacheBag,
'errorReporter' => $mockErrorReporter,
'basicAuth' => new StaticBasicAuthorizer( $authError ),
] );
}
private function createMockStatsFactory( string $expectedValue ): StatsFactory {
$statsCache = new StatsCache();
$emitter = OutputFormats::getNewEmitter(
'mediawiki',
$statsCache,
OutputFormats::getNewFormatter( OutputFormats::DOGSTATSD )
);
$transport = $this->createMock( UDPTransport::class );
$transport->expects( $this->once() )->method( "emit" )
->with( $this->matchesRegularExpression( $expectedValue ) );
$emitter = $emitter->withTransport( $transport );
return new StatsFactory( $statsCache, $emitter, new NullLogger );
}
public function testEmptyPath() {
// The URI doesn't contain the "/" suffix, so the relative path is empty.
$request = new RequestData( [ 'uri' => new Uri( '/rest' ) ] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 308, $response->getStatusCode() );
$this->assertSame( '/rest/', $response->getHeaderLine( 'location' ) );
}
public function testRootPath() {
// The URI contains only the "/" suffix.
// This should be sufficient to be routed to the prefix-less modules.
// The "/" path is mapped to the HelloHandler in createRouter().
$request = new RequestData( [ 'uri' => new Uri( '/rest/' ) ] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode(), (string)$response->getBody() );
}
public function testPrefixMismatch() {
$request = new RequestData( [ 'uri' => new Uri( '/bogus' ) ] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 404, $response->getStatusCode() );
}
public function testWrongMethod() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/hello' ),
'method' => 'TRACE'
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 405, $response->getStatusCode() );
$this->assertSame( 'Method Not Allowed', $response->getReasonPhrase() );
$this->assertSame( 'HEAD, GET', $response->getHeaderLine( 'Allow' ) );
}
public function testGetFromUglyPath() {
$request = new RequestData( [
'uri' => new Uri( '/w/rest.php/mock/v1/RouterTest/hello' ),
'method' => 'GET'
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
}
public function testHeadToGet() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/hello' ),
'method' => 'HEAD'
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
}
public function testCorsPreflight() {
$cors = $this->getCorsUtils();
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/hello' ),
'method' => 'OPTIONS'
] );
$router = $this->createRouter( $request );
$router->setCors( $cors );
$response = $router->execute( $request );
$this->assertSame( 204, $response->getStatusCode() );
$this->assertSame(
[ 'HEAD', 'GET', ],
$response->getHeader( 'Access-Control-Allow-Methods' )
);
}
public function testNoMatch() {
$request = new RequestData( [ 'uri' => new Uri( '/rest/bogus' ) ] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 404, $response->getStatusCode() );
// TODO: add more information to the response body and test for its presence here
}
/**
* Constructs a handler that throws an HttpException
*/
public static function throwHandlerFactory(): Handler {
return new class extends Handler {
public function execute() {
throw new HttpException( 'Mock error', 555 );
}
};
}
/**
* Constructs a handler that throws a RuntimeException with a custom code
*/
public static function fatalHandlerFactory(): Handler {
return new class extends Handler {
public function execute() {
throw new RuntimeException( 'Fatal mock error', 12345 );
}
};
}
/**
* Constructs a handler that throws a RedirectException
*/
public static function throwRedirectHandlerFactory(): Handler {
return new class extends Handler {
public function execute() {
throw new RedirectException( 301, 'http://example.com' );
}
};
}
/**
* Constructs a handler that throws a ResponseException with status 200
*/
public static function throwWrappedHandlerFactory(): Handler {
return new class extends Handler {
public function execute() {
$response = $this->getResponseFactory()->create();
$response->setStatus( 200 );
throw new ResponseException( $response );
}
};
}
public function testHttpException() {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/RouterTest/throw' ) ] );
$router = $this->createRouter( $request );
$stats = $this->createMockStatsFactory(
"/^mediawiki\.rest_api_errors_total:1\|c\|#path:mock_v1_RouterTest_throw,method:GET,status:555\nmediawiki\.stats_buffered_total:1\|c$/"
);
$router->setStats( $stats );
$response = $router->execute( $request );
$stats->flush();
$this->assertSame( 555, $response->getStatusCode(), (string)$response->getBody() );
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
$this->assertSame( 'Mock error', $data['message'] );
}
public function testFatalException() {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/RouterTest/fatal' ) ] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 500, $response->getStatusCode(), (string)$response->getBody() );
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
$this->assertStringContainsString( 'RuntimeException', $data['message'] );
$this->assertNotEmpty( $this->reportedErrors );
$this->assertInstanceOf( RuntimeException::class, $this->reportedErrors[0] );
}
public function testRedirectException() {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/RouterTest/throwRedirect' ) ] );
$router = $this->createRouter( $request );
$stats = $this->createMockStatsFactory(
"/^mediawiki\.rest_api_latency_seconds:\d+\.\d+\|ms\|#path:mock_v1_RouterTest_throwRedirect,method:GET,status:301\nmediawiki\.stats_buffered_total:1\|c$/"
);
$router->setStats( $stats );
$response = $router->execute( $request );
$stats->flush();
$this->assertSame( 301, $response->getStatusCode(), (string)$response->getBody() );
$this->assertSame( 'http://example.com', $response->getHeaderLine( 'Location' ) );
}
public function testRedirectDefinition() {
// This route is defined in testRoutes.json without specifying a class or factory.
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/RouterTest/redirect' ) ] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 308, $response->getStatusCode(), (string)$response->getBody() );
$this->assertSame( '/rest/mock/RouterTest/redirectTarget', $response->getHeaderLine( 'Location' ) );
}
public function testResponseException() {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/RouterTest/throwWrapped' ) ] );
$router = $this->createRouter( $request );
$stats = $this->createMockStatsFactory(
"/^mediawiki\.rest_api_latency_seconds:\d+\.\d+\|ms\|#path:mock_v1_RouterTest_throwWrapped,method:GET,status:200\nmediawiki\.stats_buffered_total:1\|c$/"
);
$router->setStats( $stats );
$response = $router->execute( $request );
$stats->flush();
$this->assertSame( 200, $response->getStatusCode(), (string)$response->getBody() );
}
public function testBasicAccess() {
// Using the throwing handler is a way to assert that the handler is not executed
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/RouterTest/throw' ) ] );
$router = $this->createRouter( $request, 'test-error' );
$response = $router->execute( $request );
$this->assertSame( 403, $response->getStatusCode() );
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
$this->assertSame( 'test-error', $data['error'] );
}
public function testAdditionalEndpoints() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock-too/RouterTest/hello/two' )
] );
$router = $this->createRouter(
$request,
null,
// NOTE: testAdditionalRoutes uses the old flat format!
[ __DIR__ . '/testRoutes.json', __DIR__ . '/testAdditionalRoutes.json' ]
);
// Routes from flat route files end up on a module that uses the empty prefix.
$this->assertSame( [ 'mock/v1', '' ], $router->getModuleNames() );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
}
public function testFlatRouteFile() {
$request = new RequestData( [
'uri' => new Uri( '/rest/ModuleTest/hello/you' )
] );
$router = $this->createRouter(
$request,
null,
[ __DIR__ . '/Module/moduleFlatRoutes.json' ]
);
$this->assertSame( [ '' ], $router->getModuleNames() );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
}
public static function providePaths() {
return [
[ '/rest/mock/v1/RouterTest/hello' ],
[ '/rest/mock-too/RouterTest/hello/two' ],
];
}
public static function provideGetRouteUrl() {
yield 'empty' => [ '', '', [], [] ];
yield 'simple route' => [ '/foo/bar', '/foo/bar' ];
yield 'simple route with query' =>
[ '/foo/bar', '/foo/bar?x=1&y=2', [ 'x' => '1', 'y' => '2' ] ];
yield 'simple route with strange query chars' =>
[ '/foo+bar', '/foo+bar?x=%23&y=%25&z=%2B', [ 'x' => '#', 'y' => '%', 'z' => '+' ] ];
yield 'route with simple path params' =>
[ '/foo/{test}/baz', '/foo/bar/baz', [], [ 'test' => 'bar' ] ];
yield 'route with strange path params' =>
[ '/foo/{test}/baz', '/foo/b%25%2F%2Bz/baz', [], [ 'test' => 'b%/+z' ] ];
yield 'space in path does not become a plus' =>
[ '/foo/{test}/baz', '/foo/b%20z/baz', [], [ 'test' => 'b z' ] ];
yield 'route with simple path params and query' =>
[ '/foo/{test}/baz', '/foo/bar/baz?x=1', [ 'x' => '1' ], [ 'test' => 'bar' ] ];
}
/**
* @dataProvider provideGetRouteUrl
*/
public function testGetRoutePath( $route, $expectedUrl, $query = [], $path = [] ) {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/route' ) ] );
$router = $this->createRouter( $request );
$path = $router->getRoutePath( $route, $path, $query );
$this->assertStringNotContainsString( self::CANONICAL_SERVER, $path );
$this->assertStringStartsWith( '/', $path );
$expected = new Uri( $expectedUrl );
$actual = new Uri( $path );
$this->assertStringContainsString( $expected->getPath(), $actual->getPath() );
$this->assertStringContainsString( $expected->getQuery(), $actual->getQuery() );
}
/**
* @dataProvider provideGetRouteUrl
*/
public function testGetRouteUrl( $route, $expectedUrl, $query = [], $path = [] ) {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/route' ) ] );
$router = $this->createRouter( $request );
$url = $router->getRouteUrl( $route, $path, $query );
$this->assertStringStartsWith( self::CANONICAL_SERVER, $url );
$uri = new Uri( $url );
$this->assertStringContainsString( $expectedUrl, $uri );
}
/**
* @dataProvider provideGetRouteUrl
*/
public function testGetPrivateRouteUrl( $route, $expectedUrl, $query = [], $path = [] ) {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/route' ) ] );
$router = $this->createRouter( $request );
$url = $router->getPrivateRouteUrl( $route, $path, $query );
$this->assertStringStartsWith( self::INTERNAL_SERVER, $url );
$uri = new Uri( $url );
$this->assertStringContainsString( $expectedUrl, $uri );
}
public function testCaching() {
$request = new RequestData( [ 'uri' => new Uri( '/rest/mock/v1/route' ) ] );
$router1 = $this->createRouter( $request );
$router1wrapper = TestingAccessWrapper::newFromObject( $router1 );
// Ensure the module map is loaded and cached
$router1->getModule( 'mock' );
// Create a second router
$router2 = $this->createRouter( $request );
$router2wrapper = TestingAccessWrapper::newFromObject( $router2 );
// Destroy $router2's ability to load modules and routes
$router2wrapper->routeFiles = [ '/this/does/not/exist' ];
// Make sure the config hash is set and matches.
$router2wrapper->configHash = $router1wrapper->configHash;
// Check that $router2 can return a module based on cached information.
// Note that this needs both levels of the cache to work.
$module2 = $router2->getModule( 'mock/v1' );
$this->assertNotNull( $module2 );
// Create a third router
$router3 = $this->createRouter( $request );
$router3wrapper = TestingAccessWrapper::newFromObject( $router3 );
// Force a different route file (but don't force the config hash)
$router3wrapper->routeFiles = [ __DIR__ . '/testAdditionalRoutes.json' ];
// This should fail, since the router should detect that the config is
// different, so it can't use cached data.
$module3 = $router3->getModule( 'mock/v1' );
$this->assertNull( $module3 );
}
public function testHandlerDisablesBodyParsing() {
// This is valid JSON, but not an object.
// Automatic parsing will fail, since it re requires
// an array to be returned.
$payload = '"just a test"';
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/stream' ),
'method' => 'PUT',
'bodyContents' => $payload,
'headers' => [ "content-type" => 'application/json' ]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
$responseStream = $response->getBody();
$this->assertSame( $payload, "$responseStream" );
}
/**
* Asserts that handlers can use a custom BodyValidator to add support for
* additional mime types, without overriding parseBodyData(). This ensures
* backwards compatibility with extensions that are not yet aware of
* parseBodyData().
*/
public function testCustomBodyValidator() {
$this->expectDeprecationAndContinue( '/overrides getBodyValidator/' );
$this->expectDeprecationAndContinue( '/Validator::validateBody/' );
$this->expectDeprecationAndContinue( '/JsonBodyValidator/' );
// This is valid JSON, but not an object.
// Automatic parsing will fail, since it re requires
// an array to be returned.
$payload = '{ "test": "yes" }';
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/old-body-validator' ),
'method' => 'PUT',
'bodyContents' => $payload,
'headers' => [ "content-type" => 'application/json-patch+json' ]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode(), (string)$response->getBody() );
}
/**
* Constructs a handler that disables body parsing
*/
public static function streamHandlerFactory(): Handler {
return new class extends Handler {
public function parseBodyData( RequestInterface $request ): ?array {
// Disable parsing
return null;
}
public function execute() {
Assert::assertNull( $this->getRequest()->getParsedBody() );
$body = $this->getRequest()->getBody();
$response = $this->getResponseFactory()->create();
$response->setBody( new StringStream( "$body" ) );
return $response;
}
};
}
/**
* Constructs a handler that uses a BodyValidator object
*/
public static function oldBodyValidatorFactory(): Handler {
return new class extends Handler {
private $postValidationSetupCalled = false;
public function getBodyValidator( $contentType ) {
if ( $contentType !== 'application/json-patch+json' ) {
throw new HttpException(
"Unsupported Content-Type",
415,
);
}
return new JsonBodyValidator( [
'test' => [
ParamValidator::PARAM_REQUIRED => true,
static::PARAM_SOURCE => 'body',
]
] );
}
public function execute() {
$body = $this->getValidatedBody();
Assert::assertIsArray( $body );
Assert::assertArrayHasKey( 'test', $body );
Assert::assertTrue( $this->postValidationSetupCalled );
return "";
}
protected function postValidationSetup() {
$this->postValidationSetupCalled = true;
}
};
}
/**
* Constructs a handler that echos a form data request body
*/
public static function formHandlerFactory(): Handler {
return new class extends Handler {
public function execute() {
return $this->getValidatedBody();
}
public function getParamSettings(): array {
return [
'foo' => [
Handler::PARAM_SOURCE => 'body'
]
];
}
public function getSupportedRequestTypes(): array {
return [
'application/x-www-form-urlencoded',
'multipart/form-data'
];
}
};
}
/**
* Constructs a handler that echos a JSON request body
*/
public static function dataHandlerFactory(): Handler {
return new class extends Handler {
public function execute() {
return $this->getValidatedBody();
}
public function getParamSettings(): array {
return [
'foo' => [
Handler::PARAM_SOURCE => 'body'
]
];
}
};
}
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' ]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 400, $response->getStatusCode() );
}
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'
]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
}
public function testPostRequestFailsWithoutBody() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
'method' => 'POST',
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 411, $response->getStatusCode() );
}
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!
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
}
public function testRequestBodyWithoutContentTypeFails() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
'method' => 'POST',
'bodyContents' => '{"foo":"bar"}', // Request body without content-type
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 415, $response->getStatusCode() );
}
public function testDeleteRequestWithoutBody() {
// Test DELETE request without body
$requestWithoutBody = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
'method' => 'DELETE',
] );
$router = $this->createRouter( $requestWithoutBody );
$responseWithoutBody = $router->execute( $requestWithoutBody );
$this->assertSame( 200, $responseWithoutBody->getStatusCode() );
}
public function testDeleteRequestWithBody() {
// Test DELETE request with body
$requestWithBody = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
'method' => 'DELETE',
'bodyContents' => '{"bodyParam":"bar"}',
'headers' => [ "content-type" => 'application/json' ]
] );
$router = $this->createRouter( $requestWithBody );
$responseWithBody = $router->execute( $requestWithBody );
$this->assertSame( 200, $responseWithBody->getStatusCode() );
}
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
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 415, $response->getStatusCode() );
}
public function testFormDataReturns415() {
$request = new RequestData( [
// NOTE: The data handler will fail with form data,
// only json is supported per default.
'uri' => new Uri( '/rest/mock/v1/RouterTest/data-handler' ),
'method' => 'POST',
'postParams' => [ 'foo' => 'bar' ],
'headers' => [ "content-type" => 'application/x-www-form-urlencoded' ]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 415, $response->getStatusCode() );
}
public function testFormDataSupported() {
// See T362850
$this->expectDeprecationAndContinue( '/The "post" source is deprecated/' );
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo_form_data' ),
'method' => 'POST',
'postParams' => [ 'foo' => 'bar' ],
'headers' => [ "content-type" => 'application/x-www-form-urlencoded' ]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
// Check if the response contains a field called 'parsedBody'
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
$this->assertSame( [ 'foo' => 'bar' ], $data[ 'parsedBody' ] );
}
public function testJsonBody() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
'method' => 'POST',
'bodyContents' => '{"bodyParam":"bar"}',
'headers' => [ "content-type" => 'application/json' ]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode() );
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
// Check the value of 'parsedBody' and 'validateBody' fields
$this->assertEquals( [ 'bodyParam' => 'bar' ], $data['parsedBody'] );
$this->assertEquals( [ 'bodyParam' => 'bar' ], $data['validatedBody'] );
$this->assertArrayNotHasKey( 'bodyParam', $data['validatedParams'] );
}
public function testFormDataBody() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo' ),
'method' => 'POST',
'postParams' => [ 'bodyParam' => 'bar' ],
'headers' => [
"content-type" => 'application/x-www-form-urlencoded',
"content-length" => 123,
]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode(), (string)$response->getBody() );
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
// The body parameter should be in parsedBody and validatedBody,
// but not in validatedParams.
$this->assertEquals( [ 'bodyParam' => 'bar' ], $data['parsedBody'] );
$this->assertEquals( [ 'bodyParam' => 'bar' ], $data['validatedBody'] );
$this->assertArrayNotHasKey( 'bodyParam', $data['validatedParams'] );
}
public function testFormDataBody_post() {
// See T362850
$this->expectDeprecationAndContinue( '/The "post" source is deprecated/' );
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo_form_data' ),
'method' => 'POST',
'postParams' => [ 'postParam' => 'bar' ],
'headers' => [
"content-type" => 'application/x-www-form-urlencoded',
"content-length" => 123,
]
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode(), (string)$response->getBody() );
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
// The post parameter should be in parsedBody and validatedParams,
// but not as in validatedBody.
$this->assertEquals( [ 'postParam' => 'bar' ], $data['parsedBody'] );
$this->assertArrayHasKey( 'postParam', $data['validatedParams'] );
$this->assertArrayNotHasKey( 'postParam', $data['validatedBody'] );
}
public function testHandlerCanAccessValidatedParams() {
$request = new RequestData( [
'uri' => new Uri( '/rest/mock/v1/RouterTest/echo/bar' ),
'method' => 'POST',
'headers' => [ "content-type" => 'application/json' ],
'bodyContents' => '{}'
] );
$router = $this->createRouter( $request );
$response = $router->execute( $request );
$this->assertSame( 200, $response->getStatusCode(), (string)$response->getBody() );
// Check if the response contains a field called 'pathParams'
$body = $response->getBody();
$body->rewind();
$data = json_decode( $body->getContents(), true );
$this->assertArrayHasKey( 'validatedParams', $data );
// Check the value of the 'pathParams' field
$validatedParams = $data['validatedParams'];
$this->assertEquals( 'bar', $validatedParams[ 'pathParam' ], (string)$response->getBody() );
}
/**
* @return CorsUtils
*/
private function getCorsUtils(): CorsUtils {
$cors = new CorsUtils(
new ServiceOptions(
CorsUtils::CONSTRUCTOR_OPTIONS,
[
MainConfigNames::AllowedCorsHeaders => [],
MainConfigNames::AllowCrossOrigin => [],
MainConfigNames::RestAllowCrossOriginCookieAuth => [],
MainConfigNames::CanonicalServer => 'testing',
MainConfigNames::CrossSiteAJAXdomains => [],
MainConfigNames::CrossSiteAJAXdomainExceptions => [],
]
),
new ResponseFactory( [] ),
new UserIdentityValue(
1,
'Test'
)
);
return $cors;
}
}