2020-12-03 17:53:55 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MediaWiki\Tests\Rest\Helper;
|
|
|
|
|
|
|
|
|
|
use BagOStuff;
|
|
|
|
|
use DeferredUpdates;
|
|
|
|
|
use EmptyBagOStuff;
|
|
|
|
|
use Exception;
|
|
|
|
|
use ExtensionRegistry;
|
2022-09-01 10:03:03 +00:00
|
|
|
use Generator;
|
2020-12-03 17:53:55 +00:00
|
|
|
use HashBagOStuff;
|
2022-09-01 10:03:03 +00:00
|
|
|
use Language;
|
2022-06-13 09:31:50 +00:00
|
|
|
use MediaWiki\Edit\SimpleParsoidOutputStash;
|
2022-07-06 14:05:52 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2022-06-28 09:49:36 +00:00
|
|
|
use MediaWiki\Page\PageRecord;
|
2022-06-17 14:00:27 +00:00
|
|
|
use MediaWiki\Parser\Parsoid\ParsoidOutputAccess;
|
2022-06-13 09:31:50 +00:00
|
|
|
use MediaWiki\Parser\Parsoid\ParsoidRenderID;
|
2022-09-06 09:07:45 +00:00
|
|
|
use MediaWiki\Rest\Handler\HtmlOutputRendererHelper;
|
2020-12-03 17:53:55 +00:00
|
|
|
use MediaWiki\Rest\LocalizedHttpException;
|
2022-08-30 10:26:39 +00:00
|
|
|
use MediaWiki\Revision\MutableRevisionRecord;
|
2022-06-28 09:49:36 +00:00
|
|
|
use MediaWiki\Revision\RevisionRecord;
|
2022-08-30 10:26:39 +00:00
|
|
|
use MediaWiki\Revision\SlotRecord;
|
2020-12-03 17:53:55 +00:00
|
|
|
use MediaWikiIntegrationTestCase;
|
|
|
|
|
use MWTimestamp;
|
|
|
|
|
use NullStatsdDataFactory;
|
2022-06-28 09:49:36 +00:00
|
|
|
use ParserOptions;
|
|
|
|
|
use ParserOutput;
|
2020-12-03 17:53:55 +00:00
|
|
|
use PHPUnit\Framework\MockObject\MockObject;
|
2022-06-28 09:49:36 +00:00
|
|
|
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
|
2022-06-28 10:30:44 +00:00
|
|
|
use Status;
|
2022-06-13 09:31:50 +00:00
|
|
|
use User;
|
2020-12-03 17:53:55 +00:00
|
|
|
use Wikimedia\Message\MessageValue;
|
|
|
|
|
use Wikimedia\Parsoid\Core\ClientError;
|
|
|
|
|
use Wikimedia\Parsoid\Core\PageBundle;
|
|
|
|
|
use Wikimedia\Parsoid\Core\ResourceLimitExceededException;
|
2022-08-30 10:26:39 +00:00
|
|
|
use WikitextContent;
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
/**
|
2022-09-06 09:07:45 +00:00
|
|
|
* @covers \MediaWiki\Rest\Handler\HtmlOutputRendererHelper
|
2020-12-03 17:53:55 +00:00
|
|
|
* @group Database
|
|
|
|
|
*/
|
2022-09-06 09:07:45 +00:00
|
|
|
class HtmlOutputRendererHelperTest extends MediaWikiIntegrationTestCase {
|
2020-12-15 22:12:49 +00:00
|
|
|
private const CACHE_EPOCH = '20001111010101';
|
|
|
|
|
|
|
|
|
|
private const TIMESTAMP_OLD = '20200101112233';
|
|
|
|
|
private const TIMESTAMP = '20200101223344';
|
|
|
|
|
private const TIMESTAMP_LATER = '20200101234200';
|
|
|
|
|
|
|
|
|
|
private const WIKITEXT_OLD = 'Hello \'\'\'Goat\'\'\'';
|
2020-12-03 17:53:55 +00:00
|
|
|
private const WIKITEXT = 'Hello \'\'\'World\'\'\'';
|
|
|
|
|
|
2022-05-16 12:43:23 +00:00
|
|
|
private const HTML_OLD = '>Goat<';
|
|
|
|
|
private const HTML = '>World<';
|
2020-12-03 17:53:55 +00:00
|
|
|
|
2022-05-24 21:13:42 +00:00
|
|
|
private const PARAM_DEFAULTS = [
|
|
|
|
|
'stash' => false,
|
|
|
|
|
];
|
|
|
|
|
|
2022-06-28 09:49:36 +00:00
|
|
|
private const MOCK_HTML = '<!DOCTYPE html><html>mocked HTML</html>';
|
|
|
|
|
|
|
|
|
|
private function exactlyOrAny( ?int $count ): InvocationOrder {
|
|
|
|
|
return $count === null ? $this->any() : $this->exactly( $count );
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 09:31:50 +00:00
|
|
|
/**
|
2022-06-28 09:49:36 +00:00
|
|
|
* @param array<string,int> $expectedCalls
|
2022-06-13 09:31:50 +00:00
|
|
|
*
|
2022-06-28 09:49:36 +00:00
|
|
|
* @return MockObject|ParsoidOutputAccess
|
2022-06-13 09:31:50 +00:00
|
|
|
*/
|
2022-06-28 09:49:36 +00:00
|
|
|
public function newMockParsoidOutputAccess( $expectedCalls = [] ): ParsoidOutputAccess {
|
|
|
|
|
$expectedCalls += [
|
|
|
|
|
'getParserOutput' => 1,
|
|
|
|
|
'getParsoidRenderID' => null,
|
|
|
|
|
'getParsoidPageBundle' => null,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$parsoid = $this->createNoOpMock( ParsoidOutputAccess::class, array_keys( $expectedCalls ) );
|
|
|
|
|
|
|
|
|
|
$parsoid->expects( $this->exactlyOrAny( $expectedCalls[ 'getParserOutput' ] ) )
|
|
|
|
|
->method( 'getParserOutput' )
|
|
|
|
|
->willReturnCallback( static function (
|
|
|
|
|
PageRecord $page,
|
|
|
|
|
ParserOptions $parserOpts,
|
|
|
|
|
?RevisionRecord $rev = null,
|
|
|
|
|
int $options = 0
|
|
|
|
|
) {
|
|
|
|
|
$pout = new ParserOutput( self::MOCK_HTML );
|
|
|
|
|
$pout->setCacheRevisionId( $rev ? $rev->getId() : $page->getLatest() );
|
|
|
|
|
$pout->setCacheTime( wfTimestampNow() ); // will use fake time
|
2022-06-28 10:30:44 +00:00
|
|
|
return Status::newGood( $pout );
|
2022-06-28 09:49:36 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
$parsoid->expects( $this->exactlyOrAny( $expectedCalls[ 'getParsoidRenderID' ] ) )
|
|
|
|
|
->method( 'getParsoidRenderID' )
|
|
|
|
|
->willReturnCallback( static function ( ParserOutput $pout ) {
|
|
|
|
|
return new ParsoidRenderID( $pout->getCacheRevisionId(), $pout->getCacheTime() );
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
$parsoid->expects( $this->exactlyOrAny( $expectedCalls[ 'getParsoidPageBundle' ] ) )
|
|
|
|
|
->method( 'getParsoidPageBundle' )
|
|
|
|
|
->willReturnCallback( static function ( ParserOutput $pout ) {
|
|
|
|
|
return new PageBundle( $pout->getRawText() );
|
|
|
|
|
} );
|
2022-06-13 09:31:50 +00:00
|
|
|
|
|
|
|
|
return $parsoid;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 17:53:55 +00:00
|
|
|
protected function setUp(): void {
|
|
|
|
|
parent::setUp();
|
|
|
|
|
|
|
|
|
|
if ( !ExtensionRegistry::getInstance()->isLoaded( 'Parsoid' ) ) {
|
|
|
|
|
$this->markTestSkipped( 'Parsoid is not configured' );
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-06 14:05:52 +00:00
|
|
|
$this->overrideConfigValue( MainConfigNames::CacheEpoch, self::CACHE_EPOCH );
|
2020-12-15 22:12:49 +00:00
|
|
|
|
2020-12-03 17:53:55 +00:00
|
|
|
// Clean up these tables after each test
|
|
|
|
|
$this->tablesUsed = [
|
|
|
|
|
'page',
|
|
|
|
|
'revision',
|
|
|
|
|
'comment',
|
|
|
|
|
'text',
|
|
|
|
|
'content'
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 09:31:50 +00:00
|
|
|
/**
|
|
|
|
|
* @param array $returns
|
|
|
|
|
*
|
2022-06-28 09:49:36 +00:00
|
|
|
* @return MockObject|User
|
2022-06-13 09:31:50 +00:00
|
|
|
*/
|
|
|
|
|
private function newUser( array $returns = [] ): MockObject {
|
|
|
|
|
$user = $this->createNoOpMock( User::class, [ 'pingLimiter' ] );
|
|
|
|
|
$user->method( 'pingLimiter' )->willReturn( $returns['pingLimiter'] ?? false );
|
|
|
|
|
return $user;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 17:53:55 +00:00
|
|
|
/**
|
|
|
|
|
* @param BagOStuff|null $cache
|
2022-06-28 09:49:36 +00:00
|
|
|
* @param ?ParsoidOutputAccess $access
|
2022-09-06 09:07:45 +00:00
|
|
|
*
|
|
|
|
|
* @return HtmlOutputRendererHelper
|
2020-12-03 17:53:55 +00:00
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
2022-09-06 09:07:45 +00:00
|
|
|
private function newHelper(
|
|
|
|
|
BagOStuff $cache = null,
|
|
|
|
|
?ParsoidOutputAccess $access = null
|
|
|
|
|
): HtmlOutputRendererHelper {
|
2020-12-15 22:12:49 +00:00
|
|
|
$cache = $cache ?: new EmptyBagOStuff();
|
2022-06-17 14:00:27 +00:00
|
|
|
$stash = new SimpleParsoidOutputStash( $cache, 1 );
|
2022-06-13 09:31:50 +00:00
|
|
|
|
2022-09-06 09:07:45 +00:00
|
|
|
$helper = new HtmlOutputRendererHelper(
|
2022-06-13 09:31:50 +00:00
|
|
|
$stash,
|
2022-06-13 15:48:44 +00:00
|
|
|
new NullStatsdDataFactory(),
|
2022-06-28 09:49:36 +00:00
|
|
|
$access ?? $this->newMockParsoidOutputAccess()
|
2020-12-03 17:53:55 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $helper;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-15 22:12:49 +00:00
|
|
|
private function getExistingPageWithRevisions( $name ) {
|
|
|
|
|
$page = $this->getNonexistingTestPage( $name );
|
|
|
|
|
|
|
|
|
|
MWTimestamp::setFakeTime( self::TIMESTAMP_OLD );
|
|
|
|
|
$this->editPage( $page, self::WIKITEXT_OLD );
|
|
|
|
|
$revisions['first'] = $page->getRevisionRecord();
|
|
|
|
|
|
|
|
|
|
MWTimestamp::setFakeTime( self::TIMESTAMP );
|
|
|
|
|
$this->editPage( $page, self::WIKITEXT );
|
|
|
|
|
$revisions['latest'] = $page->getRevisionRecord();
|
|
|
|
|
|
|
|
|
|
MWTimestamp::setFakeTime( self::TIMESTAMP_LATER );
|
|
|
|
|
return [ $page, $revisions ];
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-30 10:26:39 +00:00
|
|
|
private function getNonExistingPageWithFakeRevision( $name ) {
|
|
|
|
|
$page = $this->getNonexistingTestPage( $name );
|
|
|
|
|
MWTimestamp::setFakeTime( self::TIMESTAMP_OLD );
|
|
|
|
|
|
2022-09-01 10:03:03 +00:00
|
|
|
$content = new WikitextContent( self::WIKITEXT_OLD );
|
2022-08-30 10:26:39 +00:00
|
|
|
$rev = new MutableRevisionRecord( $page->getTitle() );
|
|
|
|
|
$rev->setPageId( $page->getId() );
|
|
|
|
|
$rev->setContent( SlotRecord::MAIN, $content );
|
|
|
|
|
|
|
|
|
|
return [ $page, $rev ];
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-15 22:12:49 +00:00
|
|
|
public function provideRevisionReferences() {
|
|
|
|
|
return [
|
|
|
|
|
'current' => [ null, [ 'html' => self::HTML, 'timestamp' => self::TIMESTAMP ] ],
|
|
|
|
|
'old' => [ 'first', [ 'html' => self::HTML_OLD, 'timestamp' => self::TIMESTAMP_OLD ] ],
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideRevisionReferences()
|
|
|
|
|
*/
|
2022-08-30 10:26:39 +00:00
|
|
|
public function testGetHtml( $revRef ) {
|
2020-12-15 22:12:49 +00:00
|
|
|
[ $page, $revisions ] = $this->getExistingPageWithRevisions( __METHOD__ );
|
|
|
|
|
$rev = $revRef ? $revisions[ $revRef ] : null;
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
$helper = $this->newHelper();
|
2022-06-13 09:31:50 +00:00
|
|
|
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser(), $rev );
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
$htmlresult = $helper->getHtml()->getRawText();
|
|
|
|
|
|
2022-06-28 09:49:36 +00:00
|
|
|
$this->assertSame( self::MOCK_HTML, $htmlresult );
|
2020-12-03 17:53:55 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-13 09:31:50 +00:00
|
|
|
public function testHtmlIsStashed() {
|
|
|
|
|
[ $page, ] = $this->getExistingPageWithRevisions( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$cache = new HashBagOStuff();
|
|
|
|
|
|
2022-06-28 09:49:36 +00:00
|
|
|
$helper = $this->newHelper( $cache );
|
2022-06-13 09:31:50 +00:00
|
|
|
|
|
|
|
|
$helper->init( $page, [ 'stash' => true ] + self::PARAM_DEFAULTS, $this->newUser() );
|
|
|
|
|
$htmlresult = $helper->getHtml()->getRawText();
|
2022-06-28 09:49:36 +00:00
|
|
|
$this->assertSame( self::MOCK_HTML, $htmlresult );
|
2022-06-13 09:31:50 +00:00
|
|
|
|
|
|
|
|
$eTag = $helper->getETag();
|
|
|
|
|
$parsoidStashKey = ParsoidRenderID::newFromETag( $eTag );
|
|
|
|
|
|
2022-06-17 14:00:27 +00:00
|
|
|
$stash = new SimpleParsoidOutputStash( $cache, 1 );
|
2022-06-13 09:31:50 +00:00
|
|
|
$this->assertNotNull( $stash->get( $parsoidStashKey ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testStashRateLimit() {
|
|
|
|
|
$page = $this->getExistingTestPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$helper = $this->newHelper();
|
|
|
|
|
|
|
|
|
|
$user = $this->newUser( [ 'pingLimiter' => true ] );
|
|
|
|
|
$helper->init( $page, [ 'stash' => true ] + self::PARAM_DEFAULTS, $user );
|
|
|
|
|
|
|
|
|
|
$this->expectException( LocalizedHttpException::class );
|
|
|
|
|
$this->expectExceptionCode( 429 );
|
|
|
|
|
$helper->getHtml();
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-15 22:12:49 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideRevisionReferences()
|
|
|
|
|
*/
|
2022-08-30 10:26:39 +00:00
|
|
|
public function testEtagLastModified( $revRef ) {
|
2020-12-15 22:12:49 +00:00
|
|
|
[ $page, $revisions ] = $this->getExistingPageWithRevisions( __METHOD__ );
|
|
|
|
|
$rev = $revRef ? $revisions[ $revRef ] : null;
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
$cache = new HashBagOStuff();
|
|
|
|
|
|
|
|
|
|
// First, test it works if nothing was cached yet.
|
|
|
|
|
$helper = $this->newHelper( $cache );
|
2022-06-13 09:31:50 +00:00
|
|
|
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser(), $rev );
|
2020-12-15 22:12:49 +00:00
|
|
|
$etag = $helper->getETag();
|
|
|
|
|
$lastModified = $helper->getLastModified();
|
2020-12-03 17:53:55 +00:00
|
|
|
$helper->getHtml(); // put HTML into the cache
|
|
|
|
|
|
2020-12-15 22:12:49 +00:00
|
|
|
// make sure the etag didn't change after getHtml();
|
|
|
|
|
$this->assertSame( $etag, $helper->getETag() );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
MWTimestamp::convert( TS_MW, $lastModified ),
|
|
|
|
|
MWTimestamp::convert( TS_MW, $helper->getLastModified() )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Now, expire the cache. etag and timestamp should change
|
|
|
|
|
$now = MWTimestamp::convert( TS_UNIX, self::TIMESTAMP_LATER ) + 10000;
|
|
|
|
|
MWTimestamp::setFakeTime( $now );
|
2020-12-03 17:53:55 +00:00
|
|
|
$this->assertTrue(
|
2020-12-15 22:12:49 +00:00
|
|
|
$page->getTitle()->invalidateCache( MWTimestamp::convert( TS_MW, $now ) ),
|
2022-03-09 01:49:21 +00:00
|
|
|
'Cannot invalidate cache'
|
2020-12-03 17:53:55 +00:00
|
|
|
);
|
|
|
|
|
DeferredUpdates::doUpdates();
|
2021-05-04 20:45:30 +00:00
|
|
|
$page->clear();
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
$helper = $this->newHelper( $cache );
|
2022-06-13 09:31:50 +00:00
|
|
|
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser(), $rev );
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
$this->assertNotSame( $etag, $helper->getETag() );
|
|
|
|
|
$this->assertSame(
|
2020-12-15 22:12:49 +00:00
|
|
|
MWTimestamp::convert( TS_MW, $now ),
|
|
|
|
|
MWTimestamp::convert( TS_MW, $helper->getLastModified() )
|
2020-12-03 17:53:55 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-30 10:26:39 +00:00
|
|
|
/**
|
2022-09-06 09:07:45 +00:00
|
|
|
* @covers \MediaWiki\Rest\Handler\HtmlOutputRendererHelper::init
|
2022-08-30 10:26:39 +00:00
|
|
|
* @covers \MediaWiki\Parser\Parsoid\ParsoidOutputAccess::parse
|
|
|
|
|
*/
|
|
|
|
|
public function testEtagLastModifiedWithPageIdentity() {
|
|
|
|
|
[ $fakePage, $fakeRevision ] = $this->getNonExistingPageWithFakeRevision( __METHOD__ );
|
|
|
|
|
$poa = $this->createMock( ParsoidOutputAccess::class );
|
|
|
|
|
$poa->expects( $this->once() )
|
|
|
|
|
->method( 'parse' )
|
|
|
|
|
->willReturnCallback( static function (
|
|
|
|
|
PageRecord $page,
|
|
|
|
|
ParserOptions $parserOpts,
|
|
|
|
|
?RevisionRecord $rev = null
|
|
|
|
|
) use ( $fakePage, $fakeRevision ) {
|
|
|
|
|
self::assertSame( $page, $fakePage, '$page and $fakePage should be the same' );
|
|
|
|
|
self::assertSame( $rev, $fakeRevision, '$rev and $fakeRevision should be the same' );
|
|
|
|
|
|
|
|
|
|
$pout = new ParserOutput( self::MOCK_HTML );
|
|
|
|
|
$pout->setCacheRevisionId( $rev ? $rev->getId() : $page->getLatest() );
|
|
|
|
|
$pout->setCacheTime( wfTimestampNow() ); // will use fake time
|
|
|
|
|
return Status::newGood( $pout );
|
|
|
|
|
} );
|
|
|
|
|
$poa->method( 'getParsoidRenderID' )
|
|
|
|
|
->willReturnCallback( static function ( ParserOutput $pout ) {
|
|
|
|
|
return new ParsoidRenderID( 1, $pout->getCacheTime() );
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
$helper = $this->newHelper( null, $poa );
|
|
|
|
|
$helper->init( $fakePage, self::PARAM_DEFAULTS, $this->newUser(), $fakeRevision );
|
|
|
|
|
$etag = $helper->getETag();
|
|
|
|
|
$lastModified = $helper->getLastModified();
|
|
|
|
|
|
|
|
|
|
$this->assertSame( $etag, $helper->getETag() );
|
|
|
|
|
$this->assertSame(
|
|
|
|
|
MWTimestamp::convert( TS_MW, $lastModified ),
|
|
|
|
|
MWTimestamp::convert( TS_MW, $helper->getLastModified() )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-24 21:13:42 +00:00
|
|
|
public function provideETagSuffix() {
|
|
|
|
|
yield 'stash + html' =>
|
2022-06-17 14:00:27 +00:00
|
|
|
[ [ 'stash' => true ], 'html', '/stash/html' ];
|
2022-05-24 21:13:42 +00:00
|
|
|
|
|
|
|
|
yield 'view html' =>
|
2022-06-17 14:00:27 +00:00
|
|
|
[ [], 'html', '/view/html' ];
|
2022-05-24 21:13:42 +00:00
|
|
|
|
|
|
|
|
yield 'stash + wrapped' =>
|
2022-06-17 14:00:27 +00:00
|
|
|
[ [ 'stash' => true ], 'with_html', '/stash/with_html' ];
|
2022-05-24 21:13:42 +00:00
|
|
|
|
|
|
|
|
yield 'view wrapped' =>
|
2022-06-17 14:00:27 +00:00
|
|
|
[ [], 'with_html', '/view/with_html' ];
|
2022-05-24 21:13:42 +00:00
|
|
|
|
|
|
|
|
yield 'stash' =>
|
2022-06-17 14:00:27 +00:00
|
|
|
[ [ 'stash' => true ], '', '/stash' ];
|
2022-05-24 21:13:42 +00:00
|
|
|
|
|
|
|
|
yield 'nothing' =>
|
2022-06-17 14:00:27 +00:00
|
|
|
[ [], '', '/view' ];
|
2022-05-24 21:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideETagSuffix()
|
|
|
|
|
*/
|
|
|
|
|
public function testETagSuffix( array $params, string $mode, string $suffix ) {
|
|
|
|
|
$page = $this->getExistingTestPage( __METHOD__ );
|
|
|
|
|
|
|
|
|
|
$cache = new HashBagOStuff();
|
|
|
|
|
|
|
|
|
|
// First, test it works if nothing was cached yet.
|
|
|
|
|
$helper = $this->newHelper( $cache );
|
2022-06-13 09:31:50 +00:00
|
|
|
$helper->init( $page, $params + self::PARAM_DEFAULTS, $this->newUser() );
|
2022-05-24 21:13:42 +00:00
|
|
|
|
|
|
|
|
$etag = $helper->getETag( $mode );
|
|
|
|
|
$etag = trim( $etag, '"' );
|
|
|
|
|
$this->assertStringEndsWith( $suffix, $etag );
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 17:53:55 +00:00
|
|
|
public function provideHandlesParsoidError() {
|
|
|
|
|
yield 'ClientError' => [
|
|
|
|
|
new ClientError( 'TEST_TEST' ),
|
|
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-html-backend-error' ),
|
|
|
|
|
400,
|
|
|
|
|
[
|
|
|
|
|
'reason' => 'TEST_TEST'
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
];
|
|
|
|
|
yield 'ResourceLimitExceededException' => [
|
|
|
|
|
new ResourceLimitExceededException( 'TEST_TEST' ),
|
|
|
|
|
new LocalizedHttpException(
|
|
|
|
|
new MessageValue( 'rest-resource-limit-exceeded' ),
|
|
|
|
|
413,
|
|
|
|
|
[
|
|
|
|
|
'reason' => 'TEST_TEST'
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideHandlesParsoidError
|
|
|
|
|
*/
|
|
|
|
|
public function testHandlesParsoidError(
|
|
|
|
|
Exception $parsoidException,
|
|
|
|
|
Exception $expectedException
|
|
|
|
|
) {
|
2020-12-15 22:12:49 +00:00
|
|
|
$page = $this->getExistingTestPage( __METHOD__ );
|
2020-12-03 17:53:55 +00:00
|
|
|
|
2022-06-28 09:49:36 +00:00
|
|
|
/** @var ParsoidOutputAccess|MockObject $access */
|
|
|
|
|
$access = $this->createNoOpMock( ParsoidOutputAccess::class, [ 'getParserOutput' ] );
|
|
|
|
|
$access->expects( $this->once() )
|
2020-12-03 17:53:55 +00:00
|
|
|
->method( 'wikitext2html' )
|
|
|
|
|
->willThrowException( $parsoidException );
|
|
|
|
|
|
2022-06-28 09:49:36 +00:00
|
|
|
$helper = $this->newHelper( null, $access );
|
2022-06-13 09:31:50 +00:00
|
|
|
$helper->init( $page, self::PARAM_DEFAULTS, $this->newUser() );
|
2020-12-03 17:53:55 +00:00
|
|
|
|
|
|
|
|
$this->expectExceptionObject( $expectedException );
|
|
|
|
|
$helper->getHtml();
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 10:03:03 +00:00
|
|
|
/**
|
|
|
|
|
* Mock the language class based on a language code.
|
|
|
|
|
*
|
|
|
|
|
* @param string $langCode
|
|
|
|
|
*
|
|
|
|
|
* @return Language|Language&MockObject|MockObject
|
|
|
|
|
*/
|
|
|
|
|
private function getLanguageMock( string $langCode ) {
|
|
|
|
|
$language = $this->createMock( Language::class );
|
|
|
|
|
$language->method( 'getCode' )->willReturn( $langCode );
|
|
|
|
|
|
|
|
|
|
return $language;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @return Generator */
|
|
|
|
|
public function provideParserOptions() {
|
|
|
|
|
$langCode = 'de';
|
|
|
|
|
$parserOptions = $this->createMock( ParserOptions::class );
|
|
|
|
|
$parserOptions->method( 'getTargetLanguage' )
|
|
|
|
|
->willReturn( $this->getLanguageMock( $langCode ) );
|
|
|
|
|
yield 'ParserOptions for "de" language' => [ $parserOptions, $langCode ];
|
|
|
|
|
|
|
|
|
|
$langCode = 'ar';
|
|
|
|
|
$parserOptions = $this->createMock( ParserOptions::class );
|
|
|
|
|
$parserOptions->method( 'getTargetLanguage' )
|
|
|
|
|
->willReturn( $this->getLanguageMock( $langCode ) );
|
|
|
|
|
yield 'ParserOptions for "ar" language' => [ $parserOptions, $langCode ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-09-06 09:07:45 +00:00
|
|
|
* @covers \MediaWiki\Rest\Handler\HtmlOutputRendererHelper::getParserOutput
|
2022-09-01 10:03:03 +00:00
|
|
|
* @dataProvider provideParserOptions
|
|
|
|
|
*/
|
|
|
|
|
public function testGetParserOutputWithLanguageOverride( $parserOptions, $expectedLangCode ) {
|
|
|
|
|
$services = $this->getServiceContainer();
|
|
|
|
|
$parserOutputAccess = $services->getParsoidOutputAccess();
|
|
|
|
|
|
|
|
|
|
[ $page, $revision ] = $this->getNonExistingPageWithFakeRevision( __METHOD__ );
|
|
|
|
|
// set oldid=0 for page creation
|
|
|
|
|
$revision->setId( 0 );
|
|
|
|
|
|
|
|
|
|
/** @var Status $status */
|
|
|
|
|
$status = $parserOutputAccess->getParserOutput( $page, $parserOptions, $revision );
|
|
|
|
|
|
|
|
|
|
$this->assertTrue( $status->isOK() );
|
|
|
|
|
// assert page title in parsoid output HTML
|
|
|
|
|
$this->assertStringContainsString( __METHOD__, $status->getValue()->getText() );
|
|
|
|
|
|
|
|
|
|
if ( $parserOptions->getTargetLanguage() !== null ) {
|
|
|
|
|
$targetLanguage = $parserOptions->getTargetLanguage()->getCode();
|
|
|
|
|
$this->assertSame( $expectedLangCode, $targetLanguage );
|
|
|
|
|
$this->assertInstanceOf( Language::class, $parserOptions->getTargetLanguage() );
|
|
|
|
|
} else {
|
|
|
|
|
$this->assertNull( $parserOptions->getTargetLanguage() );
|
|
|
|
|
$this->assertNull( $expectedLangCode );
|
|
|
|
|
// the default target language is english.
|
|
|
|
|
$targetLanguage = 'en';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// assert the page language in parsoid output HTML
|
|
|
|
|
$this->assertStringContainsString(
|
|
|
|
|
'lang="' . $targetLanguage . '"',
|
|
|
|
|
$status->getValue()->getText()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// assert the content language in parsoid output HTML
|
|
|
|
|
$this->assertStringContainsString(
|
|
|
|
|
'content="' . $targetLanguage . '"',
|
|
|
|
|
$status->getValue()->getText()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// also check that the correct wiki text content is returned in <section> tags
|
|
|
|
|
$this->assertStringContainsString( 'Hello ', $status->getValue()->getText() );
|
|
|
|
|
$this->assertStringContainsString( 'Goat', $status->getValue()->getText() );
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 17:53:55 +00:00
|
|
|
}
|