wiki.techinc.nl/tests/phpunit/unit/includes/Rest/Handler/UserContributionsHandlerTest.php
Umherirrender 2909d06a08 Use new namespace for revision related classes
All revision related classes are namespaced MediaWiki\Revision
instead of MediaWiki\Storage since 1.32. The old namespaced
class names are deprecated and only kept for backwards-compatibility.

Bug: T305784
Change-Id: I34e492d84d9fc4bc78481667202716d93b3c43cb
2022-04-14 23:03:43 +02:00

382 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\Revision\MutableRevisionRecord;
use MediaWiki\Tests\Unit\DummyServicesTrait;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserIdentityValue;
use Message;
use MockTitleTrait;
use PHPUnit\Framework\MockObject\MockObject;
use Wikimedia\Message\MessageValue;
/**
* @covers \MediaWiki\Rest\Handler\UserContributionsHandler
*/
class UserContributionsHandlerTest extends \MediaWikiUnitTestCase {
use DummyServicesTrait;
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->getUser()->equals( $actualPerformer->getUser() )
);
$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();
}
return new UserContributionsHandler(
$contributionsLookup,
$this->getDummyUserNameUtils()
);
}
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->getUser();
$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 );
$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' ),
'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 ),
'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 ),
'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 );
}
}