424 lines
15 KiB
PHP
424 lines
15 KiB
PHP
<?php
|
|
|
|
use cebe\openapi\Reader;
|
|
use cebe\openapi\ReferenceContext;
|
|
use cebe\openapi\spec\Discriminator;
|
|
use cebe\openapi\spec\Reference;
|
|
use cebe\openapi\spec\Schema;
|
|
use cebe\openapi\spec\Type;
|
|
|
|
/**
|
|
* @covers \cebe\openapi\spec\Schema
|
|
* @covers \cebe\openapi\spec\Discriminator
|
|
*/
|
|
class SchemaTest extends \PHPUnit\Framework\TestCase
|
|
{
|
|
public function testRead()
|
|
{
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson(<<<JSON
|
|
{
|
|
"type": "string",
|
|
"format": "email"
|
|
}
|
|
JSON
|
|
, Schema::class);
|
|
|
|
$result = $schema->validate();
|
|
$this->assertEquals([], $schema->getErrors());
|
|
$this->assertTrue($result);
|
|
|
|
$this->assertEquals(Type::STRING, $schema->type);
|
|
$this->assertEquals('email', $schema->format);
|
|
|
|
// additionalProperties defaults to true.
|
|
$this->assertTrue($schema->additionalProperties);
|
|
// nullable Default value is false.
|
|
$this->assertFalse($schema->nullable);
|
|
// readOnly Default value is false.
|
|
$this->assertFalse($schema->readOnly);
|
|
// writeOnly Default value is false.
|
|
$this->assertFalse($schema->writeOnly);
|
|
// deprecated Default value is false.
|
|
$this->assertFalse($schema->deprecated);
|
|
}
|
|
|
|
public function testNullable()
|
|
{
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "string"}', Schema::class);
|
|
$this->assertEquals(Type::STRING, $schema->type);
|
|
$this->assertFalse($schema->nullable);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "string", "nullable": false}', Schema::class);
|
|
$this->assertEquals(Type::STRING, $schema->type);
|
|
$this->assertFalse($schema->nullable);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "string", "nullable": true}', Schema::class);
|
|
$this->assertEquals(Type::STRING, $schema->type);
|
|
$this->assertTrue($schema->nullable);
|
|
|
|
// nullable is undefined if no type is given
|
|
$schema = Reader::readFromJson('{"oneOf": [{"type": "string"}, {"type": "integer"}]}', Schema::class);
|
|
$this->assertNull($schema->type);
|
|
$this->assertNull($schema->nullable);
|
|
}
|
|
|
|
public function testMinMax()
|
|
{
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "integer"}', Schema::class);
|
|
$this->assertNull($schema->minimum);
|
|
$this->assertNull($schema->exclusiveMinimum);
|
|
$this->assertNull($schema->maximum);
|
|
$this->assertNull($schema->exclusiveMaximum);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "integer", "minimum": 1}', Schema::class);
|
|
$this->assertEquals(1, $schema->minimum);
|
|
$this->assertFalse($schema->exclusiveMinimum);
|
|
$this->assertNull($schema->maximum);
|
|
$this->assertNull($schema->exclusiveMaximum);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "integer", "minimum": 1, "exclusiveMinimum": true}', Schema::class);
|
|
$this->assertEquals(1, $schema->minimum);
|
|
$this->assertTrue($schema->exclusiveMinimum);
|
|
$this->assertNull($schema->maximum);
|
|
$this->assertNull($schema->exclusiveMaximum);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "integer", "maximum": 10}', Schema::class);
|
|
$this->assertEquals(10, $schema->maximum);
|
|
$this->assertFalse($schema->exclusiveMaximum);
|
|
$this->assertNull($schema->minimum);
|
|
$this->assertNull($schema->exclusiveMinimum);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson('{"type": "integer", "maximum": 10, "exclusiveMaximum": true}', Schema::class);
|
|
$this->assertEquals(10, $schema->maximum);
|
|
$this->assertTrue($schema->exclusiveMaximum);
|
|
$this->assertNull($schema->minimum);
|
|
$this->assertNull($schema->exclusiveMinimum);
|
|
}
|
|
|
|
public function testReadObject()
|
|
{
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromJson(<<<'JSON'
|
|
{
|
|
"type": "object",
|
|
"required": [
|
|
"name"
|
|
],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"address": {
|
|
"$ref": "#/components/schemas/Address"
|
|
},
|
|
"age": {
|
|
"type": "integer",
|
|
"format": "int32",
|
|
"minimum": 0
|
|
}
|
|
}
|
|
}
|
|
JSON
|
|
, Schema::class);
|
|
|
|
$result = $schema->validate();
|
|
$this->assertEquals([], $schema->getErrors());
|
|
$this->assertTrue($result);
|
|
|
|
$this->assertEquals(Type::OBJECT, $schema->type);
|
|
$this->assertEquals(['name'], $schema->required);
|
|
$this->assertEquals(Type::INTEGER, $schema->properties['age']->type);
|
|
$this->assertEquals(0, $schema->properties['age']->minimum);
|
|
|
|
// additionalProperties defaults to true.
|
|
$this->assertTrue($schema->additionalProperties);
|
|
// nullable Default value is false.
|
|
$this->assertFalse($schema->nullable);
|
|
// readOnly Default value is false.
|
|
$this->assertFalse($schema->readOnly);
|
|
// writeOnly Default value is false.
|
|
$this->assertFalse($schema->writeOnly);
|
|
// deprecated Default value is false.
|
|
$this->assertFalse($schema->deprecated);
|
|
// exclusiveMinimum Default value is null when no minimum is specified.
|
|
$this->assertNull($schema->exclusiveMinimum);
|
|
// exclusiveMaximum Default value is null when no maximum is specified.
|
|
$this->assertNull($schema->exclusiveMaximum);
|
|
}
|
|
|
|
public function testDiscriminator()
|
|
{
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromYaml(<<<'YAML'
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Cat'
|
|
- $ref: '#/components/schemas/Dog'
|
|
- $ref: '#/components/schemas/Lizard'
|
|
discriminator:
|
|
map:
|
|
cat: Cat
|
|
dog: Dog
|
|
YAML
|
|
, Schema::class);
|
|
|
|
$result = $schema->validate();
|
|
$this->assertEquals([
|
|
'Discriminator is missing required property: propertyName'
|
|
], $schema->getErrors());
|
|
$this->assertFalse($result);
|
|
|
|
/** @var $schema Schema */
|
|
$schema = Reader::readFromYaml(<<<'YAML'
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Cat'
|
|
- $ref: '#/components/schemas/Dog'
|
|
- $ref: '#/components/schemas/Lizard'
|
|
discriminator:
|
|
propertyName: type
|
|
mapping:
|
|
cat: Cat
|
|
monster: https://gigantic-server.com/schemas/Monster/schema.json
|
|
YAML
|
|
, Schema::class);
|
|
|
|
$result = $schema->validate();
|
|
$this->assertEquals([], $schema->getErrors());
|
|
$this->assertTrue($result);
|
|
|
|
$this->assertInstanceOf(Discriminator::class, $schema->discriminator);
|
|
$this->assertEquals('type', $schema->discriminator->propertyName);
|
|
$this->assertEquals([
|
|
'cat' => 'Cat',
|
|
'monster' => 'https://gigantic-server.com/schemas/Monster/schema.json',
|
|
], $schema->discriminator->mapping);
|
|
}
|
|
|
|
public function testCreateionFromObjects()
|
|
{
|
|
$schema = new Schema([
|
|
'allOf' => [
|
|
new Schema(['type' => 'integer']),
|
|
new Schema(['type' => 'string']),
|
|
],
|
|
'additionalProperties' => new Schema([
|
|
'type' => 'object',
|
|
]),
|
|
'discriminator' => new Discriminator([
|
|
'mapping' => ['A' => 'B'],
|
|
]),
|
|
]);
|
|
|
|
$this->assertSame('integer', $schema->allOf[0]->type);
|
|
$this->assertSame('string', $schema->allOf[1]->type);
|
|
$this->assertInstanceOf(Schema::class, $schema->additionalProperties);
|
|
$this->assertSame('object', $schema->additionalProperties->type);
|
|
$this->assertSame(['A' => 'B'], $schema->discriminator->mapping);
|
|
}
|
|
|
|
|
|
public function badSchemaProvider()
|
|
{
|
|
yield [['properties' => ['a' => 'foo']], 'Unable to instantiate cebe\openapi\spec\Schema Object with data \'foo\''];
|
|
yield [['properties' => ['a' => 42]], 'Unable to instantiate cebe\openapi\spec\Schema Object with data \'42\''];
|
|
yield [['properties' => ['a' => false]], 'Unable to instantiate cebe\openapi\spec\Schema Object with data \'\''];
|
|
yield [['properties' => ['a' => new stdClass()]], "Unable to instantiate cebe\openapi\spec\Schema Object with data 'stdClass Object\n(\n)\n'"];
|
|
|
|
yield [['additionalProperties' => 'foo'], 'Schema::$additionalProperties MUST be either boolean or a Schema/Reference object, "string" given'];
|
|
yield [['additionalProperties' => 42], 'Schema::$additionalProperties MUST be either boolean or a Schema/Reference object, "integer" given'];
|
|
yield [['additionalProperties' => new stdClass()], 'Schema::$additionalProperties MUST be either boolean or a Schema/Reference object, "stdClass" given'];
|
|
// The last one can be supported in future, but now SpecBaseObjects::__construct() requires array explicitly
|
|
}
|
|
|
|
/**
|
|
* @dataProvider badSchemaProvider
|
|
*/
|
|
public function testPathsCanNotBeCreatedFromBullshit($config, $expectedException)
|
|
{
|
|
$this->expectException(\cebe\openapi\exceptions\TypeErrorException::class);
|
|
$this->expectExceptionMessage($expectedException);
|
|
|
|
new Schema($config);
|
|
}
|
|
|
|
public function testAllOf()
|
|
{
|
|
$json = <<<'JSON'
|
|
{
|
|
"components": {
|
|
"schemas": {
|
|
"identifier": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "string"}
|
|
}
|
|
},
|
|
"person": {
|
|
"allOf": [
|
|
{"$ref": "#/components/schemas/identifier"},
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
JSON;
|
|
$openApi = Reader::readFromJson($json);
|
|
$this->assertInstanceOf(Schema::class, $identifier = $openApi->components->schemas['identifier']);
|
|
$this->assertInstanceOf(Schema::class, $person = $openApi->components->schemas['person']);
|
|
|
|
$this->assertEquals('object', $identifier->type);
|
|
$this->assertTrue(is_array($person->allOf));
|
|
$this->assertCount(2, $person->allOf);
|
|
|
|
$this->assertInstanceOf(Reference::class, $person->allOf[0]);
|
|
$this->assertInstanceOf(Schema::class, $refResolved = $person->allOf[0]->resolve(new ReferenceContext($openApi, 'tmp://openapi.yaml')));
|
|
$this->assertInstanceOf(Schema::class, $person->allOf[1]);
|
|
|
|
$this->assertEquals('object', $refResolved->type);
|
|
$this->assertEquals('object', $person->allOf[1]->type);
|
|
|
|
$this->assertArrayHasKey('id', $refResolved->properties);
|
|
$this->assertArrayHasKey('name', $person->allOf[1]->properties);
|
|
}
|
|
|
|
/**
|
|
* Ensure Schema properties are accessable and have default values.
|
|
*/
|
|
public function testSchemaProperties()
|
|
{
|
|
$schema = new Schema([]);
|
|
$validProperties = [
|
|
// https://github.com/OAI/OpenAPI-Specification/blob/3.0.2/versions/3.0.2.md#schema-object
|
|
// The following properties are taken directly from the JSON Schema definition and follow the same specifications:
|
|
'title' => null,
|
|
'multipleOf' => null,
|
|
'maximum' => null,
|
|
'exclusiveMaximum' => null,
|
|
'minimum' => null,
|
|
'exclusiveMinimum' => null,
|
|
'maxLength' => null,
|
|
'minLength' => null,
|
|
'pattern' => null,
|
|
'maxItems' => null,
|
|
'minItems' => null,
|
|
'uniqueItems' => false,
|
|
'maxProperties' => null,
|
|
'minProperties' => null,
|
|
'required' => null, // if set, it should not be an empty array, according to the spec
|
|
'enum' => null, // if it is an array, it means restriction of values
|
|
// The following properties are taken from the JSON Schema definition but their definitions were adjusted to the OpenAPI Specification.
|
|
'type' => null,
|
|
'allOf' => null,
|
|
'oneOf' => null,
|
|
'anyOf' => null,
|
|
'not' => null,
|
|
'items' => null,
|
|
'properties' => [],
|
|
'additionalProperties' => true,
|
|
'description' => null,
|
|
'format' => null,
|
|
'default' => null,
|
|
// Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation:
|
|
'nullable' => false,
|
|
'readOnly' => false,
|
|
'writeOnly' => false,
|
|
'xml' => null,
|
|
'externalDocs' => null,
|
|
'example' => null,
|
|
'deprecated' => false,
|
|
];
|
|
|
|
foreach($validProperties as $property => $defaultValue) {
|
|
$this->assertEquals($defaultValue, $schema->$property, "testing property '$property'");
|
|
}
|
|
}
|
|
|
|
public function testRefAdditionalProperties()
|
|
{
|
|
$json = <<<'JSON'
|
|
{
|
|
"components": {
|
|
"schemas": {
|
|
"booleanProperties": {
|
|
"type": "boolean"
|
|
},
|
|
"person": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": {"$ref": "#/components/schemas/booleanProperties"}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
JSON;
|
|
$openApi = Reader::readFromJson($json);
|
|
$this->assertInstanceOf(Schema::class, $booleanProperties = $openApi->components->schemas['booleanProperties']);
|
|
$this->assertInstanceOf(Schema::class, $person = $openApi->components->schemas['person']);
|
|
|
|
$this->assertEquals('boolean', $booleanProperties->type);
|
|
$this->assertInstanceOf(Reference::class, $person->additionalProperties);
|
|
|
|
$this->assertInstanceOf(Schema::class, $refResolved = $person->additionalProperties->resolve(new ReferenceContext($openApi, 'tmp://openapi.yaml')));
|
|
|
|
$this->assertEquals('boolean', $refResolved->type);
|
|
|
|
$schema = new Schema(['additionalProperties' => new Reference(['$ref' => '#/here'], Schema::class)]);
|
|
$this->assertInstanceOf(Reference::class, $schema->additionalProperties);
|
|
}
|
|
|
|
/**
|
|
* Ensure that a property named "$ref" is not interpreted as a reference.
|
|
* @link https://github.com/OAI/OpenAPI-Specification/issues/2173
|
|
*/
|
|
public function testPropertyNameRef()
|
|
{
|
|
$json = <<<'JSON'
|
|
{
|
|
"components": {
|
|
"schemas": {
|
|
"person": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"$ref": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
JSON;
|
|
$openApi = Reader::readFromJson($json);
|
|
$this->assertInstanceOf(Schema::class, $person = $openApi->components->schemas['person']);
|
|
|
|
$this->assertEquals(['name', '$ref'], array_keys($person->properties));
|
|
$this->assertInstanceOf(Schema::class, $person->properties['name']);
|
|
$this->assertInstanceOf(Schema::class, $person->properties['$ref']);
|
|
$this->assertEquals('string', $person->properties['name']->type);
|
|
$this->assertEquals('string', $person->properties['$ref']->type);
|
|
}
|
|
}
|