wiki.techinc.nl/tests/phpunit/includes/Revision/MutableRevisionSlotsTest.php
daniel ec34eb39b9 MutableRevisionRecord: ensure consistent hash and size
This ensures that getSha1() and getSize() will return values consistent
with the revision's content, even if the content was changed indirectly
via the MutableRevisionSlots object returned from getSlots(), after
getSha1() or getSized() had already been called and the values cached.

While mechanism that triggers T239717 remains unknown, the root cause
appears to be that the cached hash and size may get out of sync with
the actual content of the new revision. The suspected mechanism is a
side-effect of a parser hook triggered during PST.

Bug: T239717
Change-Id: I0b61eb639282334df884313cdfbb3521fbfe7e88
2020-04-20 09:51:08 -07:00

171 lines
6.1 KiB
PHP

<?php
namespace MediaWiki\Tests\Revision;
use Content;
use InvalidArgumentException;
use MediaWiki\Revision\MutableRevisionSlots;
use MediaWiki\Revision\RevisionAccessException;
use MediaWiki\Revision\RevisionSlots;
use MediaWiki\Revision\SlotRecord;
use WikitextContent;
/**
* @covers \MediaWiki\Revision\MutableRevisionSlots
*/
class MutableRevisionSlotsTest extends RevisionSlotsTest {
/**
* @param SlotRecord[] $slots
* @return RevisionSlots
*/
protected function newRevisionSlots( $slots = [] ) {
return new MutableRevisionSlots( $slots );
}
public function provideConstructorFailue() {
yield 'array or the wrong thing' => [
[ 1, 2, 3 ]
];
}
/**
* @dataProvider provideConstructorFailue
* @param array $slots
*
* @covers \MediaWiki\Revision\RevisionSlots::__construct
* @covers \MediaWiki\Revision\RevisionSlots::setSlotsInternal
*/
public function testConstructorFailue( $slots ) {
$this->expectException( InvalidArgumentException::class );
new MutableRevisionSlots( $slots );
}
public function testSetMultipleSlots() {
$slots = new MutableRevisionSlots();
$this->assertSame( [], $slots->getSlots() );
$slotA = SlotRecord::newUnsaved( 'some', new WikitextContent( 'A' ) );
$slots->setSlot( $slotA );
$this->assertTrue( $slots->hasSlot( 'some' ) );
$this->assertSame( $slotA, $slots->getSlot( 'some' ) );
$this->assertSame( [ 'some' => $slotA ], $slots->getSlots() );
$slotB = SlotRecord::newUnsaved( 'other', new WikitextContent( 'B' ) );
$slots->setSlot( $slotB );
$this->assertTrue( $slots->hasSlot( 'other' ) );
$this->assertSame( $slotB, $slots->getSlot( 'other' ) );
$this->assertSame( [ 'some' => $slotA, 'other' => $slotB ], $slots->getSlots() );
}
public function testSetExistingSlotOverwritesSlot() {
$slots = new MutableRevisionSlots();
$this->assertSame( [], $slots->getSlots() );
$slotA = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
$slots->setSlot( $slotA );
$this->assertSame( $slotA, $slots->getSlot( SlotRecord::MAIN ) );
$this->assertSame( [ 'main' => $slotA ], $slots->getSlots() );
$slotB = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'B' ) );
$slots->setSlot( $slotB );
$this->assertSame( $slotB, $slots->getSlot( SlotRecord::MAIN ) );
$this->assertSame( [ 'main' => $slotB ], $slots->getSlots() );
}
/**
* @param string $role
* @param Content $content
* @return SlotRecord
*/
private function newSavedSlot( $role, Content $content ) {
return SlotRecord::newSaved( 7, 7, 'xyz', SlotRecord::newUnsaved( $role, $content ) );
}
public function testInheritSlotOverwritesSlot() {
$slots = new MutableRevisionSlots();
$slotA = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
$slots->setSlot( $slotA );
$slotB = $this->newSavedSlot( SlotRecord::MAIN, new WikitextContent( 'B' ) );
$slotC = $this->newSavedSlot( 'foo', new WikitextContent( 'C' ) );
$slots->inheritSlot( $slotB );
$slots->inheritSlot( $slotC );
$this->assertSame( [ 'main', 'foo' ], $slots->getSlotRoles() );
$this->assertNotSame( $slotB, $slots->getSlot( SlotRecord::MAIN ) );
$this->assertNotSame( $slotC, $slots->getSlot( 'foo' ) );
$this->assertTrue( $slots->getSlot( SlotRecord::MAIN )->isInherited() );
$this->assertTrue( $slots->getSlot( 'foo' )->isInherited() );
$this->assertSame( $slotB->getContent(), $slots->getSlot( SlotRecord::MAIN )->getContent() );
$this->assertSame( $slotC->getContent(), $slots->getSlot( 'foo' )->getContent() );
}
public function testSetContentOfExistingSlotOverwritesContent() {
$slots = new MutableRevisionSlots();
$this->assertSame( [], $slots->getSlots() );
$slotA = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
$slots->setSlot( $slotA );
$this->assertSame( $slotA, $slots->getSlot( SlotRecord::MAIN ) );
$this->assertSame( [ 'main' => $slotA ], $slots->getSlots() );
$newContent = new WikitextContent( 'B' );
$slots->setContent( SlotRecord::MAIN, $newContent );
$this->assertSame( $newContent, $slots->getContent( SlotRecord::MAIN ) );
}
public function testRemoveExistingSlot() {
$slotA = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
$slots = new MutableRevisionSlots( [ $slotA ] );
$this->assertSame( [ 'main' => $slotA ], $slots->getSlots() );
$slots->removeSlot( SlotRecord::MAIN );
$this->assertSame( [], $slots->getSlots() );
$this->expectException( RevisionAccessException::class );
$slots->getSlot( SlotRecord::MAIN );
}
public function testNewFromParentRevisionSlots() {
/** @var SlotRecord[] $parentSlots */
$parentSlots = [
'some' => $this->newSavedSlot( 'some', new WikitextContent( 'X' ) ),
'other' => $this->newSavedSlot( 'other', new WikitextContent( 'Y' ) ),
];
$slots = MutableRevisionSlots::newFromParentRevisionSlots( $parentSlots );
$this->assertSame( [ 'some', 'other' ], $slots->getSlotRoles() );
$this->assertNotSame( $parentSlots['some'], $slots->getSlot( 'some' ) );
$this->assertNotSame( $parentSlots['other'], $slots->getSlot( 'other' ) );
$this->assertTrue( $slots->getSlot( 'some' )->isInherited() );
$this->assertTrue( $slots->getSlot( 'other' )->isInherited() );
$this->assertSame( $parentSlots['some']->getContent(), $slots->getContent( 'some' ) );
$this->assertSame( $parentSlots['other']->getContent(), $slots->getContent( 'other' ) );
}
public function testResetCallback() {
$counter = 0;
$callback = function () use ( &$counter ) {
$counter++;
};
$slots = new MutableRevisionSlots( [], $callback );
$slot = SlotRecord::newUnsaved( SlotRecord::MAIN, new WikitextContent( 'A' ) );
$slots->setSlot( $slot );
$this->assertSame( 1, $counter, 'setSlot triggers callback' );
$slots->setContent( 'other', new WikitextContent( 'Y' ) );
$this->assertSame( 2, $counter, 'setContent triggers callback' );
$slot = $this->newSavedSlot( 'some', new WikitextContent( 'X' ) );
$slots->inheritSlot( $slot );
$this->assertSame( 3, $counter, 'inheritSlot triggers callback' );
$slots->removeSlot( SlotRecord::MAIN );
$this->assertSame( 4, $counter, 'inheritSlot triggers callback' );
}
}