wiki.techinc.nl/tests/phpunit/structure/RestStructureTest.php
Umherirrender 62002cdcf1 build: Update mediawiki/mediawiki-codesniffer to 35.0.0
Change-Id: Idb413be4b8cba8611afdc022af59810ce1a4531e
2021-01-31 13:34:38 +00:00

182 lines
5.8 KiB
PHP

<?php
use MediaWiki\Rest\CorsUtils;
use MediaWiki\Rest\EntryPoint;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\PathTemplateMatcher\PathMatcher;
use MediaWiki\Rest\RequestData;
use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\Router;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\TestingAccessWrapper;
/**
* Checks that all REST Handlers, core and extensions, conform to the conventions:
* - parameters in path have correct PARAM_SOURCE
* - path parameters not in path are not required
* - do not have inconsistencies in the parameter definitions
*/
class RestStructureTest extends MediaWikiIntegrationTestCase {
/** @var Router */
private $router;
/**
* Initialize/fetch the Router instance for testing
* @return Router
*/
private function getRouter() {
if ( !$this->router ) {
$context = new DerivativeContext( RequestContext::getMain() );
$context->setLanguage( 'en' );
$context->setTitle(
Title::makeTitle( NS_SPECIAL, 'Badtitle/dummy title for RestStructureTest' )
);
$responseFactory = $this->createNoOpMock( ResponseFactory::class );
$cors = $this->createNoOpMock( CorsUtils::class );
$this->router = TestingAccessWrapper::newFromClass( EntryPoint::class )
->createRouter( $context, new RequestData(), $responseFactory, $cors );
}
return $this->router;
}
/**
* @dataProvider providePathParameters
*/
public function testPathParameters( array $spec ) : void {
$router = TestingAccessWrapper::newFromObject( $this->getRouter() );
$request = new RequestData();
$handler = $router->createHandler( $request, $spec );
$params = $handler->getParamSettings();
$dataName = $this->dataName();
// Test that all parameters in the path exist and are declared as such
$matcher = TestingAccessWrapper::newFromObject( new PathMatcher );
$pathParams = [];
foreach ( explode( '/', $spec['path'] ) as $part ) {
$param = $matcher->getParamName( $part );
if ( $param !== false ) {
$this->assertArrayHasKey( $param, $params, "Path parameter $param exists" );
$this->assertSame( 'path', $params[$param][Handler::PARAM_SOURCE] ?? null,
"$dataName: Path parameter {{$param}} must have PARAM_SOURCE = 'path'" );
$pathParams[$param] = true;
}
}
// Test that any path parameters not in the path aren't marked as required
foreach ( $params as $param => $settings ) {
if ( ( $settings[Handler::PARAM_SOURCE] ?? null ) === 'path' &&
!isset( $pathParams[$param] )
) {
$this->assertFalse( $settings[ParamValidator::PARAM_REQUIRED] ?? false,
"$dataName, parameter $param: PARAM_REQUIRED cannot be true for a path parameter "
. 'not in the path'
);
}
}
// In case there were no path parameters
$this->addToAssertionCount( 1 );
}
public function providePathParameters() : Iterator {
$router = TestingAccessWrapper::newFromObject( $this->getRouter() );
foreach ( $router->getAllRoutes() as $spec ) {
$method = $spec['method'] ?? 'GET';
$method = implode( ",", (array)$method );
yield "Handler {$method} {$spec['path']}" => [ $spec ];
}
}
/**
* @dataProvider provideParameters
*/
public function testParameters( array $spec, string $name, $settings ) : void {
static $sources = [ 'path', 'query', 'post' ];
$router = TestingAccessWrapper::newFromObject( $this->getRouter() );
$request = new RequestData();
$dataName = $this->dataName();
$this->assertNotSame( '', $name, "$dataName: Name cannot be empty" );
$paramValidator = TestingAccessWrapper::newFromObject( $router->restValidator )->paramValidator;
$ret = $paramValidator->checkSettings( $name, $settings, [ 'source' => 'unspecified' ] );
// REST-specific parameters
$ret['allowedKeys'][] = Handler::PARAM_SOURCE;
if ( !in_array( $settings[Handler::PARAM_SOURCE] ?? '', $sources, true ) ) {
$ret['issues'][Handler::PARAM_SOURCE] = "PARAM_SOURCE must be 'path', 'query', or 'post'";
}
// Warn about unknown keys. Don't fail, they might be for forward- or back-compat.
if ( is_array( $settings ) ) {
$keys = array_diff(
array_keys( $settings ),
$ret['allowedKeys']
);
if ( $keys ) {
$this->addWarning(
"$dataName: Unrecognized settings keys were used: " . implode( ', ', $keys )
);
}
}
if ( count( $ret['issues'] ) === 1 ) {
$this->fail( "$dataName: Validation failed: " . reset( $ret['issues'] ) );
} elseif ( $ret['issues'] ) {
$this->fail( "$dataName: Validation failed:\n* " . implode( "\n* ", $ret['issues'] ) );
}
// Check message existence
$done = [];
foreach ( $ret['messages'] as $msg ) {
// We don't really care about the parameters, so do it simply
$key = $msg->getKey();
if ( !isset( $done[$key] ) ) {
$done[$key] = true;
$this->assertTrue( Message::newFromKey( $key )->exists(),
"$dataName: Parameter message $key exists" );
}
}
}
public function provideParameters() : Iterator {
$router = TestingAccessWrapper::newFromObject( $this->getRouter() );
$request = new RequestData();
foreach ( $router->getAllRoutes() as $spec ) {
$handler = $router->createHandler( $request, $spec );
$params = $handler->getParamSettings();
foreach ( $params as $param => $settings ) {
$method = $spec['method'] ?? 'GET';
$method = implode( ",", (array)$method );
yield "Handler {$method} {$spec['path']}, parameter $param" => [ $spec, $param, $settings ];
}
}
}
public function testRoutePathAndMethodForDuplicates() {
$router = TestingAccessWrapper::newFromObject( $this->getRouter() );
$routes = [];
foreach ( $router->getAllRoutes() as $spec ) {
$method = $spec['method'] ?? 'GET';
$method = (array)$method;
foreach ( $method as $m ) {
$key = "{$m} {$spec['path']}";
$this->assertArrayNotHasKey( $key, $routes, "{$key} already exists in routes" );
$routes[$key] = true;
}
}
}
}