wiki.techinc.nl/tests/phpunit/unit/includes/Settings/Source/JsonSchemaTraitTest.php
Martin Urbanec 7e3a77da4e JsonSchemaTrait: Add support for inlined references
In I130326e5762e706be4889e308eeec45a6c7683c5 we introduced
support for json schema references. In some use cases
it may be more suitable to compute the references
and inline them in the resulting schema.

Introduce an $inlineReferences flag to do so.

Possible follow-up:
Memorization of already resolved and normalized references
could speed up the full schema processing, T363678.

Bug: T362098
Co-Authored-by: Sergio Gimeno <sgimeno@wikimedia.org>
Change-Id: I570faddca7216a65db2e8e70b12997bf7b5a2933
2024-04-29 10:15:03 +00:00

366 lines
8.5 KiB
PHP

<?php
namespace MediaWiki\Tests\Unit\Settings\Source;
use InvalidArgumentException;
use MediaWiki\Settings\Source\JsonSchemaTrait;
use MediaWiki\Settings\Source\RefLoopException;
use PHPUnit\Framework\TestCase;
/**
* @covers \MediaWiki\Settings\Source\JsonSchemaTrait
*/
class JsonSchemaTraitTest extends TestCase {
use JsonSchemaTrait;
public static function providePhpDocToJson() {
yield 'int' => [ 'int', 'integer' ];
yield 'nullable int' => [ '?int', [ 'integer', 'null' ] ];
yield 'array input' => [ [ 'list', 'number' ], [ 'array', 'number' ] ];
yield 'false or null' => [ 'false|null', [ 'boolean', 'null' ] ];
yield 'string or float' => [ 'string|float', [ 'string', 'number' ] ];
yield 'array or object' => [ 'array|object', [ 'array', 'object' ] ];
yield 'dict or map' => [ 'dict|map', 'object' ];
yield 'stdClass' => [ 'stdClass', 'object' ];
yield 'nullable list' => [ '?list', [ 'array', 'null' ] ];
yield 'Class' => [ 'Something', 'Something' ];
yield 'keep integer' => [ 'integer', 'integer' ];
yield 'keep number' => [ 'number', 'number' ];
yield 'keep null' => [ 'null', 'null' ];
}
/**
* @dataProvider providePhpDocToJson
* @param string $phpDoc
* @param string|string[] $json
*/
public function testPhpDocToJson( $phpDoc, $json ) {
$actual = self::phpDocToJson( $phpDoc );
$this->assertSame( $json, $actual );
}
public static function provideNormalizeJsonSchema() {
yield 'nullable int' => [
[ 'type' => '?int' ],
[ 'type' => [ 'integer', 'null' ] ]
];
yield 'additionalProperties' => [
[
'type' => '?map',
'additionalProperties' => [ 'type' => 'list' ]
],
[
'type' => [ 'object', 'null' ],
'additionalProperties' => [ 'type' => 'array' ]
]
];
yield 'items' => [
[
'type' => 'list|false',
'items' => [ 'type' => 'float' ]
],
[
'type' => [ 'array', 'boolean' ],
'items' => [ 'type' => 'number' ]
]
];
yield 'properties' => [
[
'type' => 'object',
'properties' => [
'foo' => [ 'type' => 'float' ],
'bar' => [ 'type' => '?list', 'items' => [ 'type' => 'float' ] ],
]
],
[
'type' => 'object',
'properties' => [
'foo' => [ 'type' => 'number' ],
'bar' => [ 'type' => [ 'array', 'null' ], 'items' => [ 'type' => 'number' ] ],
]
]
];
yield 'nested' => [
[
'type' => '?dict',
'additionalProperties' => [
'type' => 'string|list',
'items' => [
'type' => 'float',
]
]
],
[
'type' => [ 'object', 'null' ],
'additionalProperties' => [
'type' => [ 'string', 'array' ],
'items' => [
'type' => 'number',
]
]
]
];
yield 'references' => [
[
'type' => 'object',
'properties' => [
'foo' => [
'$ref' => [
'class' => ExampleDefinitionsClass::class,
'field' => 'SOME_SCHEMA',
]
],
]
],
[
'type' => 'object',
'properties' => [
'foo' => [ '$ref' => '#/$defs/MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::SOME_SCHEMA' ],
]
],
[
'MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::SOME_SCHEMA' => [
'type' => 'string',
]
]
];
yield 'definitions' => [
[
'type' => 'object',
'properties' => [
'foo' => [
'$ref' => [
'class' => ExampleDefinitionsClass::class,
'field' => 'REFERENCING_SCHEMA',
]
],
]
],
[
'type' => 'object',
'properties' => [
'foo' => [ '$ref' => '#/$defs/MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::REFERENCING_SCHEMA' ],
]
],
[
'MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::REFERENCING_SCHEMA' => [
'$ref' => '#/$defs/MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::SOME_SCHEMA',
],
'MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::SOME_SCHEMA' => [
'type' => 'string',
]
]
];
yield 'inlined' => [
[
'type' => 'object',
'properties' => [
'foo' => [
'$ref' => [
'class' => ExampleDefinitionsClass::class,
'field' => 'REFERENCING_SCHEMA',
]
],
]
],
[
'type' => 'object',
'properties' => [
'foo' => [ 'type' => 'string' ],
]
],
[],
true
];
}
/**
* @dataProvider provideNormalizeJsonSchema
* @param array $schema
* @param array $expectedSchema
* @param array $expectedDefs
* @param bool $inlinedReferences
*/
public function testNormalizeJsonSchema( $schema, $expectedSchema, $expectedDefs = [], $inlinedReferences = false ) {
$actualDefs = [];
$actual = self::normalizeJsonSchema(
$schema,
$actualDefs,
'provideNormalizeJsonSchema',
'foo',
$inlinedReferences
);
$this->assertSame( $expectedSchema, $actual );
$this->assertEquals( $expectedDefs, $actualDefs );
}
public static function provideCycleDetection() {
yield 'cycle' => [
[
'type' => 'object',
'properties' => [
'foo' => [
'$ref' => [
'class' => ExampleDefinitionsClass::class,
'field' => 'CYCLED_SCHEMA',
]
]
]
]
];
}
/**
* @dataProvider provideCycleDetection
* @param array $schema
*/
public function testCycleDetection( $schema ) {
$actualDefs = [];
$this->expectException( RefLoopException::class );
$this->expectExceptionMessage(
'Found a loop while resolving reference MediaWiki.Tests.Unit.Settings.Source.ExampleDefinitionsClass::CYCLED_SCHEMA in foo. Root schema location: provideCycleDetection-cycle'
);
self::normalizeJsonSchema(
$schema,
$actualDefs,
'provideCycleDetection-cycle',
'foo'
);
}
public static function provideJsonToPhpDoc() {
yield 'integer' => [ 'integer', 'int' ];
yield 'double' => [ 'double', 'float' ]; // For good measure.
yield 'integer or null' => [ [ 'integer', 'null' ], '?int' ];
yield 'boolean' => [ 'boolean', 'bool' ];
yield 'string or number' => [ [ 'string', 'number' ], 'string|float' ];
yield 'array' => [ 'array', 'array' ];
yield 'object' => [ 'object', 'array' ]; // Assoc array. Could be optional.
yield 'Class' => [ 'Something', 'Something' ];
yield 'keep int' => [ 'int', 'int' ];
yield 'keep float' => [ 'float', 'float' ];
yield 'keep null' => [ 'null', 'null' ];
}
/**
* @dataProvider provideJsonToPhpDoc
* @param string|string[] $json
* @param string $phpDoc
*/
public function testJsonToPhpDoc( $json, $phpDoc ) {
$actual = self::jsonToPhpDoc( $json );
$this->assertSame( $phpDoc, $actual );
}
public static function provideJsonToPhpDoc_invalidArgument() {
yield 'null' => [ null ];
yield 'list with null' => [ [ 'int', null ] ];
}
/**
* @dataProvider provideJsonToPhpDoc_invalidArgument
* @param mixed $bad
*/
public function testJsonToPhpDoc_invalidArgument( $bad ) {
$this->expectException( InvalidArgumentException::class );
self::jsonToPhpDoc( $bad );
}
public function testPhpDocToJson_invalidArgument() {
$this->expectException( InvalidArgumentException::class );
self::phpDocToJson( null );
}
public static function provideGetDefaultFromJsonSchema() {
yield 'empty' => [ [], null ];
yield 'no default, no properties' => [
[ 'type' => 'string' ],
null
];
yield 'simple default, no properties' => [
[ 'type' => 'string', 'default' => 'kitten' ],
'kitten'
];
yield 'no default, but properties' => [
[
'properties' => [
'a' => [ 'type' => 'int' ],
'b' => [ 'type' => 'int' ],
]
],
[ 'a' => null, 'b' => null ]
];
yield 'default from properties' => [
[
'properties' => [
'a' => [ 'default' => 1 ],
'b' => [ 'default' => 2 ],
]
],
[ 'a' => 1, 'b' => 2 ]
];
yield 'combined default' => [
[
'default' => [
'a' => 11,
'x' => 99
],
'properties' => [
'a' => [ 'default' => 1 ],
'b' => [ 'default' => 2 ],
]
],
[ 'a' => 1, 'x' => 99, 'b' => 2 ]
];
yield 'nested properties' => [
[
'properties' => [
'a' => [ 'default' => 1 ],
'b' => [
'default' => [],
'properties' => [
'x' => [ 'default' => 7 ],
'y' => [
'properties' => [
'foo' => [ 'default' => 13 ],
'bar' => [ 'type' => 'int' ]
]
],
]
],
]
],
[
'a' => 1,
'b' => [
'x' => 7,
'y' => [ 'foo' => 13, 'bar' => null ]
]
]
];
}
/**
* @dataProvider provideGetDefaultFromJsonSchema
* @param array $schema
* @param mixed $default
*/
public function testGetDefaultFromJsonSchema( $schema, $default ) {
$actual = self::getDefaultFromJsonSchema( $schema );
$this->assertSame( $default, $actual );
}
}