• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 20528769615

26 Dec 2025 08:17PM UTC coverage: 25.119%. First build
20528769615

Pull #7629

github

web-flow
Merge 4691c25d0 into 38d474d1b
Pull Request #7629: fix: add support for normalization/denormalization with attributes

24 of 236 new or added lines in 8 files covered. (10.17%)

14638 of 58274 relevant lines covered (25.12%)

29.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/JsonSchema/Tests/SchemaFactoryTest.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\JsonSchema\Tests;
15

16
use ApiPlatform\JsonSchema\DefinitionNameFactory;
17
use ApiPlatform\JsonSchema\Schema;
18
use ApiPlatform\JsonSchema\SchemaFactory;
19
use ApiPlatform\JsonSchema\Tests\Fixtures\ApiResource\ChildAttributeDummy;
20
use ApiPlatform\JsonSchema\Tests\Fixtures\ApiResource\OverriddenOperationDummy;
21
use ApiPlatform\JsonSchema\Tests\Fixtures\ApiResource\ParentAttributeDummy;
22
use ApiPlatform\JsonSchema\Tests\Fixtures\DummyResourceInterface;
23
use ApiPlatform\JsonSchema\Tests\Fixtures\Enum\GenderTypeEnum;
24
use ApiPlatform\JsonSchema\Tests\Fixtures\GenericChild;
25
use ApiPlatform\JsonSchema\Tests\Fixtures\NotAResource;
26
use ApiPlatform\JsonSchema\Tests\Fixtures\NotAResourceWithUnionIntersectTypes;
27
use ApiPlatform\JsonSchema\Tests\Fixtures\Serializable;
28
use ApiPlatform\Metadata\ApiProperty;
29
use ApiPlatform\Metadata\ApiResource;
30
use ApiPlatform\Metadata\Get;
31
use ApiPlatform\Metadata\Operations;
32
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
33
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
34
use ApiPlatform\Metadata\Property\PropertyNameCollection;
35
use ApiPlatform\Metadata\Put;
36
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
37
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
38
use ApiPlatform\Metadata\ResourceClassResolverInterface;
39
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
40
use PHPUnit\Framework\TestCase;
41
use Prophecy\Argument;
42
use Prophecy\PhpUnit\ProphecyTrait;
43
use Symfony\Component\PropertyInfo\Type as LegacyType;
44
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
45
use Symfony\Component\TypeInfo\Type;
46

47
class SchemaFactoryTest extends TestCase
48
{
49
    use ProphecyTrait;
50

51
    #[IgnoreDeprecations]
52
    public function testBuildSchemaForNonResourceClassLegacy(): void
53
    {
54
        if (!class_exists(LegacyType::class)) {
×
55
            $this->markTestSkipped();
×
56
        }
57
        $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.');
×
58
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
59

60
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
61
        $propertyNameCollectionFactoryProphecy->create(NotAResource::class, Argument::cetera())->willReturn(new PropertyNameCollection(['foo', 'bar', 'genderType']));
×
62

63
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
64
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn(
×
65
            (new ApiProperty())
×
66
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])
×
67
                ->withReadable(true)
×
68
                ->withSchema(['type' => 'string'])
×
69
        );
×
70
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn(
×
71
            (new ApiProperty())
×
72
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_INT)])
×
73
                ->withReadable(true)
×
74
                ->withDefault('default_bar')
×
75
                ->withExample('example_bar')
×
76
                ->withSchema(['type' => 'integer', 'default' => 'default_bar', 'example' => 'example_bar'])
×
77
        );
×
78
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'genderType', Argument::cetera())->willReturn(
×
79
            (new ApiProperty())
×
80
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT)])
×
81
                ->withReadable(true)
×
82
                ->withDefault('male')
×
83
                ->withSchema(['type' => 'object', 'default' => 'male', 'example' => 'male'])
×
84
        );
×
85

86
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
87
        $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false);
×
88

89
        $definitionNameFactory = new DefinitionNameFactory();
×
90

91
        $schemaFactory = new SchemaFactory(
×
92
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
93
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
94
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
95
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
96
            definitionNameFactory: $definitionNameFactory,
×
97
        );
×
98
        $resultSchema = $schemaFactory->buildSchema(NotAResource::class);
×
99

100
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
101
        $definitions = $resultSchema->getDefinitions();
×
102

103
        $this->assertSame((new \ReflectionClass(NotAResource::class))->getShortName(), $rootDefinitionKey);
×
104
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
105
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
106
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
107
        $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]);
×
108
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
109
        $this->assertArrayHasKey('foo', $definitions[$rootDefinitionKey]['properties']);
