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

api-platform / core / 15394793169

02 Jun 2025 02:22PM UTC coverage: 21.851% (-0.03%) from 21.877%
15394793169

push

github

web-flow
chore: use type-info:^7.3 (#7185)

0 of 168 new or added lines in 12 files covered. (0.0%)

15 existing lines in 7 files now uncovered.

11381 of 52084 relevant lines covered (21.85%)

10.6 hits per line

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

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

16
use ApiPlatform\JsonApi\Serializer\ItemNormalizer;
17
use ApiPlatform\JsonApi\Serializer\ReservedAttributeNameConverter;
18
use ApiPlatform\JsonApi\Tests\Fixtures\CircularReference;
19
use ApiPlatform\JsonApi\Tests\Fixtures\Dummy;
20
use ApiPlatform\JsonApi\Tests\Fixtures\RelatedDummy;
21
use ApiPlatform\Metadata\ApiProperty;
22
use ApiPlatform\Metadata\ApiResource;
23
use ApiPlatform\Metadata\Get;
24
use ApiPlatform\Metadata\IriConverterInterface;
25
use ApiPlatform\Metadata\Operations;
26
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
27
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
28
use ApiPlatform\Metadata\Property\PropertyNameCollection;
29
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
30
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
31
use ApiPlatform\Metadata\ResourceClassResolverInterface;
32
use Doctrine\Common\Collections\ArrayCollection;
33
use PHPUnit\Framework\TestCase;
34
use Prophecy\Argument;
35
use Prophecy\PhpUnit\ProphecyTrait;
36
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
37
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
38
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
39
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
40
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
41
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
42
use Symfony\Component\Serializer\Serializer;
43
use Symfony\Component\Serializer\SerializerInterface;
44
use Symfony\Component\TypeInfo\Type;
45

46
/**
47
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
48
 */
49
class ItemNormalizerTest extends TestCase
50
{
51
    use ProphecyTrait;
52

53
    public function testNormalize(): void
54
    {
55
        $dummy = new Dummy();
×
56
        $dummy->setId(10);
×
57
        $dummy->setName('hello');
×
58

59
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
60
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->willReturn(new PropertyNameCollection(['id', 'name', '\bad_property']));
×
61

62
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
63
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->willReturn((new ApiProperty())->withReadable(true));
×
64
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'id', Argument::any())->willReturn((new ApiProperty())->withReadable(true)->withIdentifier(true));
×
65
        $propertyMetadataFactoryProphecy->create(Dummy::class, '\bad_property', Argument::any())->willReturn((new ApiProperty())->withReadable(true));
×
66

67
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
68
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/10');
×
69

70
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
71
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
72
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
73
        $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class);
×
74
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
75
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
76

77
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
78
        $propertyAccessorProphecy->getValue($dummy, 'id')->willReturn(10);
×
79
        $propertyAccessorProphecy->getValue($dummy, 'name')->willReturn('hello');
×
80

81
        $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
82
        $resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [
×
83
            (new ApiResource())
×
84
                ->withShortName('Dummy')
×
85
                ->withDescription('A dummy')
×
86
                ->withUriTemplate('/dummy')
×
87
                ->withOperations(new Operations(['get' => (new Get())->withShortName('Dummy')])),
×
88
        ]));
×
89

90
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
91
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
92
        $serializerProphecy->normalize('hello', ItemNormalizer::FORMAT, Argument::type('array'))->willReturn('hello');
×
93
        $serializerProphecy->normalize(10, ItemNormalizer::FORMAT, Argument::type('array'))->willReturn(10);
×
94
        $serializerProphecy->normalize(null, ItemNormalizer::FORMAT, Argument::type('array'))->willReturn(null);
×
95

96
        $normalizer = new ItemNormalizer(
×
97
            $propertyNameCollectionFactoryProphecy->reveal(),
×
98
            $propertyMetadataFactoryProphecy->reveal(),
×
99
            $iriConverterProphecy->reveal(),
×
100
            $resourceClassResolverProphecy->reveal(),
×
101
            $propertyAccessorProphecy->reveal(),
×
102
            new ReservedAttributeNameConverter(),
×
103
            null,
×
104
            [],
×
105
            $resourceMetadataCollectionFactoryProphecy->reveal(),
×
106
        );
×
107

108
        $normalizer->setSerializer($serializerProphecy->reveal());
×
109

110
        $expected = [
×
111
            'data' => [
×
112
                'type' => 'Dummy',
×
113
                'id' => '/dummies/10',
×
114
                'attributes' => [
×
115
                    '_id' => 10,
×
116
                    'name' => 'hello',
×
117
                ],
×
118
            ],
×
119
        ];
×
120

121
        $this->assertEquals($expected, $normalizer->normalize($dummy, ItemNormalizer::FORMAT));
