wiki.techinc.nl/tests/phpunit/unit/includes/Rest/Handler/HandlerTestTrait.php
Matthias Mullie c4a36b0023 Introduce SearchResultThumbnailProvider & move hook + NS_FILE thumbs in
What was previously a REST API-only feature (the thumbnails
hook allowing for thumbnails for non-file pages via the
PageImages extension) is now also being adopted in the main
search page.

That hook will now be called with NS_FILE result thumbnails
pre-filled, which was not the case previously. PageImages
essentially duplicated NS_FILE thumbnail logic that was
already present in Special:Search, so that can (and will
in a follow-up patch) then be removed there. Special:Search
will then simply take whatever is produced from the provider
(which will include both NS_FILE thumbs - which it handled
already - as well as whatever else it receives from the hook),
as will the REST API (which already received both)

Since thumbnails can now come in for multiple namespaces &
having some of those results with & others without a thumbnail
can be quite jarring, it was decided that we'd display
placeholder images (for certain namespaces). This is now
controlled by $wgThumbnailNamespaces.

I also split up a few things in FullSearchResultWidget::
generateFileHtml for more clarity.

Meanwhile also updated mediawiki.special.search.styles.less
to use variables for known colors.

Also implemented a 'transform' (required for testing this
change properly) and 'getDisplayWidthHeight' (it became
needed after implementing transform) callback function for
mock Files, and updated some existing tests in response to
these changes.

And some more Rest test files have been updated to allow
passing around a HookContainer instead of only an array of
hooks (from which a new HookContainer would then be created)
to allow the same container to be used across all relevant
objects, who may have it injected as dependency.

Bug: T306883
Change-Id: I2a679b51758020d3e822da01a1bde1ae632b0b0a
2022-10-03 10:52:50 +00:00

233 lines
7.2 KiB
PHP

