wiki.techinc.nl/tests/phpunit/includes/api/ApiBaseTest.php
Reedy 85396a9c99 tests: Fix @covers and @coversDefaultClass to have leading \
Change-Id: I5629f91387f2ac453ee4341bfe4bba310bd52f03
2024-02-16 22:43:56 +00:00

1671 lines
46 KiB
PHP
Raw Blame History

<?php
use MediaWiki\Api\Validator\SubmoduleDef;
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Context\DerivativeContext;
use MediaWiki\Context\RequestContext;
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 MessageSpecifier
? [ $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'] );
}
}