3 namespace MediaWiki\Tests\Revision
;
5 use CommentStoreComment
;
7 use MediaWiki\MediaWikiServices
;
8 use MediaWiki\Revision\MutableRevisionRecord
;
9 use MediaWiki\Revision\RevisionRecord
;
10 use MediaWiki\Revision\SlotRecord
;
11 use MediaWiki\Storage\SqlBlobStore
;
16 use Wikimedia\TestingAccessWrapper
;
20 * Tests RevisionStore against the post-migration MCR DB schema.
22 * @covers \MediaWiki\Revision\RevisionStore
24 * @group RevisionStore
29 class McrRevisionStoreDbTest
extends RevisionStoreDbTestBase
{
31 use McrSchemaOverride
;
33 protected function assertRevisionExistsInDatabase( RevisionRecord
$rev ) {
34 $numberOfSlots = count( $rev->getSlotRoles() );
36 // new schema is written
40 [ 'slot_revision_id' => $rev->getId() ],
41 [ [ (string)$numberOfSlots ] ]
44 $store = MediaWikiServices
::getInstance()->getRevisionStore();
45 $revQuery = $store->getSlotsQueryInfo( [ 'content' ] );
51 'slot_revision_id' => $rev->getId(),
53 [ [ (string)$numberOfSlots ] ],
58 parent
::assertRevisionExistsInDatabase( $rev );
62 * @param SlotRecord $a
63 * @param SlotRecord $b
65 protected function assertSameSlotContent( SlotRecord
$a, SlotRecord
$b ) {
66 parent
::assertSameSlotContent( $a, $b );
68 // Assert that the same content ID has been used
69 $this->assertSame( $a->getContentId(), $b->getContentId() );
72 public function provideInsertRevisionOn_successes() {
73 foreach ( parent
::provideInsertRevisionOn_successes() as $case ) {
77 yield
'Multi-slot revision insertion' => [
80 'main' => new WikitextContent( 'Chicken' ),
81 'aux' => new TextContent( 'Egg' ),
84 'comment' => $this->getRandomCommentStoreComment(),
85 'timestamp' => '20171117010101',
91 public function provideNewNullRevision() {
92 foreach ( parent
::provideNewNullRevision() as $case ) {
97 Title
::newFromText( 'UTPage_notAutoCreated' ),
100 'main' => new WikitextContent( 'Chicken' ),
101 'aux' => new WikitextContent( 'Omelet' ),
104 CommentStoreComment
::newUnsavedComment( __METHOD__
. ' comment multi' ),
108 public function provideNewMutableRevisionFromArray() {
109 foreach ( parent
::provideNewMutableRevisionFromArray() as $case ) {
113 yield
'Basic array, multiple roles' => [
117 'timestamp' => '20171017114835',
118 'user_text' => '111.0.1.2',
120 'minor_edit' => false,
124 'sha1' => '89qs83keq9c9ccw9olvvm4oc9oq50ii',
125 'comment' => 'Goat Comment!',
127 'main' => new WikitextContent( 'Söme Cöntent' ),
128 'aux' => new TextContent( 'Öther Cöntent' ),
134 public function testGetQueryInfo_NoSlotDataJoin() {
135 $store = MediaWikiServices
::getInstance()->getRevisionStore();
136 $queryInfo = $store->getQueryInfo();
138 // with the new schema enabled, query info should not join the main slot info
139 $this->assertFalse( array_key_exists( 'a_slot_data', $queryInfo['tables'] ) );
140 $this->assertFalse( array_key_exists( 'a_slot_data', $queryInfo['joins'] ) );
144 * @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
145 * @covers \MediaWiki\Revision\RevisionStore::insertSlotRowOn
146 * @covers \MediaWiki\Revision\RevisionStore::insertContentRowOn
148 public function testInsertRevisionOn_T202032() {
149 // This test only makes sense for MySQL
150 if ( $this->db
->getType() !== 'mysql' ) {
151 $this->assertTrue( true );
155 // NOTE: must be done before checking MAX(rev_id)
156 $page = $this->getTestPage();
158 $maxRevId = $this->db
->selectField( 'revision', 'MAX(rev_id)' );
160 // Construct a slot row that will conflict with the insertion of the next revision ID,
161 // to emulate the failure mode described in T202032. Nothing will ever read this row,
162 // we just need it to trigger a primary key conflict.
163 $this->db
->insert( 'slots', [
164 'slot_revision_id' => $maxRevId +
1,
166 'slot_content_id' => 0,
170 $rev = new MutableRevisionRecord( $page->getTitle() );
171 $rev->setTimestamp( '20180101000000' );
172 $rev->setComment( CommentStoreComment
::newUnsavedComment( 'test' ) );
173 $rev->setUser( $this->getTestUser()->getUser() );
174 $rev->setContent( 'main', new WikitextContent( 'Text' ) );
175 $rev->setPageId( $page->getId() );
177 $store = MediaWikiServices
::getInstance()->getRevisionStore();
178 $return = $store->insertRevisionOn( $rev, $this->db
);
180 $this->assertSame( $maxRevId +
2, $return->getId() );
182 // is the new revision correct?
183 $this->assertRevisionCompleteness( $return );
184 $this->assertRevisionRecordsEqual( $rev, $return );
186 // can we find it directly in the database?
187 $this->assertRevisionExistsInDatabase( $return );
189 // can we load it from the store?
190 $loaded = $store->getRevisionById( $return->getId() );
191 $this->assertRevisionCompleteness( $loaded );
192 $this->assertRevisionRecordsEqual( $return, $loaded );
196 * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
201 protected function getSlotRevisionConditions( $revId ) {
202 return [ 'slot_revision_id' => $revId ];
206 * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
207 * @throws \MWException
209 public function testNewRevisionsFromBatch_error() {
210 $page = $this->getTestPage();
211 $text = __METHOD__
. 'b-ä';
212 /** @var Revision $rev1 */
213 $rev1 = $page->doEditContent(
214 new WikitextContent( $text . '1' ),
218 $this->getTestUser()->getUser()
219 )->value
['revision'];
220 $invalidRow = $this->revisionToRow( $rev1 );
221 $invalidRow->rev_id
= 100500;
222 $result = MediaWikiServices
::getInstance()->getRevisionStore()
223 ->newRevisionsFromBatch(
224 [ $this->revisionToRow( $rev1 ), $invalidRow ],
226 'slots' => [ SlotRecord
::MAIN
],
230 $this->assertFalse( $result->isGood() );
231 $this->assertNotEmpty( $result->getErrors() );
232 $records = $result->getValue();
233 $this->assertRevisionRecordMatchesRevision( $rev1, $records[$rev1->getId()] );
234 $this->assertSame( $text . '1',
235 $records[$rev1->getId()]->getContent( SlotRecord
::MAIN
)->serialize() );
236 $this->assertEquals( $page->getTitle()->getDBkey(),
237 $records[$rev1->getId()]->getPageAsLinkTarget()->getDBkey() );
238 $this->assertNull( $records[$invalidRow->rev_id
] );
239 $this->assertSame( [ [
241 'message' => 'internalerror',
243 "Couldn't find slots for rev 100500"
245 ] ], $result->getErrors() );
249 * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
251 public function testNewRevisionFromBatchUsesGetBlobBatch() {
252 $page1 = $this->getTestPage();
253 $text = __METHOD__
. 'b-ä';
254 $editStatus = $this->editPage( $page1->getTitle()->getPrefixedDBkey(), $text . '1' );
255 $this->assertTrue( $editStatus->isGood(), 'Sanity: must create revision 1' );
256 /** @var Revision $rev1 */
257 $rev1 = $editStatus->getValue()['revision'];
259 $contentAddress = $rev1->getRevisionRecord()->getSlot( SlotRecord
::MAIN
)->getAddress();
260 $mockBlobStore = $this->getMockBuilder( SqlBlobStore
::class )
261 ->disableOriginalConstructor()
264 ->expects( $this->once() )
265 ->method( 'getBlobBatch' )
266 ->with( [ $contentAddress ], $this->anything() )
267 ->willReturn( StatusValue
::newGood( [
268 $contentAddress => 'Content_From_Mock'
271 ->expects( $this->never() )
272 ->method( 'getBlob' );
274 $revStore = MediaWikiServices
::getInstance()
275 ->getRevisionStoreFactory()
276 ->getRevisionStore();
277 $wrappedRevStore = TestingAccessWrapper
::newFromObject( $revStore );
278 $wrappedRevStore->blobStore
= $mockBlobStore;
280 $result = $revStore->newRevisionsFromBatch(
281 [ $this->revisionToRow( $rev1 ) ],
283 'slots' => [ SlotRecord
::MAIN
],
287 $this->assertTrue( $result->isGood() );
288 $this->assertSame( 'Content_From_Mock',
289 ContentHandler
::getContentText( $result->getValue()[$rev1->getId()]
290 ->getContent( SlotRecord
::MAIN
) ) );