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

api-platform / core / 20720509963

05 Jan 2026 03:38PM UTC coverage: 25.305% (+0.007%) from 25.298%
20720509963

push

github

soyuka
Merge 4.2

61 of 324 new or added lines in 38 files covered. (18.83%)

30 existing lines in 15 files now uncovered.

14741 of 58254 relevant lines covered (25.3%)

30.23 hits per line

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

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

16
use ApiPlatform\Hydra\Serializer\CollectionNormalizer;
17
use ApiPlatform\Hydra\Tests\Fixtures\Foo;
18
use ApiPlatform\JsonLd\ContextBuilder;
19
use ApiPlatform\JsonLd\ContextBuilderInterface;
20
use ApiPlatform\Metadata\IriConverterInterface;
21
use ApiPlatform\Metadata\ResourceClassResolverInterface;
22
use ApiPlatform\Metadata\UrlGeneratorInterface;
23
use ApiPlatform\Serializer\AbstractItemNormalizer;
24
use ApiPlatform\State\Pagination\PaginatorInterface;
25
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
26
use PHPUnit\Framework\TestCase;
27
use Prophecy\Argument;
28
use Prophecy\PhpUnit\ProphecyTrait;
29
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
30
use Symfony\Component\Serializer\SerializerInterface;
31

32
/**
33
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
34
 * @author Kévin Dunglas <dunglas@gmail.com>
35
 */
36
class CollectionNormalizerTest extends TestCase
37
{
38
    use ProphecyTrait;
39

40
    public function testSupportsNormalize(): void
41
    {
42
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
43
        $iriConvert = $this->prophesize(IriConverterInterface::class);
×
44
        $contextBuilder = $this->prophesize(ContextBuilderInterface::class);
×
45
        $contextBuilder->getResourceContextUri('Foo')->willReturn('/contexts/Foo');
×
46
        $iriConvert->getIriFromResource('Foo', UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos');
×
47

48
        $normalizer = new CollectionNormalizer($contextBuilder->reveal(), $resourceClassResolverProphecy->reveal(), $iriConvert->reveal());
×
49

50
        $this->assertTrue($normalizer->supportsNormalization([], CollectionNormalizer::FORMAT, ['resource_class' => 'Foo']));
×
51
        $this->assertTrue($normalizer->supportsNormalization([], CollectionNormalizer::FORMAT, ['resource_class' => 'Foo', 'api_sub_level' => true]));
×
52
        $this->assertTrue($normalizer->supportsNormalization([], CollectionNormalizer::FORMAT, []));
×
53
        $this->assertTrue($normalizer->supportsNormalization(new \ArrayObject(), CollectionNormalizer::FORMAT, ['resource_class' => 'Foo']));
×
54
        $this->assertFalse($normalizer->supportsNormalization([], 'xml', ['resource_class' => 'Foo']));
×
55
        $this->assertFalse($normalizer->supportsNormalization(new \ArrayObject(), 'xml', ['resource_class' => 'Foo']));
×
56
        $this->assertEmpty($normalizer->getSupportedTypes('xml'));
×
57
        $this->assertSame([
×
58
            'native-array' => true,
×
59
            '\Traversable' => true,
×
60
        ], $normalizer->getSupportedTypes($normalizer::FORMAT));
×
61
    }
62

63
    public function testNormalizeResourceCollection(): void
64
    {
65
        $fooOne = new Foo();
×
66
        $fooOne->id = 1;
×
67
        $fooOne->bar = 'baz';
×
68

69
        $fooThree = new Foo();
×
70
        $fooThree->id = 3;
×
71
        $fooThree->bar = 'bzz';
×
72

73
        $data = [$fooOne, $fooThree];
×
74

75
        $normalizedFooOne = [
×
76
            '@id' => '/foos/1',
×
77
            '@type' => 'Foo',
×
78
            'bar' => 'baz',
×
79
        ];
×
80

81
        $normalizedFooThree = [
×
82
            '@id' => '/foos/3',
×
83
            '@type' => 'Foo',
×
84
            'bar' => 'bzz',
×
85
        ];
×
86

87
        $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
×
88
        $contextBuilderProphecy->getResourceContextUri(Foo::class)->willReturn('/contexts/Foo');
×
89

90
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
91
        $resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);
×
92

93
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
94
        $iriConverterProphecy->getIriFromResource(Foo::class, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos');
×
95

96
        $delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
×
97
        $delegateNormalizerProphecy->normalize($fooOne, CollectionNormalizer::FORMAT, Argument::allOf(
×
98
            Argument::withEntry('resource_class', Foo::class),
×
99
            Argument::withEntry('api_sub_level', true)
×
100
        ))->willReturn($normalizedFooOne);
×
101
        $delegateNormalizerProphecy->normalize($fooThree, CollectionNormalizer::FORMAT, Argument::allOf(
×
102
            Argument::withEntry('resource_class', Foo::class),
×
103
            Argument::withEntry('api_sub_level', true)
×
104
        ))->willReturn($normalizedFooThree);
×
105

106
        $normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
×
107
        $normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
×
108

109
        $actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
×
110
            'operation_name' => 'get',
×
111
            'resource_class' => Foo::class,
×
112
        ]);
×
113

114
        $this->assertEquals([
×
115
            '@context' => '/contexts/Foo',
×
116
            '@id' => '/foos',
×
117
            '@type' => 'hydra:Collection',
×
118
            'hydra:member' => [
×
119
                $normalizedFooOne,
×
120
                $normalizedFooThree,
×
121
            ],
×
122
            'hydra:totalItems' => 2,
×
123
        ], $actual);
×
124
    }
