aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php')
-rw-r--r--tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php345
1 files changed, 345 insertions, 0 deletions
diff --git a/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php b/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php
new file mode 100644
index 000000000000..10e5ccdc8905
--- /dev/null
+++ b/tests/phpunit/integration/includes/Rest/Handler/PageHTMLHandlerTest.php
@@ -0,0 +1,345 @@
+<?php
+
+namespace MediaWiki\Tests\Rest\Handler;
+
+use BagOStuff;
+use DeferredUpdates;
+use EmptyBagOStuff;
+use Exception;
+use ExtensionRegistry;
+use HashBagOStuff;
+use HashConfig;
+use MediaWiki\Json\JsonUnserializer;
+use MediaWiki\Parser\ParserCacheFactory;
+use MediaWiki\Rest\Handler\PageHTMLHandler;
+use MediaWiki\Rest\LocalizedHttpException;
+use MediaWiki\Rest\RequestData;
+use MediaWikiIntegrationTestCase;
+use MWTimestamp;
+use NullStatsdDataFactory;
+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;
+use WikiPage;
+
+/**
+ * @covers \MediaWiki\Rest\Handler\PageHTMLHandler
+ * @group Database
+ */
+class PageHTMLHandlerTest extends MediaWikiIntegrationTestCase {
+ use HandlerTestTrait;
+
+ private const WIKITEXT = 'Hello \'\'\'World\'\'\'';
+
+ private const HTML = '<p>Hello <b>World</b></p>';
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ // Clean up these tables after each test
+ $this->tablesUsed = [
+ 'page',
+ 'revision',
+ 'comment',
+ 'text',
+ 'content'
+ ];
+ }
+
+ /**
+ * Checks whether Parsoid extension is installed and skips the test if it's not.
+ */
+ private function checkParsoidInstalled() {
+ if ( !ExtensionRegistry::getInstance()->isLoaded( 'Parsoid' ) ) {
+ $this->markTestSkipped( 'Skip test, since parsoid is not configured' );
+ }
+ }
+
+ /**
+ * @param BagOStuff|null $cache
+ * @param Parsoid|MockObject|null $parsoid
+ * @return PageHTMLHandler
+ * @throws Exception
+ */
+ private function newHandler( BagOStuff $cache = null, Parsoid $parsoid = null ): PageHTMLHandler {
+ $handler = new PageHTMLHandler(
+ new HashConfig( [
+ 'RightsUrl' => 'https://example.com/rights',
+ 'RightsText' => 'some rights',
+ ] ),
+ $this->getServiceContainer()->getPermissionManager(),
+ $this->getServiceContainer()->getRevisionLookup(),
+ $this->getServiceContainer()->getTitleFormatter(),
+ $this->getServiceContainer()->getTitleFactory(),
+ new ParserCacheFactory(
+ $cache ?: new EmptyBagOStuff(),
+ "1234567",
+ $this->createHookContainer(),
+ new JsonUnserializer(),
+ new NullStatsdDataFactory(),
+ new NullLogger()
+ ),
+ $this->getServiceContainer()->getWikiPageFactory()
+ );
+
+ if ( $parsoid !== null ) {
+ $wrapper = TestingAccessWrapper::newFromObject( $handler );
+ $wrapper->parsoid = $parsoid;
+ }
+
+ return $handler;
+ }
+
+ public function testExecuteBare() {
+ $page = $this->getExistingTestPage( 'HtmlEndpointTestPage/with/slashes' );
+ $request = new RequestData(
+ [ 'pathParams' => [ 'title' => $page->getTitle()->getPrefixedText() ] ]
+ );
+
+ $htmlUrl = 'https://wiki.example.com/rest/v1/page/HtmlEndpointTestPage%2Fwith%2Fslashes/html';
+
+ $handler = $this->newHandler();
+ $config = [ 'format' => 'bare' ];
+ $data = $this->executeHandlerAndGetBodyData( $handler, $request, $config );
+
+ $this->assertResponseData( $page, $data );
+ $this->assertSame( $htmlUrl, $data['html_url'] );
+ }
+
+ public function testExecuteWithHtml() {
+ $this->checkParsoidInstalled();
+ $page = $this->getExistingTestPage( 'HtmlEndpointTestPage/with/slashes' );
+ $this->assertTrue(
+ $this->editPage( $page, self::WIKITEXT )->isGood(),
+ 'Sanity: edited a page'
+ );
+
+ $request = new RequestData(
+ [ 'pathParams' => [ 'title' => $page->getTitle()->getPrefixedText() ] ]
+ );
+
+ $handler = $this->newHandler();
+ $data = $this->executeHandlerAndGetBodyData( $handler, $request, [
+ 'format' => 'with_html'
+ ] );
+
+ $this->assertResponseData( $page, $data );
+ $this->assertStringContainsString( '<!DOCTYPE html>', $data['html'] );
+ $this->assertStringContainsString( '<html', $data['html'] );
+ $this->assertStringContainsString( self::HTML, $data['html'] );
+ }
+
+ public function testExecuteHtmlOnly() {
+ $this->checkParsoidInstalled();
+ $page = $this->getExistingTestPage( 'HtmlEndpointTestPage/with/slashes' );
+ $this->assertTrue(
+ $this->editPage( $page, self::WIKITEXT )->isGood(),
+ 'Sanity: edited a page'
+ );
+
+ $request = new RequestData(
+ [ 'pathParams' => [ 'title' => $page->getTitle()->getPrefixedText() ] ]
+ );
+
+ $handler = $this->newHandler();
+ $response = $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+
+ $htmlResponse = (string)$response->getBody();
+ $this->assertStringContainsString( '<!DOCTYPE html>', $htmlResponse );
+ $this->assertStringContainsString( '<html', $htmlResponse );
+ $this->assertStringContainsString( self::HTML, $htmlResponse );
+ }
+
+ public function testHtmlIsCached() {
+ $this->checkParsoidInstalled();
+
+ $page = $this->getExistingTestPage( 'HtmlEndpointTestPage/with/slashes' );
+ $request = new RequestData(
+ [ 'pathParams' => [ 'title' => $page->getTitle()->getPrefixedText() ] ]
+ );
+
+ $cache = new HashBagOStuff();
+ $parsoid = $this->createNoOpMock( Parsoid::class, [ 'wikitext2html' ] );
+ $parsoid->expects( $this->once() )
+ ->method( 'wikitext2html' )
+ ->willReturn( new PageBundle( 'mocked HTML', null, null, '1.0' ) );
+
+ $handler = $this->newHandler( $cache, $parsoid );
+ $response = $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+ $htmlResponse = (string)$response->getBody();
+ $this->assertStringContainsString( 'mocked HTML', $htmlResponse );
+
+ // check that we can run the test again and ensure that the parse is only run once
+ $handler = $this->newHandler( $cache, $parsoid );
+ $response = $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+ $htmlResponse = (string)$response->getBody();
+ $this->assertStringContainsString( 'mocked HTML', $htmlResponse );
+ }
+
+ public function testEtagLastModified() {
+ $this->checkParsoidInstalled();
+
+ $time = time();
+ MWTimestamp::setFakeTime( $time );
+
+ $page = $this->getExistingTestPage( 'HtmlEndpointTestPage/with/slashes' );
+ $request = new RequestData(
+ [ 'pathParams' => [ 'title' => $page->getTitle()->getPrefixedText() ] ]
+ );
+
+ $cache = new HashBagOStuff();
+
+ // First, test it works if nothing was cached yet.
+ // Make some time pass since page was created:
+ MWTimestamp::setFakeTime( $time + 10 );
+ $handler = $this->newHandler( $cache );
+ $response = $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+ $this->assertArrayHasKey( 'ETag', $response->getHeaders() );
+ $etag = $response->getHeaderLine( 'ETag' );
+ $this->assertArrayHasKey( 'Last-Modified', $response->getHeaders() );
+ $this->assertSame( MWTimestamp::convert( TS_RFC2822, $time ),
+ $response->getHeaderLine( 'Last-Modified' ) );
+
+ // Now, test that headers work when getting from cache too.
+ $handler = $this->newHandler( $cache );
+ $response = $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+ $this->assertArrayHasKey( 'ETag', $response->getHeaders() );
+ $this->assertNotSame( $etag, $response->getHeaderLine( 'ETag' ) );
+ $etag = $response->getHeaderLine( 'ETag' );
+ $this->assertArrayHasKey( 'Last-Modified', $response->getHeaders() );
+ $this->assertSame( MWTimestamp::convert( TS_RFC2822, $time + 10 ),
+ $response->getHeaderLine( 'Last-Modified' ) );
+
+ // 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();
+
+ $handler = $this->newHandler( $cache );
+ $response = $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+ $this->assertArrayHasKey( 'ETag', $response->getHeaders() );
+ $this->assertNotSame( $etag, $response->getHeaderLine( 'ETag' ) );
+ $this->assertArrayHasKey( 'Last-Modified', $response->getHeaders() );
+ $this->assertSame( MWTimestamp::convert( TS_RFC2822, $time ),
+ $response->getHeaderLine( 'Last-Modified' ) );
+ }
+
+ 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
+ ) {
+ $this->checkParsoidInstalled();
+
+ $page = $this->getExistingTestPage( 'HtmlEndpointTestPage/with/slashes' );
+ $request = new RequestData(
+ [ 'pathParams' => [ 'title' => $page->getTitle()->getPrefixedText() ] ]
+ );
+
+ $parsoid = $this->createNoOpMock( Parsoid::class, [ 'wikitext2html' ] );
+ $parsoid->expects( $this->once() )
+ ->method( 'wikitext2html' )
+ ->willThrowException( $parsoidException );
+
+ $handler = $this->newHandler( null, $parsoid );
+ $this->expectExceptionObject( $expectedException );
+ $this->executeHandler( $handler, $request, [
+ 'format' => 'html'
+ ] );
+ }
+
+ public function testExecute_missingparam() {
+ $request = new RequestData();
+
+ $this->expectExceptionObject(
+ new LocalizedHttpException(
+ new MessageValue( "paramvalidator-missingparam", [ 'title' ] ),
+ 400
+ )
+ );
+
+ $handler = $this->newHandler();
+ $this->executeHandler( $handler, $request );
+ }
+
+ public function testExecute_error() {
+ $request = new RequestData( [ 'pathParams' => [ 'title' => 'DoesNotExist8237456assda1234' ] ] );
+
+ $this->expectExceptionObject(
+ new LocalizedHttpException(
+ new MessageValue( "rest-nonexistent-title", [ 'testing' ] ),
+ 404
+ )
+ );
+
+ $handler = $this->newHandler();
+ $this->executeHandler( $handler, $request );
+ }
+
+ /**
+ * @param WikiPage $page
+ * @param array $data
+ */
+ private function assertResponseData( WikiPage $page, array $data ): void {
+ $this->assertSame( $page->getId(), $data['id'] );
+ $this->assertSame( $page->getTitle()->getPrefixedDBkey(), $data['key'] );
+ $this->assertSame( $page->getTitle()->getPrefixedText(), $data['title'] );
+ $this->assertSame( $page->getLatest(), $data['latest']['id'] );
+ $this->assertSame(
+ wfTimestampOrNull( TS_ISO_8601, $page->getTimestamp() ),
+ $data['latest']['timestamp']
+ );
+ $this->assertSame( CONTENT_MODEL_WIKITEXT, $data['content_model'] );
+ $this->assertSame( 'https://example.com/rights', $data['license']['url'] );
+ $this->assertSame( 'some rights', $data['license']['title'] );
+ }
+
+}