×
110
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['foo']);
×
111
        $this->assertArrayNotHasKey('default', $definitions[$rootDefinitionKey]['properties']['foo']);
×
112
        $this->assertArrayNotHasKey('example', $definitions[$rootDefinitionKey]['properties']['foo']);
×
113
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['foo']['type']);
×
114
        $this->assertArrayHasKey('bar', $definitions[$rootDefinitionKey]['properties']);
×
115
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['bar']);
×
116
        $this->assertArrayHasKey('default', $definitions[$rootDefinitionKey]['properties']['bar']);
×
117
        $this->assertArrayHasKey('example', $definitions[$rootDefinitionKey]['properties']['bar']);
×
118
        $this->assertSame('integer', $definitions[$rootDefinitionKey]['properties']['bar']['type']);
×
119
        $this->assertSame('default_bar', $definitions[$rootDefinitionKey]['properties']['bar']['default']);
×
120
        $this->assertSame('example_bar', $definitions[$rootDefinitionKey]['properties']['bar']['example']);
×
121

122
        $this->assertArrayHasKey('genderType', $definitions[$rootDefinitionKey]['properties']);
×
123
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
124
        $this->assertArrayHasKey('default', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
125
        $this->assertArrayHasKey('example', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
126
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']);
×
127
        $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['default']);
×
128
        $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['example']);
×
129
    }
130

131
    public function testBuildSchemaForNonResourceClass(): void
132
    {
133
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) { // @phpstan-ignore-line symfony/property-info 6.4 is still allowed and this may be true
×
134
            $this->markTestSkipped('This test only supports type-info component');
×
135
        }
136

137
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
138

139
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
140
        $propertyNameCollectionFactoryProphecy->create(NotAResource::class, Argument::cetera())->willReturn(new PropertyNameCollection(['foo', 'bar', 'genderType', 'items']));
×
141

142
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
143
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn(
×
144
            (new ApiProperty())
×
145
                ->withNativeType(Type::string())
×
146
                ->withReadable(true)
×
147
                ->withSchema(['type' => 'string'])
×
148
        );
×
149
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn(
×
150
            (new ApiProperty())
×
151
                ->withNativeType(Type::int())
×
152
                ->withReadable(true)
×
153
                ->withDefault('default_bar')
×
154
                ->withExample('example_bar')
×
155
                ->withSchema(['type' => 'integer', 'default' => 'default_bar', 'example' => 'example_bar'])
×
156
        );
×
157
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'genderType', Argument::cetera())->willReturn(
×
158
            (new ApiProperty())
×
159
                ->withNativeType(Type::object())
×
160
                ->withReadable(true)
×
161
                ->withDefault('male')
×
162
                ->withSchema(['type' => 'object', 'default' => 'male', 'example' => 'male'])
×
163
        );
×
164
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'items', Argument::cetera())->willReturn(
×
165
            (new ApiProperty())
×
166
                ->withNativeType(
×
167
                    Type::generic(Type::object(GenericChild::class), Type::int()),
×
168
                )
×
169
                ->withReadable(true)
×
170
                ->withSchema(['type' => Schema::UNKNOWN_TYPE])
×
171
        );
×
172

173
        $propertyNameCollectionFactoryProphecy->create(GenericChild::class, Argument::cetera())
×
174
            ->willReturn(new PropertyNameCollection(['property']));
×
175
        $propertyMetadataFactoryProphecy->create(GenericChild::class, 'property', Argument::cetera())
×
176
            ->willReturn(
×
177
                (new ApiProperty())
×
178
                    ->withNativeType(Type::string())
×
179
                    ->withReadable(true)
×
180
                    ->withSchema(['type' => 'string'])
×
181
            );
×
182

183
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
184
        $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false);
×
185
        $resourceClassResolverProphecy->isResourceClass(GenericChild::class)->willReturn(false);
×
186

187
        $definitionNameFactory = new DefinitionNameFactory();
×
188

189
        $schemaFactory = new SchemaFactory(
×
190
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
191
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
192
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
193
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
194
            definitionNameFactory: $definitionNameFactory,
×
195
        );
×
196
        $resultSchema = $schemaFactory->buildSchema(NotAResource::class);
×
197

198
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
199
        $definitions = $resultSchema->getDefinitions();
×
200

201
        $this->assertSame((new \ReflectionClass(NotAResource::class))->getShortName(), $rootDefinitionKey);
×
202
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
203
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
204
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
205
        $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]);
×
206
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
207
        $this->assertArrayHasKey('foo', $definitions[$rootDefinitionKey]['properties']);
×
208
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['foo']);
×
209
        $this->assertArrayNotHasKey('default', $definitions[$rootDefinitionKey]['properties']['foo']);
