During development a lot of classes were placed in MediaWiki\Storage\. The precedent set would mean that every class relating to something stored in a database table, plus all related value classes and such, would go into that namespace. Let's put them into MediaWiki\Revision\ instead. Then future classes related to the 'page' table can go into MediaWiki\Page\, future classes related to the 'user' table can go into MediaWiki\User\, and so on. Note I didn't move DerivedPageDataUpdater, PageUpdateException, PageUpdater, or RevisionSlotsUpdate in this patch. If these are kept long-term, they probably belong in MediaWiki\Page\ or MediaWiki\Edit\ instead. Bug: T204158 Change-Id: I16bea8927566a3c73c07e4f4afb3537e05aa04a5
408 lines
13 KiB
PHP
408 lines
13 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Tests\Revision;
|
|
|
|
use InvalidArgumentException;
|
|
use LogicException;
|
|
use MediaWiki\Revision\IncompleteRevisionException;
|
|
use MediaWiki\Revision\SlotRecord;
|
|
use MediaWiki\Revision\SuppressedDataException;
|
|
use MediaWikiTestCase;
|
|
use WikitextContent;
|
|
|
|
/**
|
|
* @covers \MediaWiki\Revision\SlotRecord
|
|
*/
|
|
class SlotRecordTest extends MediaWikiTestCase {
|
|
|
|
private function makeRow( $data = [] ) {
|
|
$data = $data + [
|
|
'slot_id' => 1234,
|
|
'slot_content_id' => 33,
|
|
'content_size' => '5',
|
|
'content_sha1' => 'someHash',
|
|
'content_address' => 'tt:456',
|
|
'model_name' => CONTENT_MODEL_WIKITEXT,
|
|
'format_name' => CONTENT_FORMAT_WIKITEXT,
|
|
'slot_revision_id' => '2',
|
|
'slot_origin' => '1',
|
|
'role_name' => 'myRole',
|
|
];
|
|
return (object)$data;
|
|
}
|
|
|
|
public function testCompleteConstruction() {
|
|
$row = $this->makeRow();
|
|
$record = new SlotRecord( $row, new WikitextContent( 'A' ) );
|
|
|
|
$this->assertTrue( $record->hasAddress() );
|
|
$this->assertTrue( $record->hasContentId() );
|
|
$this->assertTrue( $record->hasRevision() );
|
|
$this->assertTrue( $record->isInherited() );
|
|
$this->assertSame( 'A', $record->getContent()->getNativeData() );
|
|
$this->assertSame( 5, $record->getSize() );
|
|
$this->assertSame( 'someHash', $record->getSha1() );
|
|
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
|
|
$this->assertSame( 2, $record->getRevision() );
|
|
$this->assertSame( 1, $record->getOrigin() );
|
|
$this->assertSame( 'tt:456', $record->getAddress() );
|
|
$this->assertSame( 33, $record->getContentId() );
|
|
$this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
|
|
$this->assertSame( 'myRole', $record->getRole() );
|
|
}
|
|
|
|
public function testConstructionDeferred() {
|
|
$row = $this->makeRow( [
|
|
'content_size' => null, // to be computed
|
|
'content_sha1' => null, // to be computed
|
|
'format_name' => function () {
|
|
return CONTENT_FORMAT_WIKITEXT;
|
|
},
|
|
'slot_revision_id' => '2',
|
|
'slot_origin' => '2',
|
|
'slot_content_id' => function () {
|
|
return null;
|
|
},
|
|
] );
|
|
|
|
$content = function () {
|
|
return new WikitextContent( 'A' );
|
|
};
|
|
|
|
$record = new SlotRecord( $row, $content );
|
|
|
|
$this->assertTrue( $record->hasAddress() );
|
|
$this->assertTrue( $record->hasRevision() );
|
|
$this->assertFalse( $record->hasContentId() );
|
|
$this->assertFalse( $record->isInherited() );
|
|
$this->assertSame( 'A', $record->getContent()->getNativeData() );
|
|
$this->assertSame( 1, $record->getSize() );
|
|
$this->assertNotNull( $record->getSha1() );
|
|
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
|
|
$this->assertSame( 2, $record->getRevision() );
|
|
$this->assertSame( 2, $record->getRevision() );
|
|
$this->assertSame( 'tt:456', $record->getAddress() );
|
|
$this->assertSame( CONTENT_FORMAT_WIKITEXT, $record->getFormat() );
|
|
$this->assertSame( 'myRole', $record->getRole() );
|
|
}
|
|
|
|
public function testNewUnsaved() {
|
|
$record = SlotRecord::newUnsaved( 'myRole', new WikitextContent( 'A' ) );
|
|
|
|
$this->assertFalse( $record->hasAddress() );
|
|
$this->assertFalse( $record->hasContentId() );
|
|
$this->assertFalse( $record->hasRevision() );
|
|
$this->assertFalse( $record->isInherited() );
|
|
$this->assertFalse( $record->hasOrigin() );
|
|
$this->assertSame( 'A', $record->getContent()->getNativeData() );
|
|
$this->assertSame( 1, $record->getSize() );
|
|
$this->assertNotNull( $record->getSha1() );
|
|
$this->assertSame( CONTENT_MODEL_WIKITEXT, $record->getModel() );
|
|
$this->assertSame( 'myRole', $record->getRole() );
|
|
}
|
|
|
|
public function provideInvalidConstruction() {
|
|
yield 'both null' => [ null, null ];
|
|
yield 'null row' => [ null, new WikitextContent( 'A' ) ];
|
|
yield 'array row' => [ [], new WikitextContent( 'A' ) ];
|
|
yield 'empty row' => [ (object)[], new WikitextContent( 'A' ) ];
|
|
yield 'null content' => [ (object)[], null ];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideInvalidConstruction
|
|
*/
|
|
public function testInvalidConstruction( $row, $content ) {
|
|
$this->setExpectedException( InvalidArgumentException::class );
|
|
new SlotRecord( $row, $content );
|
|
}
|
|
|
|
public function testGetContentId_fails() {
|
|
$record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
$this->setExpectedException( IncompleteRevisionException::class );
|
|
|
|
$record->getContentId();
|
|
}
|
|
|
|
public function testGetAddress_fails() {
|
|
$record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
$this->setExpectedException( IncompleteRevisionException::class );
|
|
|
|
$record->getAddress();
|
|
}
|
|
|
|
public function provideIncomplete() {
|
|
$unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
yield 'unsaved' => [ $unsaved ];
|
|
|
|
$parent = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
|
|
$inherited = SlotRecord::newInherited( $parent );
|
|
yield 'inherited' => [ $inherited ];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideIncomplete
|
|
*/
|
|
public function testGetRevision_fails( SlotRecord $record ) {
|
|
$record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
$this->setExpectedException( IncompleteRevisionException::class );
|
|
|
|
$record->getRevision();
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideIncomplete
|
|
*/
|
|
public function testGetOrigin_fails( SlotRecord $record ) {
|
|
$record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
$this->setExpectedException( IncompleteRevisionException::class );
|
|
|
|
$record->getOrigin();
|
|
}
|
|
|
|
public function provideHashStability() {
|
|
yield [ '', 'phoiac9h4m842xq45sp7s6u21eteeq1' ];
|
|
yield [ 'Lorem ipsum', 'hcr5u40uxr81d3nx89nvwzclfz6r9c5' ];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideHashStability
|
|
*/
|
|
public function testHashStability( $text, $hash ) {
|
|
// Changing the output of the hash function will break things horribly!
|
|
|
|
$this->assertSame( $hash, SlotRecord::base36Sha1( $text ) );
|
|
|
|
$record = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( $text ) );
|
|
$this->assertSame( $hash, $record->getSha1() );
|
|
}
|
|
|
|
public function testNewWithSuppressedContent() {
|
|
$input = new SlotRecord( $this->makeRow(), new WikitextContent( 'A' ) );
|
|
$output = SlotRecord::newWithSuppressedContent( $input );
|
|
|
|
$this->setExpectedException( SuppressedDataException::class );
|
|
$output->getContent();
|
|
}
|
|
|
|
public function testNewInherited() {
|
|
$row = $this->makeRow( [ 'slot_revision_id' => 7, 'slot_origin' => 7 ] );
|
|
$parent = new SlotRecord( $row, new WikitextContent( 'A' ) );
|
|
|
|
// This would happen while doing an edit, before saving revision meta-data.
|
|
$inherited = SlotRecord::newInherited( $parent );
|
|
|
|
$this->assertSame( $parent->getContentId(), $inherited->getContentId() );
|
|
$this->assertSame( $parent->getAddress(), $inherited->getAddress() );
|
|
$this->assertSame( $parent->getContent(), $inherited->getContent() );
|
|
$this->assertTrue( $inherited->isInherited() );
|
|
$this->assertTrue( $inherited->hasOrigin() );
|
|
$this->assertFalse( $inherited->hasRevision() );
|
|
|
|
// make sure we didn't mess with the internal state of $parent
|
|
$this->assertFalse( $parent->isInherited() );
|
|
$this->assertSame( 7, $parent->getRevision() );
|
|
|
|
// This would happen while doing an edit, after saving the revision meta-data
|
|
// and content meta-data.
|
|
$saved = SlotRecord::newSaved(
|
|
10,
|
|
$inherited->getContentId(),
|
|
$inherited->getAddress(),
|
|
$inherited
|
|
);
|
|
$this->assertSame( $parent->getContentId(), $saved->getContentId() );
|
|
$this->assertSame( $parent->getAddress(), $saved->getAddress() );
|
|
$this->assertSame( $parent->getContent(), $saved->getContent() );
|
|
$this->assertTrue( $saved->isInherited() );
|
|
$this->assertTrue( $saved->hasRevision() );
|
|
$this->assertSame( 10, $saved->getRevision() );
|
|
|
|
// make sure we didn't mess with the internal state of $parent or $inherited
|
|
$this->assertSame( 7, $parent->getRevision() );
|
|
$this->assertFalse( $inherited->hasRevision() );
|
|
}
|
|
|
|
public function testNewSaved() {
|
|
// This would happen while doing an edit, before saving revision meta-data.
|
|
$unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
|
|
// This would happen while doing an edit, after saving the revision meta-data
|
|
// and content meta-data.
|
|
$saved = SlotRecord::newSaved( 10, 20, 'theNewAddress', $unsaved );
|
|
$this->assertFalse( $saved->isInherited() );
|
|
$this->assertTrue( $saved->hasOrigin() );
|
|
$this->assertTrue( $saved->hasRevision() );
|
|
$this->assertTrue( $saved->hasAddress() );
|
|
$this->assertTrue( $saved->hasContentId() );
|
|
$this->assertSame( 'theNewAddress', $saved->getAddress() );
|
|
$this->assertSame( 20, $saved->getContentId() );
|
|
$this->assertSame( 'A', $saved->getContent()->getNativeData() );
|
|
$this->assertSame( 10, $saved->getRevision() );
|
|
$this->assertSame( 10, $saved->getOrigin() );
|
|
|
|
// make sure we didn't mess with the internal state of $unsaved
|
|
$this->assertFalse( $unsaved->hasAddress() );
|
|
$this->assertFalse( $unsaved->hasContentId() );
|
|
$this->assertFalse( $unsaved->hasRevision() );
|
|
}
|
|
|
|
public function provideNewSaved_LogicException() {
|
|
$freshRow = $this->makeRow( [
|
|
'content_id' => 10,
|
|
'content_address' => 'address:1',
|
|
'slot_origin' => 1,
|
|
'slot_revision_id' => 1,
|
|
] );
|
|
|
|
$freshSlot = new SlotRecord( $freshRow, new WikitextContent( 'A' ) );
|
|
yield 'mismatching address' => [ 1, 10, 'address:BAD', $freshSlot ];
|
|
yield 'mismatching revision' => [ 5, 10, 'address:1', $freshSlot ];
|
|
yield 'mismatching content ID' => [ 1, 17, 'address:1', $freshSlot ];
|
|
|
|
$inheritedRow = $this->makeRow( [
|
|
'content_id' => null,
|
|
'content_address' => null,
|
|
'slot_origin' => 0,
|
|
'slot_revision_id' => 1,
|
|
] );
|
|
|
|
$inheritedSlot = new SlotRecord( $inheritedRow, new WikitextContent( 'A' ) );
|
|
yield 'inherited, but no address' => [ 1, 10, 'address:2', $inheritedSlot ];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideNewSaved_LogicException
|
|
*/
|
|
public function testNewSaved_LogicException(
|
|
$revisionId,
|
|
$contentId,
|
|
$contentAddress,
|
|
SlotRecord $protoSlot
|
|
) {
|
|
$this->setExpectedException( LogicException::class );
|
|
SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
|
|
}
|
|
|
|
public function provideNewSaved_InvalidArgumentException() {
|
|
$unsaved = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
|
|
|
|
yield 'bad revision id' => [ 'xyzzy', 5, 'address', $unsaved ];
|
|
yield 'bad content id' => [ 7, 'xyzzy', 'address', $unsaved ];
|
|
yield 'bad content address' => [ 7, 5, 77, $unsaved ];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideNewSaved_InvalidArgumentException
|
|
*/
|
|
public function testNewSaved_InvalidArgumentException(
|
|
$revisionId,
|
|
$contentId,
|
|
$contentAddress,
|
|
SlotRecord $protoSlot
|
|
) {
|
|
$this->setExpectedException( InvalidArgumentException::class );
|
|
SlotRecord::newSaved( $revisionId, $contentId, $contentAddress, $protoSlot );
|
|
}
|
|
|
|
public function provideHasSameContent() {
|
|
$fail = function () {
|
|
self::fail( 'There should be no need to actually load the content.' );
|
|
};
|
|
|
|
$a100a1 = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'A',
|
|
'content_size' => 100,
|
|
'content_sha1' => 'hash-a',
|
|
'content_address' => 'xxx:a1',
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
$a100a1b = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'A',
|
|
'content_size' => 100,
|
|
'content_sha1' => 'hash-a',
|
|
'content_address' => 'xxx:a1',
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
$a100null = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'A',
|
|
'content_size' => 100,
|
|
'content_sha1' => 'hash-a',
|
|
'content_address' => null,
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
$a100a2 = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'A',
|
|
'content_size' => 100,
|
|
'content_sha1' => 'hash-a',
|
|
'content_address' => 'xxx:a2',
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
$b100a1 = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'B',
|
|
'content_size' => 100,
|
|
'content_sha1' => 'hash-a',
|
|
'content_address' => 'xxx:a1',
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
$a200a1 = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'A',
|
|
'content_size' => 200,
|
|
'content_sha1' => 'hash-a',
|
|
'content_address' => 'xxx:a2',
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
$a100x1 = new SlotRecord(
|
|
$this->makeRow(
|
|
[
|
|
'model_name' => 'A',
|
|
'content_size' => 100,
|
|
'content_sha1' => 'hash-x',
|
|
'content_address' => 'xxx:x1',
|
|
]
|
|
),
|
|
$fail
|
|
);
|
|
|
|
yield 'same instance' => [ $a100a1, $a100a1, true ];
|
|
yield 'no address' => [ $a100a1, $a100null, true ];
|
|
yield 'same address' => [ $a100a1, $a100a1b, true ];
|
|
yield 'different address' => [ $a100a1, $a100a2, true ];
|
|
yield 'different model' => [ $a100a1, $b100a1, false ];
|
|
yield 'different size' => [ $a100a1, $a200a1, false ];
|
|
yield 'different hash' => [ $a100a1, $a100x1, false ];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideHasSameContent
|
|
*/
|
|
public function testHasSameContent( SlotRecord $a, SlotRecord $b, $sameContent ) {
|
|
$this->assertSame( $sameContent, $a->hasSameContent( $b ) );
|
|
$this->assertSame( $sameContent, $b->hasSameContent( $a ) );
|
|
}
|
|
|
|
}
|