Anomie has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/401770 )

Change subject: Add tests for ApiFormatBase
......................................................................

Add tests for ApiFormatBase

Ensuring proper behavior of the base class lets comprehensive tests of
subclasses be simpler.

This also adjusts ApiFormatTestBase to be a bit more usable, passing an
array of options through to encodeData() instead of just a class name.
And removes the unused 'SKIP' from testGeneralEncoding, but allows
expecting an exception (for use in I63ce42dd).

Change-Id: Ib2a1fa0b04860b09105376881ff8411f9534c453
---
M includes/api/ApiFormatBase.php
A tests/phpunit/includes/api/format/ApiFormatBaseTest.php
M tests/phpunit/includes/api/format/ApiFormatTestBase.php
3 files changed, 382 insertions(+), 13 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core 
refs/changes/70/401770/1

diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 4348fc8..5b72d44 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -308,11 +308,13 @@
                                // T68776: wfMangleFlashPolicy() is needed to 
avoid a nasty bug in
                                // Flash, but what it does isn't friendly for 
the API, so we need to
                                // work around it.
+                               // @codeCoverageIgnoreStart
                                if ( preg_match( 
'/\<\s*cross-domain-policy\s*\>/i', $json ) ) {
                                        $json = preg_replace(
                                                
'/\<(\s*cross-domain-policy\s*)\>/i', '\\u003C$1\\u003E', $json
                                        );
                                }
