wiki.techinc.nl/tests/phpunit/unit/includes/Rest/Handler/UserContributionsHandlerTest.php
Umherirrender a1de8b8700 Tests: Mark more more closures as static
Result of a new sniff I25a17fb22b6b669e817317a0f45051ae9c608208

Bug: T274036
Change-Id: I695873737167a75f0d94901fa40383a33984ca55
2021-02-09 02:55:57 +00:00

390 lines
12 KiB
PHP

<?php
namespace MediaWiki\Tests\Rest\Handler;
use CommentStoreComment;
use MediaWiki\Permissions\Authority;
use MediaWiki\Rest\Handler\UserContributionsHandler;
use MediaWiki\Rest\LocalizedHttpException;
use MediaWiki\Rest\RequestData;
use MediaWiki\Rest\RequestInterface;
use MediaWiki\Revision\ContributionsLookup;
use MediaWiki\Revision\ContributionsSegment;
use MediaWiki\Storage\MutableRevisionRecord;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use MediaWiki\User\UserNameUtils;
use Message;
use MockTitleTrait;
use PHPUnit\Framework\MockObject\MockObject;
use Wikimedia\Message\MessageValue;
/**
* @covers \MediaWiki\Rest\Handler\UserContributionsHandler
*/
class UserContributionsHandlerTest extends \MediaWikiUnitTestCase {
use HandlerTestTrait;
use MockTitleTrait;
private const DEFAULT_LIMIT = 20;
private function makeFakeRevisions( int $numRevs, int $limit, int $segment = 1 ) {
$revisions = [];
$title = $this->makeMockTitle( 'Main_Page', [ 'id' => 1 ] );
for ( $i = $numRevs; $i >= 1; $i-- ) {
$rev = new MutableRevisionRecord( $title );
$ogTimestamp = '2020010100000';
$rev->setId( $i );
$rev->setSize( 256 );
$rev->setComment( CommentStoreComment::newUnsavedComment( 'Edit ' . $i ) );
$rev->setTimestamp( $ogTimestamp . $i );
$revisions[] = $rev;
}
return array_slice( $revisions, $segment - 1, $limit );
}
/**
* @param int $numRevisions
* @param string[] $tags
* @param array $deltas
* @param array $flags
*
* @return ContributionsLookup|MockObject
*/
private function newContributionsLookup( $numRevisions = 5, $tags = [], $deltas = [], $flags = [] ) {
/** @var MockObject|ContributionsLookup $mockContributionsLookup */
$mockContributionsLookup = $this->createNoOpMock( ContributionsLookup::class,
[ 'getContributions' ]
);
$fakeRevisions = $this->makeFakeRevisions( $numRevisions, 2 );
foreach ( $tags as $revId => $tagArray ) {
$tags[ $revId ] = [];
foreach ( $tagArray as $name ) {
$mockMessage = $this->createNoOpMock( Message::class, [ 'parse', 'getKey' ] );
$mockMessage->method( 'parse' )->willReturn( "<i>$name</i>" );
$mockMessage->method( 'getKey' )->willReturn( "tag-$name" );
$tags[ $revId ][ $name ] = $mockMessage;
}
}
$fakeSegment = $this->makeSegment( $fakeRevisions, $tags, $deltas, $flags );
$mockContributionsLookup->method( 'getContributions' )->willReturn( $fakeSegment );
return $mockContributionsLookup;
}
/**
* Returns a mock ContributionLookup that asserts getContributions()
* is called with the same params that were originally passed into the request.
* @param RequestInterface $request
* @param UserIdentity $target
* @param Authority $performer
* @return ContributionsLookup|MockObject
*/
private function newContributionsLookupForRequest(
RequestInterface $request,
UserIdentity $target,
Authority $performer
) {
$limit = $request->getQueryParams()['limit'] ?? self::DEFAULT_LIMIT;
$segment = $request->getQueryParams()['segment'] ?? '';
$tag = $request->getQueryParams()['tag'] ?? null;
$fakeRevisions = $this->makeFakeRevisions( 5, $limit );
$fakeSegment = $this->makeSegment( $fakeRevisions );
$mockContributionsLookup = $this->createNoOpMock( ContributionsLookup::class,
[ 'getContributions' ]
);
$mockContributionsLookup->method( 'getContributions' )
->willReturnCallback(
function (
$actualTarget,
$actualLimit,
$actualPerformer,
$actualSegment,
$actualTag
) use ( $target, $limit, $performer, $segment, $tag, $fakeSegment ) {
$this->assertSame( $target->getName(), $actualTarget->getName() );
$this->assertSame( $limit, $actualLimit );
$this->assertTrue(
$performer->getActor()->equals( $actualPerformer->getActor() )
);
$this->assertSame( $segment, $actualSegment );
$this->assertSame( $tag, $actualTag );
return $fakeSegment;
}
);
return $mockContributionsLookup;
}
/**
* @param ContributionsLookup|null $contributionsLookup
*
* @return UserContributionsHandler
*/
private function newHandler( ContributionsLookup $contributionsLookup = null ) {
if ( !$contributionsLookup ) {
$contributionsLookup = $this->newContributionsLookup();
}
$mockUserNameUtils = $this->createNoOpMock( UserNameUtils::class,
[ 'isIP' ]
);
$mockUserNameUtils->method( 'isIP' )
->willReturnCallback( static function ( $name ) {
return $name === '127.0.0.1';
} );
return new UserContributionsHandler(
$contributionsLookup,
$mockUserNameUtils
);
}
private function makeSegment( $revisions, array $tags = [], $deltas = [], array $flags = [] ) {
if ( $revisions !== [] ) {
$latestRevision = $revisions[ count( $revisions ) - 1 ];
$earliestRevision = $revisions[0];
$before = 'before|' . $latestRevision->getTimestamp();
$after = 'after|' . $earliestRevision->getTimestamp();
return new ContributionsSegment( $revisions, $tags, $before, $after, $deltas, $flags );
}
return new ContributionsSegment( $revisions, $tags, null, null, $deltas, $flags );
}
public function provideValidQueryParameters() {
yield [ [] ];
yield [ [ 'limit' => self::DEFAULT_LIMIT ] ];
yield [ [ 'tag' => 'test', 'limit' => 7 ] ];
yield [ [ 'segment' => 'before|20200101000005' ] ];
yield [ [ 'segment' => 'after|20200101000001' ] ];
}
/**
* @param array $queryParams
* @dataProvider provideValidQueryParameters
*/
public function testThatParametersAreHandledCorrectlyForMeEndpoint( $queryParams ) {
$request = new RequestData( [ 'queryParams' => $queryParams ] );
$performer = $this->mockRegisteredUltimateAuthority();
$performingUser = $performer->getActor();
$validatedParams = [
'user' => null,
'limit' => $queryParams['limit'] ?? self::DEFAULT_LIMIT,
'tag' => $queryParams['tag'] ?? null,
'segment' => $queryParams['segment'] ?? '',
];
$mockContributionsLookup = $this->newContributionsLookupForRequest( $request, $performingUser, $performer );
$handler = $this->newHandler( $mockContributionsLookup );
$response = $this->executeHandler( $handler, $request, [ 'mode' => 'me' ],
[], $validatedParams, [], $performer );
$this->assertSame( 200, $response->getStatusCode() );
}
/**
* @param array $queryParams
* @dataProvider provideValidQueryParameters
*/
public function testThatParametersAreHandledCorrectlyForUserEndpoint( $queryParams ) {
$username = 'Test';
$target = new UserIdentityValue( 7, $username, 7 );
$performer = $this->mockRegisteredUltimateAuthority();
$request = new RequestData( [
'pathParams' => [ 'user' => $target->getName() ],
'queryParams' => $queryParams ]
);
$validatedParams =
[
'user' => $target,
'limit' => $queryParams['limit'] ?? self::DEFAULT_LIMIT,
'tag' => $queryParams['tag'] ?? null,
'segment' => $queryParams['segment'] ?? '',
];
$mockContributionsLookup = $this->newContributionsLookupForRequest( $request, $target, $performer );
$handler = $this->newHandler( $mockContributionsLookup );
$response = $this->executeHandler( $handler, $request, [ 'mode' => 'user' ], [], $validatedParams, [],
$performer );
$this->assertSame( 200, $response->getStatusCode() );
}
public function testThatAnonymousUserReturns401() {
$handler = $this->newHandler();
$request = new RequestData( [] );
// UserDef transforms parameter name to ip
$validatedParams = [
'ip' => new UserIdentityValue( 0, '127.0.0.1', 0 ),
'limit' => self::DEFAULT_LIMIT,
'tag' => null,
'segment' => ''
];
$this->expectExceptionObject(
new LocalizedHttpException( new MessageValue( 'rest-permission-denied-anon' ), 401 )
);
$this->executeHandler( $handler, $request, [ 'mode' => 'me' ], [], $validatedParams );
}
public function testThatUnknownUserReturns404() {
$handler = $this->newHandler();
$username = 'UNKNOWN';
$request = new RequestData( [ 'pathParams' => [ 'user' => $username ] ] );
$validatedParams = [
'user' => new UserIdentityValue( 0, $username, 0 ),
'limit' => self::DEFAULT_LIMIT,
'tag' => null,
'segment' => ''
];
$this->expectExceptionObject(
new LocalizedHttpException( new MessageValue( 'rest-nonexistent-user' ), 404 )
);
$this->executeHandler( $handler, $request, [ 'mode' => 'user' ], [], $validatedParams );
}
public function testThatIpUserReturns200() {
$handler = $this->newHandler();
$username = '127.0.0.1';
$requestData = [ 'pathParams' => [ 'user' => $username ] ];
$request = new RequestData( $requestData );
$validatedParams = [
'user' => new UserIdentityValue( 0, $username, 0 ),
'limit' => self::DEFAULT_LIMIT,
'tag' => null,
'segment' => ''
];
$data = $this->executeHandlerAndGetBodyData( $handler, $request, [ 'mode' => 'user' ], [], $validatedParams );
$this->assertArrayHasKey( 'contributions', $data );
}
public function provideThatResponseConformsToSchema() {
$basePath = 'https://wiki.example.com/rest/me/contributions';
yield [ 0,
[],
[],
[ 'newest' => true, 'oldest' => true ],
[],
[
'older' => null,
'newer' => $basePath . '?limit=20',
'latest' => $basePath . '?limit=20',
'contributions' => []
]
];
yield [ 0,
[],
[],
[ 'newest' => true, 'oldest' => true ],
[ 'tag' => 'test' ],
[
'older' => null,
'newer' => $basePath . '?limit=20&tag=test',
'latest' => $basePath . '?limit=20&tag=test',
'contributions' => []
]
];
yield [ 1,
[ 1 => [ 'frob' ] ],
[ 1 => 256 ],
[ 'newest' => true, 'oldest' => true ],
[ 'limit' => 7 ],
[
'older' => null,
'newer' => $basePath . '?limit=7&segment=after%7C20200101000001',
'latest' => $basePath . '?limit=7',
'contributions' => [
[
'id' => 1,
'comment' => 'Edit 1',
'timestamp' => '2020-01-01T00:00:01Z',
'delta' => 256,
'size' => 256,
'tags' => [ [ 'name' => 'frob', 'description' => '<i>frob</i>' ] ],
'type' => 'revision',
'page' => [
'id' => 1,
'key' => 'Main_Page',
'title' => 'Main Page'
]
]
]
]
];
yield [ 5,
[ 5 => [ 'frob', 'nitz' ] ],
[ 1 => 256, 2 => 256, 3 => 256, 4 => null, 5 => 256 ],
[ 'newest' => true ],
[ 'tag' => 'test' ],
[
'older' => $basePath . '?limit=20&tag=test&segment=before%7C20200101000004',
'newer' => $basePath . '?limit=20&tag=test&segment=after%7C20200101000005',
'latest' => $basePath . '?limit=20&tag=test',
'contributions' => [
[
'id' => 5,
'comment' => 'Edit 5',
'timestamp' => '2020-01-01T00:00:05Z',
'delta' => 256,
'size' => 256,
'tags' => [
[ 'name' => 'frob', 'description' => '<i>frob</i>' ],
[ 'name' => 'nitz', 'description' => '<i>nitz</i>' ]
],
'type' => 'revision',
'page' => [
'id' => 1,
'key' => 'Main_Page',
'title' => 'Main Page'
]
],
[
'id' => 4,
'comment' => 'Edit 4',
'timestamp' => '2020-01-01T00:00:04Z',
'delta' => null,
'size' => 256,
'tags' => [],
'type' => 'revision',
'page' => [
'id' => 1,
'key' => 'Main_Page',
'title' => 'Main Page'
]
]
]
]
];
}
/**
* @dataProvider provideThatResponseConformsToSchema
*/
public function testThatResponseConformsToSchema(
$numRevisions,
$tags,
$deltas,
$flags,
$query,
$expectedResponse
) {
$lookup = $this->newContributionsLookup( $numRevisions, $tags, $deltas, $flags );
$handler = $this->newHandler( $lookup );
$request = new RequestData( [ 'queryParams' => $query ] );
$validatedParams = [
'user' => null,
'limit' => $query['limit'] ?? self::DEFAULT_LIMIT,
'tag' => $query['tag'] ?? null,
'segment' => $query['segment'] ?? '',
];
$config = [ 'path' => '/me/contributions', 'mode' => 'me' ];
$response = $this->executeHandlerAndGetBodyData( $handler, $request, $config, [], $validatedParams, [],
$this->mockRegisteredUltimateAuthority() );
$this->assertSame( $expectedResponse, $response );
}
}