×
122
    }
123

124
    public function testNormalizeCircularReference(): void
125
    {
126
        $circularReferenceEntity = new CircularReference();
×
127
        $circularReferenceEntity->id = 1;
×
128
        $circularReferenceEntity->parent = $circularReferenceEntity;
×
129

130
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
131
        $iriConverterProphecy->getIriFromResource($circularReferenceEntity, Argument::cetera())->willReturn('/circular_references/1');
×
132

133
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
134
        $resourceClassResolverProphecy->isResourceClass(CircularReference::class)->willReturn(true);
×
135
        $resourceClassResolverProphecy->getResourceClass($circularReferenceEntity, null)->willReturn(CircularReference::class);
×
NEW
136
        $resourceClassResolverProphecy->getResourceClass(null, CircularReference::class)->willReturn(CircularReference::class);
×
137

138
        $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
139
        $resourceMetadataCollectionFactoryProphecy->create(CircularReference::class)->willReturn(new ResourceMetadataCollection('CircularReference'));
×
140

NEW
141
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
NEW
142
        $propertyNameCollectionFactoryProphecy->create(CircularReference::class, [])->willReturn(new PropertyNameCollection());
×
143

144
        $normalizer = new ItemNormalizer(
×
NEW
145
            $propertyNameCollectionFactoryProphecy->reveal(),
×
146
            $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(),
×
147
            $iriConverterProphecy->reveal(),
×
148
            $resourceClassResolverProphecy->reveal(),
×
149
            $this->prophesize(PropertyAccessorInterface::class)->reveal(),
×
150
            new ReservedAttributeNameConverter(),
×
151
            null,
×
152
            [],
×
153
            $resourceMetadataCollectionFactoryProphecy->reveal(),
×
154
        );
×
155

156
        $normalizer->setSerializer($this->prophesize(SerializerInterface::class)->reveal());
×
157

158
        $circularReferenceLimit = 2;
×
159
        if (!interface_exists(AdvancedNameConverterInterface::class) && method_exists($normalizer, 'setCircularReferenceLimit')) {
×
160
            $normalizer->setCircularReferenceLimit($circularReferenceLimit);
×
161

162
            $context = [
×
NEW
163
                'api_empty_resource_as_iri' => true,
×
164
                'circular_reference_limit' => [spl_object_hash($circularReferenceEntity) => 2],
×
165
                'cache_error' => function (): void {},
×
166
            ];
×
167
        } else {
168
            $context = [
×
NEW
169
                'api_empty_resource_as_iri' => true,
×
170
                'circular_reference_limit' => $circularReferenceLimit,
×
171
                'circular_reference_limit_counters' => [spl_object_hash($circularReferenceEntity) => 2],
×
172
                'cache_error' => function (): void {},
×
173
            ];
×
174
        }
175

176
        $this->assertSame('/circular_references/1', $normalizer->normalize($circularReferenceEntity, ItemNormalizer::FORMAT, $context));
×
177
    }
178

179
    public function testNormalizeNonExistentProperty(): void
180
    {
181
        $this->expectException(NoSuchPropertyException::class);
×
182

183
        $dummy = new Dummy();
×
184
        $dummy->setId(1);
×
185

186
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
187
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->willReturn(new PropertyNameCollection(['bar']));
×
188

189
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
190
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'bar', Argument::any())->willReturn((new ApiProperty())->withReadable(true));
×
191

192
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
193
        $iriConverterProphecy->getIriFromResource($dummy, Argument::cetera())->willReturn('/dummies/1');
×
194

195
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
196
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
197
        $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class);
×
198
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
199
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
200

201
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
202
        $propertyAccessorProphecy->getValue($dummy, 'bar')->willThrow(new NoSuchPropertyException());
×
203

204
        $serializerProphecy = $this->prophesize(Serializer::class);
×
205

206
        $resourceMetadataCollectionFactoryProphecy = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
207
        $resourceMetadataCollectionFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection('Dummy', [
×
208
            (new ApiResource())
×
209
                ->withShortName('Dummy')
×
210
                ->withOperations(new Operations(['get' => (new Get())->withShortName('Dummy')])),
×
211
        ]));
×
212

213
        $normalizer = new ItemNormalizer(
×
214
            $propertyNameCollectionFactoryProphecy->reveal(),
×
215
            $propertyMetadataFactoryProphecy->reveal(),
×
216
            $iriConverterProphecy->reveal(),
×
217
            $resourceClassResolverProphecy->reveal(),
×
218
            $propertyAccessorProphecy->reveal(),
×
219
            new ReservedAttributeNameConverter(),
×
220
            null,
×
221
            [],
×
222
            $resourceMetadataCollectionFactoryProphecy->reveal(),
×
223
        );
