aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/integration
diff options
context:
space:
mode:
authordaniel <dkinzler@wikimedia.org>2020-11-20 21:46:41 +0100
committerPpchelko <ppchelko@wikimedia.org>2020-12-02 18:08:12 +0000
commitb98f7a6fc1bd2b6ffd8e2df46d4f6449e14e9227 (patch)
tree4ee35a6f9a32217d06ee5e8f93cc23c39eed583d /tests/phpunit/integration
parentd2565533c4c68ac27dd3a992d74a6a7f4dbd0a58 (diff)
downloadmediawikicore-b98f7a6fc1bd2b6ffd8e2df46d4f6449e14e9227.tar.gz
mediawikicore-b98f7a6fc1bd2b6ffd8e2df46d4f6449e14e9227.zip
Extract helper classes from PageHTMLHandler
This extracts two helper classes from PageHTMLHandler: * PageContentHelper for accessing page content. This replaces the LatestRevisionContentHandler mase class. * ParsoidHtmlHelper for generating HTML from wikitext using parsoid. The idea is to decouple the functionality from the REST handlers, so we can easily mix and match functionality to create a handler for the new per-revision HTML endpoint. Bug: T267981 Bug: T267982 Change-Id: I3226833d12e51c959712d642b0195de1fe1ef979
Diffstat (limited to 'tests/phpunit/integration')
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/PageContentHelperTest.php277
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php5
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/ParsoidHTMLHelperTest.php214
3 files changed, 494 insertions, 2 deletions
diff --git a/tests/phpunit/integration/includes/Rest/Handler/PageContentHelperTest.php b/tests/phpunit/integration/includes/Rest/Handler/PageContentHelperTest.php
new file mode 100644
index 000000000000..e3c06fbedddd
--- /dev/null
+++ b/tests/phpunit/integration/includes/Rest/Handler/PageContentHelperTest.php
@@ -0,0 +1,277 @@
+<?php
+
+namespace MediaWiki\Tests\Rest\Helper;
+
+use HashConfig;
+use MediaWiki\Rest\Handler\PageContentHelper;
+use MediaWiki\Rest\HttpException;
+use MediaWiki\Rest\Response;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
+use MediaWikiIntegrationTestCase;
+use Title;
+
+/**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper
+ * @group Database
+ */
+class PageContentHelperTest extends MediaWikiIntegrationTestCase {
+
+ private const NO_REVISION_ETAG = '"b620cd7841f9ea8f545f11cc44ce794f848fa2d3"';
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ // Clean up these tables after each test
+ $this->tablesUsed = [
+ 'page',
+ 'revision',
+ 'comment',
+ 'text',
+ 'content'
+ ];
+ }
+
+ /**
+ * @return PageContentHelper
+ */
+ private function newHelper( $params = [], $user = null ): PageContentHelper {
+ $helper = new PageContentHelper(
+ new HashConfig( [
+ 'RightsUrl' => 'https://example.com/rights',
+ 'RightsText' => 'some rights',
+ ] ),
+ $this->getServiceContainer()->getPermissionManager(),
+ $this->getServiceContainer()->getRevisionLookup(),
+ $this->getServiceContainer()->getTitleFormatter(),
+ $this->getServiceContainer()->getTitleFactory()
+ );
+
+ $user = $user ?: $this->getTestUser()->getUser();
+ $helper->init( $user, $params );
+ return $helper;
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getRole()
+ */
+ public function testGetRole() {
+ $helper = $this->newHelper();
+ $this->assertSame( SlotRecord::MAIN, $helper->getRole() );
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitleText()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitle()
+ */
+ public function testGetTitle() {
+ $helper = $this->newHelper( [ 'title' => 'Foo' ] );
+ $this->assertSame( 'Foo', $helper->getTitleText() );
+
+ $this->assertInstanceOf( Title::class, $helper->getTitle() );
+ $this->assertSame( 'Foo', $helper->getTitle()->getPrefixedDBkey() );
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTargetRevision()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getPageContent()
+ */
+ public function testGetTargetRevisionAndContent() {
+ $page = $this->getExistingTestPage( __METHOD__ );
+ $rev = $page->getRevisionRecord();
+
+ $helper = $this->newHelper( [ 'title' => $page->getTitle()->getPrefixedDBkey() ] );
+
+ $targetRev = $helper->getTargetRevision();
+ $this->assertInstanceOf( RevisionRecord::class, $targetRev );
+ $this->assertSame( $rev->getId(), $targetRev->getId() );
+
+ $pageContent = $helper->getPageContent();
+ $this->assertSame(
+ $rev->getContent( SlotRecord::MAIN )->serialize(),
+ $pageContent->serialize()
+ );
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitleText()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitle()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::isAccessible()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::hasContent()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTargetRevision()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getPageContent()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getLastModified()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getETag()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::checkAccess()
+ */
+ public function testNoTitle() {
+ $helper = $this->newHelper();
+
+ $this->assertNull( $helper->getTitleText() );
+ $this->assertFalse( $helper->getTitle() );
+
+ $this->assertFalse( $helper->hasContent() );
+ $this->assertFalse( $helper->isAccessible() );
+
+ $this->assertFalse( $helper->getTargetRevision() );
+
+ $this->assertNull( $helper->getLastModified() );
+ $this->assertSame( self::NO_REVISION_ETAG, $helper->getETag() );
+
+ try {
+ $helper->getPageContent();
+ $this->fail( 'Expected HttpException' );
+ } catch ( HttpException $ex ) {
+ $this->assertSame( 404, $ex->getCode() );
+ }
+
+ try {
+ $helper->checkAccess();
+ $this->fail( 'Expected HttpException' );
+ } catch ( HttpException $ex ) {
+ $this->assertSame( 404, $ex->getCode() );
+ }
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitleText()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitle()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::isAccessible()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::hasContent()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTargetRevision()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getPageContent()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getLastModified()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getETag()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::checkAccess()
+ */
+ public function testNonExistingPage() {
+ $page = $this->getNonexistingTestPage( __METHOD__ );
+ $title = $page->getTitle();
+ $helper = $this->newHelper( [ 'title' => $title->getPrefixedDBkey() ] );
+
+ $this->assertSame( $title->getPrefixedDBkey(), $helper->getTitleText() );
+ $this->assertSame( $title->getPrefixedDBkey(), $helper->getTitle()->getPrefixedDBkey() );
+
+ $this->assertFalse( $helper->hasContent() );
+ $this->assertFalse( $helper->isAccessible() );
+
+ $this->assertFalse( $helper->getTargetRevision() );
+
+ $this->assertNull( $helper->getLastModified() );
+ $this->assertSame( self::NO_REVISION_ETAG, $helper->getETag() );
+
+ try {
+ $helper->getPageContent();
+ $this->fail( 'Expected HttpException' );
+ } catch ( HttpException $ex ) {
+ $this->assertSame( 404, $ex->getCode() );
+ }
+
+ try {
+ $helper->checkAccess();
+ $this->fail( 'Expected HttpException' );
+ } catch ( HttpException $ex ) {
+ $this->assertSame( 404, $ex->getCode() );
+ }
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitleText()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTitle()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::isAccessible()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::hasContent()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getTargetRevision()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getPageContent()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getLastModified()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getETag()
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::checkAccess()
+ */
+ public function testForbidenPage() {
+ $this->mergeMwGlobalArrayValue(
+ 'wgGroupPermissions', [
+ '*' => [ 'read' => false ],
+ 'user' => [ 'read' => false ],
+ 'autoconfirmed' => [ 'read' => false ],
+ ]
+ );
+
+ $user = $this->getTestUser()->getUser();
+
+ $page = $this->getExistingTestPage( __METHOD__ );
+ $title = $page->getTitle();
+ $helper = $this->newHelper( [ 'title' => $title->getPrefixedDBkey() ], $user );
+
+ $this->assertSame( $title->getPrefixedDBkey(), $helper->getTitleText() );
+ $this->assertSame( $title->getPrefixedDBkey(), $helper->getTitle()->getPrefixedDBkey() );
+
+ $this->assertTrue( $helper->hasContent() );
+ $this->assertFalse( $helper->isAccessible() );
+
+ $this->assertNull( $helper->getLastModified() );
+
+ try {
+ $helper->checkAccess();
+ $this->fail( 'Expected HttpException' );
+ } catch ( HttpException $ex ) {
+ $this->assertSame( 403, $ex->getCode() );
+ }
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::getParamSettings()
+ */
+ public function testParameterSettings() {
+ $helper = $this->newHelper();
+ $settings = $helper->getParamSettings();
+ $this->assertArrayHasKey( 'title', $settings );
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::setCacheControl()
+ */
+ public function testCacheControl() {
+ $helper = $this->newHelper();
+
+ $response = new Response();
+
+ $helper->setCacheControl( $response ); // default
+ $this->assertStringContainsString( 'max-age=5', $response->getHeaderLine( 'Cache-Control' ) );
+
+ $helper->setCacheControl( $response, 2 ); // explicit
+ $this->assertStringContainsString( 'max-age=2', $response->getHeaderLine( 'Cache-Control' ) );
+
+ $helper->setCacheControl( $response, 1000 * 1000 ); // too big
+ $this->assertStringContainsString( 'max-age=5', $response->getHeaderLine( 'Cache-Control' ) );
+ }
+
+ /**
+ * @covers \MediaWiki\Rest\Handler\PageContentHelper::constructMetadata()
+ */
+ public function testConstructMetadata() {
+ $page = $this->getExistingTestPage( __METHOD__ );
+ $title = $page->getTitle();
+
+ $revision = $page->getRevisionRecord();
+ $content = $revision->getContent( SlotRecord::MAIN );
+ $expected = [
+ 'id' => $title->getArticleID(),
+ 'key' => $title->getPrefixedDBkey(),
+ 'title' => $title->getPrefixedText(),
+ 'latest' => [
+ 'id' => $revision->getId(),
+ 'timestamp' => wfTimestampOrNull( TS_ISO_8601, $revision->getTimestamp() )
+ ],
+ 'content_model' => $content->getModel(),
+ 'license' => [
+ 'url' => 'https://example.com/rights',
+ 'title' => 'some rights',
+ ]
+ ];
+
+ $helper = $this->newHelper( [ 'title' => $title->getPrefixedDBkey() ] );
+ $data = $helper->constructMetadata();
+
+ $this->assertEquals( $expected, $data );
+ }
+
+}
diff --git a/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php b/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php
index fecc42199f95..94bfa643999e 100644
--- a/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php
+++ b/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php
@@ -88,8 +88,9 @@ class PageHTMLHandlerTest extends MediaWikiIntegrationTestCase {
);
if ( $parsoid !== null ) {
- $wrapper = TestingAccessWrapper::newFromObject( $handler );
- $wrapper->parsoid = $parsoid;
+ $handlerWrapper = TestingAccessWrapper::newFromObject( $handler );
+ $helperWrapper = TestingAccessWrapper::newFromObject( $handlerWrapper->htmlHelper );
+ $helperWrapper->parsoid = $parsoid;
}
return $handler;
diff --git a/tests/phpunit/integration/includes/Rest/Handler/ParsoidHTMLHelperTest.php b/tests/phpunit/integration/includes/Rest/Handler/ParsoidHTMLHelperTest.php
new file mode 100644
index 000000000000..c295c28e7156
--- /dev/null
+++ b/tests/phpunit/integration/includes/Rest/Handler/ParsoidHTMLHelperTest.php
@@ -0,0 +1,214 @@
+<?php
+
+namespace MediaWiki\Tests\Rest\Helper;
+
+use BagOStuff;
+use DeferredUpdates;
+use EmptyBagOStuff;
+use Exception;
+use ExtensionRegistry;
+use HashBagOStuff;
+use MediaWiki\Rest\Handler\ParsoidHTMLHelper;
+use MediaWiki\Rest\LocalizedHttpException;
+use MediaWikiIntegrationTestCase;
+use MWTimestamp;
+use NullStatsdDataFactory;
+use ParserCache;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\NullLogger;
+use Wikimedia\Message\MessageValue;
+use Wikimedia\Parsoid\Core\ClientError;
+use Wikimedia\Parsoid\Core\PageBundle;
+use Wikimedia\Parsoid\Core\ResourceLimitExceededException;
+use Wikimedia\Parsoid\Parsoid;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @covers \MediaWiki\Rest\Handler\ParsoidHTMLHelper
+ * @group Database
+ */
+class ParsoidHTMLHelperTest extends MediaWikiIntegrationTestCase {
+
+ private const WIKITEXT = 'Hello \'\'\'World\'\'\'';
+
+ private const HTML = '<p>Hello <b>World</b></p>';
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ if ( !ExtensionRegistry::getInstance()->isLoaded( 'Parsoid' ) ) {
+ $this->markTestSkipped( 'Parsoid is not configured' );
+ }
+
+ // Clean up these tables after each test
+ $this->tablesUsed = [
+ 'page',
+ 'revision',
+ 'comment',
+ 'text',
+ 'content'
+ ];
+ }
+
+ /**
+ * @param BagOStuff|null $cache
+ * @param Parsoid|MockObject|null $parsoid
+ * @return ParsoidHTMLHelper
+ * @throws Exception
+ */
+ private function newHelper( BagOStuff $cache = null, Parsoid $parsoid = null ): ParsoidHTMLHelper {
+ $parserCache = new ParserCache(
+ 'Test',
+ $cache ?: new EmptyBagOStuff(),
+ 0,
+ $this->getServiceContainer()->getHookContainer(),
+ $this->getServiceContainer()->getJsonCodec(),
+ new NullStatsdDataFactory(),
+ new NullLogger()
+ );
+
+ $helper = new ParsoidHTMLHelper(
+ $parserCache,
+ $this->getServiceContainer()->getWikiPageFactory()
+ );
+
+ if ( $parsoid !== null ) {
+ $wrapper = TestingAccessWrapper::newFromObject( $helper );
+ $wrapper->parsoid = $parsoid;
+ }
+
+ return $helper;
+ }
+
+ public function testGetHtml() {
+ $page = $this->getExistingTestPage( 'HtmlHelperTestPage/with/slashes' );
+ $this->assertTrue(
+ $this->editPage( $page, self::WIKITEXT )->isGood(),
+ 'Sanity: edited a page'
+ );
+
+ $helper = $this->newHelper();
+ $helper->init( $page->getTitle() );
+
+ $htmlresult = $helper->getHtml()->getRawText();
+
+ $this->assertStringContainsString( '<!DOCTYPE html>', $htmlresult );
+ $this->assertStringContainsString( '<html', $htmlresult );
+ $this->assertStringContainsString( self::HTML, $htmlresult );
+ }
+
+ public function testHtmlIsCached() {
+ $page = $this->getExistingTestPage( 'HtmlHelperTestPage/with/slashes' );
+
+ $cache = new HashBagOStuff();
+ $parsoid = $this->createNoOpMock( Parsoid::class, [ 'wikitext2html' ] );
+ $parsoid->expects( $this->once() )
+ ->method( 'wikitext2html' )
+ ->willReturn( new PageBundle( 'mocked HTML', null, null, '1.0' ) );
+
+ $helper = $this->newHelper( $cache, $parsoid );
+
+ $helper->init( $page->getTitle() );
+ $htmlresult = $helper->getHtml()->getRawText();
+ $this->assertStringContainsString( 'mocked HTML', $htmlresult );
+
+ // check that we can run the test again and ensure that the parse is only run once
+ $helper = $this->newHelper( $cache, $parsoid );
+ $helper->init( $page->getTitle() );
+ $htmlresult = $helper->getHtml()->getRawText();
+ $this->assertStringContainsString( 'mocked HTML', $htmlresult );
+ }
+
+ public function testEtagLastModified() {
+ $time = time();
+ MWTimestamp::setFakeTime( $time );
+
+ $page = $this->getExistingTestPage( 'HtmlHelperTestPage/with/slashes' );
+
+ $cache = new HashBagOStuff();
+
+ // First, test it works if nothing was cached yet.
+ // Make some time pass since page was created:
+ MWTimestamp::setFakeTime( $time + 10 );
+ $helper = $this->newHelper( $cache );
+ $helper->init( $page->getTitle() );
+ $etag = $helper->getETag(); // remember etag using LastModified
+ $helper->getHtml(); // put HTML into the cache
+
+ // Now, test that headers work when getting from cache too.
+ $helper = $this->newHelper( $cache );
+ $helper->init( $page->getTitle() );
+
+ $this->assertNotSame( $etag, $helper->getETag() );
+ $etag = $helper->getETag();
+ $this->assertSame(
+ MWTimestamp::convert( TS_RFC2822, $time + 10 ),
+ MWTimestamp::convert( TS_RFC2822, $helper->getLastModified() )
+ );
+
+ // Now, expire the cache
+ $time += 1000;
+ MWTimestamp::setFakeTime( $time );
+ $this->assertTrue(
+ $page->getTitle()->invalidateCache( MWTimestamp::convert( TS_MW, $time ) ),
+ 'Sanity: can invalidate cache'
+ );
+ DeferredUpdates::doUpdates();
+
+ $helper = $this->newHelper( $cache );
+ $helper->init( $page->getTitle() );
+
+ $this->assertNotSame( $etag, $helper->getETag() );
+ $this->assertSame(
+ MWTimestamp::convert( TS_RFC2822, $time ),
+ MWTimestamp::convert( TS_RFC2822, $helper->getLastModified() )
+ );
+ }
+
+ 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
+ * @param Exception $parsoidException
+ * @param Exception $expectedException
+ */
+ public function testHandlesParsoidError(
+ Exception $parsoidException,
+ Exception $expectedException
+ ) {
+ $page = $this->getExistingTestPage( 'HtmlHelperTestPage/with/slashes' );
+
+ $parsoid = $this->createNoOpMock( Parsoid::class, [ 'wikitext2html' ] );
+ $parsoid->expects( $this->once() )
+ ->method( 'wikitext2html' )
+ ->willThrowException( $parsoidException );
+
+ $helper = $this->newHelper( null, $parsoid );
+ $helper->init( $page->getTitle() );
+
+ $this->expectExceptionObject( $expectedException );
+ $helper->getHtml();
+ }
+
+}