3 use MediaWiki\Cache\LinkBatchFactory
;
4 use MediaWiki\CommentFormatter\CommentFormatter
;
5 use MediaWiki\Context\RequestContext
;
6 use MediaWiki\HookContainer\HookContainer
;
7 use MediaWiki\Linker\LinkRenderer
;
8 use MediaWiki\Pager\DeletedContribsPager
;
9 use MediaWiki\Revision\RevisionRecord
;
10 use MediaWiki\Revision\RevisionStore
;
11 use MediaWiki\Title\NamespaceInfo
;
12 use MediaWiki\Title\Title
;
13 use MediaWiki\User\User
;
14 use MediaWiki\User\UserFactory
;
15 use MediaWiki\User\UserIdentityValue
;
16 use Wikimedia\Rdbms\IConnectionProvider
;
17 use Wikimedia\TestingAccessWrapper
;
21 * @covers \MediaWiki\Pager\DeletedContribsPager
23 class DeletedContribsPagerTest
extends MediaWikiIntegrationTestCase
{
24 private static User
$user;
26 /** @var DeletedContribsPager */
29 /** @var HookContainer */
30 private $hookContainer;
32 /** @var LinkRenderer */
33 private $linkRenderer;
35 /** @var IConnectionProvider */
38 /** @var RevisionStore */
39 private $revisionStore;
41 /** @var NamespaceInfo */
42 private $namespaceInfo;
44 /** @var CommentFormatter */
45 private $commentFormatter;
47 /** @var LinkBatchFactory */
48 private $linkBatchFactory;
50 /** @var UserFactory */
53 protected function setUp(): void
{
56 $services = $this->getServiceContainer();
57 $this->hookContainer
= $services->getHookContainer();
58 $this->linkRenderer
= $services->getLinkRenderer();
59 $this->dbProvider
= $services->getConnectionProvider();
60 $this->revisionStore
= $services->getRevisionStore();
61 $this->namespaceInfo
= $services->getNamespaceInfo();
62 $this->commentFormatter
= $services->getCommentFormatter();
63 $this->linkBatchFactory
= $services->getLinkBatchFactory();
64 $this->userFactory
= $services->getUserFactory();
65 $this->pager
= $this->getDeletedContribsPager();
68 private function getDeletedContribsPager( $target = 'Some test user', $namespace = 0 ) {
69 $target = UserIdentityValue
::newAnonymous( $target );
71 return new DeletedContribsPager(
77 $this->commentFormatter
,
78 $this->linkBatchFactory
,
80 RequestContext
::getMain(),
81 [ 'namespace' => $namespace ],
87 * Flow uses DeletedContribsPager::reallyDoQuery hook to provide something other then
88 * stdClass as a row, and then manually formats its own row in ContributionsLineEnding.
89 * Emulate this behaviour and check that it works.
91 public function testDeletedContribProvidedByHook() {
92 $this->setTemporaryHook( 'DeletedContribsPager::reallyDoQuery', static function ( &$data ) {
93 $data = [ [ new class() {
94 public $ar_timestamp = 12345;
95 public $testing = 'TESTING';
96 public $ar_namespace = NS_MAIN
;
97 public $ar_title = 'Test';
98 public $ar_rev_id = null;
101 $this->setTemporaryHook( 'DeletedContributionsLineEnding', function ( $pager, &$ret, $row ) {
102 $this->assertSame( 'TESTING', $row->testing
);
103 $ret .= 'FROM_HOOK!';
105 $pager = $this->getDeletedContribsPager();
106 $this->assertStringContainsString( 'FROM_HOOK!', $pager->getBody() );
109 public static function provideEmptyResultIntegration() {
111 [ 'target' => '', 'namespace' => '' ],
112 [ 'target' => '127.0.0.1', 'namespace' => '' ],
113 [ 'target' => 'UserWithNoEdits', 'namespace' => 1 ],
115 foreach ( $cases as $case ) {
121 * Confirm that the query is valid for various filter options.
123 * @dataProvider provideEmptyResultIntegration
125 public function testEmptyResultIntegration( $options ) {
126 $pager = $this->getDeletedContribsPager(
128 $options['namespace'],
130 $this->assertIsString( $pager->getBody() );
131 $this->assertSame( 0, $pager->getNumRows() );
134 public function testPopulatedIntegrationNoPermissions() {
135 $pager = $this->getDeletedContribsPager( self
::$user->getName() );
137 $this->assertIsString( $pager->getBody() );
138 $this->assertSame( 1, $pager->getNumRows() );
141 public function testPopulatedIntegrationWithPermissions() {
142 $this->setGroupPermissions( [ '*' => [
143 'deletedhistory' => true,
144 'deletedtext' => true,
148 $pager = $this->getDeletedContribsPager( self
::$user->getName() );
149 $this->assertIsString( $pager->getBody() );
150 $this->assertSame( 2, $pager->getNumRows() );
151 $this->assertStringContainsString( '>+9<', $pager->getBody() );
154 public function testParentRevisionSizePreloading() {
155 $this->setGroupPermissions( [ '*' => [
156 'deletedhistory' => true,
157 'deletedtext' => true,
161 $pager = $this->getDeletedContribsPager( self
::$user->getName() );
162 // Make sure the query leaves (at least) one row unselected
163 // so that we can test loading from parent revision ids
164 TestingAccessWrapper
::newFromObject( $pager )->deletedOnly
= true;
165 $this->assertIsString( $pager->getBody() );
166 $this->assertSame( 1, $pager->getNumRows() );
167 $this->assertStringContainsString( '>+9<', $pager->getBody() );
170 public function addDBDataOnce() {
171 self
::$user = $this->getTestUser()->getUser();
172 $title = Title
::makeTitle( NS_MAIN
, 'DeletedContribsPagerTest' );
174 // Make two edits (one will be revdel'd)
175 $this->editPage( $title, 'Test', '', NS_MAIN
, self
::$user );
176 $status = $this->editPage( $title, 'Test content.', '', NS_MAIN
, self
::$user );
178 // Delete the page where the edits were made
179 $page = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $title );
180 $this->deletePage( $page );
182 // Suppress the second edit
183 $this->getDb()->newUpdateQueryBuilder()
184 ->update( 'archive' )
186 'ar_deleted' => RevisionRecord
::DELETED_USER | RevisionRecord
::DELETED_TEXT
,
187 // This is to ensure the minor edits path doesn't encounter an error
188 'ar_minor_edit' => 1,
191 'ar_rev_id' => $status->getNewRevision()->getId()