×
224

225
        $normalizer->setSerializer($serializerProphecy->reveal());
×
226

227
        $normalizer->normalize($dummy, ItemNormalizer::FORMAT);
×
228
    }
229

230
    public function testDenormalize(): void
231
    {
232
        $data = [
×
233
            'data' => [
×
234
                'type' => 'dummy',
×
235
                'attributes' => [
×
236
                    'name' => 'foo',
×
237
                    'ghost' => 'invisible',
×
238
                ],
×
239
                'relationships' => [
×
240
                    'relatedDummy' => [
×
241
                        'data' => [
×
242
                            'type' => 'related-dummy',
×
243
                            'id' => '/related_dummies/1',
×
244
                        ],
×
245
                    ],
×
246
                    'relatedDummies' => [
×
247
                        'data' => [
×
248
                            [
×
249
                                'type' => 'related-dummy',
×
250
                                'id' => '/related_dummies/2',
×
251
                            ],
×
252
                        ],
×
253
                    ],
×
254
                ],
×
255
            ],
×
256
        ];
×
257

258
        $relatedDummy1 = new RelatedDummy();
×
259
        $relatedDummy1->setId(1);
×
260
        $relatedDummy2 = new RelatedDummy();
×
261
        $relatedDummy2->setId(2);
×
262

263
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
264
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::any())->willReturn(new PropertyNameCollection(['name', 'ghost', 'relatedDummy', 'relatedDummies']));
×
265

266
        $relatedDummyType = Type::object(RelatedDummy::class);
×
267
        $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class), Type::int());
×
268

269
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
270
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', Argument::any())->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
271
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'ghost', Argument::any())->willReturn((new ApiProperty())->withNativeType(Type::string())->withDescription('')->withReadable(false)->withWritable(true));
×
272
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::any())->willReturn((new ApiProperty())->withNativeType($relatedDummyType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
273
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', Argument::any())->willReturn((new ApiProperty())->withNativeType($relatedDummiesType)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
×
274

275
        $getItemFromIriSecondArgCallback = fn ($arg): bool => \is_array($arg) && isset($arg['fetch_data']) && true === $arg['fetch_data'];
×
276

277
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
278
        $iriConverterProphecy->getResourceFromIri('/related_dummies/1', Argument::that($getItemFromIriSecondArgCallback))->willReturn($relatedDummy1);
×
279
        $iriConverterProphecy->getResourceFromIri('/related_dummies/2', Argument::that($getItemFromIriSecondArgCallback))->willReturn($relatedDummy2);
×
280

281
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
282
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'name', 'foo')->will(function (): void {});
×
283
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'ghost', 'invisible')->willThrow(new NoSuchPropertyException());
×
284
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummy', $relatedDummy1)->will(function (): void {});
×
285
        $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'relatedDummies', [$relatedDummy2])->will(function (): void {});
×
286

287
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
288
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
289
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
290
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
291
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
292

293
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
294
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
295

296
        $resourceMetadataCollectionFactory = $this->prophesize(ResourceMetadataCollectionFactoryInterface::class);
×
297
        $resourceMetadataCollectionFactory->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [
×
298
            (new ApiResource())->withOperations(new Operations([new Get(name: 'get')])),
×
299
        ]
×
300
        ));
×
301
        $resourceMetadataCollectionFactory->create(RelatedDummy::class)->willReturn(new ResourceMetadataCollection(RelatedDummy::class, [
×
302
            (new ApiResource())->withOperations(new Operations([new Get(name: 'get')])),
×
303
        ]
×
304
        ));
×
305

306
        $normalizer = new ItemNormalizer(
×
307
            $propertyNameCollectionFactoryProphecy->reveal(),
×
308
            $propertyMetadataFactoryProphecy->reveal(),
×
309
            $iriConverterProphecy->reveal(),
×
310
            $resourceClassResolverProphecy->reveal(),
×
311
            $propertyAccessorProphecy->reveal(),
×
312
            new ReservedAttributeNameConverter(),
×
313
            null,
×
314
            [],
×
315
            $resourceMetadataCollectionFactory->reveal(),
×
316
        );
×
317
        $normalizer->setSerializer($serializerProphecy->reveal());
×
318

319
        $this->assertInstanceOf(Dummy::class, $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT));
×
320
    }
321

322
    public function testDenormalizeUpdateOperationNotAllowed(): void
