Reapply the reverted commit which removed dynamic property assignment.
But continue to allow extra fields to be passed to newFromRow() for the
benefit of subclasses.
This reverts commit 9e8ff12a59.
Change-Id: Ia3c14c51541e06264891a51763a8dadde83fbab7
403 lines
11 KiB
PHP
403 lines
11 KiB
PHP
<?php
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
|
|
/**
|
|
* @coversDefaultClass LocalRepo
|
|
* @group Database
|
|
*/
|
|
class LocalRepoTest extends MediaWikiIntegrationTestCase {
|
|
/**
|
|
* @param array $extraInfo To pass to LocalRepo constructor
|
|
* @return LocalRepo
|
|
*/
|
|
private function newRepo( array $extraInfo = [] ) {
|
|
return new LocalRepo( $extraInfo + [
|
|
'name' => 'local',
|
|
'backend' => 'local-backend',
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* @param array $extraInfo To pass to constructor
|
|
* @param bool $expected
|
|
* @dataProvider provideHasSha1Storage
|
|
* @covers ::__construct
|
|
*/
|
|
public function testHasSha1Storage( array $extraInfo, $expected ) {
|
|
$this->assertSame( $expected, $this->newRepo( $extraInfo )->hasSha1Storage() );
|
|
}
|
|
|
|
public static function provideHasSha1Storage() {
|
|
return [
|
|
[ [], false ],
|
|
[ [ 'storageLayout' => 'sha256' ], false ],
|
|
[ [ 'storageLayout' => 'sha1' ], true ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param string $prefix 'img' or 'oi'
|
|
* @param string $expectedClass 'LocalFile' or 'OldLocalFile'
|
|
* @dataProvider provideNewFileFromRow
|
|
* @covers ::newFileFromRow
|
|
*/
|
|
public function testNewFileFromRow( $prefix, $expectedClass ) {
|
|
$this->editPage( 'File:Test_file', 'Some description' );
|
|
|
|
$row = (object)[
|
|
"{$prefix}_name" => 'Test_file',
|
|
"{$prefix}_user" => '1',
|
|
"{$prefix}_timestamp" => '12345678910111',
|
|
"{$prefix}_metadata" => '',
|
|
"{$prefix}_sha1" => sha1( '' ),
|
|
"{$prefix}_size" => '0',
|
|
"{$prefix}_height" => '0',
|
|
"{$prefix}_width" => '0',
|
|
"{$prefix}_bits" => '0',
|
|
"{$prefix}_media_type" => 'UNKNOWN',
|
|
"{$prefix}_description_text" => '',
|
|
"{$prefix}_description_data" => null,
|
|
];
|
|
if ( $prefix === 'oi' ) {
|
|
$row->oi_archive_name = 'Archive_name';
|
|
$row->oi_deleted = '0';
|
|
}
|
|
$file = $this->newRepo()->newFileFromRow( $row );
|
|
$this->assertInstanceOf( $expectedClass, $file );
|
|
$this->assertSame( 'Test_file', $file->getName() );
|
|
$this->assertSame( 1, $file->getUploader()->getId() );
|
|
}
|
|
|
|
public static function provideNewFileFromRow() {
|
|
return [
|
|
'img' => [ 'img', LocalFile::class ],
|
|
'oi' => [ 'oi', OldLocalFile::class ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::newFileFromRow
|
|
*/
|
|
public function testNewFileFromRow_invalid() {
|
|
$this->expectException( MWException::class );
|
|
$this->expectExceptionMessage( 'LocalRepo::newFileFromRow: invalid row' );
|
|
|
|
$row = (object)[
|
|
"img_user" => '1',
|
|
"img_timestamp" => '12345678910111',
|
|
"img_metadata" => '',
|
|
"img_sha1" => sha1( '' ),
|
|
"img_size" => '0',
|
|
"img_height" => '0',
|
|
"img_width" => '0',
|
|
"img_bits" => '0',
|
|
];
|
|
$file = $this->newRepo()->newFileFromRow( $row );
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::newFromArchiveName
|
|
*/
|
|
public function testNewFromArchiveName() {
|
|
$this->editPage( 'File:Test_file', 'Some description' );
|
|
|
|
$file = $this->newRepo()->newFromArchiveName( 'Test_file', 'b' );
|
|
$this->assertInstanceOf( OldLocalFile::class, $file );
|
|
$this->assertSame( 'Test_file', $file->getName() );
|
|
|
|
$page = $this->getExistingTestPage( 'File:Test_file' );
|
|
$file = $this->newRepo()->newFromArchiveName( $page, 'b' );
|
|
$this->assertInstanceOf( OldLocalFile::class, $file );
|
|
$this->assertSame( 'Test_file', $file->getName() );
|
|
}
|
|
|
|
// TODO cleanupDeletedBatch, deletedFileHasKey, hiddenFileHasKey
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::cleanupDeletedBatch
|
|
*/
|
|
public function testCleanupDeletedBatch_sha1Storage() {
|
|
$this->assertEquals( Status::newGood(),
|
|
$this->newRepo( [ 'storageLayout' => 'sha1' ] )->cleanupDeletedBatch( [] ) );
|
|
}
|
|
|
|
/**
|
|
* @param string $input
|
|
* @param string $expected
|
|
* @dataProvider provideGetHashFromKey
|
|
* @covers ::getHashFromKey
|
|
*/
|
|
public function testGetHashFromKey( $input, $expected ) {
|
|
$this->assertSame( $expected, LocalRepo::getHashFromKey( $input ) );
|
|
}
|
|
|
|
public static function provideGetHashFromKey() {
|
|
return [
|
|
[ '', false ],
|
|
[ '.', false ],
|
|
[ 'a.', 'a' ],
|
|
[ '.b', 'b' ],
|
|
[ '..c', 'c' ],
|
|
[ 'd.x', 'd' ],
|
|
[ '.e.x', 'e' ],
|
|
[ '..f.x', 'f' ],
|
|
[ 'g..x', 'g' ],
|
|
[ '01234567890123456789012345678901.x', '1234567890123456789012345678901' ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::checkRedirect
|
|
*/
|
|
public function testCheckRedirect_nonRedirect() {
|
|
$this->editPage( 'File:Not a redirect', 'Not a redirect' );
|
|
$this->assertFalse(
|
|
$this->newRepo()->checkRedirect( Title::makeTitle( NS_FILE, 'Not a redirect' ) ) );
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::checkRedirect
|
|
* @covers ::getSharedCacheKey
|
|
*/
|
|
public function testCheckRedirect_redirect() {
|
|
$this->editPage( 'File:Redirect', '#REDIRECT [[File:Target]]' );
|
|
|
|
$target = $this->newRepo()->checkRedirect( Title::makeTitle( NS_FILE, 'Redirect' ) );
|
|
$this->assertEquals( 'File:Target', $target->getPrefixedText() );
|
|
|
|
$page = $this->getExistingTestPage( 'File:Redirect' );
|
|
$target = $this->newRepo()->checkRedirect( $page );
|
|
$this->assertEquals( 'File:Target', $target->getPrefixedText() );
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::checkRedirect
|
|
* @covers ::getSharedCacheKey
|
|
*/
|
|
public function testCheckRedirectSharedEmptyCache() {
|
|
$dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
|
|
$mockBag = $this->getMockBuilder( EmptyBagOStuff::class )
|
|
->onlyMethods( [ 'makeKey', 'makeGlobalKey' ] )
|
|
->getMock();
|
|
$mockBag->expects( $this->exactly( 0 ) )
|
|
->method( 'makeKey' )
|
|
->withConsecutive(
|
|
[ 'filerepo-file-redirect', 'local', md5( 'Redirect' ) ]
|
|
);
|
|
$mockBag->expects( $this->once() )
|
|
->method( 'makeGlobalKey' )
|
|
->withConsecutive(
|
|
[ 'filerepo-file-redirect', $dbDomain, md5( 'Redirect' ) ]
|
|
)->willReturnOnConsecutiveCalls(
|
|
implode( ':', [ 'filerepo-file-redirect', $dbDomain, md5( 'Redirect' ) ] )
|
|
);
|
|
|
|
$wanCache = new WANObjectCache( [ 'cache' => $mockBag ] );
|
|
$repo = $this->newRepo( [ 'wanCache' => $wanCache ] );
|
|
|
|
$this->editPage( 'File:Redirect', '#REDIRECT [[File:Target]]' );
|
|
$this->assertEquals( 'File:Target',
|
|
$repo->checkRedirect( Title::makeTitle( NS_FILE, 'Redirect' ) )->getPrefixedText() );
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::checkRedirect
|
|
*/
|
|
public function testCheckRedirect_invalidFile() {
|
|
$this->expectException( MWException::class );
|
|
$this->expectExceptionMessage( '`Notafile` is not a valid file title.' );
|
|
$this->newRepo()->checkRedirect( Title::makeTitle( NS_MAIN, 'Notafile' ) );
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::findBySha1
|
|
*/
|
|
public function testFindBySha1() {
|
|
$this->markTestIncomplete( "Haven't figured out how to upload files yet" );
|
|
|
|
$repo = $this->newRepo();
|
|
|
|
$tmpFileFactory = MediaWikiServices::getInstance()->getTempFSFileFactory();
|
|
foreach ( [ 'File1', 'File2', 'File3' ] as $name ) {
|
|
$fsFile = $tmpFileFactory->newTempFSFile( '' );
|
|
file_put_contents( $fsFile->getPath(), "$name contents" );
|
|
$localFile = $repo->newFile( $name );
|
|
$localFile->upload( $fsFile, 'Uploaded', "$name desc" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @covers ::__construct
|
|
* @covers ::getSharedCacheKey
|
|
* @covers ::checkRedirect
|
|
* @covers ::invalidateImageRedirect
|
|
*/
|
|
public function testInvalidateImageRedirect() {
|
|
global $wgTestMe;
|
|
$wgTestMe = true;
|
|
$repo = $this->newRepo(
|
|
[ 'wanCache' => new WANObjectCache( [ 'cache' => new HashBagOStuff ] ) ] );
|
|
|
|
$title = Title::makeTitle( NS_FILE, 'Redirect' );
|
|
|
|
$this->editPage( 'File:Redirect', '#REDIRECT [[File:Target]]' );
|
|
|
|
$this->assertSame( 'File:Target',
|
|
$repo->checkRedirect( $title )->getPrefixedText() );
|
|
|
|
$this->editPage( 'File:Redirect', 'No longer a redirect' );
|
|
|
|
$this->assertSame( 'File:Target',
|
|
$repo->checkRedirect( $title )->getPrefixedText() );
|
|
|
|
$repo->invalidateImageRedirect( $title );
|
|
|
|
$this->markTestIncomplete(
|
|
"Can't figure out how to get image redirect validation to take effect" );
|
|
|
|
$this->assertSame( false, $repo->checkRedirect( $title ) );
|
|
}
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo() {
|
|
$this->setMwGlobals( [
|
|
'wgFavicon' => '//example.com/favicon.ico',
|
|
'wgSitename' => 'Test my site',
|
|
] );
|
|
|
|
$repo = $this->newRepo( [ 'favicon' => 'Hey, this option is ignored in LocalRepo!' ] );
|
|
|
|
$this->assertSame( [
|
|
'name' => 'local',
|
|
'displayname' => 'Test my site',
|
|
'rootUrl' => false,
|
|
'local' => true,
|
|
'url' => false,
|
|
'thumbUrl' => false,
|
|
'initialCapital' => true,
|
|
// XXX This assumes protocol-relative will get expanded to http instead of https
|
|
'favicon' => 'http://example.com/favicon.ico',
|
|
], $repo->getInfo() );
|
|
}
|
|
|
|
// XXX The following getInfo tests are really testing FileRepo, not LocalRepo, but we want to
|
|
// make sure they're true for LocalRepo too. How should we do this? A trait?
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_name() {
|
|
$this->assertSame( 'some-name',
|
|
$this->newRepo( [ 'name' => 'some-name' ] )->getInfo()['name'] );
|
|
}
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_displayName() {
|
|
$this->assertSame( wfMessage( 'shared-repo' )->text(),
|
|
$this->newRepo( [ 'name' => 'not-local' ] )->getInfo()['displayname'] );
|
|
}
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_displayNameCustomMsg() {
|
|
$this->editPage( 'MediaWiki:Shared-repo-name-not-local', 'Name to display please' );
|
|
// Allow the message to take effect
|
|
MediaWikiServices::getInstance()->getMessageCache()->enable();
|
|
|
|
$this->assertSame( 'Name to display please',
|
|
$this->newRepo( [ 'name' => 'not-local' ] )->getInfo()['displayname'] );
|
|
}
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_rootUrl() {
|
|
$this->assertSame( 'https://my.url',
|
|
$this->newRepo( [ 'url' => 'https://my.url' ] )->getInfo()['rootUrl'] );
|
|
}
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_rootUrlCustomized() {
|
|
$this->assertSame(
|
|
'https://my.url/some/sub/dir',
|
|
$this->newRepo( [
|
|
'url' => 'https://my.url',
|
|
'zones' => [ 'public' => [ 'url' => 'https://my.url/some/sub/dir' ] ],
|
|
] )->getInfo()['rootUrl']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_local() {
|
|
$this->assertFalse( $this->newRepo( [ 'name' => 'not-local' ] )->getInfo()['local'] );
|
|
}
|
|
|
|
/**
|
|
* @param string $setting
|
|
* @dataProvider provideGetInfo_optionalSettings
|
|
* @covers ::getInfo
|
|
*/
|
|
public function testGetInfo_optionalSettings( $setting ) {
|
|
$this->assertSame( 'dummy test value',
|
|
$this->newRepo( [ $setting => 'dummy test value' ] )->getInfo()[$setting] );
|
|
}
|
|
|
|
public static function provideGetInfo_optionalSettings() {
|
|
return [
|
|
[ 'url' ],
|
|
[ 'thumbUrl' ],
|
|
[ 'initialCapital' ],
|
|
[ 'descBaseUrl' ],
|
|
[ 'scriptDirUrl' ],
|
|
[ 'articleUrl' ],
|
|
[ 'fetchDescription' ],
|
|
[ 'descriptionCacheExpiry' ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideSkipWriteOperationIfSha1
|
|
* @covers ::store
|
|
* @covers ::storeBatch
|
|
* @covers ::cleanupBatch
|
|
* @covers ::publish
|
|
* @covers ::publishBatch
|
|
* @covers ::delete
|
|
* @covers ::deleteBatch
|
|
* @covers ::skipWriteOperationIfSha1
|
|
*/
|
|
public function testSkipWriteOperationIfSha1( $method, ...$args ) {
|
|
$repo = $this->newRepo( [ 'storageLayout' => 'sha1' ] );
|
|
$this->assertEquals( Status::newGood(), $repo->$method( ...$args ) );
|
|
}
|
|
|
|
public static function provideSkipWriteOperationIfSha1() {
|
|
return [
|
|
[ 'store', '', '', '' ],
|
|
[ 'storeBatch', [ '' ] ],
|
|
[ 'cleanupBatch', [ '' ] ],
|
|
[ 'publish', '', '', '' ],
|
|
[ 'publishBatch', [ '' ] ],
|
|
[ 'delete', '', '' ],
|
|
[ 'deleteBatch', [ '' ] ],
|
|
];
|
|
}
|
|
}
|