wiki.techinc.nl/tests/phpunit/unit/includes/Settings/Config/ConfigSchemaAggregatorTest.php
daniel 404f4a8512 Fix edge cases in schema validation.
Since PHP arrays make no clear distinction between lists (JSON arrays)
and maps (JSON objects), some edge case handling is needed to make
validation work reliably when we declare types for all arrays:

1) Allow array keys to be ignored, so an associative PHP array validates
as a JSON array. This is needed for the SessionProviders setting.

2) Allow associative arrays with numeric keys to validate as JSON
objects. This is done by ignoring the type validation when numeric keys
are detected. A warning is returned in the status object.

3) Work around validation failing on float values that are expected to
be integers. All numbers come from the yaml parser as floats, and the
"integer" type in JSON schema should accept floats with if the
fractional part is 0. But that doesn't seem to work, we need to cast the
values to integers explicitly.

Also, this fixes some mistakes in the schema: LockManagers is a list,
so it should use the JSON type "array". NamespacesToBeSearchedDefault
is a map (JSON object), even though it uses numeric keys. The Actions
registry is also a map.

Change-Id: I9d0453d740c377b7cce574df743536c39a0ec619
2022-03-08 13:59:19 +01:00

168 lines
5.2 KiB
PHP

<?php
namespace MediaWiki\Tests\Unit\Settings\Config;
use HashConfig;
use InvalidArgumentException;
use MediaWiki\Settings\Config\ConfigSchemaAggregator;
use MediaWiki\Settings\Config\MergeStrategy;
use MediaWiki\Settings\SettingsBuilderException;
use PHPUnit\Framework\TestCase;
/**
* @covers \MediaWiki\Settings\Config\ConfigSchemaAggregator
*/
class ConfigSchemaAggregatorTest extends TestCase {
public function testAddSchema() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemas( [ 'foo' => [ 'type' => 'string', ], ] );
$aggregator->addSchemas( [ 'bar' => [ 'type' => 'string', ], ] );
$this->assertTrue( $aggregator->hasSchemaFor( 'foo' ) );
$this->assertTrue( $aggregator->hasSchemaFor( 'foo' ) );
}
public function testAddSchemaOverride() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemas( [ 'foo' => [ 'type' => 'string', ], ] );
$this->expectException( SettingsBuilderException::class );
$aggregator->addSchemas( [ 'foo' => [ 'type' => 'string', ], ] );
}
public function testGetDefaultFor() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemas( [
'no_default' => [ 'type' => 'string', ],
'with_default' => [ 'type' => 'string', 'default' => 'bla', ],
] );
$this->assertFalse( $aggregator->hasDefaultFor( 'does_not_exist' ) );
$this->assertFalse( $aggregator->hasDefaultFor( 'no_default' ) );
$this->assertTrue( $aggregator->hasDefaultFor( 'with_default' ) );
$this->assertSame( 'bla', $aggregator->getDefaultFor( 'with_default' ) );
}
public function testGetDefauts() {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemas( [
'no_default' => [ 'type' => 'string', ],
'with_default' => [ 'type' => 'string', 'default' => 'bla', ],
'another_with_default' => [ 'type' => 'string', 'default' => 'blabla', ],
] );
$this->assertEquals( [
'with_default' => 'bla',
'another_with_default' => 'blabla',
], $aggregator->getDefaults() );
$this->assertFalse( $aggregator->hasDefaultFor( 'no_default' ) );
$this->assertTrue( $aggregator->hasDefaultFor( 'with_default' ) );
$this->assertSame( 'bla', $aggregator->getDefaultFor( 'with_default' ) );
}
public function provideGetMergeStrategiesFor() {
yield 'no schema' => [ null, null ];
yield 'no strategy' => [ [ 'default' => '' ], null ];
yield 'with strategy' => [ [ 'mergeStrategy' => 'array_merge' ], 'array_merge' ];
yield 'with strategy and type=array' => [
[
'type' => 'array',
'mergeStrategy' => 'replace'
],
'replace'
];
yield 'without strategy and type=array' => [
[ 'type' => 'array' ],
'array_merge'
];
yield 'with strategy and type=object' => [
[
'type' => 'object',
'mergeStrategy' => 'array_plus_2d'
],
'array_plus_2d'
];
yield 'without strategy and type=object' => [
[ 'type' => 'object' ],
'array_plus'
];
}
/**
* @dataProvider provideGetMergeStrategiesFor
*/
public function testGetMergeStrategyFor( $schema, $expected ) {
$aggregator = new ConfigSchemaAggregator();
if ( $schema ) {
$aggregator->addSchemas( [ 'test' => $schema, ] );
}
$strategy = $aggregator->getMergeStrategyFor( 'test' );
$this->assertSame(
$expected,
$strategy ? $strategy->getName() : null
);
}
public function provideValidate() {
yield 'invalid config' => [
'config-schema' => [ 'foo' => [ 'type' => 'string', ], ],
'config' => [ 'foo' => 1 ],
'valid' => false,
];
yield 'all good' => [
'config-schema' => [ 'foo' => [ 'type' => 'string', ], ],
'config' => [ 'foo' => 'bar', ],
'valid' => true,
];
yield 'missing key' => [
'config-schema' => [ 'foo' => [ 'type' => 'string', ], ],
'config' => [ 'bar' => 'bar' ],
'valid' => false,
];
yield 'no schema was added' => [
'config-schema' => [],
'config' => [ 'foo' => 'bar', ],
'valid' => true,
];
yield 'key is in config but has no schema' => [
'config-schema' => [ 'foo' => [ 'type' => 'array', 'mergeStrategy' => MergeStrategy::ARRAY_MERGE ], ],
'config' => [ 'foo' => [], 'baz' => false, ],
'valid' => true,
];
yield 'assoc array where list is expected' => [
'config-schema' => [ 'foo' => [ 'type' => 'array', ], ],
'config' => [ 'foo' => [ 'x' => 1 ] ],
'valid' => false,
];
yield 'map with numeric keys' => [
'config-schema' => [ 'foo' => [ 'type' => 'object', ], ],
'config' => [ 'foo' => [ 0 => 'x', 1 => 'y' ] ],
'valid' => true,
];
yield 'array with ignoreKeys' => [
'config-schema' => [ 'foo' => [ 'type' => 'array', 'ignoreKeys' => true ], ],
'config' => [ 'foo' => [ 'x' => 'bla', 'blabla' ] ],
'valid' => true,
];
}
/**
* @dataProvider provideValidate
*/
public function testValidateConfig( array $schemas, array $config, bool $valid ) {
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemas( $schemas );
$status = $aggregator->validateConfig( new HashConfig( $config ) );
$this->assertSame( $valid, $status->isOK() );
}
public function testValidateInvalidSchema() {
$this->expectException( InvalidArgumentException::class );
$aggregator = new ConfigSchemaAggregator();
$aggregator->addSchemas( [ 'foo' => [ 'type' => 1 ] ] );
$aggregator->validateConfig( new HashConfig( [ 'foo' => 'bar' ] ) );
}
}