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

api-platform / core / 5761736306

pending completion
5761736306

push

github

web-flow
fix(openapi): model Example, Header and Reference (#5716)

81 of 81 new or added lines in 4 files covered. (100.0%)

9297 of 16116 relevant lines covered (57.69%)

52.5 hits per line

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

0.0
/src/OpenApi/Tests/Serializer/OpenApiNormalizerTest.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\OpenApi\Tests\Serializer;
15

16
use ApiPlatform\JsonSchema\SchemaFactory;
17
use ApiPlatform\JsonSchema\TypeFactory;
18
use ApiPlatform\Metadata\ApiProperty;
19
use ApiPlatform\Metadata\ApiResource;
20
use ApiPlatform\Metadata\Delete;
21
use ApiPlatform\Metadata\Get;
22
use ApiPlatform\Metadata\GetCollection;
23
use ApiPlatform\Metadata\HttpOperation;
24
use ApiPlatform\Metadata\Operations;
25
use ApiPlatform\Metadata\Post;
26
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
27
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Property\PropertyNameCollection;
29
use ApiPlatform\Metadata\Put;
30
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
31
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
32
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
33
use ApiPlatform\Metadata\Resource\ResourceNameCollection;
34
use ApiPlatform\OpenApi\Factory\OpenApiFactory;
35
use ApiPlatform\OpenApi\Model\Components;
36
use ApiPlatform\OpenApi\Model\Info;
37
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
38
use ApiPlatform\OpenApi\Model\Parameter;
39
use ApiPlatform\OpenApi\Model\Paths;
40
use ApiPlatform\OpenApi\Model\Schema;
41
use ApiPlatform\OpenApi\Model\Server;
42
use ApiPlatform\OpenApi\OpenApi;
43
use ApiPlatform\OpenApi\Options;
44
use ApiPlatform\OpenApi\Serializer\OpenApiNormalizer;
45
use ApiPlatform\State\Pagination\PaginationOptions;
46
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy;
47
use PHPUnit\Framework\TestCase;
48
use Prophecy\Argument;
49
use Prophecy\PhpUnit\ProphecyTrait;
50
use Psr\Container\ContainerInterface;
51
use Symfony\Component\PropertyInfo\Type;
52
use Symfony\Component\Serializer\Encoder\JsonEncoder;
53
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
54
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
55
use Symfony\Component\Serializer\Serializer;
56

57
class OpenApiNormalizerTest extends TestCase
58
{
59
    use ProphecyTrait;
60

61
    private const OPERATION_FORMATS = [
62
        'input_formats' => ['jsonld' => ['application/ld+json']],
63
        'output_formats' => ['jsonld' => ['application/ld+json']],
64
    ];
65

66
    public function testNormalizeWithSchemas(): void
67
    {
68
        $openApi = new OpenApi(new Info('My API', '1.0.0', 'An amazing API'), [new Server('https://example.com')], new Paths(), new Components(new \ArrayObject(['z' => new Schema(), 'b' => new Schema()])));
×
69
        $encoders = [new JsonEncoder()];
×
70
        $normalizers = [new ObjectNormalizer()];
×
71

72
        $serializer = new Serializer($normalizers, $encoders);
×
73
        $normalizers[0]->setSerializer($serializer);
×
74

75
        $normalizer = new OpenApiNormalizer($normalizers[0]);
×
76

77
        $array = $normalizer->normalize($openApi);
×
78

79
        $this->assertSame(array_keys($array['components']['schemas']), ['b', 'z']);
×
80
    }
81

82
    public function testNormalizeWithEmptySchemas(): void
83
    {
84
        $openApi = new OpenApi(new Info('My API', '1.0.0', 'An amazing API'), [new Server('https://example.com')], new Paths(), new Components(new \ArrayObject()));
×
85
        $encoders = [new JsonEncoder()];
×
86
        $normalizers = [new ObjectNormalizer()];
×
87

88
        $serializer = new Serializer($normalizers, $encoders);
×
89
        $normalizers[0]->setSerializer($serializer);
×
90

91
        $normalizer = new OpenApiNormalizer($normalizers[0]);
×
92

93
        $array = $normalizer->normalize($openApi);
×
94
        $this->assertCount(0, $array['components']['schemas']);
×
95
    }
96

97
    public function testNormalize(): void
98
    {
99
        $resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
×
100
        $resourceNameCollectionFactoryProphecy->create()->shouldBeCalled()->willReturn(new ResourceNameCollection([Dummy::class, 'Zorro']));
×
101
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
102
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id', 'name', 'description', 'dummyDate']));
×
103
        $propertyNameCollectionFactoryProphecy->create('Zorro', Argument::any())->shouldBeCalled()->willReturn(new PropertyNameCollection(['id']));
×
104

105
        $baseOperation = (new HttpOperation())->withTypes(['http://schema.example.com/Dummy'])
×
106
                                              ->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])
×
107
                                              ->withClass(Dummy::class)
×
108
                                              ->withShortName('Dummy')
×
109
                                              ->withDescription('This is a dummy.');
×
110

111
        $dummyMetadata = new ResourceMetadataCollection(Dummy::class, [
×
112
            (new ApiResource())->withOperations(new Operations(
×
113
                [
×
114
                    'get' => (new Get())->withUriTemplate('/dummies/{id}')->withOperation($baseOperation),
×
115
                    'put' => (new Put())->withUriTemplate('/dummies/{id}')->withOperation($baseOperation),
×
116
                    'delete' => (new Delete())->withUriTemplate('/dummies/{id}')->withOperation($baseOperation),
×
117
                    'get_collection' => (new GetCollection())->withUriTemplate('/dummies')->withOperation($baseOperation),
×
118
                    'post' => (new Post())->withUriTemplate('/dummies')->withOpenapi(new OpenApiOperation(
×
119
                        security: [],
×
120
                        servers: ['url' => '/test'],
×
121
                    ))->withOperation($baseOperation),
×
122
                ]
×
123
            )),
×
124
        ]);
×
125

126
        $zorroBaseOperation = (new HttpOperation())
×
127
            ->withTypes(['http://schema.example.com/Zorro'])
×
128
            ->withInputFormats(self::OPERATION_FORMATS['input_formats'])->withOutputFormats(self::OPERATION_FORMATS['output_formats'])
×
129
            ->withClass('Zorro')
×
130
            ->withShortName('Zorro')
×
131
            ->withDescription('This is zorro.');
×
132

133
        $zorroMetadata = new ResourceMetadataCollection(Dummy::class, [
×
134
            (new ApiResource())->withOperations(new Operations(
×
135
                [
×
136
                    'get' => (new Get())->withUriTemplate('/zorros/{id}')->withOperation($zorroBaseOperation),
×
137
                    'get_collection' => (new GetCollection())->withUriTemplate('/zorros')->withOperation($zorroBaseOperation),
×
138
                ]
×
139
            )),
×
140
        ]);
×
141

142
        $resourceCollectionMetadataFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
143
        $resourceCollectionMetadataFactoryProphecy->create(Dummy::class)->shouldBeCalled()->willReturn($dummyMetadata);
×
144
        $resourceCollectionMetadataFactoryProphecy->create('Zorro')->shouldBeCalled()->willReturn($zorroMetadata);
×
145

146
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
147
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::any())->shouldBeCalled()->willReturn(
×
148
            (new ApiProperty())
×
149
                ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])
