wiki.techinc.nl/tests/phpunit/includes/api/ApiBaseTest.php
Gergő Tisza 7c03a6f331
API: Allow use of multivalue parameter documentation for templates
Allow the use of PARAM_HELP_MSG_PER_VALUE for PARAM_TYPE='string'
and PARAM_ISMULTI=true, which is the setting used for templated
parameters (PARAM_TEMPLATE_VARS).

It's common for templated parameters to take the variable parts
of the name from some fixed set, but they are required to use
PARAM_TYPE=string so it makes sense to allow the use of multi-
valued parameter documentation for that case.
The other option would have been to allow templated parameters
to be enums, which also makes sense and could be a complimentary
improvement; there are two reasons to prefer the current approach:

* Getting the list of allowed values might have a performance
  impact that we only want to incur when the (optional) parameter
  is used, while an enum requires the value list to be generated
  every time the API module is used. So there would be some
  value in using string type for templated parameters with a
  known set of documentable values even if enum were also allowed.
* Per-value documentation might be useful outside the template
  use case for non-restricted multi-valued strings: maybe all
  values are allowed but there are some values with special
  meanings that are worth documenting.

Change-Id: I53f9ae840c0a7eee76c4b57f95390b5045707efd
2023-01-17 21:21:22 -08:00

1656 lines
46 KiB
PHP
Raw Blame History

