Skip to content

Commit

Permalink
Add tests for attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
Zakhar Shokel committed Apr 24, 2024
1 parent 8d73ff6 commit 9ff07dc
Show file tree
Hide file tree
Showing 14 changed files with 514 additions and 66 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [1.8.0]
### Added
- Support for `doctrine/annotations: ^2.0`
- Support for PHP 8 attributes
- Support for `doctrine/annotations: ^2.0`

## [1.7.0]
### Added
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"doctrine/annotations": "^1.14 || ^2.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^9.5",
"phpunit/phpunit": "^9.6",
"mockery/mockery": "^1.3.6",
"symfony/yaml": "^3.4.34|^4.3|^5.4|^6.0",
"doctrine/doctrine-bundle": "^1.12.0|^2.1",
Expand Down
1 change: 0 additions & 1 deletion src/Attribute/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace Paysera\Bundle\ApiBundle\Attribute;

use Attribute;
use Paysera\Bundle\ApiBundle\Annotation\Validation;
use Paysera\Bundle\ApiBundle\Entity\QueryResolverOptions;
use Paysera\Bundle\ApiBundle\Entity\RestRequestOptions;
use Paysera\Bundle\ApiBundle\Exception\ConfigurationException;
Expand Down
4 changes: 3 additions & 1 deletion src/Service/RoutingLoader/RoutingAttributeLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/
class RoutingAttributeLoader extends AttributeRouteControllerLoader
{
private const ATTRIBUTE_SUPPORT_PHP_VERSION_ID = 80100; // `new` in initializers support is required

/**
* @var RestRequestHelper
*/
Expand Down Expand Up @@ -57,7 +59,7 @@ protected function configureRoute(

$this->loadAnnotations($route, $class, $method);

if (PHP_VERSION_ID >= 80000) {
if (PHP_VERSION_ID >= self::ATTRIBUTE_SUPPORT_PHP_VERSION_ID) {
$this->loadAttributes($route, $class, $method);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

namespace Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller;

use Paysera\Bundle\ApiBundle\Attribute\RequiredPermissions;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[RequiredPermissions(permissions: ['ROLE_USER'])]
class AttributedClassRequiredPermissionsController
{
/**
* @Route(path="/attributed/class/testRequiredPermissions", methods={"GET"})
*/
#[RequiredPermissions(permissions: ['ROLE_USER'])]
#[RequiredPermissions(permissions: ['ROLE_ADMIN'])]
#[RequiredPermissions(permissions: ['ROLE_USER'])]
public function test(): Response
{
return new Response('OK');
}

/**
* @Route(path="/attributed/class/simpleAction", methods={"GET"})
*/
public function simpleAction(): Response
{
return new Response('OK');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);

namespace Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller;

use Paysera\Bundle\ApiBundle\Attribute\Body;
use Paysera\Bundle\ApiBundle\Attribute\Validation;
use Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Entity\MyObject;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

#[Validation(groups: ['internal_field1_email'], violationPathMap: ['internalField1' => 'internal.field1'])]
class AttributedClassValidationController
{
/**
* @Route(path="/attributed/class/testValidation", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
#[Validation(groups: ['field1_email'], violationPathMap: ['field1' => 'my_mapped_key'])]
public function testValidation(MyObject $resource): Response
{
// should fail validation
return new Response('FAIL');
}

/**
* @Route(path="/attributed/class/testValidationFromClass", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
public function testValidationFromClass(MyObject $resource): Response
{
// should fail validation
return new Response('FAIL');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
<?php
declare(strict_types=1);

namespace Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller;

use Paysera\Bundle\ApiBundle\Attribute\Body;
use Paysera\Bundle\ApiBundle\Attribute\BodyContentType;
use Paysera\Bundle\ApiBundle\Attribute\PathAttribute;
use Paysera\Bundle\ApiBundle\Attribute\Query;
use Paysera\Bundle\ApiBundle\Attribute\RequiredPermissions;
use Paysera\Bundle\ApiBundle\Attribute\ResponseNormalization;
use Paysera\Bundle\ApiBundle\Attribute\Validation;
use Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Entity\MyObject;
use Paysera\Pagination\Entity\Pager;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class AttributedController
{
/**
* @Route(path="/attributed/testBodyNormalizationWithExtractedKeyValue", methods={"POST"})
*/
#[Body(parameterName: 'keyValueInBody', denormalizationType: 'extract:key')]
public function testBodyNormalizationWithExtractedKeyValue(string $keyValueInBody = 'default'): Response
{
return new Response($keyValueInBody);
}

/**
* @Route(path="/attributed/testBodyNormalizationWithDenormalizationGroup", methods={"POST"})
*/
#[Body(parameterName: 'keyValueInBody', denormalizationType: 'extract:key', denormalizationGroup: 'custom')]
public function testBodyNormalizationWithDenormalizationGroup(string $keyValueInBody = 'default'): Response
{
return new Response($keyValueInBody);
}

/**
* @Route(path="/attributed/testBodyNormalizationWithRequiredBody", methods={"POST"})
*/
#[Body(parameterName: 'body', denormalizationType: 'extract:key')]
public function testBodyNormalizationWithRequiredBody(string $body): Response
{
// should fail as we don't pass any body
return new Response('FAIL');
}

/**
* @Route(path="/attributed/testBodyAndResponseNormalization", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
public function testBodyAndResponseNormalization(MyObject $resource): MyObject
{
return $resource;
}

/**
* @Route(path="/attributed/testBodyNormalizationWithCustomContentType", methods={"POST"})
*/
#[Body(parameterName: 'body', denormalizationType: 'prefixed')]
#[BodyContentType(supportedContentTypes: ['text/plain'])]
public function testBodyNormalizationWithCustomContentType(string $body): Response
{
return new Response($body);
}

/**
* @Route(path="/attributed/testBodyNormalizationWithCustomContentTypeAndJsonDecode", methods={"POST"})
*/
#[Body(parameterName: 'keyValueInBody', denormalizationType: 'extract:key')]
#[BodyContentType(supportedContentTypes: ['text/plain'], jsonEncodedBody: true)]
public function testBodyNormalizationWithCustomContentTypeAndJsonDecode(string $keyValueInBody): Response
{
return new Response($keyValueInBody);
}

/**
* @Route(path="/attributed/testBodyNormalizationWithSemiContentTypeRestriction", methods={"POST"})
*/
#[Body(parameterName: 'body', denormalizationType: 'prefixed')]
#[BodyContentType(supportedContentTypes: ['image/jpeg', 'text/*'])]
public function testBodyNormalizationWithSemiContentTypeRestriction(string $body): Response
{
return new Response($body);
}

/**
* @Route(path="/attributed/testBodyNormalizationWithValidation", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
#[Validation(groups: ['field1_email'], violationPathMap: ['field1' => 'my_mapped_key'])]
public function testBodyNormalizationWithValidation(MyObject $resource): Response
{
// should fail validation
return new Response('FAIL');
}

/**
* @Route(path="/attributed/testBodyNormalizationWithInnerTypeValidation", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
#[Validation(groups: ['internal_field1_email'])]
public function testBodyNormalizationWithInnerTypeValidation(MyObject $resource): Response
{
// should fail validation
return new Response('FAIL');
}

/**
* @Route(path="/attributed/testBodyValidationCanBeTurnedOff", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
#[Validation(enabled: false)]
public function testBodyValidationCanBeTurnedOff(MyObject $resource): Response
{
return new Response('OK');
}

/**
* @Route(path="/attributed/testBodyValidationCanBeTurnedOffWithEmptyGroups", methods={"POST"})
*/
#[Body(parameterName: 'resource')]
#[Validation(groups: [])]
public function testBodyValidationCanBeTurnedOffWithEmptyGroups(MyObject $resource): Response
{
return new Response('OK');
}

/**
* @Route(path="/attributed/testPathAttribute/{id}", methods={"GET"})
* @Route(path="/attributed/testPathAttribute", methods={"GET"})
*/
#[PathAttribute(parameterName: 'parameter', pathPartName: 'id', resolverType: 'prefixed')]
public function testPathAttribute(string $parameter = 'default'): Response
{
return new Response($parameter);
}

/**
* @Route(path="/attributed/testPathAttributeWithFindingObject/{id}", methods={"GET"})
*/
#[PathAttribute(parameterName: 'myObject', pathPartName: 'id')]
public function testPathAttributeWithFindingObject(MyObject $myObject): Response
{
return new Response($myObject->getField1());
}

/**
* @Route(path="/attributed/testPathAttributeWithFailedResolution/{id}", methods={"GET"})
*/
#[PathAttribute(parameterName: 'myObject', pathPartName: 'id', resolverType: 'always_null')]
public function testPathAttributeWithFailedResolution(MyObject $myObject): Response
{
// should fail before calling controller
return new Response('FAIL');
}

/**
* @Route(path="/attributed/testQueryResolver", methods={"GET"})
*/
#[Query(parameterName: 'parameter', denormalizationType: 'extract:parameter')]
public function testQueryResolver(string $parameter): Response
{
return new Response($parameter);
}

/**
* @Route(path="/attributed/testQueryResolverWithDenormalizationGroup", methods={"GET"})
*/
#[Query(parameterName: 'parameter', denormalizationType: 'extract:parameter', denormalizationGroup: 'custom')]
public function testQueryResolverWithDenormalizationGroup(string $parameter): Response
{
return new Response($parameter);
}

/**
* @Route(path="/attributed/testQueryResolverPagerLimitIs42", methods={"GET"})
*/
#[Query(parameterName: 'pager')]
public function testQueryResolverPagerLimitIs42(Pager $pager): Response
{
return new Response($pager->getLimit() === 42 ? 'OK' : 'FAIL');
}

/**
* @Route(path="/attributed/testQueryResolverHasDefaultValidation", methods={"GET"})
*/
#[Query(parameterName: 'myObject')]
public function testQueryResolverHasDefaultValidation(MyObject $myObject): Response
{
// should fail validation
return new Response('FAIL');
}

/**
* @Route(path="/attributed/testQueryResolverCanTurnOffValidation", methods={"GET"})
*/
#[Query(parameterName: 'myObject', validation: new Validation(enabled: false))]
public function testQueryResolverCanTurnOffValidation(MyObject $myObject): Response
{
return new Response('OK');
}

/**
* @Route(path="/attributed/testQueryResolverCanTurnOffValidationWithEmptyGroups", methods={"GET"})
*/
#[Query(parameterName: 'myObject', validation: new Validation(groups: []))]
public function testQueryResolverCanTurnOffValidationWithEmptyGroups(MyObject $myObject): Response
{
return new Response('OK');
}

/**
* @Route(path="/attributed/testQueryResolverValidationWithInvalidData", methods={"GET"})
*/
#[Query(parameterName: 'myObject', validation: new Validation(groups: ['field1_email'], violationPathMap: ['field1' => 'mapped_key']))]
public function testQueryResolverValidationWithInvalidData(MyObject $myObject): Response
{
// should fail validation
return new Response('FAIL');
}

/**
* @Route(path="/attributed/testRequiredPermissions", methods={"GET"})
*/
#[RequiredPermissions(permissions: ['ROLE_USER', 'ROLE_ADMIN'])]
public function testRequiredPermissions(): Response
{
return new Response('OK');
}

/**
* @Route(path="/attributed/testResponseNormalization", methods={"GET"})
*/
#[ResponseNormalization(normalizationType: 'my_object_custom')]
public function testResponseNormalization(): MyObject
{
return (new MyObject())
->setField1('hi')
;
}

/**
* @Route(path="/attributed/testResponseNormalizationWithNormalizationGroup", methods={"GET"})
*/
#[ResponseNormalization(normalizationGroup: 'custom')]
public function testResponseNormalizationWithNormalizationGroup(): MyObject
{
return (new MyObject())
->setField1('hi')
;
}

/**
* @Route(path="/attributed/testResponseNormalizationWithGuessedNormalizer", methods={"GET"})
*/
#[ResponseNormalization]
public function testResponseNormalizationWithGuessedNormalizer(): MyObject
{
return (new MyObject())
->setField1('hi')
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,16 @@

<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\AnnotatedController"
public="true"/>
<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\AttributedController"
public="true"/>
<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\AnnotatedClassRequiredPermissionsController"
public="true"/>
<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\AttributedClassRequiredPermissionsController"
public="true"/>
<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\AnnotatedClassValidationController"
public="true"/>
<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\AttributedClassValidationController"
public="true"/>
<service id="Paysera\Bundle\ApiBundle\Tests\Functional\Fixtures\FixtureTestBundle\Controller\PagedQueryController"
public="true">
<argument type="service" id="doctrine.orm.entity_manager"/>
Expand Down
Loading

0 comments on commit 9ff07dc

Please sign in to comment.