×
210
        $this->assertArrayNotHasKey('example', $definitions[$rootDefinitionKey]['properties']['foo']);
×
211
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['foo']['type']);
×
212
        $this->assertArrayHasKey('bar', $definitions[$rootDefinitionKey]['properties']);
×
213
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['bar']);
×
214
        $this->assertArrayHasKey('default', $definitions[$rootDefinitionKey]['properties']['bar']);
×
215
        $this->assertArrayHasKey('example', $definitions[$rootDefinitionKey]['properties']['bar']);
×
216
        $this->assertSame('integer', $definitions[$rootDefinitionKey]['properties']['bar']['type']);
×
217
        $this->assertSame('default_bar', $definitions[$rootDefinitionKey]['properties']['bar']['default']);
×
218
        $this->assertSame('example_bar', $definitions[$rootDefinitionKey]['properties']['bar']['example']);
×
219

220
        $this->assertArrayHasKey('genderType', $definitions[$rootDefinitionKey]['properties']);
×
221
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
222
        $this->assertArrayHasKey('default', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
223
        $this->assertArrayHasKey('example', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
224
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']);
×
225
        $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['default']);
×
226
        $this->assertSame('male', $definitions[$rootDefinitionKey]['properties']['genderType']['example']);
×
227

228
        $this->assertArrayHasKey('items', $definitions[$rootDefinitionKey]['properties']);
×
229
        $this->assertArrayHasKey('$ref', $definitions[$rootDefinitionKey]['properties']['items']);
×
230
        $this->assertSame('#/definitions/GenericChild', $definitions[$rootDefinitionKey]['properties']['items']['$ref']);
×
231
    }
232

233
    #[IgnoreDeprecations]
234
    public function testBuildSchemaForNonResourceClassWithUnionIntersectTypesLegacy(): void
235
    {
236
        if (!class_exists(LegacyType::class)) {
×
237
            $this->markTestSkipped();
×
238
        }
239
        $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.');
×
240
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
241

242
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
243
        $propertyNameCollectionFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, Argument::cetera())->willReturn(new PropertyNameCollection(['ignoredProperty', 'unionType', 'intersectType']));
×
244

245
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
246
        $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'ignoredProperty', Argument::cetera())->willReturn(
×
247
            (new ApiProperty())
×
248
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, nullable: true)])
×
249
                ->withReadable(true)
×
250
                ->withSchema(['type' => ['string', 'null']])
×
251
        );
×
252
        $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'unionType', Argument::cetera())->willReturn(
×
253
            (new ApiProperty())
×
254
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, nullable: true), new LegacyType(LegacyType::BUILTIN_TYPE_INT, nullable: true), new LegacyType(LegacyType::BUILTIN_TYPE_FLOAT, nullable: true)])
×
255
                ->withReadable(true)
×
256
                ->withSchema(['oneOf' => [
×
257
                    ['type' => ['string', 'null']],
×
258
                    ['type' => ['integer', 'null']],
×
259
                ]])
×
260
        );
×
261
        $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'intersectType', Argument::cetera())->willReturn(
×
262
            (new ApiProperty())
×
263
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, class: Serializable::class), new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, class: DummyResourceInterface::class)])
×
264
                ->withReadable(true)
×
265
                ->withSchema(['type' => 'object'])
×
266
        );
×
267

268
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
269
        $resourceClassResolverProphecy->isResourceClass(NotAResourceWithUnionIntersectTypes::class)->willReturn(false);
×
270

271
        $definitionNameFactory = new DefinitionNameFactory();
×
272

273
        $schemaFactory = new SchemaFactory(
×
274
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
275
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
276
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
277
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
278
            definitionNameFactory: $definitionNameFactory,
×
279
        );
×
280
        $resultSchema = $schemaFactory->buildSchema(NotAResourceWithUnionIntersectTypes::class);
×
281

282
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
283
        $definitions = $resultSchema->getDefinitions();
×
284

285
        $this->assertSame((new \ReflectionClass(NotAResourceWithUnionIntersectTypes::class))->getShortName(), $rootDefinitionKey);
×
286
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
287
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
288
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
289
        $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]);
×
290
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
291

292
        $this->assertArrayHasKey('ignoredProperty', $definitions[$rootDefinitionKey]['properties']);
×
293
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['ignoredProperty']);
×
294
        $this->assertSame(['string', 'null'], $definitions[$rootDefinitionKey]['properties']['ignoredProperty']['type']);
×
295
        $this->assertArrayHasKey('unionType', $definitions[$rootDefinitionKey]['properties']);
×
296
        $this->assertArrayHasKey('oneOf', $definitions[$rootDefinitionKey]['properties']['unionType']);
