This code didn't work because the $GLOBALS array is exposed by reference.
Once this reference was broken by unset(), the rest just manipulated a
local array that happens to be called "GLOBALS". It must not be unset or
re-assigned. It can only be changed in-place.
Before this, the execution of a MediaWikiUnitTestCase test stored a
copy of GLOBALS in unitGlobals, then lost the GLOBALS pointer and
created a new variable called "GLOBALS". As such, the tearDown() function
didn't do what it meant to do, either – which then results in odd
failures like T230023
Rewrite it as follows:
* In setup, store the current GLOBALS keys and values, then reduce
GLOBALS to only the whitelisted keys and values.
* In teardown, restore the original state.
* As optimisation, do this from setUpBeforeClass as well, so that
there are relatively few globals to reset between tests.
(Thanks @Simetrical!)
The following tests were previously passing by accident under
MediaWikiUnitTestCase but actually did depend on global config.
* MainSlotRoleHandlerTest (…, ContentHandler, $wgContentHandlers)
* SlotRecordTest (…, ContentHandler, $wgContentHandlers)
* WikiReferenceTest (wfParseUrl, $wgUrlProtocols)
* DifferenceEngineSlotDiffRendererTest (DifferenceEngine, wfDebug, …)
* SlotDiffRendererTest (…, ContentHandler, $wgContentHandlers)
* FileBackendDBRepoWrapperTest (wfWikiID, "Backend domain ID not provided")
* JpegMetadataExtractorTest (…, wfDebug, …, LoggerFactory, …)
* ParserFactoryTest (…, wfDebug, …, LoggerFactory, InvalidArgumentException)
* MediaWikiPageNameNormalizerTest (…, wfDebug, …, LoggerFactory, …)
* SiteExporterTest (SiteImporter, wfLogWarning, …)
* SiteImporterTest (Site::newForType, $wgSiteTypes)
* ZipDirectoryReaderTest (…, wfDebug, …, LoggerFactory, …)
Bug: T230023
Change-Id: Ic22075bb5e81b7c2c4c1b8647547aa55306a10a7
* Common code for test environment initialisation and teardown
*/
class TestSetup {
+ public static $bootstrapGlobals;
+
+ /**
+ * For use in MediaWikiUnitTestCase.
+ *
+ * This should be called before DefaultSettings.php or Setup.php loads.
+ */
+ public static function snapshotGlobals() {
+ self::$bootstrapGlobals = [];
+ foreach ( $GLOBALS as $key => $_ ) {
+ // Support: HHVM (avoid self-ref)
+ if ( $key !== 'GLOBALS' ) {
+ self::$bootstrapGlobals[ $key ] =& $GLOBALS[$key];
+ }
+ }
+ }
+
/**
* This should be called before Setup.php, e.g. from the finalSetup() method
* of a Maintenance subclass
use MediaWikiCoversValidator;
use MediaWikiTestCaseTrait;
- private $unitGlobals = [];
+ private static $originalGlobals;
+ private static $unitGlobals;
- protected function setUp() {
- parent::setUp();
- $reflection = new ReflectionClass( $this );
+ public static function setUpBeforeClass() {
+ parent::setUpBeforeClass();
+
+ $reflection = new ReflectionClass( static::class );
$dirSeparator = DIRECTORY_SEPARATOR;
if ( stripos( $reflection->getFilename(), "${dirSeparator}unit${dirSeparator}" ) === false ) {
- $this->fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
+ self::fail( 'This unit test needs to be in "tests/phpunit/unit"!' );
+ }
+
+ if ( defined( 'HHVM_VERSION' ) ) {
+ // There are a number of issues we encountered in trying to make this
+ // work on HHVM. Specifically, once an MediaWikiIntegrationTestCase executes
+ // before us, the original globals go missing. This might have to do with
+ // one of the non-unit tests passing GLOBALS somewhere and causing HHVM
+ // to get confused somehow.
+ return;
+ }
+
+ self::$unitGlobals =& TestSetup::$bootstrapGlobals;
+ // The autoloader may change between bootstrap and the first test,
+ // so (lazily) capture these here instead.
+ self::$unitGlobals['wgAutoloadClasses'] =& $GLOBALS['wgAutoloadClasses'];
+ self::$unitGlobals['wgAutoloadLocalClasses'] =& $GLOBALS['wgAutoloadLocalClasses'];
+ // This value should always be true.
+ self::$unitGlobals['wgAutoloadAttemptLowercase'] = true;
+
+ // Would be nice if we coud simply replace $GLOBALS as a whole,
+ // but unsetting or re-assigning that breaks the reference of this magic
+ // variable. Thus we have to modify it in place.
+ self::$originalGlobals = [];
+ foreach ( $GLOBALS as $key => $_ ) {
+ // Stash current values
+ self::$originalGlobals[$key] =& $GLOBALS[$key];
+
+ // Remove globals not part of the snapshot (see bootstrap.php, phpunit.php).
+ // Support: HHVM (avoid self-ref)
+ if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$unitGlobals ) ) {
+ unset( $GLOBALS[$key] );
+ }
}
- $this->unitGlobals = $GLOBALS;
- unset( $GLOBALS );
- $GLOBALS = [];
- // Add back the minimal set of globals needed for unit tests to run for core +
- // extensions/skins.
- foreach ( $this->unitGlobals['wgPhpUnitBootstrapGlobals'] ?? [] as $key => $value ) {
- $GLOBALS[ $key ] = $this->unitGlobals[ $key ];
+ // Restore values from the early snapshot
+ // Not by ref because tests must not be able to modify the snapshot.
+ foreach ( self::$unitGlobals as $key => $value ) {
+ $GLOBALS[ $key ] = $value;
}
}
protected function tearDown() {
- $GLOBALS = $this->unitGlobals;
+ if ( !defined( 'HHVM_VERSION' ) ) {
+ // Quick reset between tests
+ foreach ( $GLOBALS as $key => $_ ) {
+ if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$unitGlobals ) ) {
+ unset( $GLOBALS[$key] );
+ }
+ }
+ foreach ( self::$unitGlobals as $key => $value ) {
+ $GLOBALS[ $key ] = $value;
+ }
+ }
+
parent::tearDown();
}
+ public static function tearDownAfterClass() {
+ if ( !defined( 'HHVM_VERSION' ) ) {
+ // Remove globals created by the test
+ foreach ( $GLOBALS as $key => $_ ) {
+ if ( $key !== 'GLOBALS' && !array_key_exists( $key, self::$originalGlobals ) ) {
+ unset( $GLOBALS[$key] );
+ }
+ }
+ // Restore values (including reference!)
+ foreach ( self::$originalGlobals as $key => &$value ) {
+ $GLOBALS[ $key ] =& $value;
+ }
+ }
+
+ parent::tearDownAfterClass();
+ }
+
/**
* Create a temporary hook handler which will be reset by tearDown.
* This replaces other handlers for the same hook.
// these variables must be defined before setup runs
$GLOBALS['IP'] = $IP;
-// Set bootstrap globals to reuse in MediaWikiUnitTestCase
-$bootstrapGlobals = [];
-foreach ( $GLOBALS as $key => $value ) {
- $bootstrapGlobals[ $key ] = $value;
-}
-$GLOBALS['wgPhpUnitBootstrapGlobals'] = $bootstrapGlobals;
-// Faking for Setup.php
+
+require_once "$IP/tests/common/TestSetup.php";
+TestSetup::snapshotGlobals();
+
+// Faking in lieu of Setup.php
$GLOBALS['wgScopeTest'] = 'MediaWiki Setup.php scope test';
$GLOBALS['wgCommandLineMode'] = true;
$GLOBALS['wgAutoloadClasses'] = [];
-require_once "$IP/tests/common/TestSetup.php";
-
wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" );
wfRequireOnceInGlobalScope( "$IP/tests/common/TestsAutoLoader.php" );
wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" );
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use MediaWiki\Revision\MainSlotRoleHandler;
+use PHPUnit\Framework\MockObject\MockObject;
+use Title;
+
+/**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler
+ */
+class MainSlotRoleHandlerTest extends \MediaWikiIntegrationTestCase {
+
+ private function makeTitleObject( $ns ) {
+ /** @var Title|MockObject $title */
+ $title = $this->getMockBuilder( Title::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $title->method( 'getNamespace' )
+ ->willReturn( $ns );
+
+ return $title;
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
+ */
+ public function testConstruction() {
+ $handler = new MainSlotRoleHandler( [] );
+ $this->assertSame( 'main', $handler->getRole() );
+ $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
+
+ $hints = $handler->getOutputLayoutHints();
+ $this->assertArrayHasKey( 'display', $hints );
+ $this->assertArrayHasKey( 'region', $hints );
+ $this->assertArrayHasKey( 'placement', $hints );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
+ */
+ public function testFetDefaultModel() {
+ $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
+
+ // For the main handler, the namespace determins the default model
+ $titleMain = $this->makeTitleObject( NS_MAIN );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
+
+ $title100 = $this->makeTitleObject( 100 );
+ $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
+ */
+ public function testIsAllowedModel() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ // For the main handler, (nearly) all models are allowed
+ $title = $this->makeTitleObject( NS_MAIN );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
+ $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
+ */
+ public function testSupportsArticleCount() {
+ $handler = new MainSlotRoleHandler( [] );
+
+ $this->assertTrue( $handler->supportsArticleCount() );
+ }
+
+}
--- /dev/null
+<?php
+
+namespace MediaWiki\Tests\Revision;
+
+use InvalidArgumentException;
+use LogicException;
+use MediaWiki\Revision\IncompleteRevisionException;
+use MediaWiki\Revision\SlotRecord;
+use MediaWiki\Revision\SuppressedDataException;
+use WikitextContent;
+
+/**
+ * @covers \MediaWiki\Revision\SlotRecord
+ */
+class SlotRecordTest extends \MediaWikiIntegrationTestCase {
+
+ 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()->getText() );
+ $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()->getText() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotEmpty( $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()->getText() );
+ $this->assertSame( 1, $record->getSize() );
+ $this->assertNotEmpty( $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 testHashComputed() {
+ $row = $this->makeRow();
+ $row->content_sha1 = '';
+
+ $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
+ $this->assertNotEmpty( $rec->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()->getText() );
+ $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 ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers WikiReference
+ */
+class WikiReferenceTest extends MediaWikiIntegrationTestCase {
+
+ public function provideGetDisplayName() {
+ return [
+ 'http' => [ 'foo.bar', 'http://foo.bar' ],
+ 'https' => [ 'foo.bar', 'http://foo.bar' ],
+
+ // apparently, this is the expected behavior
+ 'invalid' => [ 'purple kittens', 'purple kittens' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetDisplayName
+ */
+ public function testGetDisplayName( $expected, $canonicalServer ) {
+ $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
+ $this->assertEquals( $expected, $reference->getDisplayName() );
+ }
+
+ public function testGetCanonicalServer() {
+ $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
+ $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
+ }
+
+ public function provideGetCanonicalUrl() {
+ return [
+ 'no fragment' => [
+ 'https://acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ null
+ ],
+ 'empty fragment' => [
+ 'https://acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ ''
+ ],
+ 'fragment' => [
+ 'https://acme.com/wiki/Foo#Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar'
+ ],
+ 'double fragment' => [
+ 'https://acme.com/wiki/Foo#Bar%23Xus',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar#Xus'
+ ],
+ 'escaped fragment' => [
+ 'https://acme.com/wiki/Foo%23Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo#Bar',
+ null
+ ],
+ 'empty path' => [
+ 'https://acme.com/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/$1',
+ 'Foo',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetCanonicalUrl
+ */
+ public function testGetCanonicalUrl(
+ $expected, $canonicalServer, $server, $path, $page, $fragmentId
+ ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
+ }
+
+ /**
+ * @dataProvider provideGetCanonicalUrl
+ * @note getUrl is an alias for getCanonicalUrl
+ */
+ public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
+ }
+
+ public function provideGetFullUrl() {
+ return [
+ 'no fragment' => [
+ '//acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ null
+ ],
+ 'empty fragment' => [
+ '//acme.com/wiki/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ ''
+ ],
+ 'fragment' => [
+ '//acme.com/wiki/Foo#Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar'
+ ],
+ 'double fragment' => [
+ '//acme.com/wiki/Foo#Bar%23Xus',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo',
+ 'Bar#Xus'
+ ],
+ 'escaped fragment' => [
+ '//acme.com/wiki/Foo%23Bar',
+ 'https://acme.com',
+ '//acme.com',
+ '/wiki/$1',
+ 'Foo#Bar',
+ null
+ ],
+ 'empty path' => [
+ '//acme.com/Foo',
+ 'https://acme.com',
+ '//acme.com',
+ '/$1',
+ 'Foo',
+ null
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetFullUrl
+ */
+ public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
+ $reference = new WikiReference( $canonicalServer, $path, $server );
+ $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends MediaWikiIntegrationTestCase {
+
+ public function testGetDiff() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ $this->assertEquals( 'xxx|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( null, $newContent );
+ $this->assertEquals( '|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+ $this->assertEquals( 'xxx|', $diff );
+ }
+
+ public function testAddModules() {
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'addModules' ] )
+ ->getMock();
+ $output->expects( $this->once() )
+ ->method( 'addModules' )
+ ->with( 'foo' );
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $slotDiffRenderer->addModules( $output );
+ }
+
+ public function testGetExtraCacheKeys() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+ $this->assertSame( [ 'foo' ], $extraCacheKeys );
+ }
+
+}
--- /dev/null
+<?php
+
+use Wikimedia\Assert\ParameterTypeException;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers SlotDiffRenderer
+ */
+class SlotDiffRendererTest extends \MediaWikiIntegrationTestCase {
+
+ /**
+ * @dataProvider provideNormalizeContents
+ */
+ public function testNormalizeContents(
+ $oldContent, $newContent, $allowedClasses,
+ $expectedOldContent, $expectedNewContent, $expectedExceptionClass
+ ) {
+ $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->getMock();
+ try {
+ // __call needs help deciding which parameter to take by reference
+ call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
+ 'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
+ $this->assertEquals( $expectedOldContent, $oldContent );
+ $this->assertEquals( $expectedNewContent, $newContent );
+ } catch ( Exception $e ) {
+ if ( !$expectedExceptionClass ) {
+ throw $e;
+ }
+ $this->assertInstanceOf( $expectedExceptionClass, $e );
+ }
+ }
+
+ public function provideNormalizeContents() {
+ return [
+ 'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
+ 'left null' => [
+ null, new WikitextContent( 'abc' ), null,
+ new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
+ ],
+ 'right null' => [
+ new WikitextContent( 'def' ), null, null,
+ new WikitextContent( 'def' ), new WikitextContent( '' ), null,
+ ],
+ 'type filter' => [
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+ ],
+ 'type filter (subclass)' => [
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
+ ],
+ 'type filter (null)' => [
+ new WikitextContent( 'abc' ), null, TextContent::class,
+ new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
+ ],
+ 'type filter failure (left)' => [
+ new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
+ null, null, ParameterTypeException::class,
+ ],
+ 'type filter failure (right)' => [
+ new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
+ null, null, ParameterTypeException::class,
+ ],
+ 'type filter (array syntax)' => [
+ new WikitextContent( 'abc' ), new JsonContent( 'def' ),
+ [ JsonContent::class, WikitextContent::class ],
+ new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
+ ],
+ 'type filter failure (array syntax)' => [
+ new WikitextContent( 'abc' ), new CssContent( 'def' ),
+ [ JsonContent::class, WikitextContent::class ],
+ null, null, ParameterTypeException::class,
+ ],
+ ];
+ }
+
+}
--- /dev/null
+<?php
+
+class FileBackendDBRepoWrapperTest extends MediaWikiIntegrationTestCase {
+ protected $backendName = 'foo-backend';
+ protected $repoName = 'pureTestRepo';
+
+ /**
+ * @dataProvider getBackendPathsProvider
+ * @covers FileBackendDBRepoWrapper::getBackendPaths
+ */
+ public function testGetBackendPaths(
+ $mocks,
+ $latest,
+ $dbReadsExpected,
+ $dbReturnValue,
+ $originalPath,
+ $expectedBackendPath,
+ $message ) {
+ list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
+
+ $dbMock->expects( $dbReadsExpected )
+ ->method( 'selectField' )
+ ->will( $this->returnValue( $dbReturnValue ) );
+
+ $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
+
+ $this->assertEquals(
+ $expectedBackendPath,
+ $newPaths[0],
+ $message );
+ }
+
+ public function getBackendPathsProvider() {
+ $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
+ $mocksForCaching = $this->getMocks();
+
+ return [
+ [
+ $mocksForCaching,
+ false,
+ $this->once(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'Public path translated correctly',
+ ],
+ [
+ $mocksForCaching,
+ false,
+ $this->never(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'LRU cache leveraged',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->once(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-public/f/o/foobar.jpg',
+ $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ 'Latest obtained',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->never(),
+ '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
+ $prefix . '-deleted/f/o/foobar.jpg',
+ $prefix . '-original/f/o/o/foobar',
+ 'Deleted path translated correctly',
+ ],
+ [
+ $this->getMocks(),
+ true,
+ $this->once(),
+ null,
+ $prefix . '-public/b/a/baz.jpg',
+ $prefix . '-public/b/a/baz.jpg',
+ 'Path left untouched if no sha1 can be found',
+ ],
+ ];
+ }
+
+ /**
+ * @covers FileBackendDBRepoWrapper::getFileContentsMulti
+ */
+ public function testGetFileContentsMulti() {
+ list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
+
+ $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
+ . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
+ $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
+ . '-public/f/o/foobar.jpg';
+
+ $dbMock->expects( $this->once() )
+ ->method( 'selectField' )
+ ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
+
+ $backendMock->expects( $this->once() )
+ ->method( 'getFileContentsMulti' )
+ ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
+
+ $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
+
+ $this->assertEquals(
+ [ $filenamePath => 'foo' ],
+ $result,
+ 'File contents paths translated properly'
+ );
+ }
+
+ protected function getMocks() {
+ $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
+ ->disableOriginalClone()
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $backendMock = $this->getMockBuilder( FSFileBackend::class )
+ ->setConstructorArgs( [ [
+ 'name' => $this->backendName,
+ 'wikiId' => wfWikiID()
+ ] ] )
+ ->getMock();
+
+ $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
+ ->setMethods( [ 'getDB' ] )
+ ->setConstructorArgs( [ [
+ 'backend' => $backendMock,
+ 'repoName' => $this->repoName,
+ 'dbHandleFactory' => null
+ ] ] )
+ ->getMock();
+
+ $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
+
+ return [ $dbMock, $backendMock, $wrapperMock ];
+ }
+}
--- /dev/null
+<?php
+/**
+ * @todo Could use a test of extended XMP segments. Hard to find programs that
+ * create example files, and creating my own in vim propbably wouldn't
+ * serve as a very good "test". (Adobe photoshop probably creates such files
+ * but it costs money). The implementation of it currently in MediaWiki is based
+ * solely on reading the standard, without any real world test files.
+ *
+ * @group Media
+ * @covers JpegMetadataExtractor
+ */
+class JpegMetadataExtractorTest extends MediaWikiIntegrationTestCase {
+
+ protected $filePath;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->filePath = __DIR__ . '/../../data/media/';
+ }
+
+ /**
+ * We also use this test to test padding bytes don't
+ * screw stuff up
+ *
+ * @param string $file Filename
+ *
+ * @dataProvider provideUtf8Comment
+ */
+ public function testUtf8Comment( $file ) {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
+ $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
+ }
+
+ public static function provideUtf8Comment() {
+ return [
+ [ 'jpeg-comment-utf.jpg' ],
+ [ 'jpeg-padding-even.jpg' ],
+ [ 'jpeg-padding-odd.jpg' ],
+ ];
+ }
+
+ /** The file is iso-8859-1, but it should get auto converted */
+ public function testIso88591Comment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
+ $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
+ }
+
+ /** Comment values that are non-textual (random binary junk) should not be shown.
+ * The example test file has a comment with a 0x5 byte in it which is a control character
+ * and considered binary junk for our purposes.
+ */
+ public function testBinaryCommentStripped() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
+ $this->assertEmpty( $res['COM'] );
+ }
+
+ /* Very rarely a file can have multiple comments.
+ * Order of comments is based on order inside the file.
+ */
+ public function testMultipleComment() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
+ $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
+ }
+
+ public function testXMPExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+ public function testPSIRExtraction() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $expected = '50686f746f73686f7020332e30003842494d04040000000'
+ . '000181c02190004746573741c02190003666f6f1c020000020004';
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
+ }
+
+ public function testXMPExtractionAltAppId() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
+ $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
+ $this->assertEquals( $expected, $res['XMP'] );
+ }
+
+ public function testIPTCHashComparisionNoHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-no-hash', $res );
+ }
+
+ public function testIPTCHashComparisionBadHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-bad-hash', $res );
+ }
+
+ public function testIPTCHashComparisionGoodHash() {
+ $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
+
+ $this->assertEquals( 'iptc-good-hash', $res );
+ }
+
+ public function testExifByteOrder() {
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
+ $expected = 'BE';
+ $this->assertEquals( $expected, $res['byteOrder'] );
+ }
+
+ public function testInfiniteRead() {
+ // test file truncated right after a segment, which previously
+ // caused an infinite loop looking for the next segment byte.
+ // Should get past infinite loop and throw in wfUnpack()
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+ }
+
+ public function testInfiniteRead2() {
+ // test file truncated after a segment's marker and size, which
+ // would cause a seek past end of file. Seek past end of file
+ // doesn't actually fail, but prevents further reading and was
+ // devolving into the previous case (testInfiniteRead).
+ $this->setExpectedException( 'MWException' );
+ $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * @covers ParserFactory
+ */
+class ParserFactoryTest extends MediaWikiIntegrationTestCase {
+ use FactoryArgTestTrait;
+
+ protected static function getFactoryClass() {
+ return ParserFactory::class;
+ }
+
+ protected static function getInstanceClass() {
+ return Parser::class;
+ }
+
+ protected static function getFactoryMethodName() {
+ return 'create';
+ }
+
+ protected static function getExtraClassArgCount() {
+ // The parser factory itself is passed to the parser
+ return 1;
+ }
+
+ protected function getOverriddenMockValueForParam( ReflectionParameter $param ) {
+ if ( $param->getPosition() === 0 ) {
+ return [ $this->createMock( MediaWiki\Config\ServiceOptions::class ) ];
+ }
+ return [];
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\Site\MediaWikiPageNameNormalizer;
+
+/**
+ * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.27
+ *
+ * @group Site
+ * @group medium
+ *
+ * @author Marius Hoch
+ */
+class MediaWikiPageNameNormalizerTest extends MediaWikiIntegrationTestCase {
+
+ /**
+ * @dataProvider normalizePageTitleProvider
+ */
+ public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
+ MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
+
+ $normalizer = new MediaWikiPageNameNormalizer(
+ new MediaWikiPageNameNormalizerTestMockHttp()
+ );
+
+ $this->assertSame(
+ $expected,
+ $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
+ );
+ }
+
+ public function normalizePageTitleProvider() {
+ // Response are taken from wikidata and kkwiki using the following API request
+ // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
+ return [
+ 'universe (Q1)' => [
+ 'Q1',
+ 'Q1',
+ '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
+ . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
+ . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+ . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
+ ],
+ 'Q404 redirects to Q395' => [
+ 'Q395',
+ 'Q404',
+ '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
+ . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
+ . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
+ . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
+ ],
+ 'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
+ 'Д',
+ 'D',
+ '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
+ . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
+ . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
+ . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
+ . '"lastrevid":2373618,"length":3501}}}}'
+ ],
+ 'there is no Q0' => [
+ false,
+ 'Q0',
+ '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
+ . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
+ . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
+ ],
+ 'invalid title' => [
+ false,
+ '{{',
+ '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
+ . '"invalidreason":"The requested page title contains invalid '
+ . 'characters: \"{\".","invalid":""}}}}'
+ ],
+ 'error on get' => [ false, 'ABC', false ]
+ ];
+ }
+
+}
+
+/**
+ * @private
+ * @see Http
+ */
+class MediaWikiPageNameNormalizerTestMockHttp extends Http {
+
+ /**
+ * @var mixed
+ */
+ public static $response;
+
+ public static function get( $url, array $options = [], $caller = __METHOD__ ) {
+ PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
+ PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
+
+ return self::$response;
+ }
+}
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @covers SiteExporter
+ *
+ * @author Daniel Kinzler
+ */
+class SiteExporterTest extends MediaWikiIntegrationTestCase {
+
+ public function testConstructor_InvalidArgument() {
+ $this->setExpectedException( InvalidArgumentException::class );
+
+ new SiteExporter( 'Foo' );
+ }
+
+ public function testExportSites() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $tmp = tmpfile();
+ $exporter = new SiteExporter( $tmp );
+
+ $exporter->exportSites( [ $foo, $acme ] );
+
+ fseek( $tmp, 0 );
+ $xml = fread( $tmp, 16 * 1024 );
+
+ $this->assertContains( '<sites ', $xml );
+ $this->assertContains( '<site>', $xml );
+ $this->assertContains( '<globalid>Foo</globalid>', $xml );
+ $this->assertContains( '</site>', $xml );
+ $this->assertContains( '<globalid>acme.com</globalid>', $xml );
+ $this->assertContains( '<group>Test</group>', $xml );
+ $this->assertContains( '<localid type="interwiki">acme</localid>', $xml );
+ $this->assertContains( '<path type="link">http://acme.com/</path>', $xml );
+ $this->assertContains( '</sites>', $xml );
+
+ // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs.
+ $xsdFile = __DIR__ . '/../../../../docs/sitelist-1.0.xsd';
+ $xsdData = file_get_contents( $xsdFile );
+
+ $document = new DOMDocument();
+ $document->loadXML( $xml, LIBXML_NONET );
+ $document->schemaValidateSource( $xsdData );
+ }
+
+ private function newSiteStore( SiteList $sites ) {
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
+
+ $store->expects( $this->once() )
+ ->method( 'saveSites' )
+ ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) {
+ foreach ( $moreSites as $site ) {
+ $sites->setSite( $site );
+ }
+ } ) );
+
+ $store->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( new SiteList() ) );
+
+ return $store;
+ }
+
+ public function provideRoundTrip() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+ $dewiki->setGlobalId( 'dewiki' );
+ $dewiki->setGroup( 'wikipedia' );
+ $dewiki->setForward( true );
+ $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+ $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+ $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+ $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+ $dewiki->setSource( 'meta.wikimedia.org' );
+
+ return [
+ 'empty' => [
+ new SiteList()
+ ],
+
+ 'some' => [
+ new SiteList( [ $foo, $acme, $dewiki ] ),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideRoundTrip()
+ */
+ public function testRoundTrip( SiteList $sites ) {
+ $tmp = tmpfile();
+ $exporter = new SiteExporter( $tmp );
+
+ $exporter->exportSites( $sites );
+
+ fseek( $tmp, 0 );
+ $xml = fread( $tmp, 16 * 1024 );
+
+ $actualSites = new SiteList();
+ $store = $this->newSiteStore( $actualSites );
+
+ $importer = new SiteImporter( $store );
+ $importer->importFromXML( $xml );
+
+ $this->assertEquals( $sites, $actualSites );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @ingroup Site
+ * @ingroup Test
+ *
+ * @group Site
+ *
+ * @covers SiteImporter
+ *
+ * @author Daniel Kinzler
+ */
+class SiteImporterTest extends MediaWikiIntegrationTestCase {
+
+ private function newSiteImporter( array $expectedSites, $errorCount ) {
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
+
+ $store->expects( $this->once() )
+ ->method( 'saveSites' )
+ ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) {
+ $this->assertSitesEqual( $expectedSites, $sites );
+ } ) );
+
+ $store->expects( $this->any() )
+ ->method( 'getSites' )
+ ->will( $this->returnValue( new SiteList() ) );
+
+ $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
+ $errorHandler->expects( $this->exactly( $errorCount ) )
+ ->method( 'error' );
+
+ $importer = new SiteImporter( $store );
+ $importer->setExceptionCallback( [ $errorHandler, 'error' ] );
+
+ return $importer;
+ }
+
+ public function assertSitesEqual( $expected, $actual, $message = '' ) {
+ $this->assertEquals(
+ $this->getSerializedSiteList( $expected ),
+ $this->getSerializedSiteList( $actual ),
+ $message
+ );
+ }
+
+ public function provideImportFromXML() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+ $dewiki->setGlobalId( 'dewiki' );
+ $dewiki->setGroup( 'wikipedia' );
+ $dewiki->setForward( true );
+ $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+ $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+ $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+ $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+ $dewiki->setSource( 'meta.wikimedia.org' );
+
+ return [
+ 'empty' => [
+ '<sites></sites>',
+ [],
+ ],
+ 'no sites' => [
+ '<sites><Foo><globalid>Foo</globalid></Foo><Bar><quux>Bla</quux></Bar></sites>',
+ [],
+ ],
+ 'minimal' => [
+ '<sites>' .
+ '<site><globalid>Foo</globalid></site>' .
+ '</sites>',
+ [ $foo ],
+ ],
+ 'full' => [
+ '<sites>' .
+ '<site><globalid>Foo</globalid></site>' .
+ '<site>' .
+ '<globalid>acme.com</globalid>' .
+ '<localid type="interwiki">acme</localid>' .
+ '<group>Test</group>' .
+ '<path type="link">http://acme.com/</path>' .
+ '</site>' .
+ '<site type="mediawiki">' .
+ '<source>meta.wikimedia.org</source>' .
+ '<globalid>dewiki</globalid>' .
+ '<localid type="interwiki">wikipedia</localid>' .
+ '<localid type="equivalent">de</localid>' .
+ '<group>wikipedia</group>' .
+ '<forward/>' .
+ '<path type="link">http://de.wikipedia.org/w/</path>' .
+ '<path type="page_path">http://de.wikipedia.org/wiki/</path>' .
+ '</site>' .
+ '</sites>',
+ [ $foo, $acme, $dewiki ],
+ ],
+ 'skip' => [
+ '<sites>' .
+ '<site><globalid>Foo</globalid></site>' .
+ '<site><barf>Foo</barf></site>' .
+ '<site>' .
+ '<globalid>acme.com</globalid>' .
+ '<localid type="interwiki">acme</localid>' .
+ '<silly>boop!</silly>' .
+ '<group>Test</group>' .
+ '<path type="link">http://acme.com/</path>' .
+ '</site>' .
+ '</sites>',
+ [ $foo, $acme ],
+ 1
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideImportFromXML
+ */
+ public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) {
+ $importer = $this->newSiteImporter( $expectedSites, $errorCount );
+ $importer->importFromXML( $xml );
+ }
+
+ public function testImportFromXML_malformed() {
+ $this->setExpectedException( Exception::class );
+
+ $store = $this->getMockBuilder( SiteStore::class )->getMock();
+ $importer = new SiteImporter( $store );
+ $importer->importFromXML( 'THIS IS NOT XML' );
+ }
+
+ public function testImportFromFile() {
+ $foo = Site::newForType( Site::TYPE_UNKNOWN );
+ $foo->setGlobalId( 'Foo' );
+
+ $acme = Site::newForType( Site::TYPE_UNKNOWN );
+ $acme->setGlobalId( 'acme.com' );
+ $acme->setGroup( 'Test' );
+ $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
+ $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
+
+ $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
+ $dewiki->setGlobalId( 'dewiki' );
+ $dewiki->setGroup( 'wikipedia' );
+ $dewiki->setForward( true );
+ $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
+ $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
+ $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
+ $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
+ $dewiki->setSource( 'meta.wikimedia.org' );
+
+ $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 );
+
+ $file = __DIR__ . '/SiteImporterTest.xml';
+ $importer->importFromFile( $file );
+ }
+
+ /**
+ * @param Site[] $sites
+ *
+ * @return array[]
+ */
+ private function getSerializedSiteList( $sites ) {
+ $serialized = [];
+
+ foreach ( $sites as $site ) {
+ $key = $site->getGlobalId();
+ $data = unserialize( $site->serialize() );
+
+ $serialized[$key] = $data;
+ }
+
+ return $serialized;
+ }
+}
--- /dev/null
+<sites version="1.0" xmlns="http://www.mediawiki.org/xml/sitelist-1.0/">
+ <site><globalid>Foo</globalid></site>
+ <site>
+ <globalid>acme.com</globalid>
+ <localid type="interwiki">acme</localid>
+ <group>Test</group>
+ <path type="link">http://acme.com/</path>
+ </site>
+ <site type="mediawiki">
+ <source>meta.wikimedia.org</source>
+ <globalid>dewiki</globalid>
+ <localid type="interwiki">wikipedia</localid>
+ <localid type="equivalent">de</localid>
+ <group>wikipedia</group>
+ <forward/>
+ <path type="link">http://de.wikipedia.org/w/</path>
+ <path type="page_path">http://de.wikipedia.org/wiki/</path>
+ </site>
+</sites>
--- /dev/null
+<?php
+
+/**
+ * @covers ZipDirectoryReader
+ */
+class ZipDirectoryReaderTest extends MediaWikiIntegrationTestCase {
+
+ protected $zipDir;
+ protected $entries;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->zipDir = __DIR__ . '/../../data/zip';
+ }
+
+ function zipCallback( $entry ) {
+ $this->entries[] = $entry;
+ }
+
+ function readZipAssertError( $file, $error, $assertMessage ) {
+ $this->entries = [];
+ $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+ $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
+ }
+
+ function readZipAssertSuccess( $file, $assertMessage ) {
+ $this->entries = [];
+ $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
+ $this->assertTrue( $status->isOK(), $assertMessage );
+ }
+
+ public function testEmpty() {
+ $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
+ }
+
+ public function testMultiDisk0() {
+ $this->readZipAssertError( 'split.zip', 'zip-unsupported',
+ 'Split zip error' );
+ }
+
+ public function testNoSignature() {
+ $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
+ 'No signature should give "wrong format" error' );
+ }
+
+ public function testSimple() {
+ $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
+ $this->assertEquals( $this->entries, [ [
+ 'name' => 'Class.class',
+ 'mtime' => '20010115000000',
+ 'size' => 1,
+ ] ] );
+ }
+
+ public function testBadCentralEntrySignature() {
+ $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
+ 'Bad central entry error' );
+ }
+
+ public function testTrailingBytes() {
+ // Due to T40432 this is now zip-wrong-format instead of zip-bad
+ $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
+ 'Trailing bytes error' );
+ }
+
+ public function testWrongCDStart() {
+ $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
+ 'Wrong CD start disk error' );
+ }
+
+ public function testCentralDirectoryGap() {
+ $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
+ 'CD gap error' );
+ }
+
+ public function testCentralDirectoryTruncated() {
+ $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
+ 'CD truncated error (should hit unpack() overrun)' );
+ }
+
+ public function testLooksLikeZip64() {
+ $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
+ 'A file which looks like ZIP64 but isn\'t, should give error' );
+ }
+}
);
}
+ public function setup() {
+ parent::setup();
+
+ require_once __DIR__ . '/../common/TestSetup.php';
+ TestSetup::snapshotGlobals();
+ }
+
public function finalSetup() {
parent::finalSetup();
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use MediaWiki\Revision\MainSlotRoleHandler;
-use MediaWikiUnitTestCase;
-use PHPUnit\Framework\MockObject\MockObject;
-use Title;
-
-/**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler
- */
-class MainSlotRoleHandlerTest extends MediaWikiUnitTestCase {
-
- private function makeTitleObject( $ns ) {
- /** @var Title|MockObject $title */
- $title = $this->getMockBuilder( Title::class )
- ->disableOriginalConstructor()
- ->getMock();
-
- $title->method( 'getNamespace' )
- ->willReturn( $ns );
-
- return $title;
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::__construct
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getRole()
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getNameMessageKey()
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getOutputLayoutHints()
- */
- public function testConstruction() {
- $handler = new MainSlotRoleHandler( [] );
- $this->assertSame( 'main', $handler->getRole() );
- $this->assertSame( 'slot-name-main', $handler->getNameMessageKey() );
-
- $hints = $handler->getOutputLayoutHints();
- $this->assertArrayHasKey( 'display', $hints );
- $this->assertArrayHasKey( 'region', $hints );
- $this->assertArrayHasKey( 'placement', $hints );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::getDefaultModel()
- */
- public function testFetDefaultModel() {
- $handler = new MainSlotRoleHandler( [ 100 => CONTENT_MODEL_TEXT ] );
-
- // For the main handler, the namespace determins the default model
- $titleMain = $this->makeTitleObject( NS_MAIN );
- $this->assertSame( CONTENT_MODEL_WIKITEXT, $handler->getDefaultModel( $titleMain ) );
-
- $title100 = $this->makeTitleObject( 100 );
- $this->assertSame( CONTENT_MODEL_TEXT, $handler->getDefaultModel( $title100 ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::isAllowedModel()
- */
- public function testIsAllowedModel() {
- $handler = new MainSlotRoleHandler( [] );
-
- // For the main handler, (nearly) all models are allowed
- $title = $this->makeTitleObject( NS_MAIN );
- $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_WIKITEXT, $title ) );
- $this->assertTrue( $handler->isAllowedModel( CONTENT_MODEL_TEXT, $title ) );
- }
-
- /**
- * @covers \MediaWiki\Revision\MainSlotRoleHandler::supportsArticleCount()
- */
- public function testSupportsArticleCount() {
- $handler = new MainSlotRoleHandler( [] );
-
- $this->assertTrue( $handler->supportsArticleCount() );
- }
-
-}
+++ /dev/null
-<?php
-
-namespace MediaWiki\Tests\Revision;
-
-use InvalidArgumentException;
-use LogicException;
-use MediaWiki\Revision\IncompleteRevisionException;
-use MediaWiki\Revision\SlotRecord;
-use MediaWiki\Revision\SuppressedDataException;
-use MediaWikiUnitTestCase;
-use WikitextContent;
-
-/**
- * @covers \MediaWiki\Revision\SlotRecord
- */
-class SlotRecordTest extends MediaWikiUnitTestCase {
-
- 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()->getText() );
- $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()->getText() );
- $this->assertSame( 1, $record->getSize() );
- $this->assertNotEmpty( $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()->getText() );
- $this->assertSame( 1, $record->getSize() );
- $this->assertNotEmpty( $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 testHashComputed() {
- $row = $this->makeRow();
- $row->content_sha1 = '';
-
- $rec = new SlotRecord( $row, new WikitextContent( 'A' ) );
- $this->assertNotEmpty( $rec->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()->getText() );
- $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 ) );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @covers WikiReference
- */
-class WikiReferenceTest extends MediaWikiUnitTestCase {
-
- public function provideGetDisplayName() {
- return [
- 'http' => [ 'foo.bar', 'http://foo.bar' ],
- 'https' => [ 'foo.bar', 'http://foo.bar' ],
-
- // apparently, this is the expected behavior
- 'invalid' => [ 'purple kittens', 'purple kittens' ],
- ];
- }
-
- /**
- * @dataProvider provideGetDisplayName
- */
- public function testGetDisplayName( $expected, $canonicalServer ) {
- $reference = new WikiReference( $canonicalServer, '/wiki/$1' );
- $this->assertEquals( $expected, $reference->getDisplayName() );
- }
-
- public function testGetCanonicalServer() {
- $reference = new WikiReference( 'https://acme.com', '/wiki/$1', '//acme.com' );
- $this->assertEquals( 'https://acme.com', $reference->getCanonicalServer() );
- }
-
- public function provideGetCanonicalUrl() {
- return [
- 'no fragment' => [
- 'https://acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- null
- ],
- 'empty fragment' => [
- 'https://acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- ''
- ],
- 'fragment' => [
- 'https://acme.com/wiki/Foo#Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar'
- ],
- 'double fragment' => [
- 'https://acme.com/wiki/Foo#Bar%23Xus',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar#Xus'
- ],
- 'escaped fragment' => [
- 'https://acme.com/wiki/Foo%23Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo#Bar',
- null
- ],
- 'empty path' => [
- 'https://acme.com/Foo',
- 'https://acme.com',
- '//acme.com',
- '/$1',
- 'Foo',
- null
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetCanonicalUrl
- */
- public function testGetCanonicalUrl(
- $expected, $canonicalServer, $server, $path, $page, $fragmentId
- ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getCanonicalUrl( $page, $fragmentId ) );
- }
-
- /**
- * @dataProvider provideGetCanonicalUrl
- * @note getUrl is an alias for getCanonicalUrl
- */
- public function testGetUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getUrl( $page, $fragmentId ) );
- }
-
- public function provideGetFullUrl() {
- return [
- 'no fragment' => [
- '//acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- null
- ],
- 'empty fragment' => [
- '//acme.com/wiki/Foo',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- ''
- ],
- 'fragment' => [
- '//acme.com/wiki/Foo#Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar'
- ],
- 'double fragment' => [
- '//acme.com/wiki/Foo#Bar%23Xus',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo',
- 'Bar#Xus'
- ],
- 'escaped fragment' => [
- '//acme.com/wiki/Foo%23Bar',
- 'https://acme.com',
- '//acme.com',
- '/wiki/$1',
- 'Foo#Bar',
- null
- ],
- 'empty path' => [
- '//acme.com/Foo',
- 'https://acme.com',
- '//acme.com',
- '/$1',
- 'Foo',
- null
- ],
- ];
- }
-
- /**
- * @dataProvider provideGetFullUrl
- */
- public function testGetFullUrl( $expected, $canonicalServer, $server, $path, $page, $fragmentId ) {
- $reference = new WikiReference( $canonicalServer, $path, $server );
- $this->assertEquals( $expected, $reference->getFullUrl( $page, $fragmentId ) );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * @covers DifferenceEngineSlotDiffRenderer
- */
-class DifferenceEngineSlotDiffRendererTest extends \MediaWikiUnitTestCase {
-
- public function testGetDiff() {
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
- $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
-
- $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
- $this->assertEquals( 'xxx|yyy', $diff );
-
- $diff = $slotDiffRenderer->getDiff( null, $newContent );
- $this->assertEquals( '|yyy', $diff );
-
- $diff = $slotDiffRenderer->getDiff( $oldContent, null );
- $this->assertEquals( 'xxx|', $diff );
- }
-
- public function testAddModules() {
- $output = $this->getMockBuilder( OutputPage::class )
- ->disableOriginalConstructor()
- ->setMethods( [ 'addModules' ] )
- ->getMock();
- $output->expects( $this->once() )
- ->method( 'addModules' )
- ->with( 'foo' );
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $slotDiffRenderer->addModules( $output );
- }
-
- public function testGetExtraCacheKeys() {
- $differenceEngine = new CustomDifferenceEngine();
- $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
- $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
- $this->assertSame( [ 'foo' ], $extraCacheKeys );
- }
-
-}
+++ /dev/null
-<?php
-
-use Wikimedia\Assert\ParameterTypeException;
-use Wikimedia\TestingAccessWrapper;
-
-/**
- * @covers SlotDiffRenderer
- */
-class SlotDiffRendererTest extends \MediaWikiUnitTestCase {
-
- /**
- * @dataProvider provideNormalizeContents
- */
- public function testNormalizeContents(
- $oldContent, $newContent, $allowedClasses,
- $expectedOldContent, $expectedNewContent, $expectedExceptionClass
- ) {
- $slotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
- ->getMock();
- try {
- // __call needs help deciding which parameter to take by reference
- call_user_func_array( [ TestingAccessWrapper::newFromObject( $slotDiffRenderer ),
- 'normalizeContents' ], [ &$oldContent, &$newContent, $allowedClasses ] );
- $this->assertEquals( $expectedOldContent, $oldContent );
- $this->assertEquals( $expectedNewContent, $newContent );
- } catch ( Exception $e ) {
- if ( !$expectedExceptionClass ) {
- throw $e;
- }
- $this->assertInstanceOf( $expectedExceptionClass, $e );
- }
- }
-
- public function provideNormalizeContents() {
- return [
- 'both null' => [ null, null, null, null, null, InvalidArgumentException::class ],
- 'left null' => [
- null, new WikitextContent( 'abc' ), null,
- new WikitextContent( '' ), new WikitextContent( 'abc' ), null,
- ],
- 'right null' => [
- new WikitextContent( 'def' ), null, null,
- new WikitextContent( 'def' ), new WikitextContent( '' ), null,
- ],
- 'type filter' => [
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
- ],
- 'type filter (subclass)' => [
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), TextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( 'def' ), null,
- ],
- 'type filter (null)' => [
- new WikitextContent( 'abc' ), null, TextContent::class,
- new WikitextContent( 'abc' ), new WikitextContent( '' ), null,
- ],
- 'type filter failure (left)' => [
- new TextContent( 'abc' ), new WikitextContent( 'def' ), WikitextContent::class,
- null, null, ParameterTypeException::class,
- ],
- 'type filter failure (right)' => [
- new WikitextContent( 'abc' ), new TextContent( 'def' ), WikitextContent::class,
- null, null, ParameterTypeException::class,
- ],
- 'type filter (array syntax)' => [
- new WikitextContent( 'abc' ), new JsonContent( 'def' ),
- [ JsonContent::class, WikitextContent::class ],
- new WikitextContent( 'abc' ), new JsonContent( 'def' ), null,
- ],
- 'type filter failure (array syntax)' => [
- new WikitextContent( 'abc' ), new CssContent( 'def' ),
- [ JsonContent::class, WikitextContent::class ],
- null, null, ParameterTypeException::class,
- ],
- ];
- }
-
-}
+++ /dev/null
-<?php
-
-class FileBackendDBRepoWrapperTest extends MediaWikiUnitTestCase {
- protected $backendName = 'foo-backend';
- protected $repoName = 'pureTestRepo';
-
- /**
- * @dataProvider getBackendPathsProvider
- * @covers FileBackendDBRepoWrapper::getBackendPaths
- */
- public function testGetBackendPaths(
- $mocks,
- $latest,
- $dbReadsExpected,
- $dbReturnValue,
- $originalPath,
- $expectedBackendPath,
- $message ) {
- list( $dbMock, $backendMock, $wrapperMock ) = $mocks;
-
- $dbMock->expects( $dbReadsExpected )
- ->method( 'selectField' )
- ->will( $this->returnValue( $dbReturnValue ) );
-
- $newPaths = $wrapperMock->getBackendPaths( [ $originalPath ], $latest );
-
- $this->assertEquals(
- $expectedBackendPath,
- $newPaths[0],
- $message );
- }
-
- public function getBackendPathsProvider() {
- $prefix = 'mwstore://' . $this->backendName . '/' . $this->repoName;
- $mocksForCaching = $this->getMocks();
-
- return [
- [
- $mocksForCaching,
- false,
- $this->once(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'Public path translated correctly',
- ],
- [
- $mocksForCaching,
- false,
- $this->never(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'LRU cache leveraged',
- ],
- [
- $this->getMocks(),
- true,
- $this->once(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-public/f/o/foobar.jpg',
- $prefix . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- 'Latest obtained',
- ],
- [
- $this->getMocks(),
- true,
- $this->never(),
- '96246614d75ba1703bdfd5d7660bb57407aaf5d9',
- $prefix . '-deleted/f/o/foobar.jpg',
- $prefix . '-original/f/o/o/foobar',
- 'Deleted path translated correctly',
- ],
- [
- $this->getMocks(),
- true,
- $this->once(),
- null,
- $prefix . '-public/b/a/baz.jpg',
- $prefix . '-public/b/a/baz.jpg',
- 'Path left untouched if no sha1 can be found',
- ],
- ];
- }
-
- /**
- * @covers FileBackendDBRepoWrapper::getFileContentsMulti
- */
- public function testGetFileContentsMulti() {
- list( $dbMock, $backendMock, $wrapperMock ) = $this->getMocks();
-
- $sha1Path = 'mwstore://' . $this->backendName . '/' . $this->repoName
- . '-original/9/6/2/96246614d75ba1703bdfd5d7660bb57407aaf5d9';
- $filenamePath = 'mwstore://' . $this->backendName . '/' . $this->repoName
- . '-public/f/o/foobar.jpg';
-
- $dbMock->expects( $this->once() )
- ->method( 'selectField' )
- ->will( $this->returnValue( '96246614d75ba1703bdfd5d7660bb57407aaf5d9' ) );
-
- $backendMock->expects( $this->once() )
- ->method( 'getFileContentsMulti' )
- ->will( $this->returnValue( [ $sha1Path => 'foo' ] ) );
-
- $result = $wrapperMock->getFileContentsMulti( [ 'srcs' => [ $filenamePath ] ] );
-
- $this->assertEquals(
- [ $filenamePath => 'foo' ],
- $result,
- 'File contents paths translated properly'
- );
- }
-
- protected function getMocks() {
- $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\IDatabase::class )
- ->disableOriginalClone()
- ->disableOriginalConstructor()
- ->getMock();
-
- $backendMock = $this->getMockBuilder( FSFileBackend::class )
- ->setConstructorArgs( [ [
- 'name' => $this->backendName,
- 'wikiId' => wfWikiID()
- ] ] )
- ->getMock();
-
- $wrapperMock = $this->getMockBuilder( FileBackendDBRepoWrapper::class )
- ->setMethods( [ 'getDB' ] )
- ->setConstructorArgs( [ [
- 'backend' => $backendMock,
- 'repoName' => $this->repoName,
- 'dbHandleFactory' => null
- ] ] )
- ->getMock();
-
- $wrapperMock->expects( $this->any() )->method( 'getDB' )->will( $this->returnValue( $dbMock ) );
-
- return [ $dbMock, $backendMock, $wrapperMock ];
- }
-}
+++ /dev/null
-<?php
-/**
- * @todo Could use a test of extended XMP segments. Hard to find programs that
- * create example files, and creating my own in vim propbably wouldn't
- * serve as a very good "test". (Adobe photoshop probably creates such files
- * but it costs money). The implementation of it currently in MediaWiki is based
- * solely on reading the standard, without any real world test files.
- *
- * @group Media
- * @covers JpegMetadataExtractor
- */
-class JpegMetadataExtractorTest extends MediaWikiUnitTestCase {
-
- protected $filePath;
-
- protected function setUp() {
- parent::setUp();
-
- $this->filePath = __DIR__ . '/../../../data/media/';
- }
-
- /**
- * We also use this test to test padding bytes don't
- * screw stuff up
- *
- * @param string $file Filename
- *
- * @dataProvider provideUtf8Comment
- */
- public function testUtf8Comment( $file ) {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file );
- $this->assertEquals( [ 'UTF-8 JPEG Comment — ¼' ], $res['COM'] );
- }
-
- public static function provideUtf8Comment() {
- return [
- [ 'jpeg-comment-utf.jpg' ],
- [ 'jpeg-padding-even.jpg' ],
- [ 'jpeg-padding-odd.jpg' ],
- ];
- }
-
- /** The file is iso-8859-1, but it should get auto converted */
- public function testIso88591Comment() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' );
- $this->assertEquals( [ 'ISO-8859-1 JPEG Comment - ¼' ], $res['COM'] );
- }
-
- /** Comment values that are non-textual (random binary junk) should not be shown.
- * The example test file has a comment with a 0x5 byte in it which is a control character
- * and considered binary junk for our purposes.
- */
- public function testBinaryCommentStripped() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' );
- $this->assertEmpty( $res['COM'] );
- }
-
- /* Very rarely a file can have multiple comments.
- * Order of comments is based on order inside the file.
- */
- public function testMultipleComment() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' );
- $this->assertEquals( [ 'foo', 'bar' ], $res['COM'] );
- }
-
- public function testXMPExtraction() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
- $this->assertEquals( $expected, $res['XMP'] );
- }
-
- public function testPSIRExtraction() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $expected = '50686f746f73686f7020332e30003842494d04040000000'
- . '000181c02190004746573741c02190003666f6f1c020000020004';
- $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
- }
-
- public function testXMPExtractionAltAppId() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
- $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' );
- $this->assertEquals( $expected, $res['XMP'] );
- }
-
- public function testIPTCHashComparisionNoHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-no-hash', $res );
- }
-
- public function testIPTCHashComparisionBadHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-bad-hash', $res );
- }
-
- public function testIPTCHashComparisionGoodHash() {
- $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
-
- $this->assertEquals( 'iptc-good-hash', $res );
- }
-
- public function testExifByteOrder() {
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' );
- $expected = 'BE';
- $this->assertEquals( $expected, $res['byteOrder'] );
- }
-
- public function testInfiniteRead() {
- // test file truncated right after a segment, which previously
- // caused an infinite loop looking for the next segment byte.
- // Should get past infinite loop and throw in wfUnpack()
- $this->setExpectedException( 'MWException' );
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
- }
-
- public function testInfiniteRead2() {
- // test file truncated after a segment's marker and size, which
- // would cause a seek past end of file. Seek past end of file
- // doesn't actually fail, but prevents further reading and was
- // devolving into the previous case (testInfiniteRead).
- $this->setExpectedException( 'MWException' );
- $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
- }
-}
+++ /dev/null
-<?php
-
-/**
- * @covers ParserFactory
- */
-class ParserFactoryTest extends MediaWikiUnitTestCase {
- use FactoryArgTestTrait;
-
- protected static function getFactoryClass() {
- return ParserFactory::class;
- }
-
- protected static function getInstanceClass() {
- return Parser::class;
- }
-
- protected static function getFactoryMethodName() {
- return 'create';
- }
-
- protected static function getExtraClassArgCount() {
- // The parser factory itself is passed to the parser
- return 1;
- }
-
- protected function getOverriddenMockValueForParam( ReflectionParameter $param ) {
- if ( $param->getPosition() === 0 ) {
- return [ $this->createMock( MediaWiki\Config\ServiceOptions::class ) ];
- }
- return [];
- }
-}
+++ /dev/null
-<?php
-
-use MediaWiki\Site\MediaWikiPageNameNormalizer;
-
-/**
- * @covers MediaWiki\Site\MediaWikiPageNameNormalizer
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @since 1.27
- *
- * @group Site
- * @group medium
- *
- * @author Marius Hoch
- */
-class MediaWikiPageNameNormalizerTest extends MediaWikiUnitTestCase {
-
- /**
- * @dataProvider normalizePageTitleProvider
- */
- public function testNormalizePageTitle( $expected, $pageName, $getResponse ) {
- MediaWikiPageNameNormalizerTestMockHttp::$response = $getResponse;
-
- $normalizer = new MediaWikiPageNameNormalizer(
- new MediaWikiPageNameNormalizerTestMockHttp()
- );
-
- $this->assertSame(
- $expected,
- $normalizer->normalizePageName( $pageName, 'https://www.wikidata.org/w/api.php' )
- );
- }
-
- public function normalizePageTitleProvider() {
- // Response are taken from wikidata and kkwiki using the following API request
- // api.php?action=query&prop=info&redirects=1&converttitles=1&format=json&titles=…
- return [
- 'universe (Q1)' => [
- 'Q1',
- 'Q1',
- '{"batchcomplete":"","query":{"pages":{"129":{"pageid":129,"ns":0,'
- . '"title":"Q1","contentmodel":"wikibase-item","pagelanguage":"en",'
- . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
- . '"touched":"2016-06-23T05:11:21Z","lastrevid":350004448,"length":58001}}}}'
- ],
- 'Q404 redirects to Q395' => [
- 'Q395',
- 'Q404',
- '{"batchcomplete":"","query":{"redirects":[{"from":"Q404","to":"Q395"}],"pages"'
- . ':{"601":{"pageid":601,"ns":0,"title":"Q395","contentmodel":"wikibase-item",'
- . '"pagelanguage":"en","pagelanguagehtmlcode":"en","pagelanguagedir":"ltr",'
- . '"touched":"2016-06-23T08:00:20Z","lastrevid":350021914,"length":60108}}}}'
- ],
- 'D converted to Д (Latin to Cyrillic) (taken from kkwiki)' => [
- 'Д',
- 'D',
- '{"batchcomplete":"","query":{"converted":[{"from":"D","to":"\u0414"}],'
- . '"pages":{"510541":{"pageid":510541,"ns":0,"title":"\u0414",'
- . '"contentmodel":"wikitext","pagelanguage":"kk","pagelanguagehtmlcode":"kk",'
- . '"pagelanguagedir":"ltr","touched":"2015-11-22T09:16:18Z",'
- . '"lastrevid":2373618,"length":3501}}}}'
- ],
- 'there is no Q0' => [
- false,
- 'Q0',
- '{"batchcomplete":"","query":{"pages":{"-1":{"ns":0,"title":"Q0",'
- . '"missing":"","contentmodel":"wikibase-item","pagelanguage":"en",'
- . '"pagelanguagehtmlcode":"en","pagelanguagedir":"ltr"}}}}'
- ],
- 'invalid title' => [
- false,
- '{{',
- '{"batchcomplete":"","query":{"pages":{"-1":{"title":"{{",'
- . '"invalidreason":"The requested page title contains invalid '
- . 'characters: \"{\".","invalid":""}}}}'
- ],
- 'error on get' => [ false, 'ABC', false ]
- ];
- }
-
-}
-
-/**
- * @private
- * @see Http
- */
-class MediaWikiPageNameNormalizerTestMockHttp extends Http {
-
- /**
- * @var mixed
- */
- public static $response;
-
- public static function get( $url, array $options = [], $caller = __METHOD__ ) {
- PHPUnit_Framework_Assert::assertInternalType( 'string', $url );
- PHPUnit_Framework_Assert::assertInternalType( 'string', $caller );
-
- return self::$response;
- }
-}
+++ /dev/null
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- *
- * @covers SiteExporter
- *
- * @author Daniel Kinzler
- */
-class SiteExporterTest extends MediaWikiUnitTestCase {
-
- public function testConstructor_InvalidArgument() {
- $this->setExpectedException( InvalidArgumentException::class );
-
- new SiteExporter( 'Foo' );
- }
-
- public function testExportSites() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $tmp = tmpfile();
- $exporter = new SiteExporter( $tmp );
-
- $exporter->exportSites( [ $foo, $acme ] );
-
- fseek( $tmp, 0 );
- $xml = fread( $tmp, 16 * 1024 );
-
- $this->assertContains( '<sites ', $xml );
- $this->assertContains( '<site>', $xml );
- $this->assertContains( '<globalid>Foo</globalid>', $xml );
- $this->assertContains( '</site>', $xml );
- $this->assertContains( '<globalid>acme.com</globalid>', $xml );
- $this->assertContains( '<group>Test</group>', $xml );
- $this->assertContains( '<localid type="interwiki">acme</localid>', $xml );
- $this->assertContains( '<path type="link">http://acme.com/</path>', $xml );
- $this->assertContains( '</sites>', $xml );
-
- // NOTE: HHVM (at least on wmf Jenkins) doesn't like file URLs.
- $xsdFile = __DIR__ . '/../../../../../docs/sitelist-1.0.xsd';
- $xsdData = file_get_contents( $xsdFile );
-
- $document = new DOMDocument();
- $document->loadXML( $xml, LIBXML_NONET );
- $document->schemaValidateSource( $xsdData );
- }
-
- private function newSiteStore( SiteList $sites ) {
- $store = $this->getMockBuilder( SiteStore::class )->getMock();
-
- $store->expects( $this->once() )
- ->method( 'saveSites' )
- ->will( $this->returnCallback( function ( $moreSites ) use ( $sites ) {
- foreach ( $moreSites as $site ) {
- $sites->setSite( $site );
- }
- } ) );
-
- $store->expects( $this->any() )
- ->method( 'getSites' )
- ->will( $this->returnValue( new SiteList() ) );
-
- return $store;
- }
-
- public function provideRoundTrip() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
- $dewiki->setGlobalId( 'dewiki' );
- $dewiki->setGroup( 'wikipedia' );
- $dewiki->setForward( true );
- $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
- $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
- $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
- $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
- $dewiki->setSource( 'meta.wikimedia.org' );
-
- return [
- 'empty' => [
- new SiteList()
- ],
-
- 'some' => [
- new SiteList( [ $foo, $acme, $dewiki ] ),
- ],
- ];
- }
-
- /**
- * @dataProvider provideRoundTrip()
- */
- public function testRoundTrip( SiteList $sites ) {
- $tmp = tmpfile();
- $exporter = new SiteExporter( $tmp );
-
- $exporter->exportSites( $sites );
-
- fseek( $tmp, 0 );
- $xml = fread( $tmp, 16 * 1024 );
-
- $actualSites = new SiteList();
- $store = $this->newSiteStore( $actualSites );
-
- $importer = new SiteImporter( $store );
- $importer->importFromXML( $xml );
-
- $this->assertEquals( $sites, $actualSites );
- }
-
-}
+++ /dev/null
-<?php
-
-/**
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- *
- * @ingroup Site
- * @ingroup Test
- *
- * @group Site
- *
- * @covers SiteImporter
- *
- * @author Daniel Kinzler
- */
-class SiteImporterTest extends MediaWikiUnitTestCase {
-
- private function newSiteImporter( array $expectedSites, $errorCount ) {
- $store = $this->getMockBuilder( SiteStore::class )->getMock();
-
- $store->expects( $this->once() )
- ->method( 'saveSites' )
- ->will( $this->returnCallback( function ( $sites ) use ( $expectedSites ) {
- $this->assertSitesEqual( $expectedSites, $sites );
- } ) );
-
- $store->expects( $this->any() )
- ->method( 'getSites' )
- ->will( $this->returnValue( new SiteList() ) );
-
- $errorHandler = $this->getMockBuilder( Psr\Log\LoggerInterface::class )->getMock();
- $errorHandler->expects( $this->exactly( $errorCount ) )
- ->method( 'error' );
-
- $importer = new SiteImporter( $store );
- $importer->setExceptionCallback( [ $errorHandler, 'error' ] );
-
- return $importer;
- }
-
- public function assertSitesEqual( $expected, $actual, $message = '' ) {
- $this->assertEquals(
- $this->getSerializedSiteList( $expected ),
- $this->getSerializedSiteList( $actual ),
- $message
- );
- }
-
- public function provideImportFromXML() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
- $dewiki->setGlobalId( 'dewiki' );
- $dewiki->setGroup( 'wikipedia' );
- $dewiki->setForward( true );
- $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
- $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
- $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
- $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
- $dewiki->setSource( 'meta.wikimedia.org' );
-
- return [
- 'empty' => [
- '<sites></sites>',
- [],
- ],
- 'no sites' => [
- '<sites><Foo><globalid>Foo</globalid></Foo><Bar><quux>Bla</quux></Bar></sites>',
- [],
- ],
- 'minimal' => [
- '<sites>' .
- '<site><globalid>Foo</globalid></site>' .
- '</sites>',
- [ $foo ],
- ],
- 'full' => [
- '<sites>' .
- '<site><globalid>Foo</globalid></site>' .
- '<site>' .
- '<globalid>acme.com</globalid>' .
- '<localid type="interwiki">acme</localid>' .
- '<group>Test</group>' .
- '<path type="link">http://acme.com/</path>' .
- '</site>' .
- '<site type="mediawiki">' .
- '<source>meta.wikimedia.org</source>' .
- '<globalid>dewiki</globalid>' .
- '<localid type="interwiki">wikipedia</localid>' .
- '<localid type="equivalent">de</localid>' .
- '<group>wikipedia</group>' .
- '<forward/>' .
- '<path type="link">http://de.wikipedia.org/w/</path>' .
- '<path type="page_path">http://de.wikipedia.org/wiki/</path>' .
- '</site>' .
- '</sites>',
- [ $foo, $acme, $dewiki ],
- ],
- 'skip' => [
- '<sites>' .
- '<site><globalid>Foo</globalid></site>' .
- '<site><barf>Foo</barf></site>' .
- '<site>' .
- '<globalid>acme.com</globalid>' .
- '<localid type="interwiki">acme</localid>' .
- '<silly>boop!</silly>' .
- '<group>Test</group>' .
- '<path type="link">http://acme.com/</path>' .
- '</site>' .
- '</sites>',
- [ $foo, $acme ],
- 1
- ],
- ];
- }
-
- /**
- * @dataProvider provideImportFromXML
- */
- public function testImportFromXML( $xml, array $expectedSites, $errorCount = 0 ) {
- $importer = $this->newSiteImporter( $expectedSites, $errorCount );
- $importer->importFromXML( $xml );
- }
-
- public function testImportFromXML_malformed() {
- $this->setExpectedException( Exception::class );
-
- $store = $this->getMockBuilder( SiteStore::class )->getMock();
- $importer = new SiteImporter( $store );
- $importer->importFromXML( 'THIS IS NOT XML' );
- }
-
- public function testImportFromFile() {
- $foo = Site::newForType( Site::TYPE_UNKNOWN );
- $foo->setGlobalId( 'Foo' );
-
- $acme = Site::newForType( Site::TYPE_UNKNOWN );
- $acme->setGlobalId( 'acme.com' );
- $acme->setGroup( 'Test' );
- $acme->addLocalId( Site::ID_INTERWIKI, 'acme' );
- $acme->setPath( Site::PATH_LINK, 'http://acme.com/' );
-
- $dewiki = Site::newForType( Site::TYPE_MEDIAWIKI );
- $dewiki->setGlobalId( 'dewiki' );
- $dewiki->setGroup( 'wikipedia' );
- $dewiki->setForward( true );
- $dewiki->addLocalId( Site::ID_INTERWIKI, 'wikipedia' );
- $dewiki->addLocalId( Site::ID_EQUIVALENT, 'de' );
- $dewiki->setPath( Site::PATH_LINK, 'http://de.wikipedia.org/w/' );
- $dewiki->setPath( MediaWikiSite::PATH_PAGE, 'http://de.wikipedia.org/wiki/' );
- $dewiki->setSource( 'meta.wikimedia.org' );
-
- $importer = $this->newSiteImporter( [ $foo, $acme, $dewiki ], 0 );
-
- $file = __DIR__ . '/SiteImporterTest.xml';
- $importer->importFromFile( $file );
- }
-
- /**
- * @param Site[] $sites
- *
- * @return array[]
- */
- private function getSerializedSiteList( $sites ) {
- $serialized = [];
-
- foreach ( $sites as $site ) {
- $key = $site->getGlobalId();
- $data = unserialize( $site->serialize() );
-
- $serialized[$key] = $data;
- }
-
- return $serialized;
- }
-}
+++ /dev/null
-<sites version="1.0" xmlns="http://www.mediawiki.org/xml/sitelist-1.0/">
- <site><globalid>Foo</globalid></site>
- <site>
- <globalid>acme.com</globalid>
- <localid type="interwiki">acme</localid>
- <group>Test</group>
- <path type="link">http://acme.com/</path>
- </site>
- <site type="mediawiki">
- <source>meta.wikimedia.org</source>
- <globalid>dewiki</globalid>
- <localid type="interwiki">wikipedia</localid>
- <localid type="equivalent">de</localid>
- <group>wikipedia</group>
- <forward/>
- <path type="link">http://de.wikipedia.org/w/</path>
- <path type="page_path">http://de.wikipedia.org/wiki/</path>
- </site>
-</sites>
+++ /dev/null
-<?php
-
-/**
- * @covers ZipDirectoryReader
- * NOTE: this test is more like an integration test than a unit test
- */
-class ZipDirectoryReaderTest extends MediaWikiUnitTestCase {
-
- protected $zipDir;
- protected $entries;
-
- protected function setUp() {
- parent::setUp();
- $this->zipDir = __DIR__ . '/../../../data/zip';
- }
-
- function zipCallback( $entry ) {
- $this->entries[] = $entry;
- }
-
- function readZipAssertError( $file, $error, $assertMessage ) {
- $this->entries = [];
- $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
- $this->assertTrue( $status->hasMessage( $error ), $assertMessage );
- }
-
- function readZipAssertSuccess( $file, $assertMessage ) {
- $this->entries = [];
- $status = ZipDirectoryReader::read( "{$this->zipDir}/$file", [ $this, 'zipCallback' ] );
- $this->assertTrue( $status->isOK(), $assertMessage );
- }
-
- public function testEmpty() {
- $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' );
- }
-
- public function testMultiDisk0() {
- $this->readZipAssertError( 'split.zip', 'zip-unsupported',
- 'Split zip error' );
- }
-
- public function testNoSignature() {
- $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format',
- 'No signature should give "wrong format" error' );
- }
-
- public function testSimple() {
- $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' );
- $this->assertEquals( $this->entries, [ [
- 'name' => 'Class.class',
- 'mtime' => '20010115000000',
- 'size' => 1,
- ] ] );
- }
-
- public function testBadCentralEntrySignature() {
- $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad',
- 'Bad central entry error' );
- }
-
- public function testTrailingBytes() {
- // Due to T40432 this is now zip-wrong-format instead of zip-bad
- $this->readZipAssertError( 'trail.zip', 'zip-wrong-format',
- 'Trailing bytes error' );
- }
-
- public function testWrongCDStart() {
- $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported',
- 'Wrong CD start disk error' );
- }
-
- public function testCentralDirectoryGap() {
- $this->readZipAssertError( 'cd-gap.zip', 'zip-bad',
- 'CD gap error' );
- }
-
- public function testCentralDirectoryTruncated() {
- $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad',
- 'CD truncated error (should hit unpack() overrun)' );
- }
-
- public function testLooksLikeZip64() {
- $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported',
- 'A file which looks like ZIP64 but isn\'t, should give error' );
- }
-}