Skip to content

Commit d3627b1

Browse files
committed
Add tests for the SoMany collection
1 parent ec9d521 commit d3627b1

File tree

5 files changed

+190
-23
lines changed

5 files changed

+190
-23
lines changed

features/bootstrap/FeatureContext.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend;
4444
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelationEmbedder;
4545
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
46+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SoMany;
4647
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThirdLevel;
4748
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\User;
4849
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\UuidIdentifierDummy;
@@ -136,6 +137,21 @@ public function thereAreDummyObjects(int $nb)
136137
$this->manager->flush();
137138
}
138139

140+
/**
141+
* @Given there are :nb of these so many objects
142+
*/
143+
public function thereAreOfTheseSoManyObjects(int $nb)
144+
{
145+
for ($i = 1; $i <= $nb; ++$i) {
146+
$dummy = new SoMany();
147+
$dummy->content = 'Many #'.$i;
148+
149+
$this->manager->persist($dummy);
150+
}
151+
152+
$this->manager->flush();
153+
}
154+
139155
/**
140156
* @Given there are :nb foo objects with fake names
141157
*/

features/hydra/collection.feature

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,99 @@ Feature: Collections support
435435
When I send a "GET" request to "/dummies?itemsPerPage=0&page=2"
436436
Then the response status code should be 400
437437
And the JSON node "hydra:description" should be equal to "Page should not be greater than 1 if itemsPerPage is equal to 0"
438+
439+
Scenario: Cursor-based pagination with an empty collection
440+
When I send a "GET" request to "/so_manies"
441+
Then the response status code should be 200
442+
And the response should be in JSON
443+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
444+
And the JSON should be valid according to this schema:
445+
"""
446+
{
447+
"type": "object",
448+
"properties": {
449+
"@context": {"pattern": "^/contexts/SoMany$"},
450+
"@id": {"pattern": "^/so_manies$"},
451+
"@type": {"pattern": "^hydra:Collection"},
452+
"hydra:view": {
453+
"type": "object",
454+
"properties": {
455+
"@id": {"pattern": "^/so_manies$"},
456+
"@type": {"pattern": "^hydra:PartialCollectionView$"}
457+
}
458+
}
459+
}
460+
}
461+
"""
462+
463+
@createSchema
464+
Scenario: Cursor-based pagination with items
465+
Given there are 10 of these so many objects
466+
When I send a "GET" request to "/so_manies?order[id]=desc"
467+
Then the response status code should be 200
468+
And the response should be in JSON
469+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
470+
And the JSON should be valid according to this schema:
471+
"""
472+
{
473+
"type": "object",
474+
"properties": {
475+
"@context": {"pattern": "^/contexts/SoMany$"},
476+
"@id": {"pattern": "^/so_manies$"},
477+
"@type": {"pattern": "^hydra:Collection"},
478+
"hydra:view": {
479+
"type": "object",
480+
"properties": {
481+
"@id": {"pattern": "^/so_manies\\?order%5Bid%5D=desc$"},
482+
"@type": {"pattern": "^hydra:PartialCollectionView$"},
483+
"hydra:previous": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Bgt%5D=10$"},
484+
"hydra:next": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Blt%5D=8$"}
485+
}
486+
}
487+
}
488+
}
489+
"""
490+
491+
@createSchema
492+
Scenario: Cursor-based pagination with items
493+
Given there are 10 of these so many objects
494+
When I send a "GET" request to "/so_manies?order[id]=desc&id[gt]=10"
495+
Then the response status code should be 200
496+
And the response should be in JSON
497+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
498+
And the JSON should be valid according to this schema:
499+
"""
500+
{
501+
"type": "object",
502+
"properties": {
503+
"@context": {"pattern": "^/contexts/SoMany$"},
504+
"@id": {"pattern": "^/so_manies$"},
505+
"@type": {"pattern": "^hydra:Collection"},
506+
"hydra:member": {
507+
"type": "array",
508+
"items": {
509+
"type": "object",
510+
"properties": {
511+
"@id": {
512+
"oneOf": [
513+
{"pattern": "^/dummies/13$"},
514+
{"pattern": "^/dummies/12$"},
515+
{"pattern": "^/dummies/11$"}
516+
]
517+
}
518+
}
519+
},
520+
"maxItems": 3
521+
},
522+
"hydra:view": {
523+
"type": "object",
524+
"properties": {
525+
"@id": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Bgt%5D=10$"},
526+
"@type": {"pattern": "^hydra:PartialCollectionView$"},
527+
"hydra:previous": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Bgt%5D=13$"},
528+
"hydra:next": {"pattern": "^/so_manies\\?order%5Bid%5D=desc&id%5Blt%5D=10$"}
529+
}
530+
}
531+
}
532+
}
533+
"""