×
297
        $this->assertCount(2, $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf']);
×
298
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][0]);
×
299
        $this->assertSame(['string', 'null'], $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][0]['type']);
×
300
        $this->assertSame(['integer', 'null'], $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][1]['type']);
×
301

302
        $this->assertArrayHasKey('intersectType', $definitions[$rootDefinitionKey]['properties']);
×
303
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['intersectType']);
×
304
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['intersectType']['type']);
×
305
    }
306

307
    public function testBuildSchemaForNonResourceClassWithUnionIntersectTypes(): void
308
    {
309
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
310

311
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
312
        $propertyNameCollectionFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, Argument::cetera())->willReturn(new PropertyNameCollection(['ignoredProperty', 'unionType', 'intersectType']));
×
313

314
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
315
        $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'ignoredProperty', Argument::cetera())->willReturn(
×
316
            (new ApiProperty())
×
317
                ->withNativeType(Type::nullable(Type::string()))
×
318
                ->withReadable(true)
×
319
                ->withSchema(['type' => ['string', 'null']])
×
320
        );
×
321
        $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'unionType', Argument::cetera())->willReturn(
×
322
            (new ApiProperty())
×
323
                ->withNativeType(Type::union(Type::string(), Type::int(), Type::float(), Type::null()))
×
324
                ->withReadable(true)
×
325
                ->withSchema(['oneOf' => [
×
326
                    ['type' => ['string', 'null']],
×
327
                    ['type' => ['integer', 'null']],
×
328
                ]])
×
329
        );
×
330
        $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'intersectType', Argument::cetera())->willReturn(
×
331
            (new ApiProperty())
×
332
                ->withNativeType(Type::intersection(Type::object(Serializable::class), Type::object(DummyResourceInterface::class)))
×
333
                ->withReadable(true)
×
334
                ->withSchema(['type' => 'object'])
×
335
        );
×
336

337
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
338
        $resourceClassResolverProphecy->isResourceClass(NotAResourceWithUnionIntersectTypes::class)->willReturn(false);
×
339

340
        $definitionNameFactory = new DefinitionNameFactory();
×
341

342
        $schemaFactory = new SchemaFactory(
×
343
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
344
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
345
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
346
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
347
            definitionNameFactory: $definitionNameFactory,
×
348
        );
×
349
        $resultSchema = $schemaFactory->buildSchema(NotAResourceWithUnionIntersectTypes::class);
×
350

351
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
352
        $definitions = $resultSchema->getDefinitions();
×
353

354
        $this->assertSame((new \ReflectionClass(NotAResourceWithUnionIntersectTypes::class))->getShortName(), $rootDefinitionKey);
×
355
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
356
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
357
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
358
        $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]);
×
359
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
360

361
        $this->assertArrayHasKey('ignoredProperty', $definitions[$rootDefinitionKey]['properties']);
×
362
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['ignoredProperty']);
×
363
        $this->assertSame(['string', 'null'], $definitions[$rootDefinitionKey]['properties']['ignoredProperty']['type']);
×
364

365
        $this->assertArrayHasKey('unionType', $definitions[$rootDefinitionKey]['properties']);
×
366
        $this->assertArrayHasKey('oneOf', $definitions[$rootDefinitionKey]['properties']['unionType']);
×
367
        $this->assertCount(2, $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf']);
×
368
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][0]);
×
369
        $this->assertSame(['string', 'null'], $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][0]['type']);
×
370
        $this->assertSame(['integer', 'null'], $definitions[$rootDefinitionKey]['properties']['unionType']['oneOf'][1]['type']);
×
371

372
        $this->assertArrayHasKey('intersectType', $definitions[$rootDefinitionKey]['properties']);
×
373
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['intersectType']);
×
374
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['intersectType']['type']);
×
375
    }
376

377
    #[IgnoreDeprecations]
378
    public function testBuildSchemaWithSerializerGroupsLegacy(): void
379
    {
380
        if (!class_exists(LegacyType::class)) {
×
381
            $this->markTestSkipped();
×
382
        }
383
        $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.');
×
384
        $shortName = (new \ReflectionClass(OverriddenOperationDummy::class))->getShortName();
×
385
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
386
        $operation = (new Put())->withName('put')->withNormalizationContext([
×
387
            'groups' => 'overridden_operation_dummy_put',
×
388
            AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
×
389
        ])->withShortName($shortName)->withValidationContext(['groups' => ['validation_groups_dummy_put']]);
×
390
        $resourceMetadataFactoryProphecy->create(OverriddenOperationDummy::class)
×
391
            ->willReturn(
×
392
                new ResourceMetadataCollection(OverriddenOperationDummy::class, [
×
393
                    (new ApiResource())->withOperations(new Operations(['put' => $operation])),
×
394
                ])
×
395
            );
×
396

397
        $serializerGroup = 'custom_operation_dummy';
×
398

399
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
400
        $propertyNameCollectionFactoryProphecy->create(OverriddenOperationDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['alias', 'description', 'genderType']));
