diff options
Diffstat (limited to 'www/wiki/tests/phpunit/includes/filerepo')
7 files changed, 1113 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php b/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php new file mode 100644 index 00000000..4c9855b0 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/FileBackendDBRepoWrapperTest.php @@ -0,0 +1,140 @@ +<?php + +class FileBackendDBRepoWrapperTest extends MediaWikiTestCase { + 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\DatabaseMysqli::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 ]; + } +} diff --git a/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php b/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php new file mode 100644 index 00000000..0d3e679a --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/FileRepoTest.php @@ -0,0 +1,55 @@ +<?php + +class FileRepoTest extends MediaWikiTestCase { + + /** + * @expectedException MWException + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionOptionCanNotBeNull() { + new FileRepo(); + } + + /** + * @expectedException MWException + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionOptionCanNotBeAnEmptyArray() { + new FileRepo( [] ); + } + + /** + * @expectedException MWException + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionOptionNeedNameKey() { + new FileRepo( [ + 'backend' => 'foobar' + ] ); + } + + /** + * @expectedException MWException + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionOptionNeedBackendKey() { + new FileRepo( [ + 'name' => 'foobar' + ] ); + } + + /** + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionWithRequiredOptions() { + $f = new FileRepo( [ + 'name' => 'FileRepoTestRepository', + 'backend' => new FSFileBackend( [ + 'name' => 'local-testing', + 'wikiId' => 'test_wiki', + 'containerPaths' => [] + ] ) + ] ); + $this->assertInstanceOf( FileRepo::class, $f ); + } +} diff --git a/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php b/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php new file mode 100644 index 00000000..9beea5b6 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/MigrateFileRepoLayoutTest.php @@ -0,0 +1,142 @@ +<?php + +/** + * @covers MigrateFileRepoLayout + */ +class MigrateFileRepoLayoutTest extends MediaWikiTestCase { + protected $tmpPrefix; + protected $migratorMock; + protected $tmpFilepath; + protected $text = 'testing'; + + protected function setUp() { + parent::setUp(); + + $filename = 'Foo.png'; + + $this->tmpPrefix = $this->getNewTempDirectory(); + + $backend = new FSFileBackend( [ + 'name' => 'local-migratefilerepolayouttest', + 'wikiId' => wfWikiID(), + 'containerPaths' => [ + 'migratefilerepolayouttest-original' => "{$this->tmpPrefix}-original", + 'migratefilerepolayouttest-public' => "{$this->tmpPrefix}-public", + 'migratefilerepolayouttest-thumb' => "{$this->tmpPrefix}-thumb", + 'migratefilerepolayouttest-temp' => "{$this->tmpPrefix}-temp", + 'migratefilerepolayouttest-deleted' => "{$this->tmpPrefix}-deleted", + ] + ] ); + + $dbMock = $this->getMockBuilder( Wikimedia\Rdbms\DatabaseMysqli::class ) + ->disableOriginalConstructor() + ->getMock(); + + $imageRow = new stdClass; + $imageRow->img_name = $filename; + $imageRow->img_sha1 = sha1( $this->text ); + + $dbMock->expects( $this->any() ) + ->method( 'select' ) + ->will( $this->onConsecutiveCalls( + new FakeResultWrapper( [ $imageRow ] ), // image + new FakeResultWrapper( [] ), // image + new FakeResultWrapper( [] ) // filearchive + ) ); + + $repoMock = $this->getMockBuilder( LocalRepo::class ) + ->setMethods( [ 'getMasterDB' ] ) + ->setConstructorArgs( [ [ + 'name' => 'migratefilerepolayouttest', + 'backend' => $backend + ] ] ) + ->getMock(); + + $repoMock + ->expects( $this->any() ) + ->method( 'getMasterDB' ) + ->will( $this->returnValue( $dbMock ) ); + + $this->migratorMock = $this->getMockBuilder( MigrateFileRepoLayout::class ) + ->setMethods( [ 'getRepo' ] )->getMock(); + $this->migratorMock + ->expects( $this->any() ) + ->method( 'getRepo' ) + ->will( $this->returnValue( $repoMock ) ); + + $this->tmpFilepath = TempFSFile::factory( + 'migratefilelayout-test-', 'png', wfTempDir() )->getPath(); + + file_put_contents( $this->tmpFilepath, $this->text ); + + $hashPath = $repoMock->getHashPath( $filename ); + + $status = $repoMock->store( + $this->tmpFilepath, + 'public', + $hashPath . $filename, + FileRepo::OVERWRITE + ); + } + + protected function deleteFilesRecursively( $directory ) { + foreach ( glob( $directory . '/*' ) as $file ) { + if ( is_dir( $file ) ) { + $this->deleteFilesRecursively( $file ); + } else { + unlink( $file ); + } + } + + rmdir( $directory ); + } + + protected function tearDown() { + foreach ( glob( $this->tmpPrefix . '*' ) as $directory ) { + $this->deleteFilesRecursively( $directory ); + } + + unlink( $this->tmpFilepath ); + + parent::tearDown(); + } + + public function testMigration() { + $this->migratorMock->loadParamsAndArgs( + null, + [ 'oldlayout' => 'name', 'newlayout' => 'sha1' ] + ); + + ob_start(); + + $this->migratorMock->execute(); + + ob_end_clean(); + + $sha1 = sha1( $this->text ); + + $expectedOriginalFilepath = $this->tmpPrefix + . '-original/' + . substr( $sha1, 0, 1 ) + . '/' + . substr( $sha1, 1, 1 ) + . '/' + . substr( $sha1, 2, 1 ) + . '/' + . $sha1; + + $this->assertEquals( + file_get_contents( $expectedOriginalFilepath ), + $this->text, + 'New sha1 file should be exist and have the right contents' + ); + + $expectedPublicFilepath = $this->tmpPrefix . '-public/f/f8/Foo.png'; + + $this->assertEquals( + file_get_contents( $expectedPublicFilepath ), + $this->text, + 'Existing name file should still and have the right contents' + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php b/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php new file mode 100644 index 00000000..5a343f65 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/RepoGroupTest.php @@ -0,0 +1,63 @@ +<?php + +/** + * @covers RepoGroup + */ +class RepoGroupTest extends MediaWikiTestCase { + + function testHasForeignRepoNegative() { + $this->setMwGlobals( 'wgForeignFileRepos', [] ); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + $this->assertFalse( RepoGroup::singleton()->hasForeignRepos() ); + } + + function testHasForeignRepoPositive() { + $this->setUpForeignRepo(); + $this->assertTrue( RepoGroup::singleton()->hasForeignRepos() ); + } + + function testForEachForeignRepo() { + $this->setUpForeignRepo(); + $fakeCallback = $this->createMock( RepoGroupTestHelper::class ); + $fakeCallback->expects( $this->once() )->method( 'callback' ); + RepoGroup::singleton()->forEachForeignRepo( + [ $fakeCallback, 'callback' ], [ [] ] ); + } + + function testForEachForeignRepoNone() { + $this->setMwGlobals( 'wgForeignFileRepos', [] ); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + $fakeCallback = $this->createMock( RepoGroupTestHelper::class ); + $fakeCallback->expects( $this->never() )->method( 'callback' ); + RepoGroup::singleton()->forEachForeignRepo( + [ $fakeCallback, 'callback' ], [ [] ] ); + } + + private function setUpForeignRepo() { + global $wgUploadDirectory; + $this->setMwGlobals( 'wgForeignFileRepos', [ [ + 'class' => ForeignAPIRepo::class, + 'name' => 'wikimediacommons', + 'backend' => 'wikimediacommons-backend', + 'apibase' => 'https://commons.wikimedia.org/w/api.php', + 'hashLevels' => 2, + 'fetchDescription' => true, + 'descriptionCacheExpiry' => 43200, + 'apiThumbCacheExpiry' => 86400, + 'directory' => $wgUploadDirectory + ] ] ); + RepoGroup::destroySingleton(); + FileBackendGroup::destroySingleton(); + } +} + +/** + * Quick helper class to use as a mock callback for RepoGroup::singleton()->forEachForeignRepo. + */ +class RepoGroupTestHelper { + function callback( FileRepo $repo, array $foo ) { + return true; + } +} diff --git a/www/wiki/tests/phpunit/includes/filerepo/StoreBatchTest.php b/www/wiki/tests/phpunit/includes/filerepo/StoreBatchTest.php new file mode 100644 index 00000000..337c65c4 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/StoreBatchTest.php @@ -0,0 +1,141 @@ +<?php + +/** + * @group FileRepo + * @group medium + */ +class StoreBatchTest extends MediaWikiTestCase { + + protected $createdFiles; + protected $date; + /** @var FileRepo */ + protected $repo; + + protected function setUp() { + global $wgFileBackends; + parent::setUp(); + + # Forge a FileRepo object to not have to rely on local wiki settings + $tmpPrefix = $this->getNewTempDirectory(); + if ( $this->getCliArg( 'use-filebackend' ) ) { + $name = $this->getCliArg( 'use-filebackend' ); + $useConfig = []; + foreach ( $wgFileBackends as $conf ) { + if ( $conf['name'] == $name ) { + $useConfig = $conf; + } + } + $useConfig['lockManager'] = LockManagerGroup::singleton()->get( $useConfig['lockManager'] ); + unset( $useConfig['fileJournal'] ); + $useConfig['name'] = 'local-testing'; // swap name + $class = $useConfig['class']; + $backend = new $class( $useConfig ); + } else { + $backend = new FSFileBackend( [ + 'name' => 'local-testing', + 'wikiId' => wfWikiID(), + 'containerPaths' => [ + 'unittests-public' => "{$tmpPrefix}/public", + 'unittests-thumb' => "{$tmpPrefix}/thumb", + 'unittests-temp' => "{$tmpPrefix}/temp", + 'unittests-deleted' => "{$tmpPrefix}/deleted", + ] + ] ); + } + $this->repo = new FileRepo( [ + 'name' => 'unittests', + 'backend' => $backend + ] ); + + $this->date = gmdate( "YmdHis" ); + $this->createdFiles = []; + } + + protected function tearDown() { + // Delete files + $this->repo->cleanupBatch( $this->createdFiles ); + parent::tearDown(); + } + + /** + * Store a file or virtual URL source into a media file name. + * + * @param string $originalName The title of the image + * @param string $srcPath The filepath or virtual URL + * @param int $flags Flags to pass into repo::store(). + * @return Status + */ + private function storeit( $originalName, $srcPath, $flags ) { + $hashPath = $this->repo->getHashPath( $originalName ); + $dstRel = "$hashPath{$this->date}!$originalName"; + $dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName ); + + $result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags ); + $result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; + $this->createdFiles[] = $result->value; + + return $result; + } + + /** + * Test storing a file using different flags. + * + * @param string $fn The title of the image + * @param string $infn The name of the file (in the filesystem) + * @param string $otherfn The name of the different file (in the filesystem) + * @param bool $fromrepo 'true' if we want to copy from a virtual URL out of the Repo. + */ + private function storecohort( $fn, $infn, $otherfn, $fromrepo ) { + $f = $this->storeit( $fn, $infn, 0 ); + $this->assertTrue( $f->isOK(), 'failed to store a new file' ); + $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + if ( $fromrepo ) { + $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE ); + $infn = $f->value; + } + // This should work because we're allowed to overwrite + $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE ); + $this->assertTrue( $f->isOK(), 'We should be allowed to overwrite' ); + $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + // This should fail because we're overwriting. + $f = $this->storeit( $fn, $infn, 0 ); + $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite' ); + $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + // This should succeed because we're overwriting the same content. + $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE_SAME ); + $this->assertTrue( $f->isOK(), 'We should be able to overwrite the same content' ); + $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + // This should fail because we're overwriting different content. + if ( $fromrepo ) { + $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE ); + $otherfn = $f->value; + } + $f = $this->storeit( $fn, $otherfn, FileRepo::OVERWRITE_SAME ); + $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite different content' ); + $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); + } + + /** + * @covers FileRepo::store + */ + public function teststore() { + global $IP; + $this->storecohort( + "Test1.png", + "$IP/tests/phpunit/data/filerepo/wiki.png", + "$IP/tests/phpunit/data/filerepo/video.png", + false + ); + $this->storecohort( + "Test2.png", + "$IP/tests/phpunit/data/filerepo/wiki.png", + "$IP/tests/phpunit/data/filerepo/video.png", + true + ); + } +} diff --git a/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php b/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php new file mode 100644 index 00000000..3f4e46b5 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/file/FileTest.php @@ -0,0 +1,389 @@ +<?php + +class FileTest extends MediaWikiMediaTestCase { + + /** + * @param string $filename + * @param bool $expected + * @dataProvider providerCanAnimate + * @covers File::canAnimateThumbIfAppropriate + */ + function testCanAnimateThumbIfAppropriate( $filename, $expected ) { + $this->setMwGlobals( 'wgMaxAnimatedGifArea', 9000 ); + $file = $this->dataFile( $filename ); + $this->assertEquals( $file->canAnimateThumbIfAppropriate(), $expected ); + } + + function providerCanAnimate() { + return [ + [ 'nonanimated.gif', true ], + [ 'jpeg-comment-utf.jpg', true ], + [ 'test.tiff', true ], + [ 'Animated_PNG_example_bouncing_beach_ball.png', false ], + [ 'greyscale-png.png', true ], + [ 'Toll_Texas_1.svg', true ], + [ 'LoremIpsum.djvu', true ], + [ '80x60-2layers.xcf', true ], + [ 'Soccer_ball_animated.svg', false ], + [ 'Bishzilla_blink.gif', false ], + [ 'animated.gif', true ], + ]; + } + + /** + * @dataProvider getThumbnailBucketProvider + * @covers File::getThumbnailBucket + */ + public function testGetThumbnailBucket( $data ) { + $this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] ); + $this->setMwGlobals( 'wgThumbnailMinimumBucketDistance', $data['minimumBucketDistance'] ); + + $fileMock = $this->getMockBuilder( File::class ) + ->setConstructorArgs( [ 'fileMock', false ] ) + ->setMethods( [ 'getWidth' ] ) + ->getMockForAbstractClass(); + + $fileMock->expects( $this->any() ) + ->method( 'getWidth' ) + ->will( $this->returnValue( $data['width'] ) ); + + $this->assertEquals( + $data['expectedBucket'], + $fileMock->getThumbnailBucket( $data['requestedWidth'] ), + $data['message'] ); + } + + public function getThumbnailBucketProvider() { + $defaultBuckets = [ 256, 512, 1024, 2048, 4096 ]; + + return [ + [ [ + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 120, + 'expectedBucket' => 256, + 'message' => 'Picking bucket bigger than requested size' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 300, + 'expectedBucket' => 512, + 'message' => 'Picking bucket bigger than requested size' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 1024, + 'expectedBucket' => 2048, + 'message' => 'Picking bucket bigger than requested size' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 2048, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because none is bigger than the requested size' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 3500, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because requested size is bigger than original' + ] ], + [ [ + 'buckets' => [ 1024 ], + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 1024, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because requested size equals biggest bucket' + ] ], + [ [ + 'buckets' => null, + 'minimumBucketDistance' => 0, + 'width' => 3000, + 'requestedWidth' => 1024, + 'expectedBucket' => false, + 'message' => 'Picking no bucket because no buckets have been specified' + ] ], + [ [ + 'buckets' => [ 256, 512 ], + 'minimumBucketDistance' => 10, + 'width' => 3000, + 'requestedWidth' => 245, + 'expectedBucket' => 256, + 'message' => 'Requested width is distant enough from next bucket for it to be picked' + ] ], + [ [ + 'buckets' => [ 256, 512 ], + 'minimumBucketDistance' => 10, + 'width' => 3000, + 'requestedWidth' => 246, + 'expectedBucket' => 512, + 'message' => 'Requested width is too close to next bucket, picking next one' + ] ], + ]; + } + + /** + * @dataProvider getThumbnailSourceProvider + * @covers File::getThumbnailSource + */ + public function testGetThumbnailSource( $data ) { + $backendMock = $this->getMockBuilder( FSFileBackend::class ) + ->setConstructorArgs( [ [ 'name' => 'backendMock', 'wikiId' => wfWikiID() ] ] ) + ->getMock(); + + $repoMock = $this->getMockBuilder( FileRepo::class ) + ->setConstructorArgs( [ [ 'name' => 'repoMock', 'backend' => $backendMock ] ] ) + ->setMethods( [ 'fileExists', 'getLocalReference' ] ) + ->getMock(); + + $fsFile = new FSFile( 'fsFilePath' ); + + $repoMock->expects( $this->any() ) + ->method( 'fileExists' ) + ->will( $this->returnValue( true ) ); + + $repoMock->expects( $this->any() ) + ->method( 'getLocalReference' ) + ->will( $this->returnValue( $fsFile ) ); + + $handlerMock = $this->getMockBuilder( BitmapHandler::class ) + ->setMethods( [ 'supportsBucketing' ] )->getMock(); + $handlerMock->expects( $this->any() ) + ->method( 'supportsBucketing' ) + ->will( $this->returnValue( $data['supportsBucketing'] ) ); + + $fileMock = $this->getMockBuilder( File::class ) + ->setConstructorArgs( [ 'fileMock', $repoMock ] ) + ->setMethods( [ 'getThumbnailBucket', 'getLocalRefPath', 'getHandler' ] ) + ->getMockForAbstractClass(); + + $fileMock->expects( $this->any() ) + ->method( 'getThumbnailBucket' ) + ->will( $this->returnValue( $data['thumbnailBucket'] ) ); + + $fileMock->expects( $this->any() ) + ->method( 'getLocalRefPath' ) + ->will( $this->returnValue( 'localRefPath' ) ); + + $fileMock->expects( $this->any() ) + ->method( 'getHandler' ) + ->will( $this->returnValue( $handlerMock ) ); + + $reflection = new ReflectionClass( $fileMock ); + $reflection_property = $reflection->getProperty( 'handler' ); + $reflection_property->setAccessible( true ); + $reflection_property->setValue( $fileMock, $handlerMock ); + + if ( !is_null( $data['tmpBucketedThumbCache'] ) ) { + $reflection_property = $reflection->getProperty( 'tmpBucketedThumbCache' ); + $reflection_property->setAccessible( true ); + $reflection_property->setValue( $fileMock, $data['tmpBucketedThumbCache'] ); + } + + $result = $fileMock->getThumbnailSource( + [ 'physicalWidth' => $data['physicalWidth'] ] ); + + $this->assertEquals( $data['expectedPath'], $result['path'], $data['message'] ); + } + + public function getThumbnailSourceProvider() { + return [ + [ [ + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => null, + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => 'fsFilePath', + 'message' => 'Path downloaded from storage' + ] ], + [ [ + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => [ 1024 => '/tmp/shouldnotexist' . rand() ], + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => 'fsFilePath', + 'message' => 'Path downloaded from storage because temp file is missing' + ] ], + [ [ + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => [ 1024 => '/tmp' ], + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => '/tmp', + 'message' => 'Temporary path because temp file was found' + ] ], + [ [ + 'supportsBucketing' => false, + 'tmpBucketedThumbCache' => null, + 'thumbnailBucket' => 1024, + 'physicalWidth' => 2048, + 'expectedPath' => 'localRefPath', + 'message' => 'Original file path because bucketing is unsupported by handler' + ] ], + [ [ + 'supportsBucketing' => true, + 'tmpBucketedThumbCache' => null, + 'thumbnailBucket' => false, + 'physicalWidth' => 2048, + 'expectedPath' => 'localRefPath', + 'message' => 'Original file path because no width provided' + ] ], + ]; + } + + /** + * @dataProvider generateBucketsIfNeededProvider + * @covers File::generateBucketsIfNeeded + */ + public function testGenerateBucketsIfNeeded( $data ) { + $this->setMwGlobals( 'wgThumbnailBuckets', $data['buckets'] ); + + $backendMock = $this->getMockBuilder( FSFileBackend::class ) + ->setConstructorArgs( [ [ 'name' => 'backendMock', 'wikiId' => wfWikiID() ] ] ) + ->getMock(); + + $repoMock = $this->getMockBuilder( FileRepo::class ) + ->setConstructorArgs( [ [ 'name' => 'repoMock', 'backend' => $backendMock ] ] ) + ->setMethods( [ 'fileExists', 'getLocalReference' ] ) + ->getMock(); + + $fileMock = $this->getMockBuilder( File::class ) + ->setConstructorArgs( [ 'fileMock', $repoMock ] ) + ->setMethods( [ 'getWidth', 'getBucketThumbPath', 'makeTransformTmpFile', + 'generateAndSaveThumb', 'getHandler' ] ) + ->getMockForAbstractClass(); + + $handlerMock = $this->getMockBuilder( JpegHandler::class ) + ->setMethods( [ 'supportsBucketing' ] )->getMock(); + $handlerMock->expects( $this->any() ) + ->method( 'supportsBucketing' ) + ->will( $this->returnValue( true ) ); + + $fileMock->expects( $this->any() ) + ->method( 'getHandler' ) + ->will( $this->returnValue( $handlerMock ) ); + + $reflectionMethod = new ReflectionMethod( File::class, 'generateBucketsIfNeeded' ); + $reflectionMethod->setAccessible( true ); + + $fileMock->expects( $this->any() ) + ->method( 'getWidth' ) + ->will( $this->returnValue( $data['width'] ) ); + + $fileMock->expects( $data['expectedGetBucketThumbPathCalls'] ) + ->method( 'getBucketThumbPath' ); + + $repoMock->expects( $data['expectedFileExistsCalls'] ) + ->method( 'fileExists' ) + ->will( $this->returnValue( $data['fileExistsReturn'] ) ); + + $fileMock->expects( $data['expectedMakeTransformTmpFile'] ) + ->method( 'makeTransformTmpFile' ) + ->will( $this->returnValue( $data['makeTransformTmpFileReturn'] ) ); + + $fileMock->expects( $data['expectedGenerateAndSaveThumb'] ) + ->method( 'generateAndSaveThumb' ) + ->will( $this->returnValue( $data['generateAndSaveThumbReturn'] ) ); + + $this->assertEquals( $data['expectedResult'], + $reflectionMethod->invoke( + $fileMock, + [ + 'physicalWidth' => $data['physicalWidth'], + 'physicalHeight' => $data['physicalHeight'] ] + ), + $data['message'] ); + } + + public function generateBucketsIfNeededProvider() { + $defaultBuckets = [ 256, 512, 1024, 2048, 4096 ]; + + return [ + [ [ + 'buckets' => $defaultBuckets, + 'width' => 256, + 'physicalWidth' => 256, + 'physicalHeight' => 100, + 'expectedGetBucketThumbPathCalls' => $this->never(), + 'expectedFileExistsCalls' => $this->never(), + 'fileExistsReturn' => null, + 'expectedMakeTransformTmpFile' => $this->never(), + 'makeTransformTmpFileReturn' => false, + 'expectedGenerateAndSaveThumb' => $this->never(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'No bucket found, nothing to generate' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => true, + 'expectedMakeTransformTmpFile' => $this->never(), + 'makeTransformTmpFileReturn' => false, + 'expectedGenerateAndSaveThumb' => $this->never(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'File already exists, no reason to generate buckets' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => false, + 'expectedMakeTransformTmpFile' => $this->once(), + 'makeTransformTmpFileReturn' => false, + 'expectedGenerateAndSaveThumb' => $this->never(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'Cannot generate temp file for bucket' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => false, + 'expectedMakeTransformTmpFile' => $this->once(), + 'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ), + 'expectedGenerateAndSaveThumb' => $this->once(), + 'generateAndSaveThumbReturn' => false, + 'expectedResult' => false, + 'message' => 'Bucket image could not be generated' + ] ], + [ [ + 'buckets' => $defaultBuckets, + 'width' => 5000, + 'physicalWidth' => 300, + 'physicalHeight' => 200, + 'expectedGetBucketThumbPathCalls' => $this->once(), + 'expectedFileExistsCalls' => $this->once(), + 'fileExistsReturn' => false, + 'expectedMakeTransformTmpFile' => $this->once(), + 'makeTransformTmpFileReturn' => new TempFSFile( '/tmp/foo' ), + 'expectedGenerateAndSaveThumb' => $this->once(), + 'generateAndSaveThumbReturn' => new ThumbnailImage( false, 'bar', false, false ), + 'expectedResult' => true, + 'message' => 'Bucket image could not be generated' + ] ], + ]; + } +} diff --git a/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php b/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php new file mode 100644 index 00000000..e25e6064 --- /dev/null +++ b/www/wiki/tests/phpunit/includes/filerepo/file/LocalFileTest.php @@ -0,0 +1,183 @@ +<?php + +/** + * These tests should work regardless of $wgCapitalLinks + * @todo Split tests into providers and test methods + */ + +class LocalFileTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgCapitalLinks', true ); + + $info = [ + 'name' => 'test', + 'directory' => '/testdir', + 'url' => '/testurl', + 'hashLevels' => 2, + 'transformVia404' => false, + 'backend' => new FSFileBackend( [ + 'name' => 'local-backend', + 'wikiId' => wfWikiID(), + 'containerPaths' => [ + 'cont1' => "/testdir/local-backend/tempimages/cont1", + 'cont2' => "/testdir/local-backend/tempimages/cont2" + ] + ] ) + ]; + $this->repo_hl0 = new LocalRepo( [ 'hashLevels' => 0 ] + $info ); + $this->repo_hl2 = new LocalRepo( [ 'hashLevels' => 2 ] + $info ); + $this->repo_lc = new LocalRepo( [ 'initialCapital' => false ] + $info ); + $this->file_hl0 = $this->repo_hl0->newFile( 'test!' ); + $this->file_hl2 = $this->repo_hl2->newFile( 'test!' ); + $this->file_lc = $this->repo_lc->newFile( 'test!' ); + } + + /** + * @covers File::getHashPath + */ + public function testGetHashPath() { + $this->assertEquals( '', $this->file_hl0->getHashPath() ); + $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() ); + $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() ); + } + + /** + * @covers File::getRel + */ + public function testGetRel() { + $this->assertEquals( 'Test!', $this->file_hl0->getRel() ); + $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() ); + $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() ); + } + + /** + * @covers File::getUrlRel + */ + public function testGetUrlRel() { + $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() ); + $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() ); + $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() ); + } + + /** + * @covers File::getArchivePath + */ + public function testGetArchivePath() { + $this->assertEquals( + 'mwstore://local-backend/test-public/archive', + $this->file_hl0->getArchivePath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-public/archive/a/a2', + $this->file_hl2->getArchivePath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-public/archive/!', + $this->file_hl0->getArchivePath( '!' ) + ); + $this->assertEquals( + 'mwstore://local-backend/test-public/archive/a/a2/!', + $this->file_hl2->getArchivePath( '!' ) + ); + } + + /** + * @covers File::getThumbPath + */ + public function testGetThumbPath() { + $this->assertEquals( + 'mwstore://local-backend/test-thumb/Test!', + $this->file_hl0->getThumbPath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-thumb/a/a2/Test!', + $this->file_hl2->getThumbPath() + ); + $this->assertEquals( + 'mwstore://local-backend/test-thumb/Test!/x', + $this->file_hl0->getThumbPath( 'x' ) + ); + $this->assertEquals( + 'mwstore://local-backend/test-thumb/a/a2/Test!/x', + $this->file_hl2->getThumbPath( 'x' ) + ); + } + + /** + * @covers File::getArchiveUrl + */ + public function testGetArchiveUrl() { + $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() ); + $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() ); + $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) ); + $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) ); + } + + /** + * @covers File::getThumbUrl + */ + public function testGetThumbUrl() { + $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() ); + $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() ); + $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) ); + $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) ); + } + + /** + * @covers File::getArchiveVirtualUrl + */ + public function testGetArchiveVirtualUrl() { + $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() ); + $this->assertEquals( + 'mwrepo://test/public/archive/a/a2', + $this->file_hl2->getArchiveVirtualUrl() + ); + $this->assertEquals( + 'mwrepo://test/public/archive/%21', + $this->file_hl0->getArchiveVirtualUrl( '!' ) + ); + $this->assertEquals( + 'mwrepo://test/public/archive/a/a2/%21', + $this->file_hl2->getArchiveVirtualUrl( '!' ) + ); + } + + /** + * @covers File::getThumbVirtualUrl + */ + public function testGetThumbVirtualUrl() { + $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() ); + $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() ); + $this->assertEquals( + 'mwrepo://test/thumb/Test%21/%21', + $this->file_hl0->getThumbVirtualUrl( '!' ) + ); + $this->assertEquals( + 'mwrepo://test/thumb/a/a2/Test%21/%21', + $this->file_hl2->getThumbVirtualUrl( '!' ) + ); + } + + /** + * @covers File::getUrl + */ + public function testGetUrl() { + $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() ); + $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() ); + } + + /** + * @covers ::wfLocalFile + */ + public function testWfLocalFile() { + $file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" ); + $this->assertThat( + $file, + $this->isInstanceOf( LocalFile::class ), + 'wfLocalFile() returns LocalFile for valid Titles' + ); + } +} |