public function execute() {
$this->markDeprecated();
- // Some versions of PHP have a broken wddx_serialize_value, see
- // PHP bug 45314. Test encoding an affected character (U+00A0)
- // to avoid this.
- $expected =
- "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
- if ( function_exists( 'wddx_serialize_value' )
- && !$this->getIsHtml()
- && wddx_serialize_value( "\xc2\xa0" ) == $expected
- ) {
+ if ( !$this->getIsHtml() && !static::useSlowPrinter() ) {
$this->printText( wddx_serialize_value( $this->getResultData() ) );
} else {
// Don't do newlines and indentation if we weren't asked
// for pretty output
$nl = ( $this->getIsHtml() ? "\n" : '' );
- $indstr = ' ';
+ $indstr = ( $this->getIsHtml() ? ' ' : '' );
$this->printText( "<?xml version=\"1.0\"?>$nl" );
$this->printText( "<wddxPacket version=\"1.0\">$nl" );
- $this->printText( "$indstr<header/>$nl" );
+ $this->printText( "$indstr<header />$nl" );
$this->printText( "$indstr<data>$nl" );
$this->slowWddxPrinter( $this->getResultData(), 4 );
$this->printText( "$indstr</data>$nl" );
}
}
+ public static function useSlowPrinter() {
+ if ( !function_exists( 'wddx_serialize_value' ) ) {
+ return true;
+ }
+
+ // Some versions of PHP have a broken wddx_serialize_value, see
+ // PHP bug 45314. Test encoding an affected character (U+00A0)
+ // to avoid this.
+ $expected =
+ "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>";
+ if ( wddx_serialize_value( "\xc2\xa0" ) !== $expected ) {
+ return true;
+ }
+
+ // Some versions of HHVM don't correctly encode ampersands.
+ $expected =
+ "<wddxPacket version='1.0'><header/><data><string>&</string></data></wddxPacket>";
+ if ( wddx_serialize_value( '&' ) !== $expected ) {
+ return true;
+ }
+
+ // Some versions of HHVM don't correctly encode empty arrays as subvalues.
+ $expected =
+ "<wddxPacket version='1.0'><header/><data><array length='1'><array length='0'></array></array></data></wddxPacket>";
+ if ( wddx_serialize_value( array( array() ) ) !== $expected ) {
+ return true;
+ }
+
+ // Some versions of HHVM don't correctly encode associative arrays with numeric keys.
+ $expected =
+ "<wddxPacket version='1.0'><header/><data><struct><var name='2'><number>1</number></var></struct></data></wddxPacket>";
+ if ( wddx_serialize_value( array( 2 => 1 ) ) !== $expected ) {
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Recursively go through the object and output its data in WDDX format.
* @param mixed $elemValue
$this->printText( $indstr . Xml::element( 'boolean',
array( 'value' => $elemValue ? 'true' : 'false' ) ) . $nl
);
+ } elseif ( $elemValue === null ) {
+ $this->printText( $indstr . Xml::element( 'null', array() ) . $nl );
} else {
ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) );
}
--- /dev/null
+<?php
+
+/**
+ * @group API
+ * @covers ApiFormatDbg
+ */
+class ApiFormatDbgTest extends ApiFormatTestBase {
+
+ protected $printerName = 'dbg';
+
+ public static function provideGeneralEncoding() {
+ $warning = "\n 'warnings' => \n array (\n 'dbg' => \n array (\n" .
+ " '*' => 'format=dbg has been deprecated. Please use format=json instead.',\n" .
+ " ),\n ),";
+
+ return array(
+ // Basic types
+ array( array( null ), "array ({$warning}\n 0 => NULL,\n)" ),
+ array( array( true ), "array ({$warning}\n 0 => true,\n)" ),
+ array( array( false ), "array ({$warning}\n 0 => false,\n)" ),
+ array( array( 42 ), "array ({$warning}\n 0 => 42,\n)" ),
+ array( array( 42.5 ), "array ({$warning}\n 0 => 42.5,\n)" ),
+ array( array( 1e42 ), "array ({$warning}\n 0 => 1.0E+42,\n)" ),
+ array( array( 'foo' ), "array ({$warning}\n 0 => 'foo',\n)" ),
+ array( array( 'fóo' ), "array ({$warning}\n 0 => 'fóo',\n)" ),
+
+ // Arrays and objects
+ array( array( array() ), "array ({$warning}\n 0 => \n array (\n ),\n)" ),
+ array( array( array( 1 ) ), "array ({$warning}\n 0 => \n array (\n 0 => 1,\n ),\n)" ),
+ array( array( array( 'x' => 1 ) ), "array ({$warning}\n 0 => \n array (\n 'x' => 1,\n ),\n)" ),
+ array( array( array( 2 => 1 ) ), "array ({$warning}\n 0 => \n array (\n 2 => 1,\n ),\n)" ),
+
+ // Content
+ array( array( '*' => 'foo' ), "array ({$warning}\n '*' => 'foo',\n)" ),
+ );
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @group API
+ * @covers ApiFormatDump
+ */
+class ApiFormatDumpTest extends ApiFormatTestBase {
+
+ protected $printerName = 'dump';
+
+ public static function provideGeneralEncoding() {
+ // Sigh. Docs claim it's a boolean, but can have values 0, 1, or 2.
+ // Fortunately wfIniGetBool does the right thing.
+ if ( wfIniGetBool( 'xdebug.overload_var_dump' ) ) {
+ return array(
+ array( array(), 'Cannot test ApiFormatDump when xDebug overloads var_dump', array( 'SKIP' => true ) ),
+ );
+ }
+
+ $warning = "\n [\"warnings\"]=>\n array(1) {\n [\"dump\"]=>\n array(1) {\n [\"*\"]=>\n" .
+ " string(64) \"format=dump has been deprecated. Please use format=json instead.\"\n" .
+ " }\n }";
+
+ return array(
+ // Basic types
+ array( array( null ), "array(2) {{$warning}\n [0]=>\n NULL\n}\n" ),
+ array( array( true ), "array(2) {{$warning}\n [0]=>\n bool(true)\n}\n" ),
+ array( array( false ), "array(2) {{$warning}\n [0]=>\n bool(false)\n}\n" ),
+ array( array( 42 ), "array(2) {{$warning}\n [0]=>\n int(42)\n}\n" ),
+ array( array( 42.5 ), "array(2) {{$warning}\n [0]=>\n float(42.5)\n}\n" ),
+ array( array( 1e42 ), "array(2) {{$warning}\n [0]=>\n float(1.0E+42)\n}\n" ),
+ array( array( 'foo' ), "array(2) {{$warning}\n [0]=>\n string(3) \"foo\"\n}\n" ),
+ array( array( 'fóo' ), "array(2) {{$warning}\n [0]=>\n string(4) \"fóo\"\n}\n" ),
+
+ // Arrays
+ array( array( array() ), "array(2) {{$warning}\n [0]=>\n array(0) {\n }\n}\n" ),
+ array( array( array( 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [0]=>\n int(1)\n }\n}\n" ),
+ array( array( array( 'x' => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [\"x\"]=>\n int(1)\n }\n}\n" ),
+ array( array( array( 2 => 1 ) ), "array(2) {{$warning}\n [0]=>\n array(1) {\n [2]=>\n int(1)\n }\n}\n" ),
+
+ // Content
+ array( array( '*' => 'foo' ), "array(2) {{$warning}\n [\"*\"]=>\n string(3) \"foo\"\n}\n" ),
+ );
+ }
+
+}
/**
* @group API
- * @group Database
- * @group medium
* @covers ApiFormatJson
*/
class ApiFormatJsonTest extends ApiFormatTestBase {
- public function testValidSyntax( ) {
- $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo' ) );
+ protected $printerName = 'json';
- $this->assertInternalType( 'array', json_decode( $data, true ) );
- $this->assertGreaterThan( 0, count( (array)$data ) );
- }
+ public static function provideGeneralEncoding() {
+ return array(
+ // Basic types
+ array( array( null ), '[null]' ),
+ array( array( true ), '[true]' ),
+ array( array( false ), '[false]' ),
+ array( array( 42 ), '[42]' ),
+ array( array( 42.5 ), '[42.5]' ),
+ array( array( 1e42 ), '[1.0e+42]' ),
+ array( array( 'foo' ), '["foo"]' ),
+ array( array( 'fóo' ), '["f\u00f3o"]' ),
+ array( array( 'fóo' ), '["fóo"]', array( 'utf8' => 1 ) ),
+
+ // Arrays and objects
+ array( array( array() ), '[[]]' ),
+ array( array( array( 1 ) ), '[[1]]' ),
+ array( array( array( 'x' => 1 ) ), '[{"x":1}]' ),
+ array( array( array( 2 => 1 ) ), '[{"2":1}]' ),
+ array( array( (object)array() ), '[{}]' ),
+
+ // Content
+ array( array( '*' => 'foo' ), '{"*":"foo"}' ),
- public function testJsonpInjection( ) {
- $data = $this->apiRequest( 'json', array( 'action' => 'query', 'meta' => 'siteinfo', 'callback' => 'myCallback' ) );
- $this->assertEquals( '/**/myCallback(', substr( $data, 0, 15 ) );
+ // Callbacks
+ array( array( 1 ), '/**/myCallback([1])', array( 'callback' => 'myCallback' ) ),
+
+ // Cross-domain mangling
+ array( array( '< Cross-Domain-Policy >' ), '["\u003C Cross-Domain-Policy \u003E"]' ),
+ );
}
+
}
/**
* @group API
- * @group Database
- * @group medium
* @covers ApiFormatNone
*/
class ApiFormatNoneTest extends ApiFormatTestBase {
- public function testValidSyntax( ) {
- $data = $this->apiRequest( 'none', array( 'action' => 'query', 'meta' => 'siteinfo' ) );
+ protected $printerName = 'none';
- $this->assertEquals( '', $data ); // No output!
+ public static function provideGeneralEncoding() {
+ return array(
+ // Basic types
+ array( array( null ), '' ),
+ array( array( true ), '' ),
+ array( array( false ), '' ),
+ array( array( 42 ), '' ),
+ array( array( 42.5 ), '' ),
+ array( array( 1e42 ), '' ),
+ array( array( 'foo' ), '' ),
+ array( array( 'fóo' ), '' ),
+
+ // Arrays and objects
+ array( array( array() ), '' ),
+ array( array( array( 1 ) ), '' ),
+ array( array( array( 'x' => 1 ) ), '' ),
+ array( array( array( 2 => 1 ) ), '' ),
+
+ // Content
+ array( array( '*' => 'foo' ), '' ),
+ );
}
+
}
/**
* @group API
- * @group Database
- * @group medium
* @covers ApiFormatPhp
*/
class ApiFormatPhpTest extends ApiFormatTestBase {
- public function testValidSyntax( ) {
- $data = $this->apiRequest( 'php', array( 'action' => 'query', 'meta' => 'siteinfo' ) );
+ protected $printerName = 'php';
- $this->assertInternalType( 'array', unserialize( $data ) );
- $this->assertGreaterThan( 0, count( (array)$data ) );
+ public static function provideGeneralEncoding() {
+ return array(
+ // Basic types
+ array( array( null ), 'a:1:{i:0;N;}' ),
+ array( array( true ), 'a:1:{i:0;b:1;}' ),
+ array( array( false ), 'a:1:{i:0;b:0;}' ),
+ array( array( 42 ), 'a:1:{i:0;i:42;}' ),
+ array( array( 42.5 ), 'a:1:{i:0;d:42.5;}' ),
+ array( array( 1e42 ), 'a:1:{i:0;d:1.0E+42;}' ),
+ array( array( 'foo' ), 'a:1:{i:0;s:3:"foo";}' ),
+ array( array( 'fóo' ), 'a:1:{i:0;s:4:"fóo";}' ),
+
+ // Arrays and objects
+ array( array( array() ), 'a:1:{i:0;a:0:{}}' ),
+ array( array( array( 1 ) ), 'a:1:{i:0;a:1:{i:0;i:1;}}' ),
+ array( array( array( 'x' => 1 ) ), 'a:1:{i:0;a:1:{s:1:"x";i:1;}}' ),
+ array( array( array( 2 => 1 ) ), 'a:1:{i:0;a:1:{i:2;i:1;}}' ),
+
+ // Content
+ array( array( '*' => 'foo' ), 'a:1:{s:1:"*";s:3:"foo";}' ),
+ );
+ }
+
+ public function testCrossDomainMangling() {
+ $config = new HashConfig( array( 'MangleFlashPolicy' => false ) );
+ $context = new RequestContext;
+ $context->setConfig( new MultiConfig( array(
+ $config,
+ $context->getConfig(),
+ ) ) );
+ $main = new ApiMain( $context );
+ $main->getResult()->addValue( null, null, '< Cross-Domain-Policy >' );
+
+ if ( !function_exists( 'wfOutputHandler' ) ) {
+ function wfOutputHandler( $s ) {
+ return $s;
+ }
+ }
+
+ $printer = $main->createPrinterByName( 'php' );
+ ob_start( 'wfOutputHandler' );
+ $printer->initPrinter();
+ $printer->execute();
+ $printer->closePrinter();
+ $ret = ob_get_clean();
+ $this->assertSame( 'a:1:{i:0;s:23:"< Cross-Domain-Policy >";}', $ret );
+
+ $config->set( 'MangleFlashPolicy', true );
+ $printer = $main->createPrinterByName( 'php' );
+ ob_start( 'wfOutputHandler' );
+ try {
+ $printer->initPrinter();
+ $printer->execute();
+ $printer->closePrinter();
+ ob_end_clean();
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( UsageException $ex ) {
+ ob_end_clean();
+ $this->assertSame(
+ 'This response cannot be represented using format=php. See https://bugzilla.wikimedia.org/show_bug.cgi?id=66776',
+ $ex->getMessage(),
+ 'Expected exception'
+ );
+ }
}
+
}
<?php
-abstract class ApiFormatTestBase extends ApiTestCase {
+abstract class ApiFormatTestBase extends MediaWikiTestCase {
/**
- * @param string $format
- * @param array $params
- * @param array $data
- *
- * @return string
+ * Name of the formatter being tested
+ * @var string
*/
- protected function apiRequest( $format, $params, $data = null ) {
- $data = parent::doApiRequest( $params, $data, true );
+ protected $printerName;
- /** @var ApiMain $module */
- $module = $data[3];
+ /**
+ * Return general data to be encoded for testing
+ * @return array See self::testGeneralEncoding
+ */
+ public static function provideGeneralEncoding() {
+ throw new Exception( 'Subclass must implement ' . __METHOD__ );
+ }
- $printer = $module->createPrinterByName( $format );
+ /**
+ * 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
+ */
+ protected function encodeData( array $params, array $data, $class = null ) {
+ $context = new RequestContext;
+ $context->setRequest( new FauxRequest( $params, true ) );
+ $main = new ApiMain( $context );
+ if ( $class !== null ) {
+ $main->getModuleManager()->addModule( $this->printerName, 'format', $class );
+ }
+ $result = $main->getResult();
+ foreach ( $data as $k => $v ) {
+ $result->addValue( null, $k, $v );
+ }
- ob_start();
- $printer->initPrinter( false );
+ $printer = $main->createPrinterByName( $this->printerName );
+ $printer->initPrinter();
$printer->execute();
- $printer->closePrinter();
- $out = ob_get_clean();
+ ob_start();
+ try {
+ $printer->closePrinter();
+ return ob_get_clean();
+ } catch ( Exception $ex ) {
+ ob_end_clean();
+ throw $ex;
+ }
+ }
- return $out;
+ /**
+ * @dataProvider provideGeneralEncoding
+ */
+ public function testGeneralEncoding( array $data, $expect, array $params = array() ) {
+ if ( isset( $params['SKIP'] ) ) {
+ $this->markTestSkipped( $expect );
+ }
+ $this->assertSame( $expect, $this->encodeData( $params, $data ) );
}
}
--- /dev/null
+<?php
+
+/**
+ * @group API
+ * @covers ApiFormatTxt
+ */
+class ApiFormatTxtTest extends ApiFormatTestBase {
+
+ protected $printerName = 'txt';
+
+ public static function provideGeneralEncoding() {
+ $warning = "\n [warnings] => Array\n (\n [txt] => Array\n (\n" .
+ " [*] => format=txt has been deprecated. Please use format=json instead.\n" .
+ " )\n\n )\n";
+
+ return array(
+ // Basic types
+ array( array( null ), "Array\n({$warning}\n [0] => \n)\n" ),
+ array( array( true ), "Array\n({$warning}\n [0] => 1\n)\n" ),
+ array( array( false ), "Array\n({$warning}\n [0] => \n)\n" ),
+ array( array( 42 ), "Array\n({$warning}\n [0] => 42\n)\n" ),
+ array( array( 42.5 ), "Array\n({$warning}\n [0] => 42.5\n)\n" ),
+ array( array( 1e42 ), "Array\n({$warning}\n [0] => 1.0E+42\n)\n" ),
+ array( array( 'foo' ), "Array\n({$warning}\n [0] => foo\n)\n" ),
+ array( array( 'fóo' ), "Array\n({$warning}\n [0] => fóo\n)\n" ),
+
+ // Arrays and objects
+ array( array( array() ), "Array\n({$warning}\n [0] => Array\n (\n )\n\n)\n" ),
+ array( array( array( 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [0] => 1\n )\n\n)\n" ),
+ array( array( array( 'x' => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [x] => 1\n )\n\n)\n" ),
+ array( array( array( 2 => 1 ) ), "Array\n({$warning}\n [0] => Array\n (\n [2] => 1\n )\n\n)\n" ),
+
+ // Content
+ array( array( '*' => 'foo' ), "Array\n({$warning}\n [*] => foo\n)\n" ),
+ );
+ }
+
+}
/**
* @group API
- * @group Database
- * @group medium
* @covers ApiFormatWddx
*/
class ApiFormatWddxTest extends ApiFormatTestBase {
- public function testValidSyntax( ) {
- if ( !function_exists( 'wddx_deserialize' ) ) {
- $this->markTestSkipped( "Function 'wddx_deserialize' not exist, skipping." );
- }
+ protected $printerName = 'wddx';
- if ( wfIsHHVM() && false === strpos( wddx_serialize_value( "Test for &" ), '&' ) ) {
- # Some version of HHVM fails to escape the ampersand
- #
- # https://phabricator.wikimedia.org/T75531
- $this->markTestSkipped( "wddx_deserialize is bugged under this version of HHVM" );
+ public static function provideGeneralEncoding() {
+ if ( ApiFormatWddx::useSlowPrinter() ) {
+ return array(
+ array( array(), 'Fast Wddx printer is unavailable', array( 'SKIP' => true ) )
+ );
}
+ return self::provideEncoding();
+ }
+
+ public static function provideEncoding() {
+ $p = '<wddxPacket version=\'1.0\'><header/><data><struct><var name=\'warnings\'><struct><var name=\'wddx\'><struct><var name=\'*\'><string>format=wddx has been deprecated. Please use format=json instead.</string></var></struct></var></struct></var>';
+ $s = '</struct></data></wddxPacket>';
+
+ return array(
+ // Basic types
+ array( array( null ), "{$p}<var name='0'><null/></var>{$s}" ),
+ array( array( true ), "{$p}<var name='0'><boolean value='true'/></var>{$s}" ),
+ array( array( false ), "{$p}<var name='0'><boolean value='false'/></var>{$s}" ),
+ array( array( 42 ), "{$p}<var name='0'><number>42</number></var>{$s}" ),
+ array( array( 42.5 ), "{$p}<var name='0'><number>42.5</number></var>{$s}" ),
+ array( array( 1e42 ), "{$p}<var name='0'><number>1.0E+42</number></var>{$s}" ),
+ array( array( 'foo' ), "{$p}<var name='0'><string>foo</string></var>{$s}" ),
+ array( array( 'fóo' ), "{$p}<var name='0'><string>fóo</string></var>{$s}" ),
+
+ // Arrays and objects
+ array( array( array() ), "{$p}<var name='0'><array length='0'></array></var>{$s}" ),
+ array( array( array( 1 ) ), "{$p}<var name='0'><array length='1'><number>1</number></array></var>{$s}" ),
+ array( array( array( 'x' => 1 ) ), "{$p}<var name='0'><struct><var name='x'><number>1</number></var></struct></var>{$s}" ),
+ array( array( array( 2 => 1 ) ), "{$p}<var name='0'><struct><var name='2'><number>1</number></var></struct></var>{$s}" ),
- $data = $this->apiRequest( 'wddx', array( 'action' => 'query', 'meta' => 'siteinfo' ) );
+ // Content
+ array( array( '*' => 'foo' ), "{$p}<var name='*'><string>foo</string></var>{$s}" ),
+ );
+ }
+
+ /**
+ * @dataProvider provideEncoding
+ */
+ public function testSlowEncoding( array $data, $expect, array $params = array() ) {
+ // Adjust expectation for differences between fast and slow printers.
+ $expect = str_replace( '\'', '"', $expect );
+ $expect = str_replace( '/>', ' />', $expect );
+ $expect = '<?xml version="1.0"?>' . $expect;
+
+ $this->assertSame( $expect, $this->encodeData( $params, $data, 'ApiFormatWddxTest_SlowWddx' ) );
+ }
+}
- $this->assertInternalType( 'array', wddx_deserialize( $data ) );
- $this->assertGreaterThan( 0, count( (array)$data ) );
+class ApiFormatWddxTest_SlowWddx extends ApiFormatWddx {
+ public static function useSlowPrinter() {
+ return true;
}
}
--- /dev/null
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @covers ApiFormatXml
+ */
+class ApiFormatXmlTest extends ApiFormatTestBase {
+
+ protected $printerName = 'xml';
+
+ protected function setUp() {
+ parent::setUp();
+ $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest.xsl' ) );
+ $page->doEditContent( new WikitextContent(
+ '<?xml version="1.0"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />'
+ ), 'Summary' );
+ $page = WikiPage::factory( Title::newFromText( 'MediaWiki:ApiFormatXmlTest' ) );
+ $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' );
+ $page = WikiPage::factory( Title::newFromText( 'ApiFormatXmlTest' ) );
+ $page->doEditContent( new WikitextContent( 'Bogus' ), 'Summary' );
+ }
+
+ public static function provideGeneralEncoding() {
+ $tests = array(
+ // Basic types
+ array( array( null ), '<?xml version="1.0"?><api><x /></api>' ),
+ array( array( true, 'a' => true ), '<?xml version="1.0"?><api a=""><x>1</x></api>' ),
+ array( array( false, 'a' => false ), '<?xml version="1.0"?><api><x></x></api>' ),
+ array( array( 42, 'a' => 42 ), '<?xml version="1.0"?><api a="42"><x>42</x></api>' ),
+ array( array( 42.5, 'a' => 42.5 ), '<?xml version="1.0"?><api a="42.5"><x>42.5</x></api>' ),
+ array( array( 1e42, 'a' => 1e42 ), '<?xml version="1.0"?><api a="1.0E+42"><x>1.0E+42</x></api>' ),
+ array( array( 'foo', 'a' => 'foo' ), '<?xml version="1.0"?><api a="foo"><x>foo</x></api>' ),
+ array( array( 'fóo', 'a' => 'fóo' ), '<?xml version="1.0"?><api a="fóo"><x>fóo</x></api>' ),
+
+ // Arrays and objects
+ array( array( array() ), '<?xml version="1.0"?><api><x /></api>' ),
+ array( array( array( 'x' => 1 ) ), '<?xml version="1.0"?><api><x x="1" /></api>' ),
+ array( array( array( 2 => 1, '_element' => 'x' ) ), '<?xml version="1.0"?><api><x><x>1</x></x></api>' ),
+
+ // Content
+ array( array( '*' => 'foo' ), '<?xml version="1.0"?><api xml:space="preserve">foo</api>' ),
+
+ // Subelements
+ array( array( 'a' => 1, 's' => 1, '_subelements' => array( 's' ) ),
+ '<?xml version="1.0"?><api a="1"><s xml:space="preserve">1</s></api>' ),
+
+ // includenamespace param
+ array( array( 'x' => 'foo' ), '<?xml version="1.0"?><api x="foo" xmlns="http://www.mediawiki.org/xml/api/" />',
+ array( 'includexmlnamespace' => 1 ) ),
+
+ // xslt param
+ array( array(), '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Invalid or non-existent stylesheet specified</xml></warnings></api>',
+ array( 'xslt' => 'DoesNotExist' ) ),
+ array( array(), '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should be in the MediaWiki namespace.</xml></warnings></api>',
+ array( 'xslt' => 'ApiFormatXmlTest' ) ),
+ array( array(), '<?xml version="1.0"?><api><warnings><xml xml:space="preserve">Stylesheet should have .xsl extension.</xml></warnings></api>',
+ array( 'xslt' => 'MediaWiki:ApiFormatXmlTest' ) ),
+ array( array(),
+ '<?xml version="1.0"?><?xml-stylesheet href="' .
+ htmlspecialchars( Title::newFromText( 'MediaWiki:ApiFormatXmlTest.xsl' )->getLocalURL( 'action=raw' ) ) .
+ '" type="text/xsl" ?><api />',
+ array( 'xslt' => 'MediaWiki:ApiFormatXmlTest.xsl' ) ),
+ );
+
+ // Add in the needed "_element" for all indexed arrays
+ $ret = array();
+ foreach ( $tests as $v ) {
+ $v[0] += array( '_element' => 'x' );
+ $ret[] = $v;
+ }
+ return $ret;
+ }
+
+ /**
+ * @dataProvider provideXmlFail
+ */
+ public function testXmlFail( array $data, $expect, array $params = array() ) {
+ try {
+ echo $this->encodeData( $params, $data ) . "\n";
+ $this->fail( "Expected exception not thrown" );
+ } catch ( MWException $ex ) {
+ $this->assertSame( $expect, $ex->getMessage(), 'Expected exception' );
+ }
+ }
+
+ public static function provideXmlFail() {
+ return array(
+ // Array without _element
+ array( array( 1 ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName().' ),
+ // Content and subelement
+ array( array( 1, 's' => array(), '*' => 2, '_element' => 'x' ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
+ array( array( 1, 's' => 1, '*' => 2, '_element' => 'x', '_subelements' => array( 's' ) ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
+ // These should fail but don't because of a long-standing bug (see T57371#639713)
+ //array( array( 1, '*' => 2, '_element' => 'x' ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
+ //array( array( 's' => array(), '*' => 2 ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
+ //array( array( 's' => 1, '*' => 2, '_subelements' => array( 's' ) ), 'Internal error in ApiFormatXml::recXmlPrint: (api, ...) has content and subelements' ),
+ );
+ }
+
+}