<?php
use MediaWiki\Api\Validator\SubmoduleDef;
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\ParamValidator\TypeDef\NamespaceDef;
use MediaWiki\Request\FauxRequest;
use Wikimedia\ParamValidator\ParamValidator;
use Wikimedia\ParamValidator\TypeDef\EnumDef;
use Wikimedia\ParamValidator\TypeDef\IntegerDef;
use Wikimedia\ParamValidator\TypeDef\StringDef;
use Wikimedia\TestingAccessWrapper;
/**
* @group API
* @group Database
* @group medium
*
* @covers ApiBase
*/
class ApiBaseTest extends ApiTestCase {
protected function setUp(): void {
parent::setUp();
$this->mergeMwGlobalArrayValue(
'wgGroupPermissions',
[
'*' => [
'read' => true,
'edit' => true,
'writeapi' => true,
'apihighlimits' => false,
],
]
);
}
/**
* This covers a variety of stub methods that return a fixed value.
*
* @dataProvider provideStubMethods
*/
public function testStubMethods( $expected, $method, $args = [] ) {
// Some of these are protected
$mock = TestingAccessWrapper::newFromObject( new MockApi() );
$result = $mock->$method( ...$args );
$this->assertSame( $expected, $result );
}
public function provideStubMethods() {
return [
[ null, 'getModuleManager' ],
[ null, 'getCustomPrinter' ],
[ [], 'getHelpUrls' ],
// @todo This is actually overridden by MockApi
// [ [], 'getAllowedParams' ],
[ true, 'shouldCheckMaxLag' ],
[ true, 'isReadMode' ],
[ false, 'isWriteMode' ],
[ false, 'mustBePosted' ],
[ false, 'isDeprecated' ],
[ false, 'isInternal' ],
[ false, 'needsToken' ],
[ null, 'getWebUITokenSalt', [ [] ] ],
[ null, 'getConditionalRequestData', [ 'etag' ] ],
[ null, 'dynamicParameterDocumentation' ],
];
}
public function testRequireOnlyOneParameterDefault() {
$mock = new MockApi();
$mock->requireOnlyOneParameter(
[ "filename" => "foo.txt", "enablechunks" => false ],
"filename", "enablechunks"
);
$this->assertTrue( true );
}
public function testRequireOnlyOneParameterZero() {
$mock = new MockApi();
$this->expectException( ApiUsageException::class );
$mock->requireOnlyOneParameter(
[ "filename" => "foo.txt", "enablechunks" => 0 ],
"filename", "enablechunks"
);
}
public function testRequireOnlyOneParameterTrue() {
$mock = new MockApi();
$this->expectException( ApiUsageException::class );
$mock->requireOnlyOneParameter(
[ "filename" => "foo.txt", "enablechunks" => true ],
"filename", "enablechunks"
);
}
public function testRequireOnlyOneParameterMissing() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'One of the parameters "foo" and "bar" is required.' );
$mock = new MockApi();
$mock->requireOnlyOneParameter(
[ "filename" => "foo.txt", "enablechunks" => false ],
"foo", "bar" );
}
public function testRequireMaxOneParameterZero() {
$mock = new MockApi();
$mock->requireMaxOneParameter(
[ 'foo' => 'bar', 'baz' => 'quz' ],
'squirrel' );
$this->assertTrue( true );
}
public function testRequireMaxOneParameterOne() {
$mock = new MockApi();
$mock->requireMaxOneParameter(
[ 'foo' => 'bar', 'baz' => 'quz' ],
'foo', 'squirrel' );
$this->assertTrue( true );
}
public function testRequireMaxOneParameterTwo() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'The parameters "foo" and "baz" can not be used together.' );
$mock = new MockApi();
$mock->requireMaxOneParameter(
[ 'foo' => 'bar', 'baz' => 'quz' ],
'foo', 'baz' );
}
public function testRequireAtLeastOneParameterZero() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'At least one of the parameters "foo" and "bar" is required.' );
$mock = new MockApi();
$mock->requireAtLeastOneParameter(
[ 'a' => 'b', 'c' => 'd' ],
'foo', 'bar' );
}
public function testRequireAtLeastOneParameterOne() {
$mock = new MockApi();
$mock->requireAtLeastOneParameter(
[ 'a' => 'b', 'c' => 'd' ],
'foo', 'a' );
$this->assertTrue( true );
}
public function testRequireAtLeastOneParameterTwo() {
$mock = new MockApi();
$mock->requireAtLeastOneParameter(
[ 'a' => 'b', 'c' => 'd' ],
'a', 'c' );
$this->assertTrue( true );
}
public function testGetTitleOrPageIdBadParams() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'The parameters "title" and "pageid" can not be used together.' );
$mock = new MockApi();
$mock->getTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
}
public function testGetTitleOrPageIdTitle() {
$mock = new MockApi();
$result = $mock->getTitleOrPageId( [ 'title' => 'Foo' ] );
$this->assertInstanceOf( WikiPage::class, $result );
$this->assertSame( 'Foo', $result->getTitle()->getPrefixedText() );
}
public function testGetTitleOrPageIdInvalidTitle() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'Bad title "|".' );
$mock = new MockApi();
$mock->getTitleOrPageId( [ 'title' => '|' ] );
}
public function testGetTitleOrPageIdSpecialTitle() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( "Namespace doesn't allow actual pages." );
$mock = new MockApi();
$mock->getTitleOrPageId( [ 'title' => 'Special:RandomPage' ] );
}
public function testGetTitleOrPageIdPageId() {
$page = $this->getExistingTestPage();
$result = ( new MockApi() )->getTitleOrPageId(
[ 'pageid' => $page->getId() ] );
$this->assertInstanceOf( WikiPage::class, $result );
$this->assertSame(
$page->getTitle()->getPrefixedText(),
$result->getTitle()->getPrefixedText()
);
}
public function testGetTitleOrPageIdInvalidPageId() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'There is no page with ID 2147483648.' );
$mock = new MockApi();
$mock->getTitleOrPageId( [ 'pageid' => 2147483648 ] );
}
public function testGetTitleFromTitleOrPageIdBadParams() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'The parameters "title" and "pageid" can not be used together.' );
$mock = new MockApi();
$mock->getTitleFromTitleOrPageId( [ 'title' => 'a', 'pageid' => 7 ] );
}
public function testGetTitleFromTitleOrPageIdTitle() {
$mock = new MockApi();
$result = $mock->getTitleFromTitleOrPageId( [ 'title' => 'Foo' ] );
$this->assertInstanceOf( Title::class, $result );
$this->assertSame( 'Foo', $result->getPrefixedText() );
}
public function testGetTitleFromTitleOrPageIdInvalidTitle() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'Bad title "|".' );
$mock = new MockApi();
$mock->getTitleFromTitleOrPageId( [ 'title' => '|' ] );
}
public function testGetTitleFromTitleOrPageIdPageId() {
$page = $this->getExistingTestPage();
$result = ( new MockApi() )->getTitleFromTitleOrPageId(
[ 'pageid' => $page->getId() ] );
$this->assertInstanceOf( Title::class, $result );
$this->assertSame( $page->getTitle()->getPrefixedText(), $result->getPrefixedText() );
}
public function testGetTitleFromTitleOrPageIdInvalidPageId() {
$this->expectException( ApiUsageException::class );
$this->expectExceptionMessage( 'There is no page with ID 298401643.' );
$mock = new MockApi();
$mock->getTitleFromTitleOrPageId( [ 'pageid' => 298401643 ] );
}
public function testGetParameter() {
$mock = $this->getMockBuilder( MockApi::class )
->onlyMethods( [ 'getAllowedParams' ] )
->getMock();
$mock->method( 'getAllowedParams' )->willReturn( [
'foo' => [
ParamValidator::PARAM_TYPE => [ 'value' ],
],
'bar' => [
ParamValidator::PARAM_TYPE => [ 'value' ],
],
] );
$wrapper = TestingAccessWrapper::newFromObject( $mock );
$context = new DerivativeContext( $mock );
$context->setRequest( new FauxRequest( [ 'foo' => 'bad', 'bar' => 'value' ] ) );
$wrapper->mMainModule = new ApiMain( $context );
// Even though 'foo' is bad, getParameter( 'bar' ) must not fail
$this->assertSame( 'value', $wrapper->getParameter( 'bar' ) );
// But getParameter( 'foo' ) must throw.
try {
$wrapper->getParameter( 'foo' );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertTrue( $this->apiExceptionHasCode( $ex, 'badvalue' ) );
}
// And extractRequestParams() must throw too.
try {
$mock->extractRequestParams();
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertTrue( $this->apiExceptionHasCode( $ex, 'badvalue' ) );
}
}
/**
* @param string|null $input
* @param array $paramSettings
* @param mixed $expected
* @param string[] $warnings
* @param array $options Key-value pairs:
* 'parseLimits': true|false
* 'apihighlimits': true|false
* 'prefix': true|false
*/
private function doGetParameterFromSettings(
$input, $paramSettings, $expected, $warnings, $options = []
) {
$mock = new MockApi();
$wrapper = TestingAccessWrapper::newFromObject( $mock );
if ( $options['prefix'] ) {
$wrapper->mModulePrefix = 'my';
$paramName = 'Param';
} else {
$paramName = 'myParam';
}
$context = new DerivativeContext( $mock );
$context->setRequest( new FauxRequest(
$input !== null ? [ 'myParam' => $input ] : [] ) );
$wrapper->mMainModule = new ApiMain( $context );
$parseLimits = $options['parseLimits'] ?? true;
if ( !empty( $options['apihighlimits'] ) ) {
$context->setUser( self::$users['sysop']->getUser() );
}
// If we're testing tags, set up some tags
if ( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
$paramSettings[ParamValidator::PARAM_TYPE] === 'tags'
) {
ChangeTags::defineTag( 'tag1' );
ChangeTags::defineTag( 'tag2' );
}
if ( $expected instanceof Exception ) {
try {
$wrapper->getParameterFromSettings( $paramName, $paramSettings,
$parseLimits );
$this->fail( 'No exception thrown' );
} catch ( Exception $ex ) {
$this->assertInstanceOf( get_class( $expected ), $ex );
if ( $ex instanceof ApiUsageException ) {
$this->assertEquals( $expected->getModulePath(), $ex->getModulePath() );
$this->assertEquals( $expected->getStatusValue(), $ex->getStatusValue() );
} else {
$this->assertEquals( $expected->getMessage(), $ex->getMessage() );
$this->assertEquals( $expected->getCode(), $ex->getCode() );
}
}
} else {
$result = $wrapper->getParameterFromSettings( $paramName,
$paramSettings, $parseLimits );
if ( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
$paramSettings[ParamValidator::PARAM_TYPE] === 'timestamp' &&
$expected === 'now'
) {
// Allow one second of fuzziness. Make sure the formats are
// correct!
$this->assertMatchesRegularExpression( '/^\d{14}$/', $result );
$this->assertLessThanOrEqual( 1,
abs( wfTimestamp( TS_UNIX, $result ) - time() ),
"Result $result differs from expected $expected by " .
'more than one second' );
} else {
$this->assertSame( $expected, $result );
}
$actualWarnings = array_map( static function ( $warn ) {
return $warn instanceof Message
? array_merge( [ $warn->getKey() ], $warn->getParams() )
: $warn;
}, $mock->warnings );
$this->assertSame( $warnings, $actualWarnings );
}
if ( !empty( $paramSettings[ParamValidator::PARAM_SENSITIVE] ) ||
( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
$paramSettings[ParamValidator::PARAM_TYPE] === 'password' )
) {
$mainWrapper = TestingAccessWrapper::newFromObject( $wrapper->getMain() );
$this->assertSame( [ 'myParam' ],
$mainWrapper->getSensitiveParams() );
}
}
/**
* @dataProvider provideGetParameterFromSettings
* @see self::doGetParameterFromSettings()
*/
public function testGetParameterFromSettings_noprefix(
$input, $paramSettings, $expected, $warnings, $options = []
) {
$options['prefix'] = false;
$this->doGetParameterFromSettings( $input, $paramSettings, $expected, $warnings, $options );
}
/**
* @dataProvider provideGetParameterFromSettings
* @see self::doGetParameterFromSettings()
*/
public function testGetParameterFromSettings_prefix(
$input, $paramSettings, $expected, $warnings, $options = []
) {
$options['prefix'] = true;
$this->doGetParameterFromSettings( $input, $paramSettings, $expected, $warnings, $options );
}
public static function provideGetParameterFromSettings() {
$warnings = [
[ 'apiwarn-badutf8', 'myParam' ],
];
$c0 = '';
$enc = '';
for ( $i = 0; $i < 32; $i++ ) {
$c0 .= chr( $i );
$enc .= ( $i === 9 || $i === 10 || $i === 13 )
? chr( $i )
: '<27>';
}
$namespaces = MediaWikiServices::getInstance()->getNamespaceInfo()->getValidNamespaces();
$returnArray = [
'Basic param' => [ 'bar', null, 'bar', [] ],
'Basic param, C0 controls' => [ $c0, null, $enc, $warnings ],
'String param' => [ 'bar', '', 'bar', [] ],
'String param, defaulted' => [ null, '', '', [] ],
'String param, empty' => [ '', 'default', '', [] ],
'String param, required, empty' => [
'',
[ ParamValidator::PARAM_DEFAULT => 'default', ParamValidator::PARAM_REQUIRED => true ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-missingparam',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '' ),
], 'missingparam' ),
[]
],
'Multi-valued parameter' => [
'a|b|c',
[ ParamValidator::PARAM_ISMULTI => true ],
[ 'a', 'b', 'c' ],
[]
],
'Multi-valued parameter, alternative separator' => [
"\x1fa|b\x1fc|d",
[ ParamValidator::PARAM_ISMULTI => true ],
[ 'a|b', 'c|d' ],
[]
],
'Multi-valued parameter, other C0 controls' => [
$c0,
[ ParamValidator::PARAM_ISMULTI => true ],
[ $enc ],
$warnings
],
'Multi-valued parameter, other C0 controls (2)' => [
"\x1f" . $c0,
[ ParamValidator::PARAM_ISMULTI => true ],
[ substr( $enc, 0, -3 ), '' ],
$warnings
],
'Multi-valued parameter with limits' => [
'a|b|c',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_ISMULTI_LIMIT1 => 3,
],
[ 'a', 'b', 'c' ],
[],
],
'Multi-valued parameter with exceeded limits' => [
'a|b|c',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_ISMULTI_LIMIT1 => 2,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-toomanyvalues',
Message::plaintextParam( 'myParam' ),
Message::numParam( 2 ),
], 'toomanyvalues', [
'limit' => 2,
'lowlimit' => 2,
'highlimit' => 500,
] ),
[]
],
'Multi-valued parameter with exceeded limits for non-bot' => [
'a|b|c',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_ISMULTI_LIMIT1 => 2,
ParamValidator::PARAM_ISMULTI_LIMIT2 => 3,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-toomanyvalues',
Message::plaintextParam( 'myParam' ),
Message::numParam( 2 ),
], 'toomanyvalues', [
'limit' => 2,
'lowlimit' => 2,
'highlimit' => 3,
] ),
[]
],
'Multi-valued parameter with non-exceeded limits for bot' => [
'a|b|c',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_ISMULTI_LIMIT1 => 2,
ParamValidator::PARAM_ISMULTI_LIMIT2 => 3,
],
[ 'a', 'b', 'c' ],
[],
[ 'apihighlimits' => true ],
],
'Multi-valued parameter with prohibited duplicates' => [
'a|b|a|c',
[ ParamValidator::PARAM_ISMULTI => true ],
[ 'a', 'b', 'c' ],
[],
],
'Multi-valued parameter with allowed duplicates' => [
'a|a',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_ALLOW_DUPLICATES => true,
],
[ 'a', 'a' ],
[],
],
'Empty boolean param' => [
'',
[ ParamValidator::PARAM_TYPE => 'boolean' ],
true,
[],
],
'Boolean param 0' => [
'0',
[ ParamValidator::PARAM_TYPE => 'boolean' ],
true,
[],
],
'Boolean param false' => [
'false',
[ ParamValidator::PARAM_TYPE => 'boolean' ],
true,
[],
],
'Deprecated parameter' => [
'foo',
[ ParamValidator::PARAM_DEPRECATED => true ],
'foo',
[ [
'paramvalidator-param-deprecated',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'foo' )
] ],
],
'Deprecated parameter with default, unspecified' => [
null,
[ ParamValidator::PARAM_DEPRECATED => true, ParamValidator::PARAM_DEFAULT => 'foo' ],
'foo',
[],
],
'Deprecated parameter with default, specified' => [
'foo',
[ ParamValidator::PARAM_DEPRECATED => true, ParamValidator::PARAM_DEFAULT => 'foo' ],
'foo',
[ [
'paramvalidator-param-deprecated',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'foo' )
] ],
],
'Deprecated parameter value' => [
'a',
[ ParamValidator::PARAM_TYPE => [ 'a' ], EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => true ] ],
'a',
[ [
'paramvalidator-deprecated-value',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'a' )
] ],
],
'Deprecated parameter value as default, unspecified' => [
null,
[
ParamValidator::PARAM_TYPE => [ 'a' ],
EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => true ],
ParamValidator::PARAM_DEFAULT => 'a'
],
'a',
[],
],
'Deprecated parameter value as default, specified' => [
'a',
[
ParamValidator::PARAM_TYPE => [ 'a' ],
EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => true ],
ParamValidator::PARAM_DEFAULT => 'a'
],
'a',
[ [
'paramvalidator-deprecated-value',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'a' )
] ],
],
'Multiple deprecated parameter values' => [
'a|b|c|d',
[
ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c', 'd' ],
EnumDef::PARAM_DEPRECATED_VALUES => [ 'b' => true, 'd' => true ],
ParamValidator::PARAM_ISMULTI => true,
],
[ 'a', 'b', 'c', 'd' ],
[
[
'paramvalidator-deprecated-value',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'b' )
],
[
'paramvalidator-deprecated-value',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'd' )
],
],
],
'Deprecated parameter value with custom warning' => [
'a',
[ ParamValidator::PARAM_TYPE => [ 'a' ], EnumDef::PARAM_DEPRECATED_VALUES => [ 'a' => 'my-msg' ] ],
'a',
[ [ 'my-msg' ] ],
],
'"*" when wildcard not allowed' => [
'*',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
],
[],
[ [
'paramvalidator-unrecognizedvalues',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '*' ),
Message::listParam( [ Message::plaintextParam( '*' ) ], 'comma' ),
Message::numParam( 1 ),
] ],
],
'Wildcard "*"' => [
'*',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
ParamValidator::PARAM_ALL => true,
],
[ 'a', 'b', 'c' ],
[],
],
'Wildcard "*" with multiples not allowed' => [
'*',
[
ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
ParamValidator::PARAM_ALL => true,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-badvalue-enumnotmulti',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '*' ),
Message::listParam( [
Message::plaintextParam( 'a' ),
Message::plaintextParam( 'b' ),
Message::plaintextParam( 'c' ),
] ),
Message::numParam( 3 ),
], 'badvalue' ),
[],
],
'Wildcard "*" with unrestricted type' => [
'*',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_ALL => true,
],
[ '*' ],
[],
],
'Wildcard "x"' => [
'x',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => [ 'a', 'b', 'c' ],
ParamValidator::PARAM_ALL => 'x',
],
[ 'a', 'b', 'c' ],
[],
],
'Namespace with wildcard' => [
'*',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'namespace',
],
$namespaces,
[],
],
// PARAM_ALL is ignored with namespace types.
'Namespace with wildcard suppressed' => [
'*',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'namespace',
ParamValidator::PARAM_ALL => false,
],
$namespaces,
[],
],
'Namespace with wildcard "x"' => [
'x',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'namespace',
ParamValidator::PARAM_ALL => 'x',
],
[],
[ [
'paramvalidator-unrecognizedvalues',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'x' ),
Message::listParam( [ Message::plaintextParam( 'x' ) ], 'comma' ),
Message::numParam( 1 ),
] ],
],
'Password' => [
'dDy+G?e?txnr.1:(@Ru',
[ ParamValidator::PARAM_TYPE => 'password' ],
'dDy+G?e?txnr.1:(@Ru',
[],
],
'Sensitive field' => [
'I am fond of pineapples',
[ ParamValidator::PARAM_SENSITIVE => true ],
'I am fond of pineapples',
[],
],
// @todo Test actual upload
'Namespace -1' => [
'-1',
[ ParamValidator::PARAM_TYPE => 'namespace' ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-badvalue-enumnotmulti',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '-1' ),
Message::listParam( array_map( [ Message::class, 'plaintextParam' ], $namespaces ) ),
Message::numParam( count( $namespaces ) ),
], 'badvalue' ),
[],
],
'Extra namespace -1' => [
'-1',
[
ParamValidator::PARAM_TYPE => 'namespace',
NamespaceDef::PARAM_EXTRA_NAMESPACES => [ -1 ],
],
-1,
[],
],
// @todo Test with PARAM_SUBMODULE_MAP unset, need
// getModuleManager() to return something real
'Nonexistent module' => [
'not-a-module-name',
[
ParamValidator::PARAM_TYPE => 'submodule',
SubmoduleDef::PARAM_SUBMODULE_MAP =>
[ 'foo' => 'foo', 'bar' => 'foo+bar' ],
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-badvalue-enumnotmulti',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'not-a-module-name' ),
Message::listParam( [
Message::plaintextParam( 'foo' ),
Message::plaintextParam( 'bar' ),
] ),
Message::numParam( 2 ),
], 'badvalue' ),
[],
],
'\\x1f with multiples not allowed' => [
"\x1f",
[],
ApiUsageException::newWithMessage( null, [
'paramvalidator-notmulti',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( "\x1f" ),
], 'badvalue' ),
[],
],
'Integer with unenforced min' => [
'-2',
[
ParamValidator::PARAM_TYPE => 'integer',
IntegerDef::PARAM_MIN => -1,
],
-1,
[ [
'paramvalidator-outofrange-min',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '-2' ),
Message::numParam( -1 ),
Message::numParam( '' ),
] ],
],
'Integer with enforced min' => [
'-2',
[
ParamValidator::PARAM_TYPE => 'integer',
IntegerDef::PARAM_MIN => -1,
ApiBase::PARAM_RANGE_ENFORCE => true,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-outofrange-min',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '-2' ),
Message::numParam( -1 ),
Message::numParam( '' ),
], 'outofrange', [ 'min' => -1, 'curmax' => null, 'max' => null, 'highmax' => null ] ),
[],
],
'Integer with unenforced max' => [
'8',
[
ParamValidator::PARAM_TYPE => 'integer',
IntegerDef::PARAM_MAX => 7,
],
7,
[ [
'paramvalidator-outofrange-max',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '8' ),
Message::numParam( '' ),
Message::numParam( 7 ),
] ],
],
'Integer with enforced max' => [
'8',
[
ParamValidator::PARAM_TYPE => 'integer',
IntegerDef::PARAM_MAX => 7,
ApiBase::PARAM_RANGE_ENFORCE => true,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-outofrange-max',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '8' ),
Message::numParam( '' ),
Message::numParam( 7 ),
], 'outofrange', [ 'min' => null, 'curmax' => 7, 'max' => 7, 'highmax' => 7 ] ),
[],
],
'Array of integers' => [
'3|12|966|-1',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'integer',
],
[ 3, 12, 966, -1 ],
[],
],
'Array of integers with unenforced min/max' => [
'3|12|966|-1',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'integer',
IntegerDef::PARAM_MIN => 0,
IntegerDef::PARAM_MAX => 100,
],
[ 3, 12, 100, 0 ],
[
[
'paramvalidator-outofrange-minmax',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '966' ),
Message::numParam( 0 ),
Message::numParam( 100 ),
],
[
'paramvalidator-outofrange-minmax',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '-1' ),
Message::numParam( 0 ),
Message::numParam( 100 ),
],
],
],
'Array of integers with enforced min/max' => [
'3|12|966|-1',
[
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'integer',
IntegerDef::PARAM_MIN => 0,
IntegerDef::PARAM_MAX => 100,
ApiBase::PARAM_RANGE_ENFORCE => true,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-outofrange-minmax',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '966' ),
Message::numParam( 0 ),
Message::numParam( 100 ),
], 'outofrange', [ 'min' => 0, 'curmax' => 100, 'max' => 100, 'highmax' => 100 ] ),
[],
],
'Limit with parseLimits false (numeric)' => [
'100',
[ ParamValidator::PARAM_TYPE => 'limit' ],
100,
[],
[ 'parseLimits' => false ],
],
'Limit with parseLimits false (max)' => [
'max',
[ ParamValidator::PARAM_TYPE => 'limit' ],
'max',
[],
[ 'parseLimits' => false ],
],
'Limit with parseLimits false (invalid)' => [
'kitten',
[ ParamValidator::PARAM_TYPE => 'limit' ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-badinteger',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'kitten' ),
], 'badinteger' ),
[],
[ 'parseLimits' => false ],
],
'Limit with no max, supplied "max"' => [
'max',
[
ParamValidator::PARAM_TYPE => 'limit',
],
PHP_INT_MAX,
[],
],
'Valid limit' => [
'100',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 100,
],
100,
[],
],
'Limit max' => [
'max',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 101,
],
100,
[],
],
'Limit max for apihighlimits' => [
'max',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 101,
],
101,
[],
[ 'apihighlimits' => true ],
],
'Limit too large' => [
'101',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 101,
],
100,
[ [
'paramvalidator-outofrange-minmax',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '101' ),
Message::numParam( 0 ),
Message::numParam( 100 ),
] ],
],
'Limit okay for apihighlimits' => [
'101',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 101,
],
101,
[],
[ 'apihighlimits' => true ],
],
'Limit too large for apihighlimits (non-internal mode)' => [
'102',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 101,
],
101,
[ [
'paramvalidator-outofrange-minmax',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '102' ),
Message::numParam( 0 ),
Message::numParam( 101 ),
] ],
[ 'apihighlimits' => true ],
],
'Limit too small' => [
'-2',
[
ParamValidator::PARAM_TYPE => 'limit',
IntegerDef::PARAM_MIN => -1,
IntegerDef::PARAM_MAX => 100,
IntegerDef::PARAM_MAX2 => 100,
],
-1,
[ [
'paramvalidator-outofrange-minmax',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '-2' ),
Message::numParam( -1 ),
Message::numParam( 100 ),
] ],
],
'Timestamp' => [
wfTimestamp( TS_UNIX, '20211221122112' ),
[ ParamValidator::PARAM_TYPE => 'timestamp' ],
'20211221122112',
[],
],
'Timestamp 0' => [
'0',
[ ParamValidator::PARAM_TYPE => 'timestamp' ],
// Magic keyword
'now',
[ [
'paramvalidator-unclearnowtimestamp',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '0' ),
] ],
],
'Timestamp empty' => [
'',
[ ParamValidator::PARAM_TYPE => 'timestamp' ],
'now',
[ [
'paramvalidator-unclearnowtimestamp',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '' ),
] ],
],
// wfTimestamp() interprets this as Unix time
'Timestamp 00' => [
'00',
[ ParamValidator::PARAM_TYPE => 'timestamp' ],
'19700101000000',
[],
],
'Timestamp now' => [
'now',
[ ParamValidator::PARAM_TYPE => 'timestamp' ],
'now',
[],
],
'Invalid timestamp' => [
'a potato',
[ ParamValidator::PARAM_TYPE => 'timestamp' ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-badtimestamp',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'a potato' ),
], 'badtimestamp' ),
[],
],
'Timestamp array' => [
'100|101',
[
ParamValidator::PARAM_TYPE => 'timestamp',
ParamValidator::PARAM_ISMULTI => 1,
],
[ wfTimestamp( TS_MW, 100 ), wfTimestamp( TS_MW, 101 ) ],
[],
],
'Expiry array' => [
'99990123123456|8888-01-23 12:34:56|indefinite',
[
ParamValidator::PARAM_TYPE => 'expiry',
ParamValidator::PARAM_ISMULTI => 1,
],
[ '9999-01-23T12:34:56Z', '8888-01-23T12:34:56Z', 'infinity' ],
[],
],
'User' => [
'foo_bar',
[ ParamValidator::PARAM_TYPE => 'user' ],
'Foo bar',
[],
],
'User prefixed with "User:"' => [
'User:foo_bar',
[ ParamValidator::PARAM_TYPE => 'user' ],
'Foo bar',
[],
],
'Invalid username "|"' => [
'|',
[ ParamValidator::PARAM_TYPE => 'user' ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-baduser',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '|' ),
], 'baduser' ),
[],
],
'Invalid username "300.300.300.300"' => [
'300.300.300.300',
[ ParamValidator::PARAM_TYPE => 'user' ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-baduser',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '300.300.300.300' ),
], 'baduser' ),
[],
],
'IP range as username' => [
'10.0.0.0/8',
[ ParamValidator::PARAM_TYPE => 'user' ],
'10.0.0.0/8',
[],
],
'IPv6 as username' => [
'::1',
[ ParamValidator::PARAM_TYPE => 'user' ],
'0:0:0:0:0:0:0:1',
[],
],
'Obsolete cloaked usemod IP address as username' => [
'1.2.3.xxx',
[ ParamValidator::PARAM_TYPE => 'user' ],
'1.2.3.xxx',
[],
],
'Invalid username containing IP address' => [
'This is [not] valid 1.2.3.xxx, ha!',
[ ParamValidator::PARAM_TYPE => 'user' ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-baduser',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'This is [not] valid 1.2.3.xxx, ha!' ),
], 'baduser' ),
[],
],
'External username' => [
'M>Foo bar',
[ ParamValidator::PARAM_TYPE => 'user' ],
'M>Foo bar',
[],
],
'Array of usernames' => [
'foo|bar',
[
ParamValidator::PARAM_TYPE => 'user',
ParamValidator::PARAM_ISMULTI => true,
],
[ 'Foo', 'Bar' ],
[],
],
'tag' => [
'tag1',
[ ParamValidator::PARAM_TYPE => 'tags' ],
[ 'tag1' ],
[],
],
'Array of one tag' => [
'tag1',
[
ParamValidator::PARAM_TYPE => 'tags',
ParamValidator::PARAM_ISMULTI => true,
],
[ 'tag1' ],
[],
],
'Array of tags' => [
'tag1|tag2',
[
ParamValidator::PARAM_TYPE => 'tags',
ParamValidator::PARAM_ISMULTI => true,
],
[ 'tag1', 'tag2' ],
[],
],
'Invalid tag' => [
'invalid tag',
[ ParamValidator::PARAM_TYPE => 'tags' ],
ApiUsageException::newWithMessage(
null,
[ 'tags-apply-not-allowed-one', 'invalid tag', 1 ],
'badtags',
[ 'disallowedtags' => [ 'invalid tag' ] ]
),
[],
],
'Unrecognized type' => [
'foo',
[ ParamValidator::PARAM_TYPE => 'nonexistenttype' ],
new DomainException( "Param myParam's type is unknown - nonexistenttype" ),
[],
],
'Too many bytes' => [
'1',
[
StringDef::PARAM_MAX_BYTES => 0,
StringDef::PARAM_MAX_CHARS => 0,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-maxbytes',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '1' ),
Message::numParam( 0 ),
Message::numParam( 1 ),
], 'maxbytes', [ 'maxbytes' => 0, 'maxchars' => 0 ] ),
[],
],
'Too many chars' => [
'§§',
[
StringDef::PARAM_MAX_BYTES => 4,
StringDef::PARAM_MAX_CHARS => 1,
],
ApiUsageException::newWithMessage( null, [
'paramvalidator-maxchars',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( '§§' ),
Message::numParam( 1 ),
Message::numParam( 2 ),
], 'maxchars', [ 'maxbytes' => 4, 'maxchars' => 1 ] ),
[],
],
'Omitted required param' => [
null,
[ ParamValidator::PARAM_REQUIRED => true ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-missingparam',
Message::plaintextParam( 'myParam' )
], 'missingparam' ),
[],
],
'Empty multi-value' => [
'',
[ ParamValidator::PARAM_ISMULTI => true ],
[],
[],
],
'Multi-value \x1f' => [
"\x1f",
[ ParamValidator::PARAM_ISMULTI => true ],
[],
[],
],
'Allowed non-multi-value with "|"' => [
'a|b',
[ ParamValidator::PARAM_TYPE => [ 'a|b' ] ],
'a|b',
[],
],
'Prohibited multi-value' => [
'a|b',
[ ParamValidator::PARAM_TYPE => [ 'a', 'b' ] ],
ApiUsageException::newWithMessage( null, [
'paramvalidator-badvalue-enumnotmulti',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( 'a|b' ),
Message::listParam( [ Message::plaintextParam( 'a' ), Message::plaintextParam( 'b' ) ] ),
Message::numParam( 2 ),
], 'badvalue' ),
[],
],
];
$integerTests = [
[ '+1', 1 ],
[ '-1', -1 ],
[ '1.5', null ],
[ '-1.5', null ],
[ '1abc', null ],
[ ' 1', null ],
[ "\t1", null, '\t1' ],
[ "\r1", null, '\r1' ],
[ "\f1", null, '\f1', 'badutf-8' ],
[ "\n1", null, '\n1' ],
[ "\v1", null, '\v1', 'badutf-8' ],
[ "\e1", null, '\e1', 'badutf-8' ],
[ "\x001", null, '\x001', 'badutf-8' ],
];
foreach ( $integerTests as $test ) {
$desc = $test[2] ?? $test[0];
$warnings = isset( $test[3] ) ?
[ [ 'apiwarn-badutf8', 'myParam' ] ] : [];
$returnArray["\"$desc\" as integer"] = [
$test[0],
[ ParamValidator::PARAM_TYPE => 'integer' ],
$test[1] ?? ApiUsageException::newWithMessage( null, [
'paramvalidator-badinteger',
Message::plaintextParam( 'myParam' ),
Message::plaintextParam( preg_replace( "/[\f\v\e\\0]/", '<27>', $test[0] ) ),
], 'badinteger' ),
$warnings,
];
}
return $returnArray;
}
/**
* @dataProvider provideGetFinalParamDescription
*/
public function testGetFinalParamDescription( $paramSettings, $expectedMessages ) {
$mock = $this->getMockBuilder( MockApi::class )
->onlyMethods( [ 'getAllowedParams', 'getModulePath' ] )
->getMock();
$mock->method( 'getAllowedParams' )->willReturn( [
'param' => $paramSettings,
] );
$mock->method( 'getModulePath' )->willReturn( 'test' );
if ( $expectedMessages instanceof Exception ) {
$this->expectExceptionObject( $expectedMessages );
}
$paramDescription = $mock->getFinalParamDescription();
$this->assertArrayHasKey( 'param', $paramDescription );
$messages = $paramDescription['param'];
$messageKeys = array_map( fn( MessageSpecifier $m ) => $m->getKey(), $messages );
$this->assertSame( $expectedMessages, $messageKeys );
}
public function provideGetFinalParamDescription() {
return [
'default message' => [
'settings' => [],
'messages' => [ 'apihelp-test-param-param' ],
],
'custom message' => [
'settings' => [ ApiBase::PARAM_HELP_MSG => 'foo' ],
'messages' => [ 'foo' ],
],
'default per-value message' => [
'settings' => [
ParamValidator::PARAM_TYPE => [ 'a', 'b' ],
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
],
'messages' => [
'apihelp-test-param-param',
'apihelp-test-paramvalue-param-a',
'apihelp-test-paramvalue-param-b',
],
],
'custom per-value message' => [
'settings' => [
ParamValidator::PARAM_TYPE => [ 'a', 'b' ],
ApiBase::PARAM_HELP_MSG_PER_VALUE => [
'a' => 'foo',
'b' => 'bar',
],
],
'messages' => [
'apihelp-test-param-param',
'foo',
'bar',
],
],
'custom per-value message for strings' => [
'settings' => [
ParamValidator::PARAM_TYPE => 'string',
ParamValidator::PARAM_ISMULTI => true,
ApiBase::PARAM_HELP_MSG_PER_VALUE => [
'a' => 'foo',
'b' => 'bar',
],
],
'messages' => [
'apihelp-test-param-param',
'foo',
'bar',
],
],
'must be multi-valued for per-value message' => [
'settings' => [
ParamValidator::PARAM_TYPE => 'string',
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
],
'messages' => new MWException(
'Internal error in ApiBase::getFinalParamDescription: '
. 'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when '
. "ParamValidator::PARAM_TYPE is an array or it is 'string' "
. 'and ParamValidator::PARAM_ISMULTI is true'
),
],
];
}
public function testErrorArrayToStatus() {
$mock = new MockApi();
$msg = new Message( 'mainpage' );
// Check empty array
$expect = Status::newGood();
$this->assertEquals( $expect, $mock->errorArrayToStatus( [] ) );
// No blocked $user, so no special block handling
$expect = Status::newGood();
$expect->fatal( 'blockedtext' );
$expect->fatal( 'autoblockedtext' );
$expect->fatal( 'systemblockedtext' );
$expect->fatal( 'mainpage' );
$expect->fatal( $msg );
$expect->fatal( $msg, 'foobar' );
$expect->fatal( 'parentheses', 'foobar' );
$this->assertEquals( $expect, $mock->errorArrayToStatus( [
[ 'blockedtext' ],
[ 'autoblockedtext' ],
[ 'systemblockedtext' ],
'mainpage',
$msg,
[ $msg, 'foobar' ],
[ 'parentheses', 'foobar' ],
] ) );
// Has a blocked $user, so special block handling
$user = $this->getMutableTestUser()->getUser();
$block = new DatabaseBlock( [
'address' => $user->getName(),
'user' => $user->getId(),
'by' => $this->getTestSysop()->getUser(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
] );
$this->getServiceContainer()->getDatabaseBlockStore()->insertBlock( $block );
$mockTrait = $this->getMockForTrait( ApiBlockInfoTrait::class );
$language = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
$mockTrait->method( 'getLanguage' )->willReturn( $language );
$userInfoTrait = TestingAccessWrapper::newFromObject( $mockTrait );
$blockinfo = [ 'blockinfo' => $userInfoTrait->getBlockDetails( $block ) ];
$expect = Status::newGood();
$expect->fatal( ApiMessage::create( 'apierror-blocked', 'blocked', $blockinfo ) );
$expect->fatal( ApiMessage::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
$expect->fatal( ApiMessage::create( 'apierror-systemblocked', 'blocked', $blockinfo ) );
$expect->fatal( 'mainpage' );
$expect->fatal( $msg );
$expect->fatal( $msg, 'foobar' );
$expect->fatal( 'parentheses', 'foobar' );
$this->assertEquals( $expect, $mock->errorArrayToStatus( [
[ 'blockedtext' ],
[ 'autoblockedtext' ],
[ 'systemblockedtext' ],
'mainpage',
$msg,
[ $msg, 'foobar' ],
[ 'parentheses', 'foobar' ],
], $user ) );
}
public function testAddBlockInfoToStatus() {
$mock = new MockApi();
$msg = new Message( 'mainpage' );
// Check empty array
$expect = Status::newGood();
$test = Status::newGood();
$mock->addBlockInfoToStatus( $test );
$this->assertEquals( $expect, $test );
// No blocked $user, so no special block handling
$expect = Status::newGood();
$expect->fatal( 'blockedtext' );
$expect->fatal( 'autoblockedtext' );
$expect->fatal( 'systemblockedtext' );
$expect->fatal( 'mainpage' );
$expect->fatal( $msg );
$expect->fatal( $msg, 'foobar' );
$expect->fatal( 'parentheses', 'foobar' );
$test = clone $expect;
$mock->addBlockInfoToStatus( $test );
$this->assertEquals( $expect, $test );
// Has a blocked $user, so special block handling
$user = $this->getMutableTestUser()->getUser();
$block = new DatabaseBlock( [
'address' => $user->getName(),
'user' => $user->getId(),
'by' => $this->getTestSysop()->getUser(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
] );
$this->getServiceContainer()->getDatabaseBlockStore()->insertBlock( $block );
$mockTrait = $this->getMockForTrait( ApiBlockInfoTrait::class );
$language = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
$mockTrait->method( 'getLanguage' )->willReturn( $language );
$userInfoTrait = TestingAccessWrapper::newFromObject( $mockTrait );
$blockinfo = [ 'blockinfo' => $userInfoTrait->getBlockDetails( $block ) ];
$expect = Status::newGood();
$expect->fatal( ApiMessage::create( 'apierror-blocked', 'blocked', $blockinfo ) );
$expect->fatal( ApiMessage::create( 'apierror-autoblocked', 'autoblocked', $blockinfo ) );
$expect->fatal( ApiMessage::create( 'apierror-systemblocked', 'blocked', $blockinfo ) );
$expect->fatal( 'mainpage' );
$expect->fatal( $msg );
$expect->fatal( $msg, 'foobar' );
$expect->fatal( 'parentheses', 'foobar' );
$test = Status::newGood();
$test->fatal( 'blockedtext' );
$test->fatal( 'autoblockedtext' );
$test->fatal( 'systemblockedtext' );
$test->fatal( 'mainpage' );
$test->fatal( $msg );
$test->fatal( $msg, 'foobar' );
$test->fatal( 'parentheses', 'foobar' );
$mock->addBlockInfoToStatus( $test, $user );
$this->assertEquals( $expect, $test );
}
public function testDieStatus() {
$mock = new MockApi();
$status = StatusValue::newGood();
$status->error( 'foo' );
$status->warning( 'bar' );
try {
$mock->dieStatus( $status );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'foo' ), 'Exception has "foo"' );
$this->assertFalse( ApiTestCase::apiExceptionHasCode( $ex, 'bar' ), 'Exception has "bar"' );
}
$status = StatusValue::newGood();
$status->warning( 'foo' );
$status->warning( 'bar' );
try {
$mock->dieStatus( $status );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'foo' ), 'Exception has "foo"' );
$this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'bar' ), 'Exception has "bar"' );
}
$status = StatusValue::newGood();
$status->setOK( false );
try {
$mock->dieStatus( $status );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'unknownerror-nocode' ),
'Exception has "unknownerror-nocode"' );
}
}
/**
* @covers ApiBase::extractRequestParams
*/
public function testExtractRequestParams() {
$request = new FauxRequest( [
'xxexists' => 'exists!',
'xxmulti' => 'a|b|c|d|{bad}',
'xxempty' => '',
'xxtemplate-a' => 'A!',
'xxtemplate-b' => 'B1|B2|B3',
'xxtemplate-c' => '',
'xxrecursivetemplate-b-B1' => 'X',
'xxrecursivetemplate-b-B3' => 'Y',
'xxrecursivetemplate-b-B4' => '?',
'xxemptytemplate-' => 'nope',
'foo' => 'a|b|c',
'xxfoo' => 'a|b|c',
'errorformat' => 'raw',
] );
$context = new DerivativeContext( RequestContext::getMain() );
$context->setRequest( $request );
$main = new ApiMain( $context );
$mock = $this->getMockBuilder( ApiBase::class )
->setConstructorArgs( [ $main, 'test', 'xx' ] )
->onlyMethods( [ 'getAllowedParams' ] )
->getMockForAbstractClass();
$mock->method( 'getAllowedParams' )->willReturn( [
'notexists' => null,
'exists' => null,
'multi' => [
ParamValidator::PARAM_ISMULTI => true,
],
'empty' => [
ParamValidator::PARAM_ISMULTI => true,
],
'template-{m}' => [
ParamValidator::PARAM_ISMULTI => true,
ApiBase::PARAM_TEMPLATE_VARS => [ 'm' => 'multi' ],
],
'recursivetemplate-{m}-{t}' => [
ApiBase::PARAM_TEMPLATE_VARS => [ 't' => 'template-{m}', 'm' => 'multi' ],
],
'emptytemplate-{m}' => [
ParamValidator::PARAM_ISMULTI => true,
ApiBase::PARAM_TEMPLATE_VARS => [ 'm' => 'empty' ],
],
'badtemplate-{e}' => [
ApiBase::PARAM_TEMPLATE_VARS => [ 'e' => 'exists' ],
],
'badtemplate2-{e}' => [
ApiBase::PARAM_TEMPLATE_VARS => [ 'e' => 'badtemplate2-{e}' ],
],
'badtemplate3-{x}' => [
ApiBase::PARAM_TEMPLATE_VARS => [ 'x' => 'foo' ],
],
] );
$this->assertEquals( [
'notexists' => null,
'exists' => 'exists!',
'multi' => [ 'a', 'b', 'c', 'd', '{bad}' ],
'empty' => [],
'template-a' => [ 'A!' ],
'template-b' => [ 'B1', 'B2', 'B3' ],
'template-c' => [],
'template-d' => null,
'recursivetemplate-a-A!' => null,
'recursivetemplate-b-B1' => 'X',
'recursivetemplate-b-B2' => null,
'recursivetemplate-b-B3' => 'Y',
], $mock->extractRequestParams() );
$used = TestingAccessWrapper::newFromObject( $main )->getParamsUsed();
sort( $used );
$this->assertEquals( [
'xxempty',
'xxexists',
'xxmulti',
'xxnotexists',
'xxrecursivetemplate-a-A!',
'xxrecursivetemplate-b-B1',
'xxrecursivetemplate-b-B2',
'xxrecursivetemplate-b-B3',
'xxtemplate-a',
'xxtemplate-b',
'xxtemplate-c',
'xxtemplate-d',
], $used );
$warnings = $mock->getResult()->getResultData( 'warnings', [ 'Strip' => 'all' ] );
$this->assertCount( 1, $warnings );
$this->assertSame( 'ignoring-invalid-templated-value', $warnings[0]['code'] );
}
}