×
401

402
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
403
        $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'alias', Argument::type('array'))->willReturn(
×
404
            (new ApiProperty())
×
405
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])
×
406
                ->withReadable(true)
×
407
                ->withSchema(['type' => 'string'])
×
408
        );
×
409
        $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'description', Argument::type('array'))->willReturn(
×
410
            (new ApiProperty())
×
411
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING)])
×
412
                ->withReadable(true)
×
413
                ->withSchema(['type' => 'string'])
×
414
        );
×
415
        $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'genderType', Argument::type('array'))->willReturn(
×
416
            (new ApiProperty())
×
417
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, false, GenderTypeEnum::class)])
×
418
                ->withReadable(true)
×
419
                ->withDefault(GenderTypeEnum::MALE)
×
420
                ->withSchema(['type' => 'object'])
×
421
        );
×
422

423
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
424
        $resourceClassResolverProphecy->isResourceClass(OverriddenOperationDummy::class)->willReturn(true);
×
425
        $resourceClassResolverProphecy->isResourceClass(GenderTypeEnum::class)->willReturn(true);
×
426

427
        $definitionNameFactory = new DefinitionNameFactory();
×
428

429
        $schemaFactory = new SchemaFactory(
×
430
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
431
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
432
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
433
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
434
            definitionNameFactory: $definitionNameFactory,
×
435
        );
×
436
        $resultSchema = $schemaFactory->buildSchema(OverriddenOperationDummy::class, 'json', Schema::TYPE_OUTPUT, null, null, ['groups' => $serializerGroup, AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false]);
×
437

438
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
439
        $definitions = $resultSchema->getDefinitions();
×
440

441
        $this->assertSame((new \ReflectionClass(OverriddenOperationDummy::class))->getShortName().'-'.$serializerGroup, $rootDefinitionKey);
×
442
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
443
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
444
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
445
        $this->assertFalse($definitions[$rootDefinitionKey]['additionalProperties']);
×
446
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
447
        $this->assertArrayHasKey('alias', $definitions[$rootDefinitionKey]['properties']);
×
448
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['alias']);
×
449
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['alias']['type']);
×
450
        $this->assertArrayHasKey('description', $definitions[$rootDefinitionKey]['properties']);
×
451
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['description']);
×
452
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['description']['type']);
×
453
        $this->assertArrayHasKey('genderType', $definitions[$rootDefinitionKey]['properties']);
×
454
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
455
        $this->assertArrayNotHasKey('default', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
456
        $this->assertArrayNotHasKey('example', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
457
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']);
×
458
    }
459

460
    public function testBuildSchemaWithSerializerGroups(): void
461
    {
462
        $shortName = (new \ReflectionClass(OverriddenOperationDummy::class))->getShortName();
×
463
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
464
        $operation = (new Put())->withName('put')->withNormalizationContext([
×
465
            'groups' => 'overridden_operation_dummy_put',
×
466
            AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
×
467
        ])->withShortName($shortName)->withValidationContext(['groups' => ['validation_groups_dummy_put']]);
×
468
        $resourceMetadataFactoryProphecy->create(OverriddenOperationDummy::class)
×
469
            ->willReturn(
×
470
                new ResourceMetadataCollection(OverriddenOperationDummy::class, [
×
471
                    (new ApiResource())->withOperations(new Operations(['put' => $operation])),
×
472
                ])
×
473
            );
×
474

475
        $serializerGroup = 'custom_operation_dummy';
×
476

477
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
478
        $propertyNameCollectionFactoryProphecy->create(OverriddenOperationDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['alias', 'description', 'genderType']));
×
479

480
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
481
        $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'alias', Argument::type('array'))->willReturn(
×
482
            (new ApiProperty())
×
483
                ->withNativeType(Type::string())
×
484
                ->withReadable(true)
×
485
                ->withSchema(['type' => 'string'])
×
486
        );
×
487
        $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'description', Argument::type('array'))->willReturn(
×
488
            (new ApiProperty())
×
489
                ->withNativeType(Type::string())
×
490
                ->withReadable(true)
×
491
                ->withSchema(['type' => 'string'])
×
492
        );