src/Hydra/Serializer/PartialCollectionViewNormalizer.php

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ public function normalize($object, $format = null, array $context = [])
7575
return $data;
7676
}
7777

78-
$metadata = $this->resourceMetadataFactory->create($context['resource_class']);
79-
$isPaginatedWithCursor = $paginated && null !== $cursorPaginationAttribute = $metadata->getAttribute('pagination_via_cursor');
78+
$metadata = isset($context['resource_class']) ? $this->resourceMetadataFactory->create($context['resource_class']) : null;
79+
$isPaginatedWithCursor = $paginated && null !== $metadata && null !== $cursorPaginationAttribute = $metadata->getAttribute('pagination_via_cursor');
8080

8181
$data['hydra:view'] = [
82-
'@id' => IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated ? $currentPage : null),
82+
'@id' => IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated && !$isPaginatedWithCursor ? $currentPage : null),
8383
'@type' => 'hydra:PartialCollectionView',
8484
];
8585

@@ -92,21 +92,22 @@ public function normalize($object, $format = null, array $context = [])
9292
$firstObject = current($objects);
9393
$lastObject = end($objects);
9494

95-
$parsed['parameters'][$cursorPaginationAttribute['field']] = [
96-
$backwardRangeOperator.'e' => (string) $accessor->getValue($lastObject, $cursorPaginationAttribute['field']),
97-
];
98-
99-
$data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters']);
100-
$data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], [
101-
$cursorPaginationAttribute['field'] => [
102-
$backwardRangeOperator => (string) $accessor->getValue($firstObject, $cursorPaginationAttribute['field']),
103-
],
104-
]));
105-
$data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], [
106-
$cursorPaginationAttribute['field'] => [
107-
$forwardRangeOperator => (string) $accessor->getValue($lastObject, $cursorPaginationAttribute['field']),
108-
],
109-
]));
95+
if (false !== $lastObject) {
96+
$data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters']);
97+
$data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], [
98+
$cursorPaginationAttribute['field'] => [
99+
$forwardRangeOperator => (string) $accessor->getValue($lastObject, $cursorPaginationAttribute['field']),
100+
],
101+
]));
102+
}
103+
104+
if (false !== $firstObject) {
105+
$data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], [
106+
$cursorPaginationAttribute['field'] => [
107+
$backwardRangeOperator => (string) $accessor->getValue($firstObject, $cursorPaginationAttribute['field']),
108+
],
109+
]));
110+
}
110111
} elseif ($paginated) {
111112
if (null !== $lastPage) {
112113
$data['hydra:view']['hydra:first'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, 1.);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiFilter;
17+
use ApiPlatform\Core\Annotation\ApiResource;
18+
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
19+
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter;
20+
use Doctrine\ORM\Mapping as ORM;
21+
22+
/**
23+
* @ORM\Entity
24+
* @ApiResource(attributes={
25+
* "pagination_partial"=true,
26+
* "pagination_via_cursor"={
27+
* "field"="id",
28+
* "direction"="DESC"
29+
* }
30+
* })
31+
*
32+
* @ApiFilter(RangeFilter::class, properties={"id"})
33+
* @ApiFilter(OrderFilter::class, properties={"id"="DESC"})
34+
*/
35+
class SoMany
36+
{
37+
/**
38+
* @ORM\Id
39+
* @ORM\Column(type="integer")
40+
* @ORM\GeneratedValue(strategy="AUTO")
41+
*/
42+
public $id;
43+
44+
/**
45+
* @ORM\Column(nullable=true)
46+
*/
47+
public $content;
48+
}

tests/Hydra/Serializer/PartialCollectionViewNormalizerTest.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Core\DataProvider\PaginatorInterface;
1717
use ApiPlatform\Core\DataProvider\PartialPaginatorInterface;
1818
use ApiPlatform\Core\Hydra\Serializer\PartialCollectionViewNormalizer;
19+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1920
use PHPUnit\Framework\TestCase;
2021
use Prophecy\Argument;
2122
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
@@ -30,17 +31,19 @@ public function testNormalizeDoesNotChangeSubLevel()
3031
{
3132
$decoratedNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
3233
$decoratedNormalizerProphecy->normalize(Argument::any(), null, ['jsonld_sub_level' => true])->willReturn(['foo' => 'bar'])->shouldBeCalled();
34+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
3335

34-
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal());
36+
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal(), $resourceMetadataFactory->reveal());
3537
$this->assertEquals(['foo' => 'bar'], $normalizer->normalize(new \stdClass(), null, ['jsonld_sub_level' => true]));
3638
}
3739