+                               // @codeCoverageIgnoreEnd
 
                                echo $json;
                        } else {
diff --git a/tests/phpunit/includes/api/format/ApiFormatBaseTest.php 
b/tests/phpunit/includes/api/format/ApiFormatBaseTest.php
new file mode 100644
index 0000000..e85ef27
--- /dev/null
+++ b/tests/phpunit/includes/api/format/ApiFormatBaseTest.php
@@ -0,0 +1,338 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group API
+ * @covers ApiFormatBase
+ */
+class ApiFormatBaseTest extends ApiFormatTestBase {
+
+       protected $printerName = 'mockbase';
+
+       public function getMockFormatter( ApiMain $main = null, $format, 
$methods = [] ) {
+               if ( $main === null ) {
+                       $context = new RequestContext;
+                       $context->setRequest( new FauxRequest( [], true ) );
+                       $main = new ApiMain( $context );
+               }
+
+               $mock = $this->getMockBuilder( ApiFormatBase::class )
+                       ->setConstructorArgs( [ $main, $format ] )
+                       ->setMethods( array_unique( array_merge( $methods, [ 
'getMimeType', 'execute' ] ) ) )
+                       ->getMock();
+               if ( !in_array( 'getMimeType', $methods, true ) ) {
+                       $mock->method( 'getMimeType' )->willReturn( 
'text/x-mock' );
+               }
+               return $mock;
+       }
+
+       protected function encodeData( array $params, array $data, $options = 
[] ) {
+               $options += [
+                       'name' => 'mock',
+                       'class' => ApiFormatBase::class,
+                       'factory' => function ( ApiMain $main, $format ) use ( 
$options ) {
+                               $mock = $this->getMockFormatter( $main, $format 
);
+                               $mock->expects( $this->once() )->method( 
'execute' )
+                                       ->willReturnCallback( function () use ( 
$mock ) {
+                                               $mock->printText( "Format 
{$mock->getFormat()}: " );
+                                               $mock->printText( "<b>ok</b>" );
+                                       } );
+
+                               if ( isset( $options['status'] ) ) {
+                                       $mock->setHttpStatus( 
$options['status'] );
+                               }
+
+                               return $mock;
+                       },
+                       'returnPrinter' => true,
+               ];
+
+               $this->setMwGlobals( [
+                       'wgApiFrameOptions' => 'DENY',
+               ] );
+
+               $ret = parent::encodeData( $params, $data, $options );
+               $printer = TestingAccessWrapper::newFromObject( $ret['printer'] 
);
+               $text = $ret['text'];
+
+               if ( $options['name'] !== 'mockfm' ) {
+                       $ct = 'text/x-mock';
+                       $file = 'api-result.mock';
+                       $status = isset( $options['status'] ) ? 
$options['status'] : null;
+               } elseif ( isset( $params['wrappedhtml'] ) ) {
+                       $ct = 'text/mediawiki-api-prettyprint-wrapped';
+                       $file = 'api-result-wrapped.json';
+                       $status = null;
+
+                       // Replace varying field
+                       $text = preg_replace( '/"time":\d+/', '"time":1234', 
$text );
+               } else {
+                       $ct = 'text/html';
+                       $file = 'api-result.html';
+                       $status = null;
+
+                       // Strip OutputPage-generated HTML
+                       if ( preg_match( '!<pre 
class="api-pretty-content">.*</pre>!s', $text, $m ) ) {
+                               $text = $m[0];
+                       }
+               }
+
+               $response = $printer->getMain()->getRequest()->response();
+               $this->assertSame( "$ct; charset=utf-8", strtolower( 
$response->getHeader( 'Content-Type' ) ) );
+               $this->assertSame( 'DENY', $response->getHeader( 
'X-Frame-Options' ) );
+               $this->assertSame( $file, $printer->getFilename() );
+               $this->assertSame( "inline; filename=\"$file\"", 
$response->getHeader( 'Content-Disposition' ) );
+               $this->assertSame( $status, $response->getStatusCode() );
+
+               return $text;
+       }
+
+       public static function provideGeneralEncoding() {
+               return [
+                       'normal' => [
+                               [],
+                               "Format MOCK: <b>ok</b>",
+                               [],
+                               [ 'name' => 'mock' ]
+                       ],
+                       'normal ignores wrappedhtml' => [
+                               [],
+                               "Format MOCK: <b>ok</b>",
+                               [ 'wrappedhtml' => 1 ],
+                               [ 'name' => 'mock' ]
+                       ],
+                       'HTML format' => [
+                               [],
+                               '<pre class="api-pretty-content">Format MOCK: 
&lt;b>ok&lt;/b></pre>',
+                               [],
+                               [ 'name' => 'mockfm' ]
+                       ],
+                       'wrapped HTML format' => [
+                               [],
+                               // phpcs:ignore Generic.Files.LineLength.TooLong
+                               '{"status":200,"statustext":"OK","html":"<pre 
class=\"api-pretty-content\">Format MOCK: 
&lt;b>ok&lt;/b></pre>","modules":["mediawiki.apipretty"],"continue":null,"time":1234}',
+                               [ 'wrappedhtml' => 1 ],
+                               [ 'name' => 'mockfm' ]
+                       ],
+                       'normal, with set status' => [
+                               [],
+                               "Format MOCK: <b>ok</b>",
+                               [],
+                               [ 'name' => 'mock', 'status' => 400 ]
+                       ],
+                       'HTML format, with set status' => [
+                               [],
+                               '<pre class="api-pretty-content">Format MOCK: 
&lt;b>ok&lt;/b></pre>',
+                               [],
+                               [ 'name' => 'mockfm', 'status' => 400 ]
+                       ],
+                       'wrapped HTML format, with set status' => [
+                               [],
+                               // phpcs:ignore Generic.Files.LineLength.TooLong
+                               '{"status":400,"statustext":"Bad 
Request","html":"<pre class=\"api-pretty-content\">Format MOCK: 
&lt;b>ok&lt;/b></pre>","modules":["mediawiki.apipretty"],"continue":null,"time":1234}',
+                               [ 'wrappedhtml' => 1 ],
+                               [ 'name' => 'mockfm', 'status' => 400 ]
+                       ],
+               ];
+       }
+
+       public function testBasics() {
+               $printer = $this->getMockFormatter( null, 'mock' );
+               $this->assertTrue( $printer->canPrintErrors() );
+               $this->assertSame(
+                       
'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Data_formats',
+                       $printer->getHelpUrls()
+               );
+       }
+
+       public function testDisable() {
+               $this->setMwGlobals( [
+                       'wgApiFrameOptions' => 'DENY',
+               ] );
+
+               $printer = $this->getMockFormatter( null, 'mock' );
+               $printer->method( 'execute' )->willReturnCallback( function () 
use ( $printer ) {
+                       $printer->printText( 'Foo' );
+               } );
+               $this->assertFalse( $printer->isDisabled() );
+               $printer->disable();
+               $this->assertTrue( $printer->isDisabled() );
+
+               $printer->setHttpStatus( 400 );
+               $printer->initPrinter();
+               $printer->execute();
+               ob_start();
+               $printer->closePrinter();
+               $this->assertSame( '', ob_get_clean() );
+               $response = $printer->getMain()->getRequest()->response();
+               $this->assertNull( $response->getHeader( 'Content-Type' ) );
+               $this->assertNull( $response->getHeader( 'X-Frame-Options' ) );
+               $this->assertNull( $response->getHeader( 'Content-Disposition' 
) );
+               $this->assertNull( $response->getStatusCode() );
+       }
+
+       public function testNullMimeType() {
+               $this->setMwGlobals( [
+                       'wgApiFrameOptions' => 'DENY',
+               ] );
+
+               $printer = $this->getMockFormatter( null, 'mock', [ 
'getMimeType' ] );
+               $printer->method( 'execute' )->willReturnCallback( function () 
use ( $printer ) {
+                       $printer->printText( 'Foo' );
+               } );
+               $printer->method( 'getMimeType' )->willReturn( null );
+               $this->assertNull( $printer->getMimeType(), 'sanity check' );
+
+               $printer->initPrinter();
+               $printer->execute();
+               ob_start();
+               $printer->closePrinter();
+               $this->assertSame( 'Foo', ob_get_clean() );
+               $response = $printer->getMain()->getRequest()->response();
+               $this->assertNull( $response->getHeader( 'Content-Type' ) );
+               $this->assertNull( $response->getHeader( 'X-Frame-Options' ) );
+               $this->assertNull( $response->getHeader( 'Content-Disposition' 
) );
+
+               $printer = $this->getMockFormatter( null, 'mockfm', [ 
'getMimeType' ] );
+               $printer->method( 'execute' )->willReturnCallback( function () 
use ( $printer ) {
+                       $printer->printText( 'Foo' );
+               } );
+               $printer->method( 'getMimeType' )->willReturn( null );
+               $this->assertNull( $printer->getMimeType(), 'sanity check' );
+               $this->assertTrue( $printer->getIsHtml(), 'sanity check' );
+
+               $printer->initPrinter();
+               $printer->execute();
+               ob_start();
+               $printer->closePrinter();
+               $this->assertSame( 'Foo', ob_get_clean() );
+               $response = $printer->getMain()->getRequest()->response();
+               $this->assertSame(
+                       'text/html; charset=utf-8', strtolower( 
$response->getHeader( 'Content-Type' ) )
+               );
+               $this->assertSame( 'DENY', $response->getHeader( 
'X-Frame-Options' ) );
+               $this->assertSame(
+                       'inline; filename="api-result.html"', 
$response->getHeader( 'Content-Disposition' )
+               );
+       }
+
+       public function testApiFrameOptions() {
+               $this->setMwGlobals( [ 'wgApiFrameOptions' => 'DENY' ] );
+               $printer = $this->getMockFormatter( null, 'mock' );
+               $printer->initPrinter();
+               $this->assertSame(
+                       'DENY',
+                       
$printer->getMain()->getRequest()->response()->getHeader( 'X-Frame-Options' )
+               );
+
+               $this->setMwGlobals( [ 'wgApiFrameOptions' => 'SAMEORIGIN' ] );
+               $printer = $this->getMockFormatter( null, 'mock' );
+               $printer->initPrinter();
+               $this->assertSame(
+                       'SAMEORIGIN',
+                       
$printer->getMain()->getRequest()->response()->getHeader( 'X-Frame-Options' )
+               );
+
+               $this->setMwGlobals( [ 'wgApiFrameOptions' => false ] );
+               $printer = $this->getMockFormatter( null, 'mock' );
+               $printer->initPrinter();
+               $this->assertNull(
+                       
$printer->getMain()->getRequest()->response()->getHeader( 'X-Frame-Options' )
+               );
+       }
+
+       public function testForceDefaultParams() {
+               $context = new RequestContext;
+               $context->setRequest( new FauxRequest( [ 'foo' => '1', 'bar' => 
'2', 'baz' => '3' ], true ) );
+               $main = new ApiMain( $context );
+               $allowedParams = [
+                       'foo' => [],
+                       'bar' => [ ApiBase::PARAM_DFLT => 'bar?' ],
+                       'baz' => 'baz!',
+               ];
+
+               $printer = $this->getMockFormatter( $main, 'mock', [ 
'getAllowedParams' ] );
+               $printer->method( 'getAllowedParams' )->willReturn( 
$allowedParams );
+               $this->assertEquals(
+                       [ 'foo' => '1', 'bar' => '2', 'baz' => '3' ],
+                       $printer->extractRequestParams(),
+                       'sanity check'
+               );
+
+               $printer = $this->getMockFormatter( $main, 'mock', [ 
'getAllowedParams' ] );
+               $printer->method( 'getAllowedParams' )->willReturn( 
$allowedParams );
+               $printer->forceDefaultParams();
+               $this->assertEquals(
+                       [ 'foo' => null, 'bar' => 'bar?', 'baz' => 'baz!' ],
+                       $printer->extractRequestParams()
+               );
+       }
+
+       public function testGetAllowedParams() {
+               $printer = $this->getMockFormatter( null, 'mock' );
+               $this->assertSame( [], $printer->getAllowedParams() );
+
+               $printer = $this->getMockFormatter( null, 'mockfm' );
+               $this->assertSame( [
+                       'wrappedhtml' => [
+                               ApiBase::PARAM_DFLT => false,
+                               ApiBase::PARAM_HELP_MSG => 
'apihelp-format-param-wrappedhtml',
+                       ]
+               ], $printer->getAllowedParams() );
+       }
+
+       public function testGetExamplesMessages() {
+               $printer = TestingAccessWrapper::newFromObject( 
$this->getMockFormatter( null, 'mock' ) );
+               $this->assertSame( [
+                       
'action=query&meta=siteinfo&siprop=namespaces&format=mock'
+                               => [ 'apihelp-format-example-generic', 'MOCK' ]
+               ], $printer->getExamplesMessages() );
+
+               $printer = TestingAccessWrapper::newFromObject( 
$this->getMockFormatter( null, 'mockfm' ) );
+               $this->assertSame( [
+                       
'action=query&meta=siteinfo&siprop=namespaces&format=mockfm'
+                               => [ 'apihelp-format-example-generic', 'MOCK' ]
+               ], $printer->getExamplesMessages() );
+       }
+
+       /**
+        * @dataProvider provideHtmlHeader
+        */
+       public function testHtmlHeader( $post, $registerNonHtml, $expect ) {
+               $context = new RequestContext;
+               $request = new FauxRequest( [ 'a' => 1, 'b' => 2 ], $post );
+               $request->setRequestURL( 'http://example.org/wx/api.php' );
+               $context->setRequest( $request );
+               $context->setLanguage( 'qqx' );
+               $main = new ApiMain( $context );
+               $printer = $this->getMockFormatter( $main, 'mockfm' );
+               $mm = $printer->getMain()->getModuleManager();
+               $mm->addModule( 'mockfm', 'format', ApiFormatBase::class, 
function () {
+                       return $mock;
+               } );
+               if ( $registerNonHtml ) {
+                       $mm->addModule( 'mock', 'format', ApiFormatBase::class, 
function () {
+                               return $mock;
+                       } );
+               }
+
+               $printer->initPrinter();
+               $printer->execute();
+               ob_start();
+               $printer->closePrinter();
+               $text = ob_get_clean();
+               $this->assertContains( $expect, $text );
+       }
+
+       public static function provideHtmlHeader() {
+               return [
+                       [ false, false, 
'(api-format-prettyprint-header-only-html: MOCK)' ],
+                       [ true, false, 
'(api-format-prettyprint-header-only-html: MOCK)' ],
+                       // phpcs:ignore Generic.Files.LineLength.TooLong
+                       [ false, true, 
'(api-format-prettyprint-header-hyperlinked: MOCK, mock, <a rel="nofollow" 
class="external free" 
href="http://example.org/wx/api.php?a=1&amp;b=2&amp;format=mock";>http://example.org/wx/api.php?a=1&amp;b=2&amp;format=mock</a>)'
 ],
+                       [ true, true, '(api-format-prettyprint-header: MOCK, 
mock)' ],
+               ];
+       }
+
+}
diff --git a/tests/phpunit/includes/api/format/ApiFormatTestBase.php 
b/tests/phpunit/includes/api/format/ApiFormatTestBase.php
index fb086e9..4169dab 100644
--- a/tests/phpunit/includes/api/format/ApiFormatTestBase.php
+++ b/tests/phpunit/includes/api/format/ApiFormatTestBase.php
@@ -11,26 +11,40 @@
        /**
         * Return general data to be encoded for testing
         * @return array See self::testGeneralEncoding
-        * @throws Exception
+        * @throws BadMethodCallException
         */
        public static function provideGeneralEncoding() {
-               throw new Exception( 'Subclass must implement ' . __METHOD__ );
+               throw new BadMethodCallException( static::class . ' must 
implement ' . __METHOD__ );
        }
 
        /**
         * Get the formatter output for the given input data
         * @param array $params Query parameters
         * @param array $data Data to encode
-        * @param string $class Printer class to use instead of the normal one
-        * @return string
+        * @param array $options Options. If passed a string, the string is 
treated
+        *  as the 'class' option.
+        *  - name: Format name, rather than $this->printerName
+        *  - class: If set, register 'name' with this class (and 'factory', if 
that's set)
+        *  - factory: Used with 'class' to register at runtime
+        *  - returnPrinter: Return the printer object
+        * @param callable|null $factory Factory to use instead of the normal 
one
+        * @return string|array The string if $options['returnPrinter'] isn't 
set, or an array if it is:
+        *  - text: Output text string
+        *  - printer: ApiFormatBase
         * @throws Exception
         */
-       protected function encodeData( array $params, array $data, $class = 
null ) {
+       protected function encodeData( array $params, array $data, $options = 
[] ) {
+               if ( is_string( $options ) ) {
+                       $options = [ 'class' => $options ];
+               }
+               $printerName = isset( $options['name'] ) ? $options['name'] : 
$this->printerName;
+
                $context = new RequestContext;
                $context->setRequest( new FauxRequest( $params, true ) );
                $main = new ApiMain( $context );
-               if ( $class !== null ) {
-                       $main->getModuleManager()->addModule( 
$this->printerName, 'format', $class );
+               if ( isset( $options['class'] ) ) {
+                       $factory = isset( $options['factory'] ) ? 
$options['factory'] : null;
+                       $main->getModuleManager()->addModule( $printerName, 
'format', $options['class'], $factory );
                }
                $result = $main->getResult();
                $result->addArrayType( null, 'default' );
@@ -38,27 +52,42 @@
                        $result->addValue( null, $k, $v );
                }
 
-               $printer = $main->createPrinterByName( $this->printerName );
+               $ret = [];
+               $printer = $main->createPrinterByName( $printerName );
                $printer->initPrinter();
                $printer->execute();
                ob_start();
                try {
                        $printer->closePrinter();
-                       return ob_get_clean();
+                       $ret['text'] = ob_get_clean();
                } catch ( Exception $ex ) {
                        ob_end_clean();
                        throw $ex;
                }
+
+               if ( !empty( $options['returnPrinter'] ) ) {
+                       $ret['printer'] = $printer;
+               }
+
+               return count( $ret ) === 1 ? $ret['text'] : $ret;
        }
 
        /**
         * @dataProvider provideGeneralEncoding
+        * @param array $data Data to be encoded
+        * @param string|Exception $expect String to expect, or exception 
expected to be thrown
+        * @param array $params Query parameters to set in the FauxRequest
+        * @param array $options Options to pass to self::encodeData()
         */
-       public function testGeneralEncoding( array $data, $expect, array 
$params = [] ) {
-               if ( isset( $params['SKIP'] ) ) {
-                       $this->markTestSkipped( $expect );
+       public function testGeneralEncoding(
+               array $data, $expect, array $params = [], array $options = []
+       ) {
+               if ( $expect instanceof Exception ) {
+                       $this->setExpectedException( get_class( $expect ), 
$expect->getMessage() );
+                       $this->encodeData( $params, $data, $options ); // 
Should throw
+               } else {
+                       $this->assertSame( $expect, $this->encodeData( $params, 
$data, $options ) );
                }
-               $this->assertSame( $expect, $this->encodeData( $params, $data ) 
);
        }
 
 }

-- 
To view, visit https://gerrit.wikimedia.org/r/401770
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib2a1fa0b04860b09105376881ff8411f9534c453
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Anomie <bjor...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to