×
493
        $propertyMetadataFactoryProphecy->create(OverriddenOperationDummy::class, 'genderType', Argument::type('array'))->willReturn(
×
494
            (new ApiProperty())
×
495
                ->withNativeType(Type::enum(GenderTypeEnum::class))
×
496
                ->withReadable(true)
×
497
                ->withDefault(GenderTypeEnum::MALE)
×
498
                ->withSchema(['type' => 'object'])
×
499
        );
×
500
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
501
        $resourceClassResolverProphecy->isResourceClass(OverriddenOperationDummy::class)->willReturn(true);
×
502
        $resourceClassResolverProphecy->isResourceClass(GenderTypeEnum::class)->willReturn(true);
×
503

504
        $definitionNameFactory = new DefinitionNameFactory();
×
505

506
        $schemaFactory = new SchemaFactory(
×
507
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
508
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
509
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
510
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
511
            definitionNameFactory: $definitionNameFactory,
×
512
        );
×
513
        $resultSchema = $schemaFactory->buildSchema(OverriddenOperationDummy::class, 'json', Schema::TYPE_OUTPUT, null, null, ['groups' => $serializerGroup, AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false]);
×
514

515
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
516
        $definitions = $resultSchema->getDefinitions();
×
517

518
        $this->assertSame((new \ReflectionClass(OverriddenOperationDummy::class))->getShortName().'-'.$serializerGroup, $rootDefinitionKey);
×
519
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
520
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
521
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
522
        $this->assertFalse($definitions[$rootDefinitionKey]['additionalProperties']);
×
523
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
524
        $this->assertArrayHasKey('alias', $definitions[$rootDefinitionKey]['properties']);
×
525
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['alias']);
×
526
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['alias']['type']);
×
527
        $this->assertArrayHasKey('description', $definitions[$rootDefinitionKey]['properties']);
×
528
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['description']);
×
529
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['description']['type']);
×
530
        $this->assertArrayHasKey('genderType', $definitions[$rootDefinitionKey]['properties']);
×
531
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
532
        $this->assertArrayNotHasKey('default', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
533
        $this->assertArrayNotHasKey('example', $definitions[$rootDefinitionKey]['properties']['genderType']);
×
534
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['genderType']['type']);
×
535
    }
536

537
    public function testBuildSchemaWithSerializerAttributes(): void
538
    {
NEW
539
        $shortName = (new \ReflectionClass(ParentAttributeDummy::class))->getShortName();
×
NEW
540
        $childShortName = (new \ReflectionClass(ChildAttributeDummy::class))->getShortName();
×
NEW
541
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
NEW
542
        $serializerAttributes = ['title', 'child' => ['name']];
×
NEW
543
        $operation = (new Get())->withName('get')->withNormalizationContext([
×
NEW
544
            'attributes' => $serializerAttributes,
×
NEW
545
            AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
×
NEW
546
        ])->withShortName($shortName);
×
NEW
547
        $resourceMetadataFactoryProphecy->create(ParentAttributeDummy::class)
×
NEW
548
            ->willReturn(
×
NEW
549
                new ResourceMetadataCollection(ParentAttributeDummy::class, [
×
NEW
550
                    (new ApiResource())->withOperations(new Operations(['get' => $operation])),
×
NEW
551
                ])
×
NEW
552
            );
×
NEW
553
        $childOperation = (new Get())->withName('get')->withShortName($childShortName);
×
NEW
554
        $resourceMetadataFactoryProphecy->create(ChildAttributeDummy::class)
×
NEW
555
            ->willReturn(
×
NEW
556
                new ResourceMetadataCollection(ChildAttributeDummy::class, [
×
NEW
557
                    (new ApiResource())->withOperations(new Operations(['get' => $childOperation])),
×
NEW
558
                ])
×
NEW
559
            );
×
560

NEW
561
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
562
        $propertyNameCollectionFactoryProphecy->create(ParentAttributeDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['title', 'child']));
×
NEW
563
        $propertyNameCollectionFactoryProphecy->create(ChildAttributeDummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['name']));
×
564

NEW
565
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
NEW
566
        $propertyMetadataFactoryProphecy->create(ParentAttributeDummy::class, 'title', Argument::type('array'))->willReturn(
×
NEW
567
            (new ApiProperty())
×
NEW
568
                ->withNativeType(Type::string())
×
NEW
569
                ->withReadable(true)
×
NEW
570
                ->withSchema(['type' => 'string'])
×
NEW
571
        );
×
NEW
572
        $propertyMetadataFactoryProphecy->create(ParentAttributeDummy::class, 'child', Argument::type('array'))->willReturn(
×
NEW
573
            (new ApiProperty())
×
NEW
574
                ->withNativeType(Type::object(ChildAttributeDummy::class))
×
NEW
575
                ->withReadable(true)
×
NEW
576
                ->withSchema(['type' => Schema::UNKNOWN_TYPE])
×
NEW
577
        );
