244 lines
7.2 KiB
PHP
244 lines
7.2 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Tests\Rest\Handler;
|
|
|
|
use GenderCache;
|
|
use Language;
|
|
use MediaWiki\Interwiki\InterwikiLookup;
|
|
use MediaWiki\Linker\LinkTarget;
|
|
use MediaWiki\Permissions\PermissionManager;
|
|
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\User\UserIdentityValue;
|
|
use MediaWikiTestCaseTrait;
|
|
use MediaWikiTitleCodec;
|
|
use NamespaceInfo;
|
|
use PHPUnit\Framework\Assert;
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
|
use Title;
|
|
use User;
|
|
use Wikimedia\Message\ITextFormatter;
|
|
use Wikimedia\Message\MessageValue;
|
|
use Wikimedia\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.
|
|
*
|
|
* @package MediaWiki\Tests\Rest\Handler
|
|
*/
|
|
trait HandlerTestTrait {
|
|
|
|
use MediaWikiTestCaseTrait;
|
|
|
|
/** @var int */
|
|
private $pageIdCounter = 0;
|
|
|
|
/**
|
|
* Expected to be provided by the class, probably inherited from TestCase.
|
|
*
|
|
* @param string $originalClassName
|
|
*
|
|
* @return MockObject
|
|
*/
|
|
abstract protected function createMock( $originalClassName ): MockObject;
|
|
|
|
/**
|
|
* Executes the given Handler on the given request.
|
|
*
|
|
* @param Handler $handler
|
|
* @param RequestInterface $request
|
|
* @param array $config
|
|
*
|
|
* @return ResponseInterface
|
|
*/
|
|
private function executeHandler( Handler $handler, RequestInterface $request, $config = [] ) {
|
|
$formatter = $this->createMock( ITextFormatter::class );
|
|
$formatter->method( 'format' )->willReturnCallback( function ( MessageValue $msg ) {
|
|
return $msg->dump();
|
|
} );
|
|
|
|
/** @var ResponseFactory|MockObject $responseFactory */
|
|
$responseFactory = new ResponseFactory( [ 'qqx' => $formatter ] );
|
|
|
|
/** @var PermissionManager|MockObject $permissionManager */
|
|
$permissionManager = $this->createNoOpMock(
|
|
PermissionManager::class, [ 'userCan', 'userHasRight' ]
|
|
);
|
|
$permissionManager->method( 'userCan' )->willReturn( true );
|
|
$permissionManager->method( 'userHasRight' )->willReturn( true );
|
|
|
|
/** @var ServiceContainer|MockObject $serviceContainer */
|
|
$serviceContainer = $this->createNoOpMock( ServiceContainer::class );
|
|
$objectFactory = new ObjectFactory( $serviceContainer );
|
|
|
|
$user = new UserIdentityValue( 0, 'Fake User', 0 );
|
|
$validator = new Validator( $objectFactory, $permissionManager, $request, $user );
|
|
|
|
/** @var Router|MockObject $router */
|
|
$router = $this->createNoOpMock( Router::class, [ 'getRouteUrl' ] );
|
|
$router->method( 'getRouteUrl' )->willReturnCallback( function ( $route, $query = [] ) {
|
|
return wfAppendQuery( 'https://wiki.example.com/rest' . $route, $query );
|
|
} );
|
|
|
|
$handler->init( $router, $request, $config, $responseFactory );
|
|
$handler->validate( $validator );
|
|
|
|
// Check conditional request headers
|
|
$earlyResponse = $handler->checkPreconditions();
|
|
if ( $earlyResponse ) {
|
|
return $earlyResponse;
|
|
}
|
|
|
|
$ret = $handler->execute();
|
|
|
|
$response = $ret instanceof Response ? $ret
|
|
: $responseFactory->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
|
|
*
|
|
* @return array
|
|
*/
|
|
private function executeHandlerAndGetBodyData(
|
|
Handler $handler,
|
|
RequestInterface $request,
|
|
$config = []
|
|
) {
|
|
$response = $this->executeHandler( $handler, $request, $config );
|
|
|
|
$this->assertTrue( $response->getStatusCode() >= 200 && $response->getStatusCode() < 300 );
|
|
$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
|
|
*
|
|
* @return HttpException
|
|
*/
|
|
private function executeHandlerAndGetHttpException(
|
|
Handler $handler,
|
|
RequestInterface $request,
|
|
$config = []
|
|
) {
|
|
try {
|
|
$this->executeHandler( $handler, $request, $config );
|
|
Assert::fail( 'Expected a HttpException to be thrown' );
|
|
} catch ( HttpException $ex ) {
|
|
return $ex;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $text
|
|
* @param array $props Additional properties to set. Supported keys:
|
|
* - id: int
|
|
* - namespace: int
|
|
*
|
|
* @return Title|MockObject
|
|
*/
|
|
private function makeMockTitle( $text, array $props = [] ) {
|
|
$id = $props['id'] ?? ++$this->pageIdCounter;
|
|
$ns = $props['namespace'] ?? 0;
|
|
$nsName = $ns ? "ns$ns:" : '';
|
|
|
|
$preText = $text;
|
|
$text = preg_replace( '/^[\w ]*?:/', '', $text );
|
|
|
|
// If no namespace prefix was given, add one if needed.
|
|
if ( $preText == $text && $ns ) {
|
|
$preText = $nsName . $text;
|
|
}
|
|
|
|
/** @var Title|MockObject $title */
|
|
$title = $this->createMock( Title::class );
|
|
|
|
$title->method( 'getText' )->willReturn( str_replace( '_', ' ', $text ) );
|
|
$title->method( 'getDBkey' )->willReturn( str_replace( ' ', '_', $text ) );
|
|
|
|
$title->method( 'getPrefixedText' )->willReturn( str_replace( '_', ' ', $preText ) );
|
|
$title->method( 'getPrefixedDBkey' )->willReturn( str_replace( ' ', '_', $preText ) );
|
|
|
|
$title->method( 'getArticleID' )->willReturn( $id );
|
|
$title->method( 'getNamespace' )->willReturn( $props['namespace'] ?? 0 );
|
|
$title->method( 'exists' )->willReturn( $id > 0 );
|
|
$title->method( 'getTouched' )->willReturn( $id ? '20200101223344' : false );
|
|
|
|
return $title;
|
|
}
|
|
|
|
/**
|
|
* @return PermissionManager|MockObject
|
|
*/
|
|
private function makeMockPermissionManager() {
|
|
/** @var PermissionManager|MockObject $permissionManager */
|
|
$permissionManager = $this->createNoOpMock(
|
|
PermissionManager::class, [ 'userCan' ]
|
|
);
|
|
$permissionManager->method( 'userCan' )
|
|
->willReturnCallback( function ( $action, User $user, LinkTarget $page ) {
|
|
return !preg_match( '/Forbidden/', $page->getText() );
|
|
} );
|
|
|
|
return $permissionManager;
|
|
}
|
|
|
|
/**
|
|
* @return MediaWikiTitleCodec
|
|
*/
|
|
private function makeMockTitleCodec() {
|
|
/** @var Language|MockObject $language */
|
|
$language = $this->createNoOpMock( Language::class, [ 'ucfirst' ] );
|
|
$language->method( 'ucfirst' )->willReturnCallback( 'ucfirst' );
|
|
|
|
/** @var GenderCache|MockObject $genderCache */
|
|
$genderCache = $this->createNoOpMock( GenderCache::class );
|
|
|
|
/** @var InterwikiLookup|MockObject $interwikiLookup */
|
|
$interwikiLookup = $this->createNoOpMock( InterwikiLookup::class );
|
|
|
|
/** @var NamespaceInfo|MockObject $namespaceInfo */
|
|
$namespaceInfo = $this->createNoOpMock( NamespaceInfo::class, [ 'isCapitalized' ] );
|
|
$namespaceInfo->method( 'isCapitalized' )->willReturn( true );
|
|
|
|
$titleCodec = new MediaWikiTitleCodec(
|
|
$language,
|
|
$genderCache,
|
|
[ 'en' ],
|
|
$interwikiLookup,
|
|
$namespaceInfo
|
|
);
|
|
|
|
return $titleCodec;
|
|
}
|
|
|
|
}
|