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

api-platform / core / 18089937549

29 Sep 2025 07:56AM UTC coverage: 21.764% (-0.3%) from 22.093%
18089937549

Pull #7416

github

web-flow
Merge 061bcc790 into abe0438be
Pull Request #7416: fix(laravel): serializer attributes on Eloquent methods

0 of 151 new or added lines in 11 files covered. (0.0%)

5028 existing lines in 173 files now uncovered.

11889 of 54626 relevant lines covered (21.76%)

25.32 hits per line

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

0.0
/src/Symfony/Tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.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\Symfony\Tests\Doctrine\EventListener;
15

16
use ApiPlatform\HttpCache\PurgerInterface;
17
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
18
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
19
use ApiPlatform\Metadata\GetCollection;
20
use ApiPlatform\Metadata\IriConverterInterface;
21
use ApiPlatform\Metadata\ResourceClassResolverInterface;
22
use ApiPlatform\Metadata\UrlGeneratorInterface;
23
use ApiPlatform\Symfony\Doctrine\EventListener\PurgeHttpCacheListener;
24
use ApiPlatform\Symfony\Tests\Fixtures\MappedEntity;
25
use ApiPlatform\Symfony\Tests\Fixtures\MappedResource;
26
use ApiPlatform\Symfony\Tests\Fixtures\NotAResource;
27
use ApiPlatform\Symfony\Tests\Fixtures\TestBundle\Entity\ContainNonResource;
28
use ApiPlatform\Symfony\Tests\Fixtures\TestBundle\Entity\Dummy;
29
use ApiPlatform\Symfony\Tests\Fixtures\TestBundle\Entity\DummyNoGetOperation;
30
use ApiPlatform\Symfony\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
31
use Doctrine\ORM\EntityManagerInterface;
32
use Doctrine\ORM\Event\OnFlushEventArgs;
33
use Doctrine\ORM\Event\PreUpdateEventArgs;
34
use Doctrine\ORM\Mapping\ClassMetadata;
35
use Doctrine\ORM\UnitOfWork;
36
use PHPUnit\Framework\TestCase;
37
use Prophecy\Argument;
38
use Prophecy\PhpUnit\ProphecyTrait;
39
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
40
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
41

42
/**
43
 * @author Kévin Dunglas <dunglas@gmail.com>
44
 */