3840
public function testNormalizeDoesNotChangeWhenNoFilterNorPagination()
3941
{
4042
$decoratedNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
4143
$decoratedNormalizerProphecy->normalize(Argument::any(), null, Argument::type('array'))->willReturn(['foo' => 'bar'])->shouldBeCalled();
44+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
4245

43-
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal());
46+
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal(), $resourceMetadataFactory->reveal());
4447
$this->assertEquals(['foo' => 'bar'], $normalizer->normalize(new \stdClass(), null, ['request_uri' => '/?page=1&pagination=1']));
4548
}
4649

@@ -96,8 +99,9 @@ private function normalizePaginator($partial = false)
9699

97100
$decoratedNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
98101
$decoratedNormalizerProphecy->normalize(Argument::type($partial ? PartialPaginatorInterface::class : PaginatorInterface::class), null, Argument::type('array'))->willReturn($decoratedNormalize)->shouldBeCalled();
102+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
99103

100-
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal(), '_page');
104+
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal(), $resourceMetadataFactory->reveal(), '_page');
101105

102106
return $normalizer->normalize($paginatorProphecy->reveal());
103107
}
@@ -106,8 +110,9 @@ public function testSupportsNormalization()
106110
{
107111
$decoratedNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
108112
$decoratedNormalizerProphecy->supportsNormalization(Argument::any(), null)->willReturn(true)->shouldBeCalled();
113+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
109114

110-
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal());
115+
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal(), $resourceMetadataFactory->reveal());
111116
$this->assertTrue($normalizer->supportsNormalization(new \stdClass()));
112117
}
113118

@@ -118,8 +123,9 @@ public function testSetNormalizer()
118123
$decoratedNormalizerProphecy = $this->prophesize(NormalizerInterface::class);
119124
$decoratedNormalizerProphecy->willImplement(NormalizerAwareInterface::class);
120125
$decoratedNormalizerProphecy->setNormalizer(Argument::type(NormalizerInterface::class))->shouldBeCalled();
126+
$resourceMetadataFactory = $this->prophesize(ResourceMetadataFactoryInterface::class);
121127

122-
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal());
128+
$normalizer = new PartialCollectionViewNormalizer($decoratedNormalizerProphecy->reveal(), $resourceMetadataFactory->reveal());
123129
$normalizer->setNormalizer($injectedNormalizer);
124130
}
125131
}

0 commit comments

Comments
 (0)