PageArchiveTestBase.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. use MediaWiki\MediaWikiServices;
  3. use MediaWiki\Storage\RevisionRecord;
  4. /**
  5. * Base class for tests of PageArchive against different database schemas.
  6. */
  7. abstract class PageArchiveTestBase extends MediaWikiTestCase {
  8. /**
  9. * @var int
  10. */
  11. protected $pageId;
  12. /**
  13. * @var PageArchive $archivedPage
  14. */
  15. protected $archivedPage;
  16. /**
  17. * A logged out user who edited the page before it was archived.
  18. * @var string $ipEditor
  19. */
  20. protected $ipEditor;
  21. /**
  22. * Revision of the first (initial) edit
  23. * @var RevisionRecord
  24. */
  25. protected $firstRev;
  26. /**
  27. * Revision of the IP edit (the second edit)
  28. * @var RevisionRecord
  29. */
  30. protected $ipRev;
  31. function __construct( $name = null, array $data = [], $dataName = '' ) {
  32. parent::__construct( $name, $data, $dataName );
  33. $this->tablesUsed = array_merge(
  34. $this->tablesUsed,
  35. [
  36. 'page',
  37. 'revision',
  38. 'ip_changes',
  39. 'text',
  40. 'archive',
  41. 'recentchanges',
  42. 'logging',
  43. 'page_props',
  44. ]
  45. );
  46. }
  47. protected function addCoreDBData() {
  48. // Blank out to avoid failures when schema overrides imposed by subclasses
  49. // affect revision storage.
  50. }
  51. /**
  52. * @return int
  53. */
  54. abstract protected function getMcrMigrationStage();
  55. /**
  56. * @return string[]
  57. */
  58. abstract protected function getMcrTablesToReset();
  59. /**
  60. * @return bool
  61. */
  62. protected function getContentHandlerUseDB() {
  63. return true;
  64. }
  65. protected function setUp() {
  66. parent::setUp();
  67. $this->tablesUsed += $this->getMcrTablesToReset();
  68. $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
  69. $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
  70. $this->setMwGlobals( 'wgContentHandlerUseDB', $this->getContentHandlerUseDB() );
  71. $this->setMwGlobals(
  72. 'wgMultiContentRevisionSchemaMigrationStage',
  73. $this->getMcrMigrationStage()
  74. );
  75. $this->overrideMwServices();
  76. // First create our dummy page
  77. $page = Title::newFromText( 'PageArchiveTest_thePage' );
  78. $page = new WikiPage( $page );
  79. $content = ContentHandler::makeContent(
  80. 'testing',
  81. $page->getTitle(),
  82. CONTENT_MODEL_WIKITEXT
  83. );
  84. $user = $this->getTestUser()->getUser();
  85. $page->doEditContent( $content, 'testing', EDIT_NEW, false, $user );
  86. $this->pageId = $page->getId();
  87. $this->firstRev = $page->getRevision()->getRevisionRecord();
  88. // Insert IP revision
  89. $this->ipEditor = '2001:db8::1';
  90. $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
  91. $ipTimestamp = wfTimestamp(
  92. TS_MW,
  93. wfTimestamp( TS_UNIX, $this->firstRev->getTimestamp() ) + 1
  94. );
  95. $rev = $revisionStore->newMutableRevisionFromArray( [
  96. 'text' => 'Lorem Ipsum',
  97. 'comment' => 'just a test',
  98. 'page' => $page->getId(),
  99. 'user_text' => $this->ipEditor,
  100. 'timestamp' => $ipTimestamp,
  101. ] );
  102. $dbw = wfGetDB( DB_MASTER );
  103. $this->ipRev = $revisionStore->insertRevisionOn( $rev, $dbw );
  104. // Delete the page
  105. $page->doDeleteArticleReal( 'Just a test deletion' );
  106. $this->archivedPage = new PageArchive( $page->getTitle() );
  107. }
  108. /**
  109. * @covers PageArchive::undelete
  110. * @covers PageArchive::undeleteRevisions
  111. */
  112. public function testUndeleteRevisions() {
  113. // TODO: MCR: Test undeletion with multiple slots. Check that slots remain untouched.
  114. // First make sure old revisions are archived
  115. $dbr = wfGetDB( DB_REPLICA );
  116. $arQuery = Revision::getArchiveQueryInfo();
  117. $row = $dbr->selectRow(
  118. $arQuery['tables'],
  119. $arQuery['fields'],
  120. [ 'ar_rev_id' => $this->ipRev->getId() ],
  121. __METHOD__,
  122. [],
  123. $arQuery['joins']
  124. );
  125. $this->assertEquals( $this->ipEditor, $row->ar_user_text );
  126. // Should not be in revision
  127. $row = $dbr->selectRow( 'revision', '1', [ 'rev_id' => $this->ipRev->getId() ] );
  128. $this->assertFalse( $row );
  129. // Should not be in ip_changes
  130. $row = $dbr->selectRow( 'ip_changes', '1', [ 'ipc_rev_id' => $this->ipRev->getId() ] );
  131. $this->assertFalse( $row );
  132. // Restore the page
  133. $this->archivedPage->undelete( [] );
  134. // Should be back in revision
  135. $revQuery = Revision::getQueryInfo();
  136. $row = $dbr->selectRow(
  137. $revQuery['tables'],
  138. $revQuery['fields'],
  139. [ 'rev_id' => $this->ipRev->getId() ],
  140. __METHOD__,
  141. [],
  142. $revQuery['joins']
  143. );
  144. $this->assertNotFalse( $row, 'row exists in revision table' );
  145. $this->assertEquals( $this->ipEditor, $row->rev_user_text );
  146. // Should be back in ip_changes
  147. $row = $dbr->selectRow( 'ip_changes', [ 'ipc_hex' ], [ 'ipc_rev_id' => $this->ipRev->getId() ] );
  148. $this->assertNotFalse( $row, 'row exists in ip_changes table' );
  149. $this->assertEquals( IP::toHex( $this->ipEditor ), $row->ipc_hex );
  150. }
  151. abstract protected function getExpectedArchiveRows();
  152. /**
  153. * @covers PageArchive::listRevisions
  154. */
  155. public function testListRevisions() {
  156. $revisions = $this->archivedPage->listRevisions();
  157. $this->assertEquals( 2, $revisions->numRows() );
  158. // Get the rows as arrays
  159. $row0 = (array)$revisions->current();
  160. $row1 = (array)$revisions->next();
  161. $expectedRows = $this->getExpectedArchiveRows();
  162. $this->assertEquals(
  163. $expectedRows[0],
  164. $row0
  165. );
  166. $this->assertEquals(
  167. $expectedRows[1],
  168. $row1
  169. );
  170. }
  171. /**
  172. * @covers PageArchive::listPagesBySearch
  173. */
  174. public function testListPagesBySearch() {
  175. $pages = PageArchive::listPagesBySearch( 'PageArchiveTest_thePage' );
  176. $this->assertSame( 1, $pages->numRows() );
  177. $page = (array)$pages->current();
  178. $this->assertSame(
  179. [
  180. 'ar_namespace' => '0',
  181. 'ar_title' => 'PageArchiveTest_thePage',
  182. 'count' => '2',
  183. ],
  184. $page
  185. );
  186. }
  187. /**
  188. * @covers PageArchive::listPagesBySearch
  189. */
  190. public function testListPagesByPrefix() {
  191. $pages = PageArchive::listPagesByPrefix( 'PageArchiveTest' );
  192. $this->assertSame( 1, $pages->numRows() );
  193. $page = (array)$pages->current();
  194. $this->assertSame(
  195. [
  196. 'ar_namespace' => '0',
  197. 'ar_title' => 'PageArchiveTest_thePage',
  198. 'count' => '2',
  199. ],
  200. $page
  201. );
  202. }
  203. public function provideGetTextFromRowThrowsInvalidArgumentException() {
  204. yield 'missing ar_text_id field' => [ [] ];
  205. yield 'ar_text_id is null' => [ [ 'ar_text_id' => null ] ];
  206. yield 'ar_text_id is zero' => [ [ 'ar_text_id' => 0 ] ];
  207. yield 'ar_text_id is "0"' => [ [ 'ar_text_id' => '0' ] ];
  208. }
  209. /**
  210. * @dataProvider provideGetTextFromRowThrowsInvalidArgumentException
  211. * @covers PageArchive::getTextFromRow
  212. */
  213. public function testGetTextFromRowThrowsInvalidArgumentException( array $row ) {
  214. $this->hideDeprecated( PageArchive::class . '::getTextFromRow' );
  215. $this->setExpectedException( InvalidArgumentException::class );
  216. $this->archivedPage->getTextFromRow( (object)$row );
  217. }
  218. /**
  219. * @covers PageArchive::getLastRevisionText
  220. */
  221. public function testGetLastRevisionText() {
  222. $this->hideDeprecated( PageArchive::class . '::getLastRevisionText' );
  223. $text = $this->archivedPage->getLastRevisionText();
  224. $this->assertSame( 'Lorem Ipsum', $text );
  225. }
  226. /**
  227. * @covers PageArchive::getLastRevisionId
  228. */
  229. public function testGetLastRevisionId() {
  230. $id = $this->archivedPage->getLastRevisionId();
  231. $this->assertSame( $this->ipRev->getId(), $id );
  232. }
  233. /**
  234. * @covers PageArchive::isDeleted
  235. */
  236. public function testIsDeleted() {
  237. $this->assertTrue( $this->archivedPage->isDeleted() );
  238. }
  239. /**
  240. * @covers PageArchive::getRevision
  241. */
  242. public function testGetRevision() {
  243. $rev = $this->archivedPage->getRevision( $this->ipRev->getTimestamp() );
  244. $this->assertNotNull( $rev );
  245. $this->assertSame( $this->pageId, $rev->getPage() );
  246. $rev = $this->archivedPage->getRevision( '22991212115555' );
  247. $this->assertNull( $rev );
  248. }
  249. /**
  250. * @covers PageArchive::getRevision
  251. */
  252. public function testGetArchivedRevision() {
  253. $rev = $this->archivedPage->getArchivedRevision( $this->ipRev->getId() );
  254. $this->assertNotNull( $rev );
  255. $this->assertSame( $this->ipRev->getTimestamp(), $rev->getTimestamp() );
  256. $this->assertSame( $this->pageId, $rev->getPage() );
  257. $rev = $this->archivedPage->getArchivedRevision( 632546 );
  258. $this->assertNull( $rev );
  259. }
  260. /**
  261. * @covers PageArchive::getPreviousRevision
  262. */
  263. public function testGetPreviousRevision() {
  264. $rev = $this->archivedPage->getPreviousRevision( $this->ipRev->getTimestamp() );
  265. $this->assertNotNull( $rev );
  266. $this->assertSame( $this->firstRev->getId(), $rev->getId() );
  267. $rev = $this->archivedPage->getPreviousRevision( $this->firstRev->getTimestamp() );
  268. $this->assertNull( $rev );
  269. // Re-create our dummy page
  270. $title = Title::newFromText( 'PageArchiveTest_thePage' );
  271. $page = new WikiPage( $title );
  272. $content = ContentHandler::makeContent(
  273. 'testing again',
  274. $page->getTitle(),
  275. CONTENT_MODEL_WIKITEXT
  276. );
  277. $user = $this->getTestUser()->getUser();
  278. $status = $page->doEditContent( $content, 'testing', EDIT_NEW, false, $user );
  279. /** @var Revision $newRev */
  280. $newRev = $status->value['revision'];
  281. // force the revision timestamp
  282. $newTimestamp = wfTimestamp(
  283. TS_MW,
  284. wfTimestamp( TS_UNIX, $this->ipRev->getTimestamp() ) + 1
  285. );
  286. $this->db->update(
  287. 'revision',
  288. [ 'rev_timestamp' => $this->db->timestamp( $newTimestamp ) ],
  289. [ 'rev_id' => $newRev->getId() ]
  290. );
  291. // check that we don't get the existing revision too soon.
  292. $rev = $this->archivedPage->getPreviousRevision( $newTimestamp );
  293. $this->assertNotNull( $rev );
  294. $this->assertSame( $this->ipRev->getId(), $rev->getId() );
  295. // check that we do get the existing revision when appropriate.
  296. $afterNewTimestamp = wfTimestamp(
  297. TS_MW,
  298. wfTimestamp( TS_UNIX, $newTimestamp ) + 1
  299. );
  300. $rev = $this->archivedPage->getPreviousRevision( $afterNewTimestamp );
  301. $this->assertNotNull( $rev );
  302. $this->assertSame( $newRev->getId(), $rev->getId() );
  303. }
  304. }