wiki.techinc.nl/tests/phpunit/includes/api/ApiBaseTest.php
Tim Starling 990acd88be Stop using nonexistent DatabaseBlock constructor options
Remove the 'user' option from the documentation and defaults. It has
been ignored since a3b4881f6f (2021, 1.37).

Fix tests which pass unknown constructor options. I found these by
temporarily patching the constructor to throw when an unknown option is
given.

Change-Id: I95cb2a38a5688d83c90ad501786f6d6bb02765ba
2023-11-06 12:03:51 +11:00

1669 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\Permissions\PermissionStatus;
use MediaWiki\Request\FauxRequest;
use MediaWiki\Status\Status;
use MediaWiki\Title\Title;
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 static 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->expectApiErrorCode( 'missingparam' );
$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->expectApiErrorCode( 'invalidparammix' );
$mock = new MockApi();
$mock->requireMaxOneParameter(
[ 'foo' => 'bar', 'baz' => 'quz' ],
'foo', 'baz' );
}
public function testRequireAtLeastOneParameterZero() {
$this->expectApiErrorCode( 'missingparam' );
$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->expectApiErrorCode( 'invalidparammix' );
$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->expectApiErrorCode( 'invalidtitle' );
$mock = new MockApi();
$mock->getTitleOrPageId( [ 'title' => '|' ] );
}
public function testGetTitleOrPageIdSpecialTitle() {
$this->expectApiErrorCode( 'pagecannotexist' );
$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->expectApiErrorCode( 'nosuchpageid' );
$mock = new MockApi();
$mock->getTitleOrPageId( [ 'pageid' => 2147483648 ] );
}
public function testGetTitleFromTitleOrPageIdBadParams() {
$this->expectApiErrorCode( 'invalidparammix' );
$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->expectApiErrorCode( 'invalidtitle' );
$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->expectApiErrorCode( 'nosuchpageid' );
$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->assertApiErrorCode( 'badvalue', $ex );
}
// And extractRequestParams() must throw too.
try {
$mock->extractRequestParams();
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
$this->assertApiErrorCode( 'badvalue', $ex );
}
}
/**
* @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( $this->getTestSysop()->getUser() );
}
// If we're testing tags, set up some tags
if ( isset( $paramSettings[ParamValidator::PARAM_TYPE] ) &&
$paramSettings[ParamValidator::PARAM_TYPE] === 'tags'
) {
$changeTagStore = $this->getServiceContainer()->getChangeTagsStore();
$changeTagStore->defineTag( 'tag1' );
$changeTagStore->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', [
'parameter' => 'myParam',
'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', [
'parameter' => 'myParam',
'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( static fn ( MessageSpecifier $m ) => $m->getKey(), $messages );
$this->assertSame( $expectedMessages, $messageKeys );
}
public static 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,
'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( 'blockedtext', 'blocked', $blockinfo ) );
// This would normally use the 'autoblocked' code, but the codes are computed from $blockinfo
// now rather than the message, and we're not faking it well enough
$expect->fatal( ApiMessage::create( 'autoblockedtext', 'blocked', $blockinfo ) );
$expect->fatal( ApiMessage::create( 'systemblockedtext', '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,
'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( 'blockedtext', 'blocked', $blockinfo ) );
// This would normally use the 'autoblocked' code, but the codes are computed from $blockinfo
// now rather than the message, and we're not faking it well enough
$expect->fatal( ApiMessage::create( 'autoblockedtext', 'blocked', $blockinfo ) );
$expect->fatal( ApiMessage::create( 'systemblockedtext', '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 static function provideDieStatus() {
$status = StatusValue::newGood();
$status->error( 'foo' );
$status->warning( 'bar' );
yield [ $status, [ 'foo' => true, 'bar' => false ] ];
$status = StatusValue::newGood();
$status->warning( 'foo' );
$status->warning( 'bar' );
yield [ $status, [ 'foo' => true, 'bar' => true ] ];
$status = StatusValue::newGood();
$status->setOK( false );
yield [ $status, [ 'unknownerror-nocode' => true ] ];
$status = PermissionStatus::newEmpty();
$status->setRateLimitExceeded();
yield [ $status, [ 'ratelimited' => true ] ];
$status = StatusValue::newFatal( 'actionthrottledtext' );
yield [ $status, [ 'ratelimited' => true ] ];
$status = StatusValue::newFatal( 'actionthrottled' );
yield [ $status, [ 'ratelimited' => true ] ];
$status = StatusValue::newFatal( 'blockedtext' );
yield [ $status, [ 'blocked' => true ] ];
$status = StatusValue::newFatal( 'autoblockedtext' );
yield [ $status, [ 'autoblocked' => true ] ];
}
/**
* @dataProvider provideDieStatus
*
* @param StatusValue $status
* @param array $expected
*/
public function testDieStatus( $status, $expected ) {
$mock = new MockApi();
try {
$mock->dieStatus( $status );
$this->fail( 'Expected exception not thrown' );
} catch ( ApiUsageException $ex ) {
foreach ( $expected as $key => $has ) {
$this->assertSame( $has, ApiTestCase::apiExceptionHasCode( $ex, $key ), "Exception has '$key'" );
}
}
}
/**
* @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'] );
}
}