Skip to content

Commit a1436c1

Browse files
committed
Additional test cases for OneOf, AnyOf and AllOf composition
1 parent 82fdff8 commit a1436c1

16 files changed

+1115
-21
lines changed

src/PropertyProcessor/Property/AbstractPropertyProcessor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ protected function addComposedValueValidator(PropertyInterface $property, array
109109

110110
$composedProperty = $propertyFactory
111111
->create(
112-
new PropertyCollectionProcessor(),
112+
$this->propertyCollectionProcessor,
113113
$this->schemaProcessor,
114114
$this->schema,
115115
$property->getName(),

tests/AbstractPHPModelGeneratorTest.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ protected function combineDataProvider(array $dataProvider1, array $dataProvider
231231
}
232232

233233
/**
234-
* Get the type for an object property
234+
* Get the annotated type for an object property
235235
*
236236
* @param object $object
237237
* @param string $property
@@ -249,6 +249,27 @@ protected function getPropertyType(object $object, string $property): string
249249

250250
return $matches[1];
251251
}
252+
253+
/**
254+
* Get the annotated return type for an object method
255+
*
256+
* @param object $object
257+
* @param string $property
258+
*
259+
* @return string
260+
*/
261+
protected function getMethodReturnType(object $object, string $method): string
262+
{
263+
$matches = [];
264+
preg_match(
265+
'/@return\s+([^\s]+)\s/',
266+
(new ReflectionClass($object))->getMethod($method)->getDocComment(),
267+
$matches
268+
);
269+
270+
return $matches[1];
271+
}
272+
252273
/**
253274
* Generate a unique name for a class
254275
*
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
<?php
2+
3+
namespace PHPModelGenerator\Tests\ComposedValue;
4+
5+
use PHPModelGenerator\Exception\InvalidArgumentException;
6+
use PHPModelGenerator\Tests\AbstractPHPModelGeneratorTest;
7+
use stdClass;
8+
9+
/**
10+
* Class ComposedAllOfTest
11+
*
12+
* @package PHPModelGenerator\Tests\ComposedValue
13+
*/
14+
class ComposedAllOfTest extends AbstractPHPModelGeneratorTest
15+
{
16+
/**
17+
* @dataProvider propertyLevelAllOfSchemaFileDataProvider
18+
*
19+
* @param string $schema
20+
*/
21+
public function testNotProvidedPropertyLevelAllOfIsValid(string $schema): void
22+
{
23+
$className = $this->generateClassFromFile($schema);
24+
25+
$object = new $className([]);
26+
$this->assertNull($object->getProperty());
27+
}
28+
29+
public function propertyLevelAllOfSchemaFileDataProvider(): array
30+
{
31+
return [
32+
'Property level composition' => ['ExtendedPropertyDefinition.json'],
33+
'Multiple objects' => ['ReferencedObjectSchema.json'],
34+
];
35+
}
36+
37+
/**
38+
* Throws an exception as it's not valid against any of the given schemas
39+
*/
40+
public function testNotProvidedObjectLevelAllOfNotMatchingAnyOptionThrowsAnException(): void
41+
{
42+
$this->expectException(InvalidArgumentException::class);
43+
$this->expectExceptionMessageRegExp('/^Invalid value for (.*?) declined by composition constraint$/');
44+
45+
$className = $this->generateClassFromFile('ObjectLevelCompositionRequired.json');
46+
47+
new $className([]);
48+
}
49+
50+
/**
51+
* Throws an exception as it's not valid against any of the given schemas
52+
*/
53+
public function testNotProvidedObjectLevelAllOfMatchingAllOptionsIsValid(): void
54+
{
55+
$className = $this->generateClassFromFile('ObjectLevelComposition.json');
56+
57+
$object = new $className([]);
58+
$this->assertEmpty($object->getIntegerProperty());
59+
$this->assertEmpty($object->getStringProperty());
60+
}
61+
62+
public function testAllOfTypePropertyHasTypeAnnotation(): void
63+
{
64+
$className = $this->generateClassFromFile('ReferencedObjectSchema.json');
65+
66+
$object = new $className([]);
67+
$regexp = '/ComposedAllOfTest[\w]*_Merged_[\w]*/';
68+
69+
$this->assertRegExp($regexp, $this->getPropertyType($object, 'property'));
70+
$this->assertRegExp($regexp, $this->getMethodReturnType($object, 'getProperty'));
71+
}
72+
73+
/**
74+
* @dataProvider validExtendedPropertyDataProvider
75+
*
76+
* @param $propertyValue
77+
*/
78+
public function testExtendedPropertyDefinitionWithValidValues($propertyValue): void
79+
{
80+
$className = $this->generateClassFromFile('ExtendedPropertyDefinition.json');
81+
82+
$object = new $className(['property' => $propertyValue]);
83+
// cast expected to float as an int is casted to an float internally for a number property
84+
$this->assertSame(is_int($propertyValue) ? (float) $propertyValue : $propertyValue, $object->getProperty());
85+
}
86+
87+
public function validExtendedPropertyDataProvider(): array
88+
{
89+
return [
90+
'null' => [null],
91+
'multiple matches - int 10' => [10],
92+
'multiple matches - int 20' => [20],
93+
'multiple matches - float 10.' => [10.],
94+
];
95+
}
96+
97+
/**
98+
* @dataProvider invalidExtendedPropertyDataProvider
99+
*
100+
* @param $propertyValue
101+
* @param string $exceptionMessage
102+
*/
103+
public function testExtendedPropertyDefinitionWithInvalidValuesThrowsAnException(
104+
$propertyValue,
105+
string $exceptionMessage
106+
): void {
107+
$this->expectException(InvalidArgumentException::class);
108+
$this->expectExceptionMessage($exceptionMessage);
109+
110+
$className = $this->generateClassFromFile('ExtendedPropertyDefinition.json');
111+
112+
new $className(['property' => $propertyValue]);
113+
}
114+
115+
public function invalidExtendedPropertyDataProvider(): array
116+
{
117+
return [
118+
'one match - int 12' => [12, 'Invalid value for property declined by composition constraint'],
119+
'one match - float 12.' => [12., 'Invalid value for property declined by composition constraint'],
120+
'one match - int 15' => [15, 'Invalid value for property declined by composition constraint'],
121+
'int 13' => [13, 'Invalid value for property declined by composition constraint'],
122+
'float 9.9' => [9.9, 'Value for property must not be smaller than 10'],
123+
'int 8' => [8, 'Value for property must not be smaller than 10'],
124+
'bool' => [true, 'invalid type for property'],
125+
'array' => [[], 'invalid type for property'],
126+
'object' => [new stdClass(), 'invalid type for property'],
127+
'string' => ['', 'invalid type for property'],
128+
];
129+
}
130+
131+
public function composedPropertyWithReferencedSchemaDataProvider(): array
132+
{
133+
return [
134+
'null' => [null],
135+
'string matching required length' => ['Hanne'],
136+
];
137+
}
138+
139+
public function testMatchingObjectPropertyWithReferencedPersonSchemaIsValid(): void
140+
{
141+
$className = $this->generateClassFromFile('ReferencedObjectSchema.json');
142+
143+
$object = new $className(['property' => ['name' => 'Ha', 'age' => 42, 'race' => 'Ho']]);
144+
145+
$this->assertTrue(is_object($object->getProperty()));
146+
$this->assertSame('Ha', $object->getProperty()->getName());
147+
$this->assertSame(42, $object->getProperty()->getAge());
148+
}
149+
150+
public function referencedPersonDataProvider(): array
151+
{
152+
return [
153+
'ReferencedObjectSchema.json' => ['ReferencedObjectSchema.json'],
154+
];
155+
}
156+
157+
/**
158+
* @dataProvider invalidObjectPropertyWithReferencedPersonSchemaDataProvider
159+
*
160+
* @param $propertyValue
161+
*/
162+
public function testNotMatchingObjectPropertyWithReferencedPersonSchemaThrowsAnException($propertyValue): void {
163+
$this->expectException(InvalidArgumentException::class);
164+
$this->expectExceptionMessage('Invalid value for property declined by composition constraint');
165+
166+
$className = $this->generateClassFromFile('ReferencedObjectSchema.json');
167+
168+
new $className(['property' => $propertyValue]);
169+
}
170+
171+
public function invalidObjectPropertyWithReferencedPersonSchemaDataProvider(): array
172+
{
173+
return [
174+
'int' => [0],
175+
'float' => [0.92],
176+
'bool' => [true],
177+
'object' => [new stdClass()],
178+
'string' => ['Hannes'],
179+
'one match - first option' => [['name' => 'Hannes', 'age' => 42]],
180+
'one match - second option' => [['race' => 'Horse']],
181+
'one match - Missing property' => [['name' => 'Hannes', 'race' => 'Horse']],
182+
'one match - Additional properties' => [['name' => 'Hannes', 'age' => 42, 'alive' => true]],
183+
'Matching object with invalid type' => [['name' => 'Hannes', 'age' => '42', 'race' => 'Horse']],
184+
'Matching object with invalid data' => [['name' => 'H', 'age' => 42, 'race' => 'Horse']],
185+
];
186+
}
187+
188+
/**
189+
* @dataProvider invalidObjectPropertyWithReferencedPetSchemaDataProvider
190+
*
191+
* @param $propertyValue
192+
*/
193+
public function testNotMatchingObjectPropertyWithReferencedPetSchemaThrowsAnException($propertyValue): void
194+
{
195+
$this->expectException(InvalidArgumentException::class);
196+
$this->expectExceptionMessage('Invalid value for property declined by composition constraint');
197+
198+
$className = $this->generateClassFromFile('ReferencedObjectSchema.json');
199+
200+
new $className(['property' => $propertyValue]);
201+
}
202+
203+
public function invalidObjectPropertyWithReferencedPetSchemaDataProvider(): array
204+
{
205+
return [
206+
'int' => [0],
207+
'float' => [0.92],
208+
'bool' => [true],
209+
'object' => [new stdClass()],
210+
'string' => ['Horse'],
211+
'empty array' => [[]],
212+
'Too many properties' => [['race' => 'Horse', 'alive' => true]],
213+
'Matching object with invalid type' => [['race' => 123]],
214+
'Matching object with invalid data' => [['race' => 'H']],
215+
];
216+
}
217+
218+
/**
219+
* @dataProvider validComposedObjectDataProvider
220+
* @dataProvider validComposedObjectWithRequiredPropertiesDataProvider
221+
*
222+
* @param array $input
223+
* @param string|null $stringPropertyValue
224+
* @param int|null $intPropertyValue
225+
*/
226+
public function testMatchingPropertyForComposedAllOfObjectIsValid(
227+
array $input,
228+
?string $stringPropertyValue,
229+
?int $intPropertyValue
230+
): void {
231+
$className = $this->generateClassFromFile('ObjectLevelComposition.json');
232+
233+
$object = new $className($input);
234+
$this->assertSame($stringPropertyValue, $object->getStringProperty());
235+
$this->assertSame($intPropertyValue, $object->getIntegerProperty());
236+
}
237+
238+
public function validComposedObjectDataProvider(): array
239+
{
240+
return [
241+
'no properties' => [[], null, null],
242+
'only additional property' => [['test' => 1234], null, null],
243+
'both null' => [['integerProperty' => null, 'stringProperty' => null], null, null],
244+
];
245+
}
246+
247+
/**
248+
* @dataProvider invalidComposedObjectDataProvider
249+
*
250+
* @param array $input
251+
*/
252+
public function testNotMatchingPropertyForComposedAllOfObjectThrowsAnException(array $input): void
253+
{
254+
$this->expectException(InvalidArgumentException::class);
255+
256+
$className = $this->generateClassFromFile('ObjectLevelComposition.json');
257+
258+
new $className($input);
259+
}
260+
261+
public function invalidComposedObjectDataProvider()
262+
{
263+
return [
264+
'both invalid types' => [['integerProperty' => '10', 'stringProperty' => 10]],
265+
'both invalid types float' => [['integerProperty' => 0.4, 'stringProperty' => 0.4]],
266+
'both invalid types bool' => [['integerProperty' => true, 'stringProperty' => false]],
267+
'both invalid types object' => [['integerProperty' => new stdClass(), 'stringProperty' => new stdClass()]],
268+
'both invalid types array' => [['integerProperty' => [], 'stringProperty' => []]],
269+
'one invalid negative int' => [['integerProperty' => -10, 'stringProperty' => -10], null, -10],
270+
'one invalid zero int' => [['integerProperty' => 0, 'stringProperty' => 0], null, 0],
271+
'one invalid positive int' => [['integerProperty' => 10, 'stringProperty' => 10], null, 10],
272+
'one invalid empty string' => [['integerProperty' => '', 'stringProperty' => ''], '', null],
273+
'one invalid numeric string' => [['integerProperty' => '100', 'stringProperty' => '100'], '100', null],
274+
'one invalid filled string' => [['integerProperty' => 'Hello', 'stringProperty' => 'Hello'], 'Hello', null],
275+
'one invalid additional property' => [['integerProperty' => 'A', 'stringProperty' => 'A', 'test' => 1234], 'A', null],
276+
];
277+
}
278+
279+
280+
/**
281+
* @dataProvider validComposedObjectWithRequiredPropertiesDataProvider
282+
* Must throw an exception as only one option matches
283+
*
284+
* @param array $input
285+
*/
286+
public function testMatchingPropertyForComposedAllOfObjectWithRequiredPropertiesThrowsAnException(
287+
array $input
288+
): void {
289+
$this->expectException(InvalidArgumentException::class);
290+
291+
$className = $this->generateClassFromFile('ObjectLevelCompositionRequired.json');
292+
293+
new $className($input);
294+
}
295+
296+
/**
297+
* @dataProvider invalidComposedObjectDataProvider
298+
*
299+
* @param array $input
300+
*/
301+
public function testNotMatchingPropertyForComposedAllOfObjectWithRequiredPropertiesThrowsAnException(
302+
array $input
303+
): void {
304+
$this->expectException(InvalidArgumentException::class);
305+
306+
$className = $this->generateClassFromFile('ObjectLevelCompositionRequired.json');
307+
308+
new $className($input);
309+
}
310+
311+
public function validComposedObjectWithRequiredPropertiesDataProvider(): array
312+
{
313+
return [
314+
'only int property' => [['integerProperty' => 4], null, 4],
315+
'only string property' => [['stringProperty' => 'B'], 'B', null],
316+
'only int property with additional property' => [['integerProperty' => 4, 'test' => 1234], null, 4],
317+
'only string property with additional property' => [['stringProperty' => 'B', 'test' => 1234], 'B', null],
318+
];
319+
}
320+
}

0 commit comments

Comments
 (0)