×
NEW
578
        $propertyMetadataFactoryProphecy->create(ChildAttributeDummy::class, 'name', Argument::type('array'))->willReturn(
×
NEW
579
            (new ApiProperty())
×
NEW
580
                ->withNativeType(Type::string())
×
NEW
581
                ->withReadable(true)
×
NEW
582
                ->withSchema(['type' => 'string'])
×
NEW
583
        );
×
NEW
584
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
NEW
585
        $resourceClassResolverProphecy->isResourceClass(ParentAttributeDummy::class)->willReturn(true);
×
NEW
586
        $resourceClassResolverProphecy->isResourceClass(ChildAttributeDummy::class)->willReturn(true);
×
587

NEW
588
        $definitionNameFactory = new DefinitionNameFactory();
×
589

NEW
590
        $schemaFactory = new SchemaFactory(
×
NEW
591
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
NEW
592
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
NEW
593
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
NEW
594
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
NEW
595
            definitionNameFactory: $definitionNameFactory,
×
NEW
596
        );
×
NEW
597
        $resultSchema = $schemaFactory->buildSchema(ParentAttributeDummy::class, 'json', Schema::TYPE_OUTPUT, null, null, ['attributes' => $serializerAttributes, AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false]);
×
598

NEW
599
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
NEW
600
        $definitions = $resultSchema->getDefinitions();
×
601

NEW
602
        $this->assertSame((new \ReflectionClass(ParentAttributeDummy::class))->getShortName().'-title_child.name', $rootDefinitionKey);
×
NEW
603
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
NEW
604
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]);
×
NEW
605
        $this->assertSame('object', $definitions[$rootDefinitionKey]['type']);
×
NEW
606
        $this->assertFalse($definitions[$rootDefinitionKey]['additionalProperties']);
×
NEW
607
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
NEW
608
        $this->assertArrayHasKey('title', $definitions[$rootDefinitionKey]['properties']);
×
NEW
609
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['title']);
×
NEW
610
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['title']['type']);
×
NEW
611
        $this->assertArrayHasKey('child', $definitions[$rootDefinitionKey]['properties']);
×
NEW
612
        $this->assertArrayHasKey('$ref', $definitions[$rootDefinitionKey]['properties']['child']);
×
NEW
613
        $this->assertSame('#/definitions/ChildAttributeDummy-name', $definitions[$rootDefinitionKey]['properties']['child']['$ref']);
×
614

NEW
615
        $childDefinitionKey = 'ChildAttributeDummy-name';
×
NEW
616
        $this->assertTrue(isset($definitions[$childDefinitionKey]));
×
NEW
617
        $this->assertArrayHasKey('type', $definitions[$childDefinitionKey]);
×
NEW
618
        $this->assertSame('object', $definitions[$childDefinitionKey]['type']);
×
NEW
619
        $this->assertFalse($definitions[$childDefinitionKey]['additionalProperties']);
×
NEW
620
        $this->assertArrayHasKey('properties', $definitions[$childDefinitionKey]);
×
NEW
621
        $this->assertArrayHasKey('name', $definitions[$childDefinitionKey]['properties']);
×
NEW
622
        $this->assertArrayHasKey('type', $definitions[$childDefinitionKey]['properties']['name']);
×
NEW
623
        $this->assertSame('string', $definitions[$childDefinitionKey]['properties']['name']['type']);
×
624
    }
625

626
    #[IgnoreDeprecations]
627
    public function testBuildSchemaForAssociativeArrayLegacy(): void
628
    {
629
        if (!class_exists(LegacyType::class)) {
×
630
            $this->markTestSkipped();
×
631
        }
632
        $this->expectUserDeprecationMessage('Since api-platform/metadata 4.2: The "ApiPlatform\Metadata\ApiProperty::withBuiltinTypes()" method is deprecated, use "ApiPlatform\Metadata\ApiProperty::withNativeType()" instead.');
×
633
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
634

635
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
636
        $propertyNameCollectionFactoryProphecy->create(NotAResource::class, Argument::cetera())->willReturn(new PropertyNameCollection(['foo', 'bar']));
×
637

638
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
639
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn(
×
640
            (new ApiProperty())
×
641
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_INT), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))])
×
642
                ->withReadable(true)
×
643
                ->withSchema(['type' => 'array', 'items' => ['string', 'int']])
×
644
        );
×
645
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn(
×
646
            (new ApiProperty())
×
647
                ->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_ARRAY, false, null, true, new LegacyType(LegacyType::BUILTIN_TYPE_STRING), new LegacyType(LegacyType::BUILTIN_TYPE_STRING))])
