refactore method addRemoveSomething, and add test for resources

This commit is contained in:
Julien Fastré 2021-05-13 16:28:03 +02:00
parent 01f35eed04
commit 05798688d0
3 changed files with 202 additions and 54 deletions

View File

@ -42,11 +42,6 @@ class ApiController extends AbstractCRUDController
return $postFetch;
}
if (NULL === $entity) {
throw $this->createNotFoundException(sprintf("The %s with id %s "
. "is not found", $this->getCrudName(), $id));
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
@ -100,7 +95,6 @@ class ApiController extends AbstractCRUDController
$entity = $this->getEntity($action, $id, $request, $_format);
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
if ($postFetch instanceof Response) {
return $postFetch;
}
@ -147,6 +141,11 @@ class ApiController extends AbstractCRUDController
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
if ($response instanceof Response) {
return $response;
}
return $this->json(
$entity,
Response::HTTP_OK,
@ -155,7 +154,13 @@ class ApiController extends AbstractCRUDController
);
}
protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors): ?Response
protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
{
return null;
}
protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
{
return null;
}
@ -165,13 +170,16 @@ class ApiController extends AbstractCRUDController
return null;
}
protected function validate(string $action, Request $request, string $_format, $entity): ConstraintViolationListInterface
protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface
{
$validationGroups = $this->getValidationGroups($action, $request, $_format, $entity);
return $this->getValidator()->validate($entity, null, $validationGroups);
}
/**
* Deserialize the content of the request into the class associated with the curd
*/
protected function deserialize(string $action, Request $request, string $_format, $entity = null): object
{
$default = [];
@ -274,6 +282,110 @@ class ApiController extends AbstractCRUDController
return $this->serializeCollection($action, $request, $_format, $paginator, $entities);
}
/**
* Add or remove an associated entity, using `add` and `remove` methods.
*
* This method:
*
* 1. Fetch the base entity (throw 404 if not found)
* 2. checkACL,
* 3. run onPostCheckACL, return response if any,
* 4. deserialize posted data into the entity given by $postedDataType, with the context in $postedDataContext
* 5. run 'add+$property' for POST method, or 'remove+$property' for DELETE method
* 6. validate the base entity (not the deserialized one). Groups are fetched from getValidationGroups, validation is perform by `validate`
* 7. run onAfterValidation
* 8. if errors, return a 422 response with errors
* 9. flush the data
* 10. run onAfterFlush
* 11. return a 202 response for DELETE with empty body, or HTTP 200 for post with serialized posted entity
*
* @param string action
* @param mixed id
* @param Request $request
* @param string $_format
* @param string $property the name of the property. This will be used to make a `add+$property` and `remove+$property` method
* @param string $postedDataType the type of the posted data (the content)
* @param string $postedDataContext a context to deserialize posted data (the content)
* @throw BadRequestException if unable to deserialize the posted data
* @throw BadRequestException if the method is not POST or DELETE
*
*/
protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, $postedDataContext = []): Response
{
$entity = $this->getEntity($action, $id, $request);
$postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
if ($postFetch instanceof Response) {
return $postFetch;
}
$response = $this->checkACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onBeforeSerialize($action, $request, $_format, $entity);
if ($response instanceof Response) {
return $response;
}
try {
$postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
} catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
throw new BadRequestException(sprintf("Unable to deserialize posted ".
"data: %s", $e->getMessage()), 0, $e);
}
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
// oups... how to use property accessor to remove element ?
$entity->{'remove'.\ucfirst($property)}($postedData);
break;
case Request::METHOD_POST:
$entity->{'add'.\ucfirst($property)}($postedData);
break;
default:
throw new BadRequestException("this method is not supported");
}
$errors = $this->validate($action, $request, $_format, $entity, [$postedData]);
$response = $this->onAfterValidation($action, $request, $_format, $entity, $errors, [$postedData]);
if ($response instanceof Response) {
return $response;
}
if ($errors->count() > 0) {
// only format accepted
return $this->json($errors, 422);
}
$this->getDoctrine()->getManager()->flush();
$response = $this->onAfterFlush($action, $request, $_format, $entity, $errors, [$postedData]);
if ($response instanceof Response) {
return $response;
}
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
return $this->json('', Response::HTTP_OK);
case Request::METHOD_POST:
return $this->json(
$postedData,
Response::HTTP_OK,
[],
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData])
);
}
}
/**
* Serialize collections
*
@ -307,7 +419,7 @@ class ApiController extends AbstractCRUDController
*
* This is called **after** the entity was altered.
*/
protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity): array
protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array
{
return [ 'groups' => [ 'read' ]];
}

View File

@ -14,6 +14,7 @@ use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
class AccompanyingCourseApiController extends ApiController
{
@ -67,51 +68,7 @@ class AccompanyingCourseApiController extends ApiController
public function resourceApi($id, Request $request, string $_format): Response
{
return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource',
\Chill\PersonBundle\Entity\AccompanyingPeriod\Resource::class);
}
public function addRemoveSomething(string $action, $id, Request $request, string $_format, $property, string $postedDataType, $postedDataContext = []): Response
{
$entity = $this->getEntity($action, $id, $request);
$this->checkACL($action, $request, $_format, $entity);
$this->onPostCheckACL($action, $request, $_format, $entity);
try {
$postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
} catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
throw new BadRequestException(sprintf("Unable to deserialize posted ".
"data: %s", $e->getMessage()), 0, $e);
}
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
// oups... how to use property accessor to remove element ?
$entity->{'remove'.\ucfirst($property)}($postedData);
break;
case Request::METHOD_POST:
$entity->{'add'.\ucfirst($property)}($postedData);
break;
default:
throw new BadRequestException("this method is not supported");
}
$errors = $this->validator->validate($entity);
if ($errors->count() > 0) {
// only format accepted
return $this->json($errors, 422);
}
$this->getDoctrine()->getManager()->flush();
switch ($request->getMethod()) {
case Request::METHOD_DELETE:
return $this->json('', 202);
case Request::METHOD_POST:
return $this->json($postedData, 200, [], ['groups' => [ 'read' ]]);
}
return $this->addRemoveSomething('resource', $id, $request, $_format, 'resource', Resource::class);
}

View File

@ -33,6 +33,7 @@ use Chill\MainBundle\Entity\Center;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
/**
* Test api for AccompanyingCourseControllerTest
@ -86,6 +87,84 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
$this->assertEquals(404, $response->getStatusCode(), "Test that the response of rest api has a status code 'not found' (404)");
}
/**
* @dataProvider dataGenerateRandomRequestorValidData
*/
public function testResourceWithValidData(AccompanyingPeriod $period, $personId, $thirdPartyId)
{
$em = self::$container->get(EntityManagerInterface::class);
// post a person
$this->client->request(
Request::METHOD_POST,
sprintf('/api/1.0/person/accompanying-course/%d/resource.json', $period->getId()),
[], // parameters
[], // files
[], // server parameters
\json_encode([ 'type' => 'accompanying_period_resource', 'resource' => [ 'type' => 'person', 'id' => $personId ]])
);
$response = $this->client->getResponse();
$data = \json_decode($response->getContent(), true);
$this->assertEquals(200, $response->getStatusCode());
$this->assertArrayHasKey('id', $data);
$this->assertEquals($personId, $data['resource']['id']);
// check into database
$resource = $em->getRepository(Resource::class)
->find($data['id']);
$this->assertInstanceOf(Resource::class, $resource);
$this->assertInstanceOf(Person::class, $resource->getResource());
$this->assertEquals($personId, $resource->getResource()->getId());
// remove the resource
$this->client->request(
Request::METHOD_DELETE,
sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()),
[],
[],
[], //server
\json_encode([ 'type' => 'accompanying_period_resource', 'id' => $resource->getId()])
);
$response = $this->client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
// post a third party
$this->client->request(
Request::METHOD_POST,
sprintf('/api/1.0/person/accompanying-course/%d/resource.json', $period->getId()),
[], // parameters
[], // files
[], // server parameters
\json_encode([ 'type' => 'accompanying_period_resource', 'resource' => [ 'type' => 'thirdparty', 'id' => $thirdPartyId ]])
);
$response = $this->client->getResponse();
$data = \json_decode($response->getContent(), true);
$this->assertEquals(200, $response->getStatusCode());
$this->assertArrayHasKey('id', $data);
$this->assertEquals($thirdPartyId, $data['resource']['id']);
// check into database
$resource = $em->getRepository(Resource::class)
->find($data['id']);
$this->assertInstanceOf(Resource::class, $resource);
$this->assertInstanceOf(ThirdParty::class, $resource->getResource());
$this->assertEquals($thirdPartyId, $resource->getResource()->getId());
// remove the resource
$this->client->request(
Request::METHOD_DELETE,
sprintf('/api/1.0/person/accompanying-course/%d/requestor.json', $period->getId()),
[],
[],
[], //server
\json_encode([ 'type' => 'accompanying_period_resource', 'id' => $resource->getId()])
);
$response = $this->client->getResponse();
$this->assertEquals(200, $response->getStatusCode());
}
/**
* @dataProvider dataGenerateRandomRequestorValidData
*/