Add tests for AccompanyingPeriodWorkEvaluationNormalizer and refactor normalization logic

This commit is contained in:
2026-01-02 17:10:54 +01:00
parent a5f3afd54e
commit de5bdf7aa6
3 changed files with 224 additions and 33 deletions

View File

@@ -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<int, AccompanyingPeriodWorkEvaluationDocument>
*/
#[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<AccompanyingPeriodWorkEvaluationDocument>
* @return ReadableCollection<AccompanyingPeriodWorkEvaluationDocument>
*/
public function getDocuments(): Collection
public function getDocuments(): ReadableCollection
{
return $this->documents;
}

View File

@@ -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

View File

@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Serializer\Normalizer;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Serializer\Normalizer\AccompanyingPeriodWorkEvaluationNormalizer;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Workflow\Registry;
/**
* @internal
*
* @coversNothing
*/
class AccompanyingPeriodWorkEvaluationNormalizerTest extends TestCase
{
use ProphecyTrait;
public function testNormalize(): 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 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'));
}
}