×
150
                ->withDescription('This is an id.')
×
151
                ->withReadable(true)
×
152
                ->withWritable(false)
×
153
                ->withIdentifier(true)
×
154
        );
×
155
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->shouldBeCalled()->willReturn(
×
156
            (new ApiProperty())
×
157
                ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])
×
158
                ->withDescription('This is a name.')
×
159
                ->withReadable(true)
×
160
                ->withWritable(true)
×
161
                ->withReadableLink(true)
×
162
                ->withWritableLink(true)
×
163
                ->withRequired(false)
×
164
                ->withIdentifier(false)
×
165
                ->withSchema(['minLength' => 3, 'maxLength' => 20, 'pattern' => '^dummyPattern$'])
×
166
        );
×
167
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'description', Argument::any())->shouldBeCalled()->willReturn(
×
168
            (new ApiProperty())
×
169
                ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_STRING)])
×
170
                ->withDescription('This is an initializable but not writable property.')
×
171
                ->withReadable(true)
×
172
                ->withWritable(false)
×
173
                ->withReadableLink(true)
×
174
                ->withWritableLink(true)
×
175
                ->withRequired(false)
×
176
                ->withIdentifier(false)
×
177
        );
×
178
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'dummyDate', Argument::any())->shouldBeCalled()->willReturn(
×
179
            (new ApiProperty())
×
180
                ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_OBJECT, true, \DateTime::class)])
×
181
                ->withDescription('This is a \DateTimeInterface object.')
×
182
                ->withReadable(true)
×
183
                ->withWritable(true)
×
184
                ->withReadableLink(true)
×
185
                ->withWritableLink(true)
×
186
                ->withRequired(false)
×
187
                ->withIdentifier(false)
×
188
        );
×
189

190
        $propertyMetadataFactoryProphecy->create('Zorro', 'id', Argument::any())->shouldBeCalled()->willReturn(
×
191
            (new ApiProperty())
×
192
                ->withBuiltinTypes([new Type(Type::BUILTIN_TYPE_INT)])
×
193
                ->withDescription('This is an id.')
×
194
                ->withReadable(true)
×
195
                ->withWritable(false)
×
196
                ->withIdentifier(true)
×
197
        );
