Merge "Add config for serving main Page from the domain root"
[lhc/web/wiklou.git] / tests / phpunit / includes / Revision / McrRevisionStoreDbTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Revision;
4
5 use CommentStoreComment;
6 use ContentHandler;
7 use MediaWiki\MediaWikiServices;
8 use MediaWiki\Revision\MutableRevisionRecord;
9 use MediaWiki\Revision\RevisionRecord;
10 use MediaWiki\Revision\SlotRecord;
11 use MediaWiki\Storage\SqlBlobStore;
12 use Revision;
13 use StatusValue;
14 use TextContent;
15 use Title;
16 use Wikimedia\TestingAccessWrapper;
17 use WikitextContent;
18
19 /**
20 * Tests RevisionStore against the post-migration MCR DB schema.
21 *
22 * @covers \MediaWiki\Revision\RevisionStore
23 *
24 * @group RevisionStore
25 * @group Storage
26 * @group Database
27 * @group medium
28 */
29 class McrRevisionStoreDbTest extends RevisionStoreDbTestBase {
30
31 use McrSchemaOverride;
32
33 protected function assertRevisionExistsInDatabase( RevisionRecord $rev ) {
34 $numberOfSlots = count( $rev->getSlotRoles() );
35
36 // new schema is written
37 $this->assertSelect(
38 'slots',
39 [ 'count(*)' ],
40 [ 'slot_revision_id' => $rev->getId() ],
41 [ [ (string)$numberOfSlots ] ]
42 );
43
44 $store = MediaWikiServices::getInstance()->getRevisionStore();
45 $revQuery = $store->getSlotsQueryInfo( [ 'content' ] );
46
47 $this->assertSelect(
48 $revQuery['tables'],
49 [ 'count(*)' ],
50 [
51 'slot_revision_id' => $rev->getId(),
52 ],
53 [ [ (string)$numberOfSlots ] ],
54 [],
55 $revQuery['joins']
56 );
57
58 parent::assertRevisionExistsInDatabase( $rev );
59 }
60
61 /**
62 * @param SlotRecord $a
63 * @param SlotRecord $b
64 */
65 protected function assertSameSlotContent( SlotRecord $a, SlotRecord $b ) {
66 parent::assertSameSlotContent( $a, $b );
67
68 // Assert that the same content ID has been used
69 $this->assertSame( $a->getContentId(), $b->getContentId() );
70 }
71
72 public function provideInsertRevisionOn_successes() {
73 foreach ( parent::provideInsertRevisionOn_successes() as $case ) {
74 yield $case;
75 }
76
77 yield 'Multi-slot revision insertion' => [
78 [
79 'content' => [
80 'main' => new WikitextContent( 'Chicken' ),
81 'aux' => new TextContent( 'Egg' ),
82 ],
83 'page' => true,
84 'comment' => $this->getRandomCommentStoreComment(),
85 'timestamp' => '20171117010101',
86 'user' => true,
87 ],
88 ];
89 }
90
91 public function provideNewNullRevision() {
92 foreach ( parent::provideNewNullRevision() as $case ) {
93 yield $case;
94 }
95
96 yield [
97 Title::newFromText( 'UTPage_notAutoCreated' ),
98 [
99 'content' => [
100 'main' => new WikitextContent( 'Chicken' ),
101 'aux' => new WikitextContent( 'Omelet' ),
102 ],
103 ],
104 CommentStoreComment::newUnsavedComment( __METHOD__ . ' comment multi' ),
105 ];
106 }
107
108 public function provideNewMutableRevisionFromArray() {
109 foreach ( parent::provideNewMutableRevisionFromArray() as $case ) {
110 yield $case;
111 }
112
113 yield 'Basic array, multiple roles' => [
114 [
115 'id' => 2,
116 'page' => 1,
117 'timestamp' => '20171017114835',
118 'user_text' => '111.0.1.2',
119 'user' => 0,
120 'minor_edit' => false,
121 'deleted' => 0,
122 'len' => 29,
123 'parent_id' => 1,
124 'sha1' => '89qs83keq9c9ccw9olvvm4oc9oq50ii',
125 'comment' => 'Goat Comment!',
126 'content' => [
127 'main' => new WikitextContent( 'Söme Cöntent' ),
128 'aux' => new TextContent( 'Öther Cöntent' ),
129 ]
130 ]
131 ];
132 }
133
134 public function testGetQueryInfo_NoSlotDataJoin() {
135 $store = MediaWikiServices::getInstance()->getRevisionStore();
136 $queryInfo = $store->getQueryInfo();
137
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'] ) );
141 }
142
143 /**
144 * @covers \MediaWiki\Revision\RevisionStore::insertRevisionOn
145 * @covers \MediaWiki\Revision\RevisionStore::insertSlotRowOn
146 * @covers \MediaWiki\Revision\RevisionStore::insertContentRowOn
147 */
148 public function testInsertRevisionOn_T202032() {
149 // This test only makes sense for MySQL
150 if ( $this->db->getType() !== 'mysql' ) {
151 $this->assertTrue( true );
152 return;
153 }
154
155 // NOTE: must be done before checking MAX(rev_id)
156 $page = $this->getTestPage();
157
158 $maxRevId = $this->db->selectField( 'revision', 'MAX(rev_id)' );
159
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,
165 'slot_role_id' => 1,
166 'slot_content_id' => 0,
167 'slot_origin' => 0
168 ], __METHOD__ );
169
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() );
176
177 $store = MediaWikiServices::getInstance()->getRevisionStore();
178 $return = $store->insertRevisionOn( $rev, $this->db );
179
180 $this->assertSame( $maxRevId + 2, $return->getId() );
181
182 // is the new revision correct?
183 $this->assertRevisionCompleteness( $return );
184 $this->assertRevisionRecordsEqual( $rev, $return );
185
186 // can we find it directly in the database?
187 $this->assertRevisionExistsInDatabase( $return );
188
189 // can we load it from the store?
190 $loaded = $store->getRevisionById( $return->getId() );
191 $this->assertRevisionCompleteness( $loaded );
192 $this->assertRevisionRecordsEqual( $return, $loaded );
193 }
194
195 /**
196 * Conditions to use together with getSlotsQueryInfo() when selecting slot rows for a given
197 * revision.
198 *
199 * @return array
200 */
201 protected function getSlotRevisionConditions( $revId ) {
202 return [ 'slot_revision_id' => $revId ];
203 }
204
205 /**
206 * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
207 * @throws \MWException
208 */
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' ),
215 __METHOD__ . 'b',
216 0,
217 false,
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 ],
225 [
226 'slots' => [ SlotRecord::MAIN ],
227 'content' => true
228 ]
229 );
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( [ [
240 'type' => 'warning',
241 'message' => 'internalerror',
242 'params' => [
243 "Couldn't find slots for rev 100500"
244 ]
245 ] ], $result->getErrors() );
246 }
247
248 /**
249 * @covers \MediaWiki\Revision\RevisionStore::newRevisionsFromBatch
250 */
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'];
258
259 $contentAddress = $rev1->getRevisionRecord()->getSlot( SlotRecord::MAIN )->getAddress();
260 $mockBlobStore = $this->getMockBuilder( SqlBlobStore::class )
261 ->disableOriginalConstructor()
262 ->getMock();
263 $mockBlobStore
264 ->expects( $this->once() )
265 ->method( 'getBlobBatch' )
266 ->with( [ $contentAddress ], $this->anything() )
267 ->willReturn( StatusValue::newGood( [
268 $contentAddress => 'Content_From_Mock'
269 ] ) );
270 $mockBlobStore
271 ->expects( $this->never() )
272 ->method( 'getBlob' );
273
274 $revStore = MediaWikiServices::getInstance()
275 ->getRevisionStoreFactory()
276 ->getRevisionStore();
277 $wrappedRevStore = TestingAccessWrapper::newFromObject( $revStore );
278 $wrappedRevStore->blobStore = $mockBlobStore;
279
280 $result = $revStore->newRevisionsFromBatch(
281 [ $this->revisionToRow( $rev1 ) ],
282 [
283 'slots' => [ SlotRecord::MAIN ],
284 'content' => true
285 ]
286 );
287 $this->assertTrue( $result->isGood() );
288 $this->assertSame( 'Content_From_Mock',
289 ContentHandler::getContentText( $result->getValue()[$rev1->getId()]
290 ->getContent( SlotRecord::MAIN ) ) );
291 }
292 }