<?php
namespace MediaWiki\Tests\Rest\Handler;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Permissions\Authority;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\HttpException;
use MediaWiki\Rest\RequestInterface;
use MediaWiki\Rest\Response;
use MediaWiki\Rest\ResponseFactory;
use MediaWiki\Rest\ResponseInterface;
use MediaWiki\Rest\Router;
use MediaWiki\Rest\Validator\Validator;
use MediaWiki\Tests\Unit\Permissions\MockAuthorityTrait;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use Wikimedia\Message\ITextFormatter;
use Wikimedia\Message\MessageValue;
use Wikimedia\ObjectFactory\ObjectFactory;
use Wikimedia\Services\ServiceContainer;
/**
* A trait providing utility functions for testing Handler classes.
* This trait is intended to be used on subclasses of MediaWikiUnitTestCase
* or MediaWikiIntegrationTestCase.
*
* @stable to use
* @package MediaWiki\Tests\Rest\Handler
*/
trait HandlerTestTrait {
use MockAuthorityTrait;
use SessionHelperTestTrait;
/**
* Calls init() on the Handler, supplying a mock Router and ResponseFactory.
*
* @internal to the trait
* @param Handler $handler
* @param RequestInterface $request
* @param array $config
* @param HookContainer|array $hooks Hook container or array of hooks
* @param Authority|null $authority
* @param bool $csrfSafe
*/
private function initHandler(
Handler $handler,
RequestInterface $request,
$config = [],
$hooks = [],
Authority $authority = null,
bool $csrfSafe = false
) {
$formatter = $this->createMock( ITextFormatter::class );
$formatter->method( 'format' )->willReturnCallback( static function ( MessageValue $msg ) {
return $msg->dump();
} );
/** @var ResponseFactory|MockObject $responseFactory */
$responseFactory = new ResponseFactory( [ 'qqx' => $formatter ] );
/** @var Router|MockObject $router */
$router = $this->createNoOpMock( Router::class, [ 'getRouteUrl' ] );
$router->method( 'getRouteUrl' )->willReturnCallback( static function ( $route, $path = [], $query = [] ) {
foreach ( $path as $param => $value ) {
$route = str_replace( '{' . $param . '}', urlencode( (string)$value ), $route );
}
return wfAppendQuery( 'https://wiki.example.com/rest' . $route, $query );
} );
$authority = $authority ?: $this->mockAnonUltimateAuthority();
$hookContainer = $hooks instanceof HookContainer ? $hooks : $this->createHookContainer( $hooks );
$handler->init( $router, $request, $config, $authority, $responseFactory, $hookContainer,
$this->getSession( $csrfSafe )
);
}
/**
* Calls validate() on the Handler, with an appropriate Validator supplied.
*
* @internal to the trait
* @param Handler $handler
* @param null|Validator $validator
* @throws HttpException
*/
private function validateHandler(
Handler $handler,
Validator $validator = null
) {
if ( !$validator ) {
/** @var ServiceContainer|MockObject $serviceContainer */
$serviceContainer = $this->createNoOpMock( ServiceContainer::class );
$objectFactory = new ObjectFactory( $serviceContainer );
$validator = new Validator( $objectFactory, $handler->getRequest(), $handler->getAuthority() );
}
$handler->validate( $validator );
}
/**
* Creates a mock Validator to bypass actual request query, path, and/or body param validation
*
* @internal to the trait
* @param array $queryPathParams
* @param array $bodyParams
* @return Validator|MockObject
*/
private function getMockValidator( array $queryPathParams, array $bodyParams ): Validator {
$validator = $this->createNoOpMock( Validator::class, [ 'validateParams', 'validateBody' ] );
if ( $queryPathParams ) {
$validator->method( 'validateParams' )->willReturn( $queryPathParams );
}
if ( $bodyParams ) {
$validator->method( 'validateBody' )->willReturn( $bodyParams );
}
return $validator;
}
/**
* Executes the given Handler on the given request.
*
* @param Handler $handler
* @param RequestInterface $request
* @param array $config
* @param HookContainer|array $hooks Hook container or array of hooks
* @param array $validatedParams Path/query params to return as already valid
* @param array $validatedBody Body params to return as already valid
* @param Authority|null $authority
* @param bool $csrfSafe
* @return ResponseInterface
*/
private function executeHandler(
Handler $handler,
RequestInterface $request,
$config = [],
$hooks = [],
$validatedParams = [],
$validatedBody = [],
Authority $authority = null,
bool $csrfSafe = false
): ResponseInterface {
// supply defaults for required fields in $config
$config += [ 'path' => '/test' ];
$this->initHandler( $handler, $request, $config, $hooks, $authority, $csrfSafe );
$validator = null;
if ( $validatedParams || $validatedBody ) {
/** @var Validator|MockObject $validator */
$validator = $this->getMockValidator( $validatedParams, $validatedBody );
}
$this->validateHandler( $handler, $validator );
// Check conditional request headers
$earlyResponse = $handler->checkPreconditions();
if ( $earlyResponse ) {
return $earlyResponse;
}
$ret = $handler->execute();
$response = $ret instanceof Response ? $ret
: $handler->getResponseFactory()->createFromReturnValue( $ret );
// Set Last-Modified and ETag headers in the response if available
$handler->applyConditionalResponseHeaders( $response );
return $response;
}
/**
* Executes the given Handler on the given request, parses the response body as JSON,
* and returns the result.
*
* @param Handler $handler
* @param RequestInterface $request
* @param array $config
* @param HookContainer|array $hooks Hook container or array of hooks
* @param array $validatedParams
* @param array $validatedBody
* @param Authority|null $authority
* @param bool $csrfSafe
* @return array
*/
private function executeHandlerAndGetBodyData(
Handler $handler,
RequestInterface $request,
$config = [],
$hooks = [],
$validatedParams = [],
$validatedBody = [],
Authority $authority = null,
bool $csrfSafe = false
): array {
$response = $this->executeHandler( $handler, $request, $config, $hooks,
$validatedParams, $validatedBody, $authority, $csrfSafe );
$this->assertTrue(
$response->getStatusCode() >= 200 && $response->getStatusCode() < 300,
'Status should be in 2xx range.'
);
$this->assertSame( 'application/json', $response->getHeaderLine( 'Content-Type' ) );
$data = json_decode( $response->getBody(), true );
$this->assertIsArray( $data, 'Body must be a JSON array' );
return $data;
}
/**
* Executes the given Handler on the given request, and returns the HttpException thrown.
* Fails if no HttpException is thrown.
*
* @param Handler $handler
* @param RequestInterface $request
* @param array $config
* @param HookContainer|array $hooks Hook container or array of hooks
*
* @return HttpException
*/
private function executeHandlerAndGetHttpException(
Handler $handler,
RequestInterface $request,
$config = [],
$hooks = []
): HttpException {
try {
$this->executeHandler( $handler, $request, $config, $hooks );
Assert::fail( 'Expected a HttpException to be thrown' );
} catch ( HttpException $ex ) {
return $ex;
}
}
}