×
198

199
        $filterLocatorProphecy = $this->prophesize(ContainerInterface::class);
×
200
        $resourceMetadataFactory = $resourceCollectionMetadataFactoryProphecy->reveal();
×
201
        $propertyNameCollectionFactory = $propertyNameCollectionFactoryProphecy->reveal();
×
202
        $propertyMetadataFactory = $propertyMetadataFactoryProphecy->reveal();
×
203

204
        $typeFactory = new TypeFactory();
×
205
        $schemaFactory = new SchemaFactory($typeFactory, $resourceMetadataFactory, $propertyNameCollectionFactory, $propertyMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
×
206
        $typeFactory->setSchemaFactory($schemaFactory);
×
207

208
        $factory = new OpenApiFactory(
×
209
            $resourceNameCollectionFactoryProphecy->reveal(),
×
210
            $resourceMetadataFactory,
×
211
            $propertyNameCollectionFactory,
×
212
            $propertyMetadataFactory,
×
213
            $schemaFactory,
×
214
            $typeFactory,
×
215
            $filterLocatorProphecy->reveal(),
×
216
            [],
×
217
            new Options('Test API', 'This is a test API.', '1.2.3', true, 'oauth2', 'authorizationCode', '/oauth/v2/token', '/oauth/v2/auth', '/oauth/v2/refresh', ['scope param'], [
×
218
                'header' => [
×
219
                    'type' => 'header',
×
220
                    'name' => 'Authorization',
×
221
                ],
×
222
                'query' => [
×
223
                    'type' => 'query',
×
224
                    'name' => 'key',
×
225
                ],
×
226
            ]),
×
227
            new PaginationOptions(true, 'page', true, 'itemsPerPage', true, 'pagination')
×
228
        );
×
229

230
        $openApi = $factory(['base_url' => '/app_dev.php/']);
×
231

232
        $pathItem = $openApi->getPaths()->getPath('/dummies/{id}');
×
233
        $operation = $pathItem->getGet();
×
234

235
        $openApi->getPaths()->addPath('/dummies/{id}', $pathItem->withGet(
×
236
            $operation->withParameters(array_merge(
×
237
                $operation->getParameters(),
×
238
                [new Parameter('fields', 'query', 'Fields to remove of the output')]
×
239
            ))
×
240
        ));
×
241

242
        $openApi = $openApi->withInfo((new Info('New Title', 'v2', 'Description of my custom API'))->withExtensionProperty('info-key', 'Info value'));
×
243
        $openApi = $openApi->withExtensionProperty('key', 'Custom x-key value');
×
244
        $openApi = $openApi->withExtensionProperty('x-value', 'Custom x-value value');
×
245

246
        $encoders = [new JsonEncoder()];
×
247
        $normalizers = [new ObjectNormalizer()];
×
248

249
        $serializer = new Serializer($normalizers, $encoders);
×
250
        $normalizers[0]->setSerializer($serializer);
×
251

252
        $normalizer = new OpenApiNormalizer($normalizers[0]);
×
253

254
        $openApiAsArray = $normalizer->normalize($openApi);
×
255

256
        // Just testing normalization specifics
257
        $this->assertSame($openApiAsArray['x-key'], 'Custom x-key value');
×
258
        $this->assertSame($openApiAsArray['x-value'], 'Custom x-value value');
×
259
        $this->assertSame($openApiAsArray['info']['x-info-key'], 'Info value');
×
260
        $this->assertArrayNotHasKey('extensionProperties', $openApiAsArray);
×
261
        // this key is null, should not be in the output
262
        $this->assertArrayNotHasKey('termsOfService', $openApiAsArray['info']);
×
263
        $this->assertArrayNotHasKey('paths', $openApiAsArray['paths']);
×
264
        $this->assertArrayHasKey('/dummies/{id}', $openApiAsArray['paths']);
×
265
        $this->assertArrayNotHasKey('servers', $openApiAsArray['paths']['/dummies/{id}']['get']);
×
266
        $this->assertArrayNotHasKey('security', $openApiAsArray['paths']['/dummies/{id}']['get']);
×
267

268
        // Security can be disabled per-operation using an empty array
269
        $this->assertEquals([], $openApiAsArray['paths']['/dummies']['post']['security']);
×
270
        $this->assertEquals(['url' => '/test'], $openApiAsArray['paths']['/dummies']['post']['servers']);
×
271

272
        // Make sure things are sorted
273
        $this->assertSame(array_keys($openApiAsArray['paths']), ['/dummies', '/dummies/{id}', '/zorros', '/zorros/{id}']);
×
274
        // Test name converter doesn't rename this property
275
        $this->assertArrayHasKey('requestBody', $openApiAsArray['paths']['/dummies']['post']);
×
276
    }
277
}
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