323
    {
324
        $this->expectException(NotNormalizableValueException::class);
×
325
        $this->expectExceptionMessage('Update is not allowed for this operation.');
×
326

327
        $normalizer = new ItemNormalizer(
×
328
            $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(),
×
329
            $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(),
×
330
            $this->prophesize(IriConverterInterface::class)->reveal(),
×
331
            $this->prophesize(ResourceClassResolverInterface::class)->reveal(),
×
332
        );
×
333

334
        $normalizer->denormalize(
×
335
            [
×
336
                'data' => [
×
337
                    'id' => 1,
×
338
                    'type' => 'dummy',
×
339
                ],
×
340
            ],
×
341
            Dummy::class,
×
342
            ItemNormalizer::FORMAT,
×
343
            [
×
344
                'api_allow_update' => false,
×
345
            ]
×
346
        );
×
347
    }
348

349
    public function testDenormalizeCollectionIsNotArray(): void
350
    {
351
        $this->expectException(NotNormalizableValueException::class);
×
352
        $this->expectExceptionMessage('The type of the "relatedDummies" attribute must be "array", "string" given.');
×
353

354
        $data = [
×
355
            'data' => [
×
356
                'type' => 'dummy',
×
357
                'relationships' => [
×
358
                    'relatedDummies' => [
×
359
                        'data' => 'foo',
×
360
                    ],
×
361
                ],
×
362
            ],
×
363
        ];
×
364

365
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
366
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies']));
×
367

368
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
369

370
        $type = Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class), Type::int());
×
371
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn((new ApiProperty())
×
372
                ->withNativeType($type)
×
373
                ->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
374
        );
×
375

376
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
377

378
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
379
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
380
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
381
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
382
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
383

384
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
385

386
        $normalizer = new ItemNormalizer(
×
387
            $propertyNameCollectionFactoryProphecy->reveal(),
×
388
            $propertyMetadataFactoryProphecy->reveal(),
×
389
            $iriConverterProphecy->reveal(),
×
390
            $resourceClassResolverProphecy->reveal(),
×
391
            $propertyAccessorProphecy->reveal(),
×
392
            new ReservedAttributeNameConverter(),
×
393
            null,
×
394
            []
×
395
        );
×
396

397
        $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT);
×
398
    }
399

400
    public function testDenormalizeCollectionWithInvalidKey(): void
401
    {
402
        $this->expectException(NotNormalizableValueException::class);
×
403
        $this->expectExceptionMessage('The type of the key "0" must be "string", "integer" given.');
×
404

405
        $data = [
×
406
            'data' => [
×
407
                'type' => 'dummy',
×
408
                'relationships' => [
×
409
                    'relatedDummies' => [
×
410
                        'data' => [
×
411
                            [
×
412
                                'type' => 'related-dummy',
×
413
                                'id' => '2',
×
414
                            ],
×
415
                        ],
×
416
                    ],
×
417
                ],
×
418
            ],
×
419
        ];
×
420

421
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
422
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummies']));
×
423

424
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
425
        $type = Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class), Type::string());
×
426
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn(
×
427
            (new ApiProperty())->withNativeType($type)->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
428
        );
×
429

430
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
431

432
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
433
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
434
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
435
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
436
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
437

438
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
439

440
        $normalizer = new ItemNormalizer(
×
441
            $propertyNameCollectionFactoryProphecy->reveal(),
×
442
            $propertyMetadataFactoryProphecy->reveal(),
×
443
            $iriConverterProphecy->reveal(),
×
444
            $resourceClassResolverProphecy->reveal(),
×
445
            $propertyAccessorProphecy->reveal(),
×
446
            new ReservedAttributeNameConverter(),
×
447
            null,
×
448
            []
×
449
        );
×
450

451
        $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT);
×
452
    }
453

454
    public function testDenormalizeRelationIsNotResourceLinkage(): void
455
    {
456
        $this->expectException(UnexpectedValueException::class);
×
457
        $this->expectExceptionMessage('Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.');
×
458

459
        $data = [
×
460
            'data' => [
×
461
                'type' => 'dummy',
×
462
                'relationships' => [
×
463
                    'relatedDummy' => [
×
464
                        'data' => 'foo',
×
465
                    ],
×
466
                ],
×
467
            ],
×
468
        ];
×
469

470
        $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
×
471
        $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['relatedDummy']));
×
472

473
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
474
        $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn(
×
475
            (new ApiProperty())->withNativeType(Type::object(RelatedDummy::class))->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)
×
476
        );
×
477

478
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
479

480
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
481
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
482
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
483
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
484
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
485

486
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
487

488
        $normalizer = new ItemNormalizer(
×
489
            $propertyNameCollectionFactoryProphecy->reveal(),
×
490
            $propertyMetadataFactoryProphecy->reveal(),
×
491
            $iriConverterProphecy->reveal(),
×
492
            $resourceClassResolverProphecy->reveal(),
×
493
            $propertyAccessorProphecy->reveal(),
×
494
            new ReservedAttributeNameConverter(),
×
495
            null,
×
496
            []
×
497
        );
×
498

499
        $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT);
×
500
    }
501
}
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