45
class PurgeHttpCacheListenerTest extends TestCase
46
{
47
    use ProphecyTrait;
48

49
    public function testOnFlush(): void
50
    {
51
        $toInsert1 = new Dummy();
×
52
        $toInsert2 = new Dummy();
×
53

54
        $toUpdate1 = new Dummy();
×
UNCOV
55
        $toUpdate1->setId(1);
×
56
        $toUpdate2 = new Dummy();
×
57
        $toUpdate2->setId(2);
×
58

59
        $toDelete1 = new Dummy();
×
UNCOV
60
        $toDelete1->setId(3);
×
61
        $toDelete2 = new Dummy();
×
62
        $toDelete2->setId(4);
×
63

64
        $toDeleteNoPurge = new DummyNoGetOperation();
×
65
        $toDeleteNoPurge->setId(5);
×
66

67
        $purgerProphecy = $this->prophesize(PurgerInterface::class);
×
68
        $purgerProphecy->purge(['/dummies', '/dummies/1', '/dummies/2', '/dummies/3', '/dummies/4'])->shouldBeCalled();
×
69

70
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
71
        $iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
×
72
        $iriConverterProphecy->getIriFromResource($toUpdate1)->willReturn('/dummies/1')->shouldBeCalled();
×
73
        $iriConverterProphecy->getIriFromResource($toUpdate2)->willReturn('/dummies/2')->shouldBeCalled();
×
74
        $iriConverterProphecy->getIriFromResource($toDelete1)->willReturn('/dummies/3')->shouldBeCalled();
×
UNCOV
75
        $iriConverterProphecy->getIriFromResource($toDelete2)->willReturn('/dummies/4')->shouldBeCalled();
×
76
        $iriConverterProphecy->getIriFromResource(Argument::type(DummyNoGetOperation::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willThrow(new InvalidArgumentException())->shouldBeCalled();
×
77
        $iriConverterProphecy->getIriFromResource(Argument::any())->willThrow(new ItemNotFoundException());
×
78

79
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
80
        $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
×
81

82
        $uowProphecy = $this->prophesize(UnitOfWork::class);
×
UNCOV
83
        $uowProphecy->getScheduledEntityInsertions()->willReturn([$toInsert1, $toInsert2])->shouldBeCalled();
×
84
        $uowProphecy->getScheduledEntityUpdates()->willReturn([$toUpdate1, $toUpdate2])->shouldBeCalled();
×
85
        $uowProphecy->getScheduledEntityDeletions()->willReturn([$toDelete1, $toDelete2, $toDeleteNoPurge])->shouldBeCalled();
×
86

UNCOV
87
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
×
88
        $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled();
×
89
        $dummyClassMetadata = new ClassMetadata(Dummy::class);
×
90
        // @phpstan-ignore-next-line
91
        $dummyClassMetadata->associationMappings = [
×
92
            'relatedDummy' => [],
×
93
            'relatedOwningDummy' => [],
×
94
        ];
×
UNCOV
95
        $emProphecy->getClassMetadata(Dummy::class)->willReturn($dummyClassMetadata)->shouldBeCalled();
×
96
        $emProphecy->getClassMetadata(DummyNoGetOperation::class)->willReturn(new ClassMetadata(DummyNoGetOperation::class))->shouldBeCalled();
×
97
        $eventArgs = new OnFlushEventArgs($emProphecy->reveal());
×
98

99
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
100
        $propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedDummy')->willReturn(true);
×
UNCOV
101
        $propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedOwningDummy')->willReturn(false);
×
102
        $propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedDummy')->willReturn(null)->shouldBeCalled();
×
103
        $propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedOwningDummy')->willReturn(null)->shouldNotBeCalled();
×
104

UNCOV
105
        $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal());
×
106
        $listener->onFlush($eventArgs);
×
107
        $listener->postFlush();
×
108

UNCOV
109
        $iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->shouldHaveBeenCalled();
×
UNCOV
110
        $iriConverterProphecy->getIriFromResource(Argument::type(DummyNoGetOperation::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->shouldHaveBeenCalled();
×
111
    }
112

113
    public function testPreUpdate(): void
114
    {
115
        $oldRelatedDummy = new RelatedDummy();
×
116
        $oldRelatedDummy->setId(1);
×
117

118
        $newRelatedDummy = new RelatedDummy();
×
119
        $newRelatedDummy->setId(2);
×
120

121
        $dummy = new Dummy();
×
122
        $dummy->setId(1);
×
123

124
        $purgerProphecy = $this->prophesize(PurgerInterface::class);
×
125
        $purgerProphecy->purge(['/dummies', '/dummies/1', '/related_dummies/old', '/related_dummies/new'])->shouldBeCalled();
×
126

127
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
128
        $iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
×
UNCOV
129
        $iriConverterProphecy->getIriFromResource($dummy)->willReturn('/dummies/1')->shouldBeCalled();
×
130
        $iriConverterProphecy->getIriFromResource($oldRelatedDummy)->willReturn('/related_dummies/old')->shouldBeCalled();
×
131
        $iriConverterProphecy->getIriFromResource($newRelatedDummy)->willReturn('/related_dummies/new')->shouldBeCalled();
×
132

133
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
134
        $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
×
135

136
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
×
137

UNCOV
138
        $classMetadata = new ClassMetadata(Dummy::class);
×
139
        $classMetadata->mapManyToOne(['fieldName' => 'relatedDummy', 'targetEntity' => RelatedDummy::class]);
×
140
        $emProphecy->getClassMetadata(Dummy::class)->willReturn($classMetadata)->shouldBeCalled();
×
141

142
        $changeSet = ['relatedDummy' => [$oldRelatedDummy, $newRelatedDummy]];
×
143
        $eventArgs = new PreUpdateEventArgs($dummy, $emProphecy->reveal(), $changeSet);
×
144

UNCOV
145
        $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal());
×
UNCOV
146
        $listener->preUpdate($eventArgs);
×
UNCOV
147
        $listener->postFlush();
×
148
    }
149

150
    public function testNothingToPurge(): void
151
    {
152
        $dummyNoGetOperation = new DummyNoGetOperation();
×
153
        $dummyNoGetOperation->setId(1);
×
154

155
        $purgerProphecy = $this->prophesize(PurgerInterface::class);
×
156
        $purgerProphecy->purge([])->shouldNotBeCalled();
×
157

UNCOV
158
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
159
        $iriConverterProphecy->getIriFromResource(Argument::type(DummyNoGetOperation::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willThrow(new InvalidArgumentException())->shouldBeCalled();
×
160
        $iriConverterProphecy->getIriFromResource($dummyNoGetOperation)->shouldNotBeCalled();
×
161

162
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
163
        $resourceClassResolverProphecy->isResourceClass(DummyNoGetOperation::class)->willReturn(true)->shouldBeCalled();
×
164
        $resourceClassResolverProphecy->getResourceClass(Argument::type(DummyNoGetOperation::class))->willReturn(DummyNoGetOperation::class)->shouldNotBeCalled();
×
165

UNCOV
166
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
×
167

168
        $classMetadata = new ClassMetadata(DummyNoGetOperation::class);
×
UNCOV
169
        $emProphecy->getClassMetadata(DummyNoGetOperation::class)->willReturn($classMetadata)->shouldBeCalled();
×
170

171
        $changeSet = ['lorem' => ['ipsum1', 'ipsum2']];
×
172
        $eventArgs = new PreUpdateEventArgs($dummyNoGetOperation, $emProphecy->reveal(), $changeSet);
×
173

UNCOV
174
        $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal());
×
UNCOV
175
        $listener->preUpdate($eventArgs);
×
UNCOV
176
        $listener->postFlush();
×
177
    }
178

179
    public function testNotAResourceClass(): void
180
    {
UNCOV
181
        $containNonResource = new ContainNonResource();
×
182
        $nonResource1 = new NotAResource('foo', 'bar');
×
183
        $nonResource2 = new NotAResource('baz', 'qux');
×
UNCOV
184
        $collectionOfNotAResource = [$nonResource1, $nonResource2];
×
185

186
        $purgerProphecy = $this->prophesize(PurgerInterface::class);
×
187
        $purgerProphecy->purge(['/dummies'])->shouldBeCalled();
×
188

UNCOV
189
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
190
        $iriConverterProphecy->getIriFromResource(Argument::type(ContainNonResource::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
×
191
        $iriConverterProphecy->getIriFromResource($nonResource1)->willThrow(new InvalidArgumentException())->shouldBeCalled();
×
UNCOV
192
        $iriConverterProphecy->getIriFromResource($nonResource2)->willThrow(new InvalidArgumentException())->shouldBeCalled();
×
193

194
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
195
        $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
×
196

UNCOV
197
        $uowProphecy = $this->prophesize(UnitOfWork::class);
×
198
        $uowProphecy->getScheduledEntityInsertions()->willReturn([$containNonResource])->shouldBeCalled();
×
199
        $uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled();
×
UNCOV
200
        $uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled();
×
201

UNCOV
202
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
×
203
        $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled();
×
204

205
        $dummyClassMetadata = new ClassMetadata(ContainNonResource::class);
×
206
        // @phpstan-ignore-next-line
207
        $dummyClassMetadata->associationMappings = [
×
208
            'notAResource' => [],
×
UNCOV
209
            'collectionOfNotAResource' => ['targetEntity' => NotAResource::class],
×
210
        ];
×
211
        $emProphecy->getClassMetadata(ContainNonResource::class)->willReturn($dummyClassMetadata);
×
212
        $eventArgs = new OnFlushEventArgs($emProphecy->reveal());
×
213

214
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
UNCOV
215
        $propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'notAResource')->willReturn(true);
×
216
        $propertyAccessorProphecy->isReadable(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->willReturn(true);
×
217
        $propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'notAResource')->shouldBeCalled()->willReturn($nonResource1);
×
218
        $propertyAccessorProphecy->getValue(Argument::type(ContainNonResource::class), 'collectionOfNotAResource')->shouldBeCalled()->willReturn($collectionOfNotAResource);
×
219

UNCOV
220
        $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(),
×
UNCOV
221
            $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal());
×
UNCOV
222
        $listener->onFlush($eventArgs);
×
223
        $listener->postFlush();
×
224
    }
225

226
    public function testAddTagsForCollection(): void
227
    {
UNCOV
228
        $dummy1 = new Dummy();
×
229
        $dummy1->setId(1);
×
230
        $dummy2 = new Dummy();
×
UNCOV
231
        $dummy2->setId(2);
×
232
        $collection = [$dummy1, $dummy2];
×
233

234
        $purgerProphecy = $this->prophesize(PurgerInterface::class);
×
235
        $purgerProphecy->purge(['/dummies/1', '/dummies/2', '/dummies'])->shouldBeCalled();
×
236

237
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
238
        $iriConverterProphecy->getIriFromResource(Argument::type(Dummy::class), UrlGeneratorInterface::ABS_PATH, new GetCollection())->willReturn('/dummies')->shouldBeCalled();
×
UNCOV
239
        $iriConverterProphecy->getIriFromResource($dummy1)->willReturn('/dummies/1')->shouldBeCalled();
×
240
        $iriConverterProphecy->getIriFromResource($dummy2)->willReturn('/dummies/2')->shouldBeCalled();
×
241

UNCOV
242
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
243
        $resourceClassResolverProphecy->isResourceClass(Argument::type('string'))->willReturn(true)->shouldBeCalled();
×
244

245
        $dummyWithCollection = new Dummy();
×
246
        $dummyWithCollection->setId(3);
×
247

248
        $uowProphecy = $this->prophesize(UnitOfWork::class);
×
249
        $uowProphecy->getScheduledEntityInsertions()->willReturn([$dummyWithCollection])->shouldBeCalled();
×
UNCOV
250
        $uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled();
×
251
        $uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled();
×
252

253
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
×
254
        $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled();
×
255

256
        $dummyClassMetadata = new ClassMetadata(Dummy::class);
×
257
        // @phpstan-ignore-next-line
UNCOV
258
        $dummyClassMetadata->associationMappings = [
×
259
            'relatedDummies' => ['targetEntity' => Dummy::class],
×
260
        ];
×
261
        $emProphecy->getClassMetadata(Dummy::class)->willReturn($dummyClassMetadata)->shouldBeCalled();
×
UNCOV
262
        $eventArgs = new OnFlushEventArgs($emProphecy->reveal());
×
263

264
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
265
        $propertyAccessorProphecy->isReadable(Argument::type(Dummy::class), 'relatedDummies')->willReturn(true);
×
UNCOV
266
        $propertyAccessorProphecy->getValue(Argument::type(Dummy::class), 'relatedDummies')->willReturn($collection)->shouldBeCalled();
×
267

UNCOV
268
        $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal());
×
UNCOV
269
        $listener->onFlush($eventArgs);
×
UNCOV
270
        $listener->postFlush();
×
271
    }
272

273
    public function testMappedResources(): void
274
    {
UNCOV
275
        $mappedEntity = new MappedEntity();
×
UNCOV
276
        $mappedEntity->setFirstName('first');
×
UNCOV
277
        $mappedEntity->setlastName('last');
×
278

UNCOV
279
        $mappedResource = new MappedResource();
×
UNCOV
280
        $mappedResource->username = $mappedEntity->getFirstName().' '.$mappedEntity->getLastName();
×
281

UNCOV
282
        $purgerProphecy = $this->prophesize(PurgerInterface::class);
×
UNCOV
283
        $purgerProphecy->purge(['/mapped_ressources'])->shouldBeCalled();
×
284

UNCOV
285
        $iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
×
286
        // the entity is not a resource, shouldn't be called
UNCOV
287
        $iriConverterProphecy->getIriFromResource(
×
UNCOV
288
            Argument::type(MappedEntity::class), UrlGeneratorInterface::ABS_PATH, new GetCollection()
×
UNCOV
289
        )->shouldNotBeCalled();
×
290
        // this should be called instead
UNCOV
291
        $iriConverterProphecy->getIriFromResource(
×
UNCOV
292
            Argument::type(MappedResource::class), UrlGeneratorInterface::ABS_PATH, new GetCollection()
×
UNCOV
293
        )->willReturn('/mapped_ressources')->shouldBeCalled();
×
294

UNCOV
295
        $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
×
UNCOV
296
        $resourceClassResolverProphecy->isResourceClass(MappedEntity::class)->willReturn(false)->shouldBeCalled();
×
297

UNCOV
298
        $objectMapperProphecy = $this->prophesize(ObjectMapperInterface::class);
×
UNCOV
299
        $objectMapperProphecy->map($mappedEntity, MappedResource::class)->shouldBeCalled()->willReturn($mappedResource);
×
300

UNCOV
301
        $uowProphecy = $this->prophesize(UnitOfWork::class);
×
UNCOV
302
        $uowProphecy->getScheduledEntityInsertions()->willReturn([$mappedEntity])->shouldBeCalled();
×
UNCOV
303
        $uowProphecy->getScheduledEntityUpdates()->willReturn([])->shouldBeCalled();
×
UNCOV
304
        $uowProphecy->getScheduledEntityDeletions()->willReturn([])->shouldBeCalled();
×
305

UNCOV
306
        $emProphecy = $this->prophesize(EntityManagerInterface::class);
×
UNCOV
307
        $emProphecy->getUnitOfWork()->willReturn($uowProphecy->reveal())->shouldBeCalled();
×
UNCOV
308
        $classMetadata = new ClassMetadata(MappedEntity::class);
×
UNCOV
309
        $classMetadata->associationMappings = [];
×
UNCOV
310
        $emProphecy->getClassMetadata(MappedEntity::class)->willReturn($classMetadata)->shouldBeCalled();
×
UNCOV
311
        $eventArgs = new OnFlushEventArgs($emProphecy->reveal());
×
312

UNCOV
313
        $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
×
314

UNCOV
315
        $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(),
×
UNCOV
316
            $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(),
×
UNCOV
317
            $objectMapperProphecy->reveal()
×
UNCOV
318
        );
×
UNCOV
319
        $listener->onFlush($eventArgs);
×
UNCOV
320
        $listener->postFlush();
×
321
    }
322
}
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

© 2025 Coveralls, Inc