Merge "Fix use of GenderCache in ApiPageSet::processTitlesArray"
[lhc/web/wiklou.git] / tests / phpunit / includes / Revision / SlotRecordTest.php
1 <?php
2
3 namespace MediaWiki\Tests\Revision;
4
5 use InvalidArgumentException;
6 use LogicException;
7 use MediaWiki\Revision\IncompleteRevisionException;
8 use MediaWiki\Revision\SlotRecord;
9 use MediaWiki\Revision\SuppressedDataException;
10 use WikitextContent;
11
12 /**
13 * @covers \MediaWiki\Revision\SlotRecord
14 */
15 class SlotRecordTest extends \MediaWikiIntegrationTestCase {
16
17 private function makeRow( $data = [] ) {
18 $data = $data + [
19 'slot_id' => 1234,
20 'slot_content_id' => 33,
21 'content_size' => '5',
22 'content_sha1' => 'someHash',
23 'content_address' => 'tt:456',
24 'model_name' => CONTENT_MODEL_WIKITEXT,
25 'format_name' => CONTENT_FORMAT_WIKITEXT,
26 'slot_revision_id' => '2',
27 'slot_origin' => '1',
28 'role_name' => 'myRole',
29 ];
30 return (object)$data;
31 }
32
33 public function testCompleteConstruction() {
34 $row = $this->makeRow();
35 $record = new SlotRecord( $row, new WikitextContent( 'A' ) );
36
37 $this->assertTrue( $record->hasAddress() );
38 $this->assertTrue( $record->hasContentId() );
39 $this->assertTrue( $record->hasRevision() );
40 $this->assertTrue( $record->isInherited() );
41 $this->assertSame( 'A', $record->getContent()->getText() );
42 $this->assertSame( 5, $record->getSize() );
43 $this->assertSame( 'someHash', $record->getSha1() );
44 $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
45 $this->assertSame( 2, $record->getRevision() );
46 $this->assertSame( 1, $record->getOrigin() );
47 $this->assertSame( 'tt:456', $record->getAddress() );
48 $this->assertSame( 33, $record->getContentId() );
49 $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
50 $this->assertSame( 'myRole', $record->getRole() );
51 }
52
53 public function testConstructionDeferred() {
54 $row = $this->makeRow( [
55 'content_size' => null, // to be computed
56 'content_sha1' => null, // to be computed
57 'format_name' => function () {
58 return CONTENT_FORMAT_WIKITEXT;
59 },
60 'slot_revision_id' => '2',
61 'slot_origin' => '2',
62 'slot_content_id' => function () {
63 return null;
64 },
65 ] );
66
67 $content = function () {
68 return new WikitextContent( 'A' );
69 };
70
71 $record = new SlotRecord( $row, $content );
72
73 $this->assertTrue( $record->hasAddress() );
74 $this->assertTrue( $record->hasRevision() );
75 $this->assertFalse( $record->hasContentId() );
76 $this->assertFalse( $record->isInherited() );
77 $this->assertSame( 'A', $record->getContent()->getText() );
78 $this->assertSame( 1, $record->getSize() );
79 $this->assertNotEmpty( $record->getSha1() );
80 $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
81 $this->assertSame( 2, $record->getRevision() );
82 $this->assertSame( 2, $record->getRevision() );
83 $this->assertSame( 'tt:456', $record->getAddress() );
84 $this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
85 $this->assertSame( 'myRole', $record->getRole() );
86 }
87
88 public function testNewUnsaved() {
89 $record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
90
91 $this->assertFalse( $record->hasAddress() );
92 $this->assertFalse( $record->hasContentId() );
93 $this->assertFalse( $record->hasRevision() );
94 $this->assertFalse( $record->isInherited() );
95 $this->assertFalse( $record->hasOrigin() );
96 $this->assertSame( 'A', $record->getContent()->getText() );
97 $this->assertSame( 1, $record->getSize() );
98 $this->assertNotEmpty( $record->getSha1() );
99 $this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
100 $this->assertSame( 'myRole', $record->getRole() );
101 }
102
103 public function provideInvalidConstruction() {
104 yield 'both null' => [ null, null ];
105 yield 'null row' => [ null, new WikitextContent( 'A' ) ];
106 yield 'array row' => [ [], new WikitextContent( 'A' ) ];
107 yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
108 yield 'null content' => [ (object)[], null ];
109 }
110
111 /**
112 * @dataProvider provideInvalidConstruction
113 */
114 public function testInvalidConstruction( $row, $content ) {
115 $this->setExpectedException( InvalidArgumentException::class );
116 new SlotRecord( $row, $content );
117 }
118
119 public function testGetContentId_fails() {
120 $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
121 $this->setExpectedException( IncompleteRevisionException::class );
122
123 $record->getContentId();
124 }
125
126 public function testGetAddress_fails() {
127 $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
128 $this->setExpectedException( IncompleteRevisionException::class );
129
130 $record->getAddress();
131 }
132
133 public function provideIncomplete() {
134 $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
135 yield 'unsaved' => [ $unsaved ];
136
137 $parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
138 $inherited = SlotRecord::newInherited( $parent );
139 yield 'inherited' => [ $inherited ];
140 }
141
142 /**
143 * @dataProvider provideIncomplete
144 */
145 public function testGetRevision_fails( SlotRecord $record ) {
146 $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
147 $this->setExpectedException( IncompleteRevisionException::class );
148
149 $record->getRevision();
150 }
151
152 /**
153 * @dataProvider provideIncomplete
154 */
155 public function testGetOrigin_fails( SlotRecord $record ) {
156 $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
157 $this->setExpectedException( IncompleteRevisionException::class );
158
159 $record->getOrigin();
160 }
161
162 public function provideHashStability() {
163 yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
164 yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
165 }
166
167 /**
168 * @dataProvider provideHashStability
169 */
170 public function testHashStability( $text, $hash ) {
171 // Changing the output of the hash function will break things horribly!
172
173 $this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
174
175 $record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
176 $this->assertSame( $hash, $record->getSha1() );
177 }
178
179 public function testHashComputed() {
180 $row = $this->makeRow();
181 $row->content_sha1 = '';
182
183 $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
184 $this->assertNotEmpty( $rec->getSha1() );
185 }
186
187 public function testNewWithSuppressedContent() {
188 $input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
189 $output = SlotRecord::newWithSuppressedContent( $input );
190
191 $this->setExpectedException( SuppressedDataException::class );
192 $output->getContent();
193 }
194
195 public function testNewInherited() {
196 $row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
197 $parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
198
199 // This would happen while doing an edit, before saving revision meta-data.
200 $inherited = SlotRecord::newInherited( $parent );
201
202 $this->assertSame( $parent->getContentId(), $inherited->getContentId() );
203 $this->assertSame( $parent->getAddress(), $inherited->getAddress() );
204 $this->assertSame( $parent->getContent(), $inherited->getContent() );
205 $this->assertTrue( $inherited->isInherited() );
206 $this->assertTrue( $inherited->hasOrigin() );
207 $this->assertFalse( $inherited->hasRevision() );
208
209 // make sure we didn't mess with the internal state of $parent
210 $this->assertFalse( $parent->isInherited() );
211 $this->assertSame( 7, $parent->getRevision() );
212
213 // This would happen while doing an edit, after saving the revision meta-data
214 // and content meta-data.
215 $saved = SlotRecord::newSaved(
216 10,
217 $inherited->getContentId(),
218 $inherited->getAddress(),
219 $inherited
220 );
221 $this->assertSame( $parent->getContentId(), $saved->getContentId() );
222 $this->assertSame( $parent->getAddress(), $saved->getAddress() );
223 $this->assertSame( $parent->getContent(), $saved->getContent() );
224 $this->assertTrue( $saved->isInherited() );
225 $this->assertTrue( $saved->hasRevision() );
226 $this->assertSame( 10, $saved->getRevision() );
227
228 // make sure we didn't mess with the internal state of $parent or $inherited
229 $this->assertSame( 7, $parent->getRevision() );
230 $this->assertFalse( $inherited->hasRevision() );
231 }
232
233 public function testNewSaved() {
234 // This would happen while doing an edit, before saving revision meta-data.
235 $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
236
237 // This would happen while doing an edit, after saving the revision meta-data
238 // and content meta-data.
239 $saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
240 $this->assertFalse( $saved->isInherited() );
241 $this->assertTrue( $saved->hasOrigin() );
242 $this->assertTrue( $saved->hasRevision() );
243 $this->assertTrue( $saved->hasAddress() );
244 $this->assertTrue( $saved->hasContentId() );
245 $this->assertSame( 'theNewAddress', $saved->getAddress() );
246 $this->assertSame( 20, $saved->getContentId() );
247 $this->assertSame( 'A', $saved->getContent()->getText() );
248 $this->assertSame( 10, $saved->getRevision() );
249 $this->assertSame( 10, $saved->getOrigin() );
250
251 // make sure we didn't mess with the internal state of $unsaved
252 $this->assertFalse( $unsaved->hasAddress() );
253 $this->assertFalse( $unsaved->hasContentId() );
254 $this->assertFalse( $unsaved->hasRevision() );
255 }
256
257 public function provideNewSaved_LogicException() {
258 $freshRow = $this->makeRow( [
259 'content_id' => 10,
260 'content_address' => 'address:1',
261 'slot_origin' => 1,
262 'slot_revision_id' => 1,
263 ] );
264
265 $freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
266 yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
267 yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
268 yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
269
270 $inheritedRow = $this->makeRow( [
271 'content_id' => null,
272 'content_address' => null,
273 'slot_origin' => 0,
274 'slot_revision_id' => 1,
275 ] );
276
277 $inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
278 yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
279 }
280
281 /**
282 * @dataProvider provideNewSaved_LogicException
283 */
284 public function testNewSaved_LogicException(
285 $revisionId,
286 $contentId,
287 $contentAddress,
288 SlotRecord $protoSlot
289 ) {
290 $this->setExpectedException( LogicException::class );
291 SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
292 }
293
294 public function provideNewSaved_InvalidArgumentException() {
295 $unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
296
297 yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
298 yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
299 yield 'bad content address' => [ 7, 5, 77, $unsaved ];
300 }
301
302 /**
303 * @dataProvider provideNewSaved_InvalidArgumentException
304 */
305 public function testNewSaved_InvalidArgumentException(
306 $revisionId,
307 $contentId,
308 $contentAddress,
309 SlotRecord $protoSlot
310 ) {
311 $this->setExpectedException( InvalidArgumentException::class );
312 SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
313 }
314
315 public function provideHasSameContent() {
316 $fail = function () {
317 self::fail( 'There should be no need to actually load the content.' );
318 };
319
320 $a100a1 = new SlotRecord(
321 $this->makeRow(
322 [
323 'model_name' => 'A',
324 'content_size' => 100,
325 'content_sha1' => 'hash-a',
326 'content_address' => 'xxx:a1',
327 ]
328 ),
329 $fail
330 );
331 $a100a1b = new SlotRecord(
332 $this->makeRow(
333 [
334 'model_name' => 'A',
335 'content_size' => 100,
336 'content_sha1' => 'hash-a',
337 'content_address' => 'xxx:a1',
338 ]
339 ),
340 $fail
341 );
342 $a100null = new SlotRecord(
343 $this->makeRow(
344 [
345 'model_name' => 'A',
346 'content_size' => 100,
347 'content_sha1' => 'hash-a',
348 'content_address' => null,
349 ]
350 ),
351 $fail
352 );
353 $a100a2 = new SlotRecord(
354 $this->makeRow(
355 [
356 'model_name' => 'A',
357 'content_size' => 100,
358 'content_sha1' => 'hash-a',
359 'content_address' => 'xxx:a2',
360 ]
361 ),
362 $fail
363 );
364 $b100a1 = new SlotRecord(
365 $this->makeRow(
366 [
367 'model_name' => 'B',
368 'content_size' => 100,
369 'content_sha1' => 'hash-a',
370 'content_address' => 'xxx:a1',
371 ]
372 ),
373 $fail
374 );
375 $a200a1 = new SlotRecord(
376 $this->makeRow(
377 [
378 'model_name' => 'A',
379 'content_size' => 200,
380 'content_sha1' => 'hash-a',
381 'content_address' => 'xxx:a2',
382 ]
383 ),
384 $fail
385 );
386 $a100x1 = new SlotRecord(
387 $this->makeRow(
388 [
389 'model_name' => 'A',
390 'content_size' => 100,
391 'content_sha1' => 'hash-x',
392 'content_address' => 'xxx:x1',
393 ]
394 ),
395 $fail
396 );
397
398 yield 'same instance' => [ $a100a1, $a100a1, true ];
399 yield 'no address' => [ $a100a1, $a100null, true ];
400 yield 'same address' => [ $a100a1, $a100a1b, true ];
401 yield 'different address' => [ $a100a1, $a100a2, true ];
402 yield 'different model' => [ $a100a1, $b100a1, false ];
403 yield 'different size' => [ $a100a1, $a200a1, false ];
404 yield 'different hash' => [ $a100a1, $a100x1, false ];
405 }
406
407 /**
408 * @dataProvider provideHasSameContent
409 */
410 public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
411 $this->assertSame( $sameContent, $a->hasSameContent( $b ) );
412 $this->assertSame( $sameContent, $b->hasSameContent( $a ) );
413 }
414
415 }