125

126
    public function testNormalizePaginator(): void
127
    {
128
        $this->assertEquals(
×
129
            [
×
130
                '@context' => '/contexts/Foo',
×
131
                '@id' => '/foo/1',
×
132
                '@type' => 'hydra:Collection',
×
133
                'hydra:member' => [
×
134
                    [
×
135
                        'name' => 'Kévin',
×
136
                        'friend' => 'Smail',
×
137
                    ],
×
138
                ],
×
139
                'hydra:totalItems' => 1312.,
×
140
            ],
×
141
            $this->normalizePaginator()
×
142
        );
×
143
    }
144

145
    public function testNormalizePartialPaginator(): void
146
    {
147
        $this->assertEquals(
×
148
            [
×
149
                '@context' => '/contexts/Foo',
×
150
                '@id' => '/foo/1',
×
151
                '@type' => 'hydra:Collection',
×
152
                'hydra:member' => [
×
153
                    0 => [
×
154
                        'name' => 'Kévin',
×
155
                        'friend' => 'Smail',
×
156
                    ],
×
157
                ],
×
158
            ],
×
159
            $this->normalizePaginator(true)
×
160
        );
×
161
    }
162

163
    private function normalizePaginator(bool $partial = false): array
164
    {
165
        $paginatorProphecy = $this->prophesize(PaginatorInterface::class);
×
166
        if ($partial) {
×
167
            $paginatorProphecy = $this->prophesize(PartialPaginatorInterface::class);
×
168
        }
169

170
        if (!$partial) {
×
171
            $paginatorProphecy->getTotalItems()->willReturn(1312);
×
172
        }
173

174
        $paginatorProphecy->rewind()->will(function (): void {});
×
175
        $paginatorProphecy->valid()->willReturn(true, false);
×
176
        $paginatorProphecy->current()->willReturn('foo');
×
177
        $paginatorProphecy->next()->will(function (): void {});
×
178

179
        $serializer = $this->prophesize(SerializerInterface::class);
×
180
        $serializer->willImplement(NormalizerInterface::class);
×
181

182
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
183
        $resourceClassResolverProphecy->getResourceClass($paginatorProphecy, 'Foo')->willReturn('Foo');
×
184

185
        $iriConvert = $this->prophesize(IriConverterInterface::class);
×
186
        $iriConvert->getIriFromResource('Foo', UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foo/1');
×
187

188
        $contextBuilder = $this->prophesize(ContextBuilderInterface::class);
×
189
        $contextBuilder->getResourceContextUri('Foo')->willReturn('/contexts/Foo');
×
190

191
        $itemNormalizer = $this->prophesize(AbstractItemNormalizer::class);
×
192
        $itemNormalizer->normalize('foo', CollectionNormalizer::FORMAT, [
×
193
            'jsonld_has_context' => true,
×
194
            'api_sub_level' => true,
×
195
            'resource_class' => 'Foo',
×
196
            'api_collection_sub_level' => true,
×
197
        ])->willReturn(['name' => 'Kévin', 'friend' => 'Smail']);
×
198

199
        $normalizer = new CollectionNormalizer($contextBuilder->reveal(), $resourceClassResolverProphecy->reveal(), $iriConvert->reveal());
×
200
        $normalizer->setNormalizer($itemNormalizer->reveal());
×
201

202
        return $normalizer->normalize($paginatorProphecy->reveal(), CollectionNormalizer::FORMAT, [
×
203
            'resource_class' => 'Foo',
×
204
        ]);
×
205
    }
206

207
    public function testNormalizeIriOnlyResourceCollection(): void
208
    {
209
        $fooOne = new Foo();
×
210
        $fooOne->id = 1;
×
211
        $fooOne->bar = 'baz';
×
212

213
        $fooThree = new Foo();
×
214
        $fooThree->id = 3;
×
215
        $fooThree->bar = 'bzz';
×
216

217
        $data = [$fooOne, $fooThree];
×
218

219
        $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
×
220
        $contextBuilderProphecy->getResourceContextUri(Foo::class)->willReturn('/contexts/Foo');
×
221

222
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
223
        $resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);
×
224

225
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
226
        $iriConverterProphecy->getIriFromResource(Foo::class, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos');
×
NEW
227
        $iriConverterProphecy->getIriFromResource($fooOne, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos/1');
×
NEW
228
        $iriConverterProphecy->getIriFromResource($fooThree, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos/3');
×
229

230
        $delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
×
231

232
        $normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
×
233
        $normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
×
234

235
        $actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
×
236
            'operation_name' => 'get',
×
237
            'iri_only' => true,
×
238
            'resource_class' => Foo::class,
×
239
        ]);
×
240

241
        $this->assertEquals([
×
242
            '@context' => '/contexts/Foo',
×
243
            '@id' => '/foos',
×
244
            '@type' => 'hydra:Collection',
×
245
            'hydra:member' => [
×
246
                '/foos/1',
×
247
                '/foos/3',
×
248
            ],
×
249
            'hydra:totalItems' => 2,
×
250
        ], $actual);
×
251
    }
252

253
    public function testNormalizeIriOnlyEmbedContextResourceCollection(): void
254
    {
255
        $fooOne = new Foo();
×
256
        $fooOne->id = 1;
×
257
        $fooOne->bar = 'baz';
×
258

259
        $fooThree = new Foo();
×
260
        $fooThree->id = 3;
×
261
        $fooThree->bar = 'bzz';
×
262

263
        $data = [$fooOne, $fooThree];
×
264

265
        $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
×
266
        $contextBuilderProphecy->getResourceContext(Foo::class)->willReturn([
×
267
            '@vocab' => 'http://localhost:8080/docs.jsonld#',
×
268
            'hydra' => 'http://www.w3.org/ns/hydra/core#',
×
269
            'hydra:member' => [
×
270
                '@type' => '@id',
×
271
            ],
×
272
        ]);
×
273

274
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
275
        $resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);
×
276

277
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
278
        $iriConverterProphecy->getIriFromResource(Foo::class, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos');
×
NEW
279
        $iriConverterProphecy->getIriFromResource($fooOne, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos/1');
×
NEW
280
        $iriConverterProphecy->getIriFromResource($fooThree, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos/3');
×
281

282
        $delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
×
283

284
        $normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
×
285
        $normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
×
286

287
        $actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
×
288
            'operation_name' => 'get',
×
289
            'iri_only' => true,
×
290
            'jsonld_embed_context' => true,
×
291
            'resource_class' => Foo::class,
×
292
        ]);
×
293

294
        $this->assertEquals([
×
295
            '@context' => [
×
296
                '@vocab' => 'http://localhost:8080/docs.jsonld#',
×
297
                'hydra' => 'http://www.w3.org/ns/hydra/core#',
×
298
                'hydra:member' => [
×
299
                    '@type' => '@id',
×
300
                ],
×
301
            ],
×
302
            '@id' => '/foos',
×
303
            '@type' => 'hydra:Collection',
×
304
            'hydra:member' => [
×
305
                '/foos/1',
×
306
                '/foos/3',
×
307
            ],
×
308
            'hydra:totalItems' => 2,
×
309
        ], $actual);
×
310
    }
311

312
    public function testNormalizeResourceCollectionWithoutPrefix(): void
313
    {
314
        $fooOne = new Foo();
×
315
        $fooOne->id = 1;
×
316
        $fooOne->bar = 'baz';
×
317

318
        $fooThree = new Foo();
×
319
        $fooThree->id = 3;
×
320
        $fooThree->bar = 'bzz';
×
321

322
        $data = [$fooOne, $fooThree];
×
323

324
        $normalizedFooOne = [
×
325
            '@id' => '/foos/1',
×
326
            '@type' => 'Foo',
×
327
            'bar' => 'baz',
×
328
        ];
×
329

330
        $normalizedFooThree = [
×
331
            '@id' => '/foos/3',
×
332
            '@type' => 'Foo',
×
333
            'bar' => 'bzz',
×
334
        ];
×
335

336
        $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class);
×
337
        $contextBuilderProphecy->getResourceContextUri(Foo::class)->willReturn('/contexts/Foo');
×
338

339
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
340
        $resourceClassResolverProphecy->getResourceClass($data, Foo::class)->willReturn(Foo::class);
×
341

342
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
343
        $iriConverterProphecy->getIriFromResource(Foo::class, UrlGeneratorInterface::ABS_PATH, Argument::any(), Argument::any())->willReturn('/foos');
×
344

345
        $delegateNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
×
346
        $delegateNormalizerProphecy->normalize($fooOne, CollectionNormalizer::FORMAT, Argument::allOf(
×
347
            Argument::withEntry('resource_class', Foo::class),
×
348
            Argument::withEntry('api_sub_level', true)
×
349
        ))->willReturn($normalizedFooOne);
×
350
        $delegateNormalizerProphecy->normalize($fooThree, CollectionNormalizer::FORMAT, Argument::allOf(
×
351
            Argument::withEntry('resource_class', Foo::class),
×
352
            Argument::withEntry('api_sub_level', true)
×
353
        ))->willReturn($normalizedFooThree);
×
354

355
        $normalizer = new CollectionNormalizer($contextBuilderProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $iriConverterProphecy->reveal());
×
356
        $normalizer->setNormalizer($delegateNormalizerProphecy->reveal());
×
357

358
        $actual = $normalizer->normalize($data, CollectionNormalizer::FORMAT, [
×
359
            'operation_name' => 'get',
×
360
            'resource_class' => Foo::class,
×
361
            ContextBuilder::HYDRA_CONTEXT_HAS_PREFIX => false,
×
362
        ]);
×
363

364
        $this->assertEquals([
×
365
            '@context' => '/contexts/Foo',
×
366
            '@id' => '/foos',
×
367
            '@type' => 'Collection',
×
368
            'member' => [
×
369
                $normalizedFooOne,
×
370
                $normalizedFooThree,
×
371
            ],
×
372
            'totalItems' => 2,
×
373
        ], $actual);
×
374
    }
375
}
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