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

api-platform / core / 14532513793

18 Apr 2025 08:46AM UTC coverage: 8.252% (+1.3%) from 6.974%
14532513793

push

github

soyuka
Merge 4.1

13093 of 158662 relevant lines covered (8.25%)

13.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);
×
136
        $resourceClassResolverProphecy->getResourceClass($circularReferenceEntity, CircularReference::class)->willReturn(CircularReference::class);
×
137
        $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true);
×
138

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

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

154
        $normalizer->setSerializer($this->prophesize(SerializerInterface::class)->reveal());
×
155

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

160
            $context = [
×
161
                'circular_reference_limit' => [spl_object_hash($circularReferenceEntity) => 2],
×
162
                'cache_error' => function (): void {},
×
163
            ];
×
164
        } else {
165
            $context = [
×
166
                'circular_reference_limit' => $circularReferenceLimit,
×
167
                'circular_reference_limit_counters' => [spl_object_hash($circularReferenceEntity) => 2],
×
168
                'cache_error' => function (): void {},
×
169
            ];
×
170
        }
171

172
        $this->assertSame('/circular_references/1', $normalizer->normalize($circularReferenceEntity, ItemNormalizer::FORMAT, $context));
×
173
    }
174

175
    public function testNormalizeNonExistentProperty(): void
176
    {
177
        $this->expectException(NoSuchPropertyException::class);
×
178

179
        $dummy = new Dummy();
×
180
        $dummy->setId(1);
×
181

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

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

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

191
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
192
        $resourceClassResolverProphecy->getResourceClass($dummy, null)->willReturn(Dummy::class);
×
193
        $resourceClassResolverProphecy->getResourceClass($dummy, Dummy::class)->willReturn(Dummy::class);
×
194
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
195
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
196

197
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
198
        $propertyAccessorProphecy->getValue($dummy, 'bar')->willThrow(new NoSuchPropertyException());
×
199

200
        $serializerProphecy = $this->prophesize(Serializer::class);
×
201

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

209
        $normalizer = new ItemNormalizer(
×
210
            $propertyNameCollectionFactoryProphecy->reveal(),
×
211
            $propertyMetadataFactoryProphecy->reveal(),
×
212
            $iriConverterProphecy->reveal(),
×
213
            $resourceClassResolverProphecy->reveal(),
×
214
            $propertyAccessorProphecy->reveal(),
×
215
            new ReservedAttributeNameConverter(),
×
216
            null,
×
217
            [],
×
218
            $resourceMetadataCollectionFactoryProphecy->reveal(),
×
219
        );
×
220

221
        $normalizer->setSerializer($serializerProphecy->reveal());
×
222

223
        $normalizer->normalize($dummy, ItemNormalizer::FORMAT);
×
224
    }
225

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

254
        $relatedDummy1 = new RelatedDummy();
×
255
        $relatedDummy1->setId(1);
×
256
        $relatedDummy2 = new RelatedDummy();
×
257
        $relatedDummy2->setId(2);
×
258

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

262
        $relatedDummyType = Type::object(RelatedDummy::class);
×
263
        $relatedDummiesType = Type::collection(Type::object(ArrayCollection::class), Type::object(RelatedDummy::class), Type::int());
×
264

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

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

273
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
274
        $iriConverterProphecy->getResourceFromIri('/related_dummies/1', Argument::that($getItemFromIriSecondArgCallback))->willReturn($relatedDummy1);
×
275
        $iriConverterProphecy->getResourceFromIri('/related_dummies/2', Argument::that($getItemFromIriSecondArgCallback))->willReturn($relatedDummy2);
×
276

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

283
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
284
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
285
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
286
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
287
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
288

289
        $serializerProphecy = $this->prophesize(SerializerInterface::class);
×
290
        $serializerProphecy->willImplement(NormalizerInterface::class);
×
291

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

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

315
        $this->assertInstanceOf(Dummy::class, $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT));
×
316
    }
317

318
    public function testDenormalizeUpdateOperationNotAllowed(): void
319
    {
320
        $this->expectException(NotNormalizableValueException::class);
×
321
        $this->expectExceptionMessage('Update is not allowed for this operation.');
×
322

323
        $normalizer = new ItemNormalizer(
×
324
            $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(),
×
325
            $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(),
×
326
            $this->prophesize(IriConverterInterface::class)->reveal(),
×
327
            $this->prophesize(ResourceClassResolverInterface::class)->reveal(),
×
328
        );
×
329

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

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

350
        $data = [
×
351
            'data' => [
×
352
                'type' => 'dummy',
×
353
                'relationships' => [
×
354
                    'relatedDummies' => [
×
355
                        'data' => 'foo',
×
356
                    ],
×
357
                ],
×
358
            ],
×
359
        ];
×
360

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

364
        $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
×
365

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

372
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
373

374
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
375
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
376
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
377
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
378
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
379

380
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
381

382
        $normalizer = new ItemNormalizer(
×
383
            $propertyNameCollectionFactoryProphecy->reveal(),
×
384
            $propertyMetadataFactoryProphecy->reveal(),
×
385
            $iriConverterProphecy->reveal(),
×
386
            $resourceClassResolverProphecy->reveal(),
×
387
            $propertyAccessorProphecy->reveal(),
×
388
            new ReservedAttributeNameConverter(),
×
389
            null,
×
390
            []
×
391
        );
×
392

393
        $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT);
×
394
    }
395

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

401
        $data = [
×
402
            'data' => [
×
403
                'type' => 'dummy',
×
404
                'relationships' => [
×
405
                    'relatedDummies' => [
×
406
                        'data' => [
×
407
                            [
×
408
                                'type' => 'related-dummy',
×
409
                                'id' => '2',
×
410
                            ],
×
411
                        ],
×
412
                    ],
×
413
                ],
×
414
            ],
×
415
        ];
×
416

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

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

426
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
427

428
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
429
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
430
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
431
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
432
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
433

434
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
435

436
        $normalizer = new ItemNormalizer(
×
437
            $propertyNameCollectionFactoryProphecy->reveal(),
×
438
            $propertyMetadataFactoryProphecy->reveal(),
×
439
            $iriConverterProphecy->reveal(),
×
440
            $resourceClassResolverProphecy->reveal(),
×
441
            $propertyAccessorProphecy->reveal(),
×
442
            new ReservedAttributeNameConverter(),
×
443
            null,
×
444
            []
×
445
        );
×
446

447
        $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT);
×
448
    }
449

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

455
        $data = [
×
456
            'data' => [
×
457
                'type' => 'dummy',
×
458
                'relationships' => [
×
459
                    'relatedDummy' => [
×
460
                        'data' => 'foo',
×
461
                    ],
×
462
                ],
×
463
            ],
×
464
        ];
×
465

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

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

474
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
475

476
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
477
        $resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
×
478
        $resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
×
479
        $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
×
480
        $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
×
481

482
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
483

484
        $normalizer = new ItemNormalizer(
×
485
            $propertyNameCollectionFactoryProphecy->reveal(),
×
486
            $propertyMetadataFactoryProphecy->reveal(),
×
487
            $iriConverterProphecy->reveal(),
×
488
            $resourceClassResolverProphecy->reveal(),
×
489
            $propertyAccessorProphecy->reveal(),
×
490
            new ReservedAttributeNameConverter(),
×
491
            null,
×
492
            []
×
493
        );
×
494

495
        $normalizer->denormalize($data, Dummy::class, ItemNormalizer::FORMAT);
×
496
    }
497
}
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