×
648
                ->withReadable(true)
×
649
                ->withSchema(['type' => 'object', 'additionalProperties' => 'string'])
×
650
        );
×
651

652
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
653
        $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false);
×
654

655
        $definitionNameFactory = new DefinitionNameFactory();
×
656

657
        $schemaFactory = new SchemaFactory(
×
658
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
659
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
660
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
661
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
662
            definitionNameFactory: $definitionNameFactory,
×
663
        );
×
664
        $resultSchema = $schemaFactory->buildSchema(NotAResource::class);
×
665

666
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
667
        $definitions = $resultSchema->getDefinitions();
×
668

669
        $this->assertSame((new \ReflectionClass(NotAResource::class))->getShortName(), $rootDefinitionKey);
×
670
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
671
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
672
        $this->assertArrayHasKey('foo', $definitions[$rootDefinitionKey]['properties']);
×
673
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['foo']);
×
674
        $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]['properties']['foo']);
×
675
        $this->assertSame('array', $definitions[$rootDefinitionKey]['properties']['foo']['type']);
×
676
        $this->assertArrayHasKey('bar', $definitions[$rootDefinitionKey]['properties']);
×
677
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['bar']);
×
678
        $this->assertArrayHasKey('additionalProperties', $definitions[$rootDefinitionKey]['properties']['bar']);
×
679
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['bar']['type']);
×
680
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['bar']['additionalProperties']);
×
681
    }
682

683
    public function testBuildSchemaForAssociativeArray(): void
684
    {
685
        $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
686

687
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
688
        $propertyNameCollectionFactoryProphecy->create(NotAResource::class, Argument::cetera())->willReturn(new PropertyNameCollection(['foo', 'bar']));
×
689

690
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
691
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'foo', Argument::cetera())->willReturn(
×
692
            (new ApiProperty())
×
693
                ->withNativeType(Type::list(Type::string()))
×
694
                ->withReadable(true)
×
695
                ->withSchema(['type' => 'array', 'items' => ['string', 'int']])
×
696
        );
×
697
        $propertyMetadataFactoryProphecy->create(NotAResource::class, 'bar', Argument::cetera())->willReturn(
×
698
            (new ApiProperty())
×
699
                ->withNativeType(Type::dict(Type::string()))
×
700
                ->withReadable(true)
×
701
                ->withSchema(['type' => 'object', 'additionalProperties' => 'string'])
×
702
        );
×
703

704
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
705
        $resourceClassResolverProphecy->isResourceClass(NotAResource::class)->willReturn(false);
×
706

707
        $definitionNameFactory = new DefinitionNameFactory();
×
708

709
        $schemaFactory = new SchemaFactory(
×
710
            resourceMetadataFactory: $resourceMetadataFactoryProphecy->reveal(),
×
711
            propertyNameCollectionFactory: $propertyNameCollectionFactoryProphecy->reveal(),
×
712
            propertyMetadataFactory: $propertyMetadataFactoryProphecy->reveal(),
×
713
            resourceClassResolver: $resourceClassResolverProphecy->reveal(),
×
714
            definitionNameFactory: $definitionNameFactory,
×
715
        );
×
716
        $resultSchema = $schemaFactory->buildSchema(NotAResource::class);
×
717

718
        $rootDefinitionKey = $resultSchema->getRootDefinitionKey();
×
719
        $definitions = $resultSchema->getDefinitions();
×
720

721
        $this->assertSame((new \ReflectionClass(NotAResource::class))->getShortName(), $rootDefinitionKey);
×
722
        $this->assertTrue(isset($definitions[$rootDefinitionKey]));
×
723
        $this->assertArrayHasKey('properties', $definitions[$rootDefinitionKey]);
×
724
        $this->assertArrayHasKey('foo', $definitions[$rootDefinitionKey]['properties']);
×
725
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['foo']);
×
726
        $this->assertArrayNotHasKey('additionalProperties', $definitions[$rootDefinitionKey]['properties']['foo']);
×
727
        $this->assertSame('array', $definitions[$rootDefinitionKey]['properties']['foo']['type']);
×
728
        $this->assertArrayHasKey('bar', $definitions[$rootDefinitionKey]['properties']);
×
729
        $this->assertArrayHasKey('type', $definitions[$rootDefinitionKey]['properties']['bar']);
×
730
        $this->assertArrayHasKey('additionalProperties', $definitions[$rootDefinitionKey]['properties']['bar']);
×
731
        $this->assertSame('object', $definitions[$rootDefinitionKey]['properties']['bar']['type']);
×
732
        $this->assertSame('string', $definitions[$rootDefinitionKey]['properties']['bar']['additionalProperties']);
×
733
    }
734
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc