From de5bdf7aa6054a39ffb1ed1d3fc8832fe5051882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 2 Jan 2026 17:10:54 +0100 Subject: [PATCH] Add tests for `AccompanyingPeriodWorkEvaluationNormalizer` and refactor normalization logic --- .../AccompanyingPeriodWorkEvaluation.php | 29 ++-- ...mpanyingPeriodWorkEvaluationNormalizer.php | 83 ++++++++-- ...yingPeriodWorkEvaluationNormalizerTest.php | 145 ++++++++++++++++++ 3 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizerTest.php diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php index 110ea3803..8c27faa24 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkEvaluation.php @@ -17,28 +17,25 @@ use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\SocialWork\Evaluation; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ReadableCollection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Attribute as Serializer; -#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['accompanying_period_work_evaluation' => AccompanyingPeriodWorkEvaluation::class])] #[ORM\Entity] #[ORM\Table('chill_person_accompanying_period_work_evaluation')] class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackUpdateInterface { - #[Serializer\Groups(['read:evaluation:include-work'])] #[ORM\ManyToOne(targetEntity: AccompanyingPeriodWork::class, inversedBy: 'accompanyingPeriodWorkEvaluations')] #[Serializer\Context(normalizationContext: ['groups' => ['read:accompanyingPeriodWork:light']], groups: ['read:evaluation:include-work'])] private ?AccompanyingPeriodWork $accompanyingPeriodWork = null; - #[Serializer\Groups(['read', 'docgen:read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])] private string $comment = ''; - #[Serializer\Groups(['read', 'docgen:read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeImmutable $createdAt = null; - #[Serializer\Groups(['read', 'docgen:read'])] #[ORM\ManyToOne(targetEntity: User::class)] private ?User $createdBy = null; @@ -49,20 +46,18 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * * @var Collection */ - #[Serializer\Groups(['read'])] #[ORM\OneToMany(mappedBy: 'accompanyingPeriodWorkEvaluation', targetEntity: AccompanyingPeriodWorkEvaluationDocument::class, cascade: ['remove', 'persist'], orphanRemoval: true)] #[ORM\OrderBy(['createdAt' => \Doctrine\Common\Collections\Criteria::DESC, 'id' => 'DESC'])] private Collection $documents; - #[Serializer\Groups(['read', 'docgen:read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeImmutable $endDate = null; - #[Serializer\Groups(['read', 'docgen:read', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['accompanying_period_work_evaluation:create'])] #[ORM\ManyToOne(targetEntity: Evaluation::class)] private ?Evaluation $evaluation = null; - #[Serializer\Groups(['read', 'docgen:read'])] #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)] @@ -75,30 +70,28 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU * This data is not persisted into database, but will appears on the data * normalized during the same request (like PUT/PATCH request) */ - #[Serializer\Groups(['read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] private $key; - #[Serializer\Groups(['read', 'docgen:read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeImmutable $maxDate = null; - #[Serializer\Groups(['read', 'docgen:read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeImmutable $startDate = null; - #[Serializer\Groups(['read', 'docgen:read'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_IMMUTABLE, nullable: true, options: ['default' => null])] private ?\DateTimeImmutable $updatedAt = null; - #[Serializer\Groups(['read', 'docgen:read'])] #[ORM\ManyToOne(targetEntity: User::class)] private ?User $updatedBy = null; - #[Serializer\Groups(['read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATEINTERVAL, nullable: true, options: ['default' => null])] private ?\DateInterval $warningInterval = null; - #[Serializer\Groups(['read', 'docgen:read', 'write', 'accompanying_period_work_evaluation:create'])] + #[Serializer\Groups(['write', 'accompanying_period_work_evaluation:create'])] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)] private ?int $timeSpent = null; @@ -138,9 +131,9 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU } /** - * @return Collection + * @return ReadableCollection */ - public function getDocuments(): Collection + public function getDocuments(): ReadableCollection { return $this->documents; } diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php index 7451b5a7e..e997bd3b7 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizer.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Serializer\Normalizer; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Workflow\Helper\MetadataExtractor; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -23,22 +24,26 @@ class AccompanyingPeriodWorkEvaluationNormalizer implements NormalizerInterface, { use NormalizerAwareTrait; - private const string IGNORE_EVALUATION = 'evaluation:ignore'; + public function __construct( + private readonly Registry $registry, + private readonly EntityWorkflowRepository $entityWorkflowRepository, + private readonly MetadataExtractor $metadataExtractor, + ) {} - public function __construct(private readonly Registry $registry, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly MetadataExtractor $metadataExtractor) {} - - /** - * @param AccompanyingPeriodWorkEvaluation $object - */ public function normalize($object, ?string $format = null, array $context = []): array { - $initial = $this->normalizer->normalize($object, $format, array_merge( - $context, - )); + if (!$object instanceof AccompanyingPeriodWorkEvaluation) { + throw new UnexpectedValueException('expected AccompanyingPeriodWorkEvaluation instance, got '.gettype($object)); + } - // due to bug: https://api-platform.com/docs/core/serialization/#collection-relation - // and also: https://github.com/symfony/symfony/issues/36965 - // we have to rewrite the documents as a collection + $groups = (array) ($context['groups'] ?? []); + if ([] === $groups) { + return []; + } + + $initial = $this->normalizeProperties($object, $format, $context); + + // we have to rewrite the documents as a collection to avoid non contiguous list $initial['documents'] = $this->normalizer->normalize( $object->getDocuments()->getValues(), $format, @@ -56,14 +61,62 @@ class AccompanyingPeriodWorkEvaluationNormalizer implements NormalizerInterface, ]); $initial['workflows'] = $this->normalizer->normalize($workflows, 'json', $context); + $initial['type'] = 'accompanying_period_work_evaluation'; + return $initial; } + private function normalizeProperties(AccompanyingPeriodWorkEvaluation $object, ?string $format, array $context): array + { + $data = []; + $groups = (array) ($context['groups'] ?? []); + + $isRead = in_array('read', $groups, true); + $isDocgenRead = in_array('docgen:read', $groups, true); + + if ($isRead || $isDocgenRead) { + $data['id'] = $object->getId(); + $data['comment'] = $object->getComment(); + $data['createdAt'] = $this->normalizer->normalize($object->getCreatedAt(), $format, $context); + $data['createdBy'] = $this->normalizer->normalize($object->getCreatedBy(), $format, $context); + $data['endDate'] = $this->normalizer->normalize($object->getEndDate(), $format, $context); + $data['evaluation'] = $this->normalizer->normalize($object->getEvaluation(), $format, $context); + $data['maxDate'] = $this->normalizer->normalize($object->getMaxDate(), $format, $context); + $data['startDate'] = $this->normalizer->normalize($object->getStartDate(), $format, $context); + $data['updatedAt'] = $this->normalizer->normalize($object->getUpdatedAt(), $format, $context); + $data['updatedBy'] = $this->normalizer->normalize($object->getUpdatedBy(), $format, $context); + $data['timeSpent'] = $object->getTimeSpent(); + } + + if ($isRead) { + $data['key'] = $object->getKey(); + $data['warningInterval'] = $this->normalizer->normalize($object->getWarningInterval(), $format, $context); + } + + // Handle 'read:evaluation:include-work' group for accompanyingPeriodWork + if (in_array('read:evaluation:include-work', $groups, true)) { + $workContext = $context; + $workContext['groups'] = ['read:accompanyingPeriodWork:light']; + $data['accompanyingPeriodWork'] = $this->normalizer->normalize($object->getAccompanyingPeriodWork(), $format, $workContext); + } + + return $data; + } + public function supportsNormalization($data, ?string $format = null, array $context = []): bool { - return 'json' === $format - && $data instanceof AccompanyingPeriodWorkEvaluation - && !\array_key_exists(self::IGNORE_EVALUATION, $context); + if ('json' !== $format || !$data instanceof AccompanyingPeriodWorkEvaluation) { + return false; + } + + $groups = (array) ($context['groups'] ?? []); + foreach (['write', 'accompanying_period_work_evaluation:create'] as $forbiddenGroup) { + if (in_array($forbiddenGroup, $groups, true)) { + return false; + } + } + + return true; } public function getSupportedTypes(?string $format): array diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizerTest.php new file mode 100644 index 000000000..2a2a92e95 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/AccompanyingPeriodWorkEvaluationNormalizerTest.php @@ -0,0 +1,145 @@ +prophesize(Registry::class); + $entityWorkflowRepository = $this->prophesize(EntityWorkflowRepository::class); + $metadataExtractor = $this->prophesize(MetadataExtractor::class); + $serializer = $this->prophesize(NormalizerInterface::class); + + $normalizer = new AccompanyingPeriodWorkEvaluationNormalizer( + $registry->reveal(), + $entityWorkflowRepository->reveal(), + $metadataExtractor->reveal() + ); + $normalizer->setNormalizer($serializer->reveal()); + + $object = new AccompanyingPeriodWorkEvaluation(); + + // Mocking calls from manualNormalize and other parts of normalize() + $serializer->normalize(null, 'json', ['groups' => ['read']])->willReturn(null); + $serializer->normalize($object->getDocuments()->getValues(), 'json', ['groups' => ['read']])->willReturn([]); + $metadataExtractor->availableWorkflowFor(AccompanyingPeriodWorkEvaluation::class)->willReturn([]); + $entityWorkflowRepository->findBy(['relatedEntityClass' => AccompanyingPeriodWorkEvaluation::class])->willReturn([]); + $serializer->normalize([], 'json', ['groups' => ['read']])->willReturn([]); + + $result = $normalizer->normalize($object, 'json', ['groups' => ['read']]); + + $this->assertArrayHasKey('type', $result); + $this->assertEquals('accompanying_period_work_evaluation', $result['type']); + $this->assertArrayHasKey('id', $result); + $this->assertArrayHasKey('comment', $result); + $this->assertArrayHasKey('key', $result); + $this->assertArrayHasKey('warningInterval', $result); + $this->assertArrayHasKey('documents', $result); + $this->assertArrayHasKey('workflows_availables', $result); + $this->assertArrayHasKey('workflows', $result); + } + + public function testNormalizeDocgenRead(): void + { + $registry = $this->prophesize(Registry::class); + $entityWorkflowRepository = $this->prophesize(EntityWorkflowRepository::class); + $metadataExtractor = $this->prophesize(MetadataExtractor::class); + $serializer = $this->prophesize(NormalizerInterface::class); + + $normalizer = new AccompanyingPeriodWorkEvaluationNormalizer( + $registry->reveal(), + $entityWorkflowRepository->reveal(), + $metadataExtractor->reveal() + ); + $normalizer->setNormalizer($serializer->reveal()); + + $object = new AccompanyingPeriodWorkEvaluation(); + + // Mocking calls + $serializer->normalize(null, 'json', ['groups' => ['docgen:read']])->willReturn(null); + $serializer->normalize($object->getDocuments()->getValues(), 'json', ['groups' => ['docgen:read']])->willReturn([]); + $metadataExtractor->availableWorkflowFor(AccompanyingPeriodWorkEvaluation::class)->willReturn([]); + $entityWorkflowRepository->findBy(['relatedEntityClass' => AccompanyingPeriodWorkEvaluation::class])->willReturn([]); + $serializer->normalize([], 'json', ['groups' => ['docgen:read']])->willReturn([]); + + $result = $normalizer->normalize($object, 'json', ['groups' => ['docgen:read']]); + + $this->assertArrayHasKey('type', $result); + $this->assertEquals('accompanying_period_work_evaluation', $result['type']); + $this->assertArrayHasKey('id', $result); + $this->assertArrayHasKey('comment', $result); + $this->assertArrayNotHasKey('key', $result); + $this->assertArrayNotHasKey('warningInterval', $result); + $this->assertArrayHasKey('documents', $result); + } + + public function testNormalizeWithoutGroups(): void + { + $registry = $this->prophesize(Registry::class); + $entityWorkflowRepository = $this->prophesize(EntityWorkflowRepository::class); + $metadataExtractor = $this->prophesize(MetadataExtractor::class); + $serializer = $this->prophesize(NormalizerInterface::class); + + $normalizer = new AccompanyingPeriodWorkEvaluationNormalizer( + $registry->reveal(), + $entityWorkflowRepository->reveal(), + $metadataExtractor->reveal() + ); + $normalizer->setNormalizer($serializer->reveal()); + + $object = new AccompanyingPeriodWorkEvaluation(); + + // When no groups are provided, it should return an empty array + $result = $normalizer->normalize($object, 'json', []); + + $this->assertSame([], $result); + } + + public function testSupportsNormalization(): void + { + $registry = $this->prophesize(Registry::class); + $entityWorkflowRepository = $this->prophesize(EntityWorkflowRepository::class); + $metadataExtractor = $this->prophesize(MetadataExtractor::class); + + $normalizer = new AccompanyingPeriodWorkEvaluationNormalizer( + $registry->reveal(), + $entityWorkflowRepository->reveal(), + $metadataExtractor->reveal() + ); + + $object = new AccompanyingPeriodWorkEvaluation(); + + $this->assertTrue($normalizer->supportsNormalization($object, 'json', ['groups' => ['read']])); + $this->assertFalse($normalizer->supportsNormalization($object, 'json', ['groups' => ['write']])); + $this->assertFalse($normalizer->supportsNormalization($object, 'json', ['groups' => ['accompanying_period_work_evaluation:create']])); + $this->assertFalse($normalizer->supportsNormalization($object, 'json', ['groups' => ['read', 'write']])); + $this->assertFalse($normalizer->supportsNormalization(new \stdClass(), 'json')); + $this->assertFalse($normalizer->supportsNormalization($object, 'xml')); + } +}