mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-07 14:39:41 +00:00
Partage d'export enregistré et génération asynchrone des exports
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<?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\MainBundle\Tests\Export\Cronjob;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Export\Cronjob\RemoveExpiredExportGenerationCronJob;
|
||||
use Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Prophecy\Argument;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Chill\MainBundle\Repository\ExportGenerationRepository;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RemoveExpiredExportGenerationCronJobTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testCanRunReturnsTrueWhenLastExecutionIsNull()
|
||||
{
|
||||
$clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00'));
|
||||
$repo = $this->prophesize(ExportGenerationRepository::class);
|
||||
$bus = $this->prophesize(MessageBusInterface::class);
|
||||
|
||||
$cronJob = new RemoveExpiredExportGenerationCronJob(
|
||||
$clock,
|
||||
$repo->reveal(),
|
||||
$bus->reveal()
|
||||
);
|
||||
|
||||
$this->assertTrue($cronJob->canRun(null));
|
||||
}
|
||||
|
||||
public function testCanRunReturnsTrueWhenLastStartIsOlderThan24Hours()
|
||||
{
|
||||
$clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00'));
|
||||
$repo = $this->prophesize(ExportGenerationRepository::class);
|
||||
$bus = $this->prophesize(MessageBusInterface::class);
|
||||
|
||||
$cronJob = new RemoveExpiredExportGenerationCronJob(
|
||||
$clock,
|
||||
$repo->reveal(),
|
||||
$bus->reveal()
|
||||
);
|
||||
|
||||
$execution = new CronJobExecution('remove-expired-export-generation');
|
||||
$execution->setLastStart(new \DateTimeImmutable('2024-06-24 09:59:59'));
|
||||
|
||||
$this->assertTrue($cronJob->canRun($execution));
|
||||
}
|
||||
|
||||
public function testCanRunReturnsFalseWhenLastStartIsWithin24Hours()
|
||||
{
|
||||
$clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00'));
|
||||
$repo = $this->prophesize(ExportGenerationRepository::class);
|
||||
$bus = $this->prophesize(MessageBusInterface::class);
|
||||
|
||||
$cronJob = new RemoveExpiredExportGenerationCronJob(
|
||||
$clock,
|
||||
$repo->reveal(),
|
||||
$bus->reveal()
|
||||
);
|
||||
|
||||
$execution = new CronJobExecution('remove-expired-export-generation');
|
||||
$execution->setLastStart(new \DateTimeImmutable('2024-06-24 10:01:00'));
|
||||
|
||||
$this->assertFalse($cronJob->canRun($execution));
|
||||
}
|
||||
|
||||
public function testRunDispatchesMessagesForExpiredExportsAndReturnsLastDeletion()
|
||||
{
|
||||
$clock = new MockClock(new \DateTimeImmutable('2024-06-25 11:21:00'));
|
||||
$repo = $this->prophesize(ExportGenerationRepository::class);
|
||||
$bus = $this->prophesize(MessageBusInterface::class);
|
||||
|
||||
$expiredExports = [
|
||||
new ExportGeneration('dummy', []),
|
||||
];
|
||||
|
||||
$repo->findExpiredExportGeneration(Argument::that(fn ($dateTime) =>
|
||||
// Ensure the repository is called with the current clock time
|
||||
$dateTime instanceof \DateTimeImmutable
|
||||
&& $dateTime->getTimestamp() === $clock->now()->getTimestamp()))->willReturn($expiredExports);
|
||||
|
||||
// Expect one RemoveExportGenerationMessage for each expired export
|
||||
$bus->dispatch(Argument::that(fn (Envelope $envelope) => $envelope->getMessage() instanceof RemoveExportGenerationMessage))
|
||||
->shouldBeCalledTimes(1)
|
||||
->will(fn ($args) => $args[0]);
|
||||
|
||||
$cronJob = new RemoveExpiredExportGenerationCronJob(
|
||||
$clock,
|
||||
$repo->reveal(),
|
||||
$bus->reveal()
|
||||
);
|
||||
|
||||
$result = $cronJob->run([]);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertEquals(['last-deletion' => $clock->now()->getTimestamp()], $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?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\MainBundle\Tests\Export;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Export\ExportConfigNormalizer;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Repository\RegroupmentRepositoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExportConfigNormalizerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testNormalizeConfig(): void
|
||||
{
|
||||
$filterEnabled = $this->prophesize(FilterInterface::class);
|
||||
$filterEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
$filterEnabled->getNormalizationVersion()->willReturn(1);
|
||||
$filterDisabled = $this->prophesize(FilterInterface::class);
|
||||
$filterDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled();
|
||||
|
||||
$aggregatorEnabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
$aggregatorEnabled->getNormalizationVersion()->willReturn(1);
|
||||
$aggregatorDisabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled();
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$export->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
$export->getNormalizationVersion()->willReturn(1);
|
||||
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
$formatter->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
$formatter->getNormalizationVersion()->willReturn(1);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal());
|
||||
$exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal());
|
||||
$exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal());
|
||||
$exportManager->getExport('export')->willReturn($export->reveal());
|
||||
|
||||
$regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class);
|
||||
|
||||
$center = $this->prophesize(Center::class);
|
||||
$center->getId()->willReturn(10);
|
||||
|
||||
$formData = [
|
||||
'centers' => ['centers' => [$center->reveal()]],
|
||||
'export' => ['test' => '0'],
|
||||
'filters' => [
|
||||
'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']],
|
||||
'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']],
|
||||
'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => ['test' => '0'],
|
||||
];
|
||||
|
||||
$expected = [
|
||||
'export' => ['form' => ['test' => '0'], 'version' => 1],
|
||||
'centers' => ['centers' => [10], 'regroupments' => []],
|
||||
'filters' => [
|
||||
'filtersEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1],
|
||||
'filterDisabled' => ['enabled' => false],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1],
|
||||
'aggregatorDisabled' => ['enabled' => false],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => [
|
||||
'form' => ['test' => '0'],
|
||||
'version' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
$exportConfigNormalizer = new ExportConfigNormalizer(
|
||||
$exportManager->reveal(),
|
||||
$this->prophesize(CenterRepositoryInterface::class)->reveal(),
|
||||
$regroupmentRepository->reveal()
|
||||
);
|
||||
|
||||
$actual = $exportConfigNormalizer->normalizeConfig('export', $formData);
|
||||
|
||||
self::assertEqualsCanonicalizing($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDenormalizeConfig(): void
|
||||
{
|
||||
$filterEnabled = $this->prophesize(FilterInterface::class);
|
||||
$filterEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
$filterDisabled = $this->prophesize(FilterInterface::class);
|
||||
$filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled();
|
||||
$filterDisabled->getFormDefaultData()->willReturn(['default' => '0']);
|
||||
|
||||
$aggregatorEnabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
$aggregatorDisabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled();
|
||||
$aggregatorDisabled->getFormDefaultData()->willReturn(['default' => '0']);
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$export->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
$formatter->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal());
|
||||
$exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal());
|
||||
$exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal());
|
||||
$exportManager->getExport('export')->willReturn($export->reveal());
|
||||
|
||||
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
|
||||
$centerRepository->find(10)->willReturn($center = new Center());
|
||||
|
||||
$regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class);
|
||||
|
||||
$serialized = [
|
||||
'centers' => ['regroupments' => [], 'centers' => [10]],
|
||||
'export' => ['form' => ['test' => '0'], 'version' => 1],
|
||||
'filters' => [
|
||||
'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1],
|
||||
'filterDisabled' => ['enabled' => false],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1],
|
||||
'aggregatorDisabled' => ['enabled' => false],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => [
|
||||
'form' => ['test' => '0'],
|
||||
'version' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
$expected = [
|
||||
'export' => ['test' => '0'],
|
||||
'filters' => [
|
||||
'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']],
|
||||
'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']],
|
||||
'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => ['test' => '0'],
|
||||
'centers' => ['centers' => [$center], 'regroupments' => []],
|
||||
];
|
||||
|
||||
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal(), $regroupmentRepository->reveal());
|
||||
$actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true);
|
||||
|
||||
self::assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testNormalizeConfigEmptyData(): void
|
||||
{
|
||||
$filterEnabled = $this->prophesize(FilterInterface::class);
|
||||
$filterEnabled->normalizeFormData([])->shouldBeCalled()->willReturn([]);
|
||||
$filterEnabled->getNormalizationVersion()->willReturn(1);
|
||||
$filterDisabled = $this->prophesize(FilterInterface::class);
|
||||
$filterDisabled->normalizeFormData([])->shouldNotBeCalled();
|
||||
|
||||
$aggregatorEnabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorEnabled->normalizeFormData([])->shouldBeCalled()->willReturn([]);
|
||||
$aggregatorEnabled->getNormalizationVersion()->willReturn(1);
|
||||
$aggregatorDisabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorDisabled->normalizeFormData([])->shouldNotBeCalled();
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$export->normalizeFormData([])->shouldBeCalled()->willReturn([]);
|
||||
$export->getNormalizationVersion()->willReturn(1);
|
||||
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
$formatter->normalizeFormData([])->shouldBeCalled()->willReturn([]);
|
||||
$formatter->getNormalizationVersion()->willReturn(1);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal());
|
||||
$exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal());
|
||||
$exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal());
|
||||
$exportManager->getExport('export')->willReturn($export->reveal());
|
||||
|
||||
$center = $this->prophesize(Center::class);
|
||||
$center->getId()->willReturn(10);
|
||||
|
||||
$regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class);
|
||||
|
||||
$formData = [
|
||||
'centers' => ['centers' => [$center->reveal()]],
|
||||
'export' => [],
|
||||
'filters' => [
|
||||
'filterEnabled' => ['enabled' => true, 'form' => []],
|
||||
'filterDisabled' => ['enabled' => false, 'form' => []],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => []],
|
||||
'aggregatorDisabled' => ['enabled' => false, 'form' => []],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => [],
|
||||
];
|
||||
|
||||
$expected = [
|
||||
'export' => ['form' => [], 'version' => 1],
|
||||
'centers' => ['centers' => [10], 'regroupments' => []],
|
||||
'filters' => [
|
||||
'filtersEnabled' => ['enabled' => true, 'form' => [], 'version' => 1],
|
||||
'filterDisabled' => ['enabled' => false],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => [], 'version' => 1],
|
||||
'aggregatorDisabled' => ['enabled' => false],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => [
|
||||
'form' => [],
|
||||
'version' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal(), $regroupmentRepository->reveal());
|
||||
|
||||
$actual = $exportConfigNormalizer->normalizeConfig('export', $formData);
|
||||
|
||||
self::assertEqualsCanonicalizing($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDenormalizeConfigWithEmptyData(): void
|
||||
{
|
||||
$filterEnabled = $this->prophesize(FilterInterface::class);
|
||||
$filterEnabled->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]);
|
||||
$filterDisabled = $this->prophesize(FilterInterface::class);
|
||||
$filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled();
|
||||
$filterDisabled->getFormDefaultData()->willReturn([]);
|
||||
|
||||
$aggregatorEnabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorEnabled->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]);
|
||||
$aggregatorDisabled = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled();
|
||||
$aggregatorDisabled->getFormDefaultData()->willReturn([]);
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$export->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]);
|
||||
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
$formatter->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal());
|
||||
$exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal());
|
||||
$exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal());
|
||||
$exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal());
|
||||
$exportManager->getExport('export')->willReturn($export->reveal());
|
||||
|
||||
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
|
||||
$centerRepository->find(10)->willReturn($center = new Center());
|
||||
|
||||
$regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class);
|
||||
|
||||
$serialized = [
|
||||
'centers' => ['centers' => [10], 'regroupments' => []],
|
||||
'export' => ['form' => [], 'version' => 1],
|
||||
'filters' => [
|
||||
'filterEnabled' => ['enabled' => true, 'form' => [], 'version' => 1],
|
||||
'filterDisabled' => ['enabled' => false],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => [], 'version' => 1],
|
||||
'aggregatorDisabled' => ['enabled' => false],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => [
|
||||
'form' => [],
|
||||
'version' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
$expected = [
|
||||
'export' => [],
|
||||
'filters' => [
|
||||
'filterEnabled' => ['enabled' => true, 'form' => []],
|
||||
'filterDisabled' => ['enabled' => false, 'form' => []],
|
||||
],
|
||||
'aggregators' => [
|
||||
'aggregatorEnabled' => ['enabled' => true, 'form' => []],
|
||||
'aggregatorDisabled' => ['enabled' => false, 'form' => []],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => [],
|
||||
'centers' => ['centers' => [$center], 'regroupments' => []],
|
||||
];
|
||||
|
||||
$exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal(), $regroupmentRepository->reveal());
|
||||
$actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true);
|
||||
|
||||
self::assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?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\MainBundle\Tests\Export;
|
||||
|
||||
use Chill\MainBundle\Export\ExportDataNormalizerTrait;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExportDataNormalizerTraitTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private function buildTrait(): object
|
||||
{
|
||||
return new class () {
|
||||
use ExportDataNormalizerTrait;
|
||||
|
||||
public function normalizeEntity(object|iterable $entity): array|int|string
|
||||
{
|
||||
return $this->normalizeDoctrineEntity($entity);
|
||||
}
|
||||
|
||||
public function denormalizeEntity(mixed $entity, ObjectRepository $repository)
|
||||
{
|
||||
return $this->denormalizeDoctrineEntity($entity, $repository);
|
||||
}
|
||||
|
||||
public function normalizeD(\DateTimeImmutable|\DateTime $date): string
|
||||
{
|
||||
return $this->normalizeDate($date);
|
||||
}
|
||||
|
||||
public function denormalizeD(string $date): \DateTimeImmutable|\DateTime
|
||||
{
|
||||
return $this->denormalizeDate($date);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function testNormalizationDoctrineEntitySingle(): void
|
||||
{
|
||||
$entity = new class () {
|
||||
public function getId(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
$repository = $this->prophesize(ObjectRepository::class);
|
||||
$repository->find(1)->willReturn($entity);
|
||||
|
||||
$normalized = $this->buildTrait()->normalizeEntity($entity);
|
||||
$actual = $this->buildTrait()->denormalizeEntity($normalized, $repository->reveal());
|
||||
|
||||
self::assertSame($entity, $actual);
|
||||
}
|
||||
|
||||
public function testNormalizationDoctrineEntityMulti(): void
|
||||
{
|
||||
$entityA = new class () {
|
||||
public function getId(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
$entityB = new class () {
|
||||
public function getId(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
|
||||
$repository = $this->prophesize(ObjectRepository::class);
|
||||
$repository->findBy(
|
||||
Argument::that(static fn ($arg): bool => in_array(1, $arg['id'] ?? []) && in_array(2, $arg['id'] ?? []))
|
||||
)->willReturn([$entityA, $entityB]);
|
||||
|
||||
$normalized = $this->buildTrait()->normalizeEntity([$entityA, $entityB]);
|
||||
$actual = $this->buildTrait()->denormalizeEntity($normalized, $repository->reveal());
|
||||
|
||||
self::assertContains(1, array_map(static fn (object $item) => $item->getId(), $actual));
|
||||
self::assertContains(2, array_map(static fn (object $item) => $item->getId(), $actual));
|
||||
self::assertCount(2, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDate
|
||||
*/
|
||||
public function testNormalizationDate(\DateTimeImmutable|\DateTime $date): void
|
||||
{
|
||||
$normalized = $this->buildTrait()->normalizeD($date);
|
||||
$actual = $this->buildTrait()->denormalizeD($normalized);
|
||||
|
||||
self::assertEquals($date, $actual);
|
||||
}
|
||||
|
||||
public static function provideDate(): iterable
|
||||
{
|
||||
yield [new \DateTimeImmutable('2024-01-15T18:57:20', new \DateTimeZone('Europe/Athens'))];
|
||||
yield [new \DateTimeImmutable('2024-01-15T18:57:30', new \DateTimeZone('America/Havana'))];
|
||||
yield [new \DateTime('2024-01-15T18:57:40', new \DateTimeZone('Europe/Madrid'))];
|
||||
yield [new \DateTime('2024-01-15T18:57:50', new \DateTimeZone('Africa/Kinshasa'))];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?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\MainBundle\Tests\Export;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Export\ExportConfigNormalizer;
|
||||
use Chill\MainBundle\Export\ExportConfigProcessor;
|
||||
use Chill\MainBundle\Export\ExportDescriptionHelper;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExportDescriptionHelperTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private const JSON_HAPPY_SCENARIO = <<<'JSON'
|
||||
{
|
||||
"export": {
|
||||
"form": [],
|
||||
"version": 1
|
||||
},
|
||||
"centers": {
|
||||
"centers": [
|
||||
1
|
||||
],
|
||||
"regroupments": []
|
||||
},
|
||||
"filters": {
|
||||
"my_filter_string": {
|
||||
"form": {
|
||||
"accepted_socialissues": [
|
||||
2,
|
||||
3
|
||||
]
|
||||
},
|
||||
"enabled": true,
|
||||
"version": 1
|
||||
},
|
||||
"my_filter_array": {
|
||||
"form": {
|
||||
"misc": true
|
||||
},
|
||||
"enabled": true,
|
||||
"version": 1
|
||||
},
|
||||
"my_filter_translatable": {
|
||||
"form": {
|
||||
"misc": true
|
||||
},
|
||||
"enabled": true,
|
||||
"version": 1
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"form": {
|
||||
"format": "xlsx",
|
||||
"activity_user_aggregator": {
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
},
|
||||
"aggregators": {
|
||||
"my_aggregator": {
|
||||
"form": {"key": 1},
|
||||
"enabled": true,
|
||||
"version": 1
|
||||
}
|
||||
},
|
||||
"pick_formatter": "spreadsheet"
|
||||
}
|
||||
JSON;
|
||||
|
||||
public function testDescribeHappyScenario(): void
|
||||
{
|
||||
$options = json_decode(self::JSON_HAPPY_SCENARIO, true);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->getUser()->willReturn($user = new User());
|
||||
|
||||
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
|
||||
$exportConfigNormalizer->denormalizeConfig('my_export', Argument::type('array'))->willReturn($options);
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$export->getTitle()->willReturn('Title');
|
||||
|
||||
$myFilterString = $this->prophesize(FilterInterface::class);
|
||||
$myFilterString->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn($string0 = 'This is a filter description');
|
||||
$myFilterArray = $this->prophesize(FilterInterface::class);
|
||||
$myFilterArray->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn([$string1 = 'This is a filter with %argument%', $arg1 = ['%argument%' => 'zero']]);
|
||||
$myFilterTranslatable = $this->prophesize(FilterInterface::class);
|
||||
$myFilterTranslatable->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))
|
||||
->willReturn(new class () implements TranslatableInterface {
|
||||
public function trans(TranslatorInterface $translator, ?string $locale = null): string
|
||||
{
|
||||
return 'translatable';
|
||||
}
|
||||
});
|
||||
|
||||
$myAggregator = $this->prophesize(AggregatorInterface::class);
|
||||
$myAggregator->getTitle()->willReturn('Some aggregator');
|
||||
|
||||
$token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']);
|
||||
$tokenStorage = new TokenStorage();
|
||||
$tokenStorage->setToken($token);
|
||||
|
||||
$exportManager = new ExportManager(
|
||||
new NullLogger(),
|
||||
$security->reveal(),
|
||||
$this->prophesize(AuthorizationHelperInterface::class)->reveal(),
|
||||
$tokenStorage,
|
||||
['my_export' => $export->reveal()],
|
||||
['my_aggregator' => $myAggregator->reveal()],
|
||||
[
|
||||
'my_filter_string' => $myFilterString->reveal(),
|
||||
'my_filter_array' => $myFilterArray->reveal(),
|
||||
'my_filter_translatable' => $myFilterTranslatable->reveal(),
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
$exportConfigProcessor = new ExportConfigProcessor($exportManager);
|
||||
|
||||
$translator = $this->prophesize(TranslatorInterface::class);
|
||||
$translator->trans('Title')->shouldBeCalled()->willReturn('Title');
|
||||
$translator->trans($string0)->shouldBeCalled()->willReturn($string0);
|
||||
$translator->trans($string1, $arg1)->shouldBeCalled()->willReturn($string1);
|
||||
$translator->trans('Some aggregator')->shouldBeCalled()->willReturn('Some aggregator');
|
||||
|
||||
$exportDescriptionHelper = new ExportDescriptionHelper(
|
||||
$exportManager,
|
||||
$exportConfigNormalizer->reveal(),
|
||||
$exportConfigProcessor,
|
||||
$translator->reveal(),
|
||||
$security->reveal(),
|
||||
);
|
||||
|
||||
$actual = $exportDescriptionHelper->describe('my_export', $options);
|
||||
|
||||
self::assertIsArray($actual);
|
||||
self::assertEquals($actual[0], 'Title');
|
||||
self::assertEquals($actual[1], 'This is a filter description');
|
||||
self::assertEquals($actual[2], 'This is a filter with %argument%');
|
||||
self::assertEquals($actual[3], 'translatable');
|
||||
self::assertEquals($actual[4], 'Some aggregator');
|
||||
}
|
||||
}
|
||||
413
src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php
Normal file
413
src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php
Normal file
@@ -0,0 +1,413 @@
|
||||
<?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\MainBundle\Tests\Export;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Regroupment;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Export\DirectExportInterface;
|
||||
use Chill\MainBundle\Export\ExportConfigNormalizer;
|
||||
use Chill\MainBundle\Export\ExportConfigProcessor;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportGenerator;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Export\FormattedExportGeneration;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
|
||||
use Doctrine\ORM\NativeQuery;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExportGeneratorTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private function buildParameter(bool $filterStat): ParameterBagInterface
|
||||
{
|
||||
return new ParameterBag(
|
||||
['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStat]]]
|
||||
);
|
||||
}
|
||||
|
||||
public function testGenerateHappyScenario()
|
||||
{
|
||||
$initialData = ['initial' => 'test'];
|
||||
$fullConfig = [
|
||||
'export' => $formExportData = ['key' => 'form1'],
|
||||
'filters' => [
|
||||
'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']],
|
||||
'disabled_filter' => ['enabled' => false],
|
||||
],
|
||||
'aggregators' => [
|
||||
'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']],
|
||||
'disabled_aggregator' => ['enabled' => false],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => $formatterData = ['key' => 'form4'],
|
||||
'centers' => ['centers' => [$centerA = new Center()], 'regroupments' => [(new Regroupment())->addCenter($centerB = new Center())]],
|
||||
];
|
||||
$user = new User();
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$filter = $this->prophesize(FilterInterface::class);
|
||||
$filter->applyOn()->willReturn('tagada');
|
||||
$aggregator = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregator->applyOn()->willReturn('tsointsoin');
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
|
||||
$query = $this->prophesize(QueryBuilder::class);
|
||||
$query->getDQL()->willReturn('dummy');
|
||||
$dqlQuery = $this->prophesize(Query::class);
|
||||
$dqlQuery->getSQL()->willReturn('dummy');
|
||||
$query->getQuery()->willReturn($dqlQuery->reveal());
|
||||
|
||||
// required methods
|
||||
$export->initiateQuery(
|
||||
['tagada', 'tsointsoin'],
|
||||
Argument::that(function ($arg) use ($centerB, $centerA) {
|
||||
if (!is_array($arg)) {
|
||||
return false;
|
||||
}
|
||||
if (2 !== count($arg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($arg as $item) {
|
||||
if ([] !== $item['circles']) {
|
||||
return false;
|
||||
}
|
||||
if (!in_array($item['center'], [$centerA, $centerB], true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
$formExportData,
|
||||
Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
|
||||
)->shouldBeCalled()->willReturn($query->reveal());
|
||||
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled()->willReturn([['result0' => '0']]);
|
||||
$export->requiredRole()->willReturn('dummy_role');
|
||||
|
||||
$filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled();
|
||||
$aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled();
|
||||
|
||||
$formatter->generate(
|
||||
[['result0' => '0']],
|
||||
$formatterData,
|
||||
'dummy',
|
||||
$formExportData,
|
||||
['dummy_filter' => $formFilterData],
|
||||
['dummy_aggregator' => $formAggregatorData],
|
||||
Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
|
||||
)
|
||||
->shouldBeCalled()
|
||||
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
|
||||
|
||||
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
|
||||
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getExport('dummy')->willReturn($export->reveal());
|
||||
$exportManager->getFilter('dummy_filter')->willReturn($filter->reveal());
|
||||
$exportManager->hasFilter('dummy_filter')->willReturn(true);
|
||||
$exportManager->hasFilter('disabled_filter')->willReturn(true);
|
||||
$exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal());
|
||||
$exportManager->hasAggregator('dummy_aggregator')->willReturn(true);
|
||||
$exportManager->hasAggregator('disabled_aggregator')->willReturn(true);
|
||||
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
|
||||
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
|
||||
|
||||
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
|
||||
$centerRepository->findActive()->shouldNotBeCalled();
|
||||
|
||||
$generator = new ExportGenerator(
|
||||
$exportManager->reveal(),
|
||||
$exportConfigNormalizer->reveal(),
|
||||
new NullLogger(),
|
||||
$authorizationHelper->reveal(),
|
||||
new CenterRegroupementResolver(),
|
||||
new ExportConfigProcessor($exportManager->reveal()),
|
||||
$this->buildParameter(true),
|
||||
$centerRepository->reveal(),
|
||||
);
|
||||
|
||||
$actual = $generator->generate('dummy', $initialData, $user);
|
||||
|
||||
self::assertEquals('export result', $actual->content);
|
||||
self::assertEquals('text/text', $actual->contentType);
|
||||
}
|
||||
|
||||
public function testGenerateNativeSqlHappyScenario()
|
||||
{
|
||||
$initialData = ['initial' => 'test'];
|
||||
$fullConfig = [
|
||||
'export' => $formExportData = ['key' => 'form1'],
|
||||
'filters' => [],
|
||||
'aggregators' => [],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => $formatterData = ['key' => 'form4'],
|
||||
'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []],
|
||||
];
|
||||
$user = new User();
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
|
||||
$query = $this->prophesize(NativeQuery::class);
|
||||
|
||||
// required methods
|
||||
$export->initiateQuery(
|
||||
[],
|
||||
[['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
|
||||
['key' => 'form1'],
|
||||
Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
|
||||
)->shouldBeCalled()->willReturn($query->reveal());
|
||||
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled()->willReturn([['result0' => '0']]);
|
||||
$export->supportsModifiers()->willReturn([]);
|
||||
$export->requiredRole()->willReturn('dummy_role');
|
||||
|
||||
$formatter->generate(
|
||||
[['result0' => '0']],
|
||||
$formatterData,
|
||||
'dummy',
|
||||
$formExportData,
|
||||
[],
|
||||
[],
|
||||
Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
|
||||
)
|
||||
->shouldBeCalled()
|
||||
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
|
||||
|
||||
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
|
||||
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getExport('dummy')->willReturn($export->reveal());
|
||||
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
|
||||
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
|
||||
|
||||
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
|
||||
$centerRepository->findActive()->shouldNotBeCalled();
|
||||
|
||||
$generator = new ExportGenerator(
|
||||
$exportManager->reveal(),
|
||||
$exportConfigNormalizer->reveal(),
|
||||
new NullLogger(),
|
||||
$authorizationHelper->reveal(),
|
||||
new CenterRegroupementResolver(),
|
||||
new ExportConfigProcessor($exportManager->reveal()),
|
||||
$this->buildParameter(true),
|
||||
$centerRepository->reveal(),
|
||||
);
|
||||
|
||||
$actual = $generator->generate('dummy', $initialData, $user);
|
||||
|
||||
self::assertInstanceOf(FormattedExportGeneration::class, $actual);
|
||||
self::assertEquals('export result', $actual->content);
|
||||
self::assertEquals('text/text', $actual->contentType);
|
||||
}
|
||||
|
||||
public function testGenerateDirectExportHappyScenario()
|
||||
{
|
||||
$initialData = ['initial' => 'test'];
|
||||
$fullConfig = [
|
||||
'export' => $formExportData = ['key' => 'form1'],
|
||||
'filters' => [],
|
||||
'aggregators' => [],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => ['form' => $formatterData = ['key' => 'form4']],
|
||||
'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []],
|
||||
];
|
||||
$user = new User();
|
||||
|
||||
$export = $this->prophesize(DirectExportInterface::class);
|
||||
|
||||
// required methods
|
||||
$export->generate(
|
||||
[['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
|
||||
['key' => 'form1'],
|
||||
Argument::that(static fn (ExportGenerationContext $context) => $user === $context->byUser),
|
||||
)->shouldBeCalled()
|
||||
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
|
||||
$export->requiredRole()->willReturn('dummy_role');
|
||||
|
||||
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
|
||||
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getExport('dummy')->willReturn($export->reveal());
|
||||
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||
$authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]);
|
||||
|
||||
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
|
||||
$centerRepository->findActive()->shouldNotBeCalled();
|
||||
|
||||
$generator = new ExportGenerator(
|
||||
$exportManager->reveal(),
|
||||
$exportConfigNormalizer->reveal(),
|
||||
new NullLogger(),
|
||||
$authorizationHelper->reveal(),
|
||||
new CenterRegroupementResolver(),
|
||||
new ExportConfigProcessor($exportManager->reveal()),
|
||||
$this->buildParameter(true),
|
||||
$centerRepository->reveal(),
|
||||
);
|
||||
|
||||
$actual = $generator->generate('dummy', $initialData, $user);
|
||||
|
||||
self::assertInstanceOf(FormattedExportGeneration::class, $actual);
|
||||
self::assertEquals('export result', $actual->content);
|
||||
self::assertEquals('text/text', $actual->contentType);
|
||||
}
|
||||
|
||||
public function testGenerateHappyScenarioWithoutCenterFiltering()
|
||||
{
|
||||
$initialData = ['initial' => 'test'];
|
||||
$fullConfig = [
|
||||
'export' => $formExportData = ['key' => 'form1'],
|
||||
'filters' => [
|
||||
'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']],
|
||||
'disabled_filter' => ['enabled' => false],
|
||||
],
|
||||
'aggregators' => [
|
||||
'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']],
|
||||
'disabled_aggregator' => ['enabled' => false],
|
||||
],
|
||||
'pick_formatter' => 'xlsx',
|
||||
'formatter' => $formatterData = ['key' => 'form4'],
|
||||
'centers' => ['centers' => [], 'regroupments' => []],
|
||||
];
|
||||
$user = new User();
|
||||
$centerA = new Center();
|
||||
$centerB = new Center();
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$filter = $this->prophesize(FilterInterface::class);
|
||||
$filter->applyOn()->willReturn('tagada');
|
||||
$aggregator = $this->prophesize(AggregatorInterface::class);
|
||||
$aggregator->applyOn()->willReturn('tsointsoin');
|
||||
$formatter = $this->prophesize(FormatterInterface::class);
|
||||
|
||||
$query = $this->prophesize(QueryBuilder::class);
|
||||
$query->getDQL()->willReturn('dummy');
|
||||
$dqlQuery = $this->prophesize(Query::class);
|
||||
$dqlQuery->getSQL()->willReturn('dummy');
|
||||
$query->getQuery()->willReturn($dqlQuery->reveal());
|
||||
|
||||
// required methods
|
||||
$export->initiateQuery(
|
||||
['tagada', 'tsointsoin'],
|
||||
Argument::that(function ($arg) use ($centerB, $centerA) {
|
||||
if (!is_array($arg)) {
|
||||
return false;
|
||||
}
|
||||
if (2 !== count($arg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($arg as $item) {
|
||||
if ([] !== $item['circles']) {
|
||||
return false;
|
||||
}
|
||||
if (!in_array($item['center'], [$centerA, $centerB], true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
['key' => 'form1'],
|
||||
Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
|
||||
)->shouldBeCalled()->willReturn($query->reveal());
|
||||
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled()->willReturn([['result0' => '0']]);
|
||||
$export->requiredRole()->willReturn('dummy_role');
|
||||
|
||||
$filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled();
|
||||
$aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
|
||||
->shouldBeCalled();
|
||||
|
||||
$formatter->generate(
|
||||
[['result0' => '0']],
|
||||
$formatterData,
|
||||
'dummy',
|
||||
$formExportData,
|
||||
['dummy_filter' => $formFilterData],
|
||||
['dummy_aggregator' => $formAggregatorData],
|
||||
Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user),
|
||||
)
|
||||
->shouldBeCalled()
|
||||
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
|
||||
|
||||
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
|
||||
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
|
||||
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getExport('dummy')->willReturn($export->reveal());
|
||||
$exportManager->getFilter('dummy_filter')->willReturn($filter->reveal());
|
||||
$exportManager->hasFilter('dummy_filter')->willReturn(true);
|
||||
$exportManager->hasFilter('disabled_filter')->willReturn(true);
|
||||
$exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal());
|
||||
$exportManager->hasAggregator('dummy_aggregator')->willReturn(true);
|
||||
$exportManager->hasAggregator('disabled_aggregator')->willReturn(true);
|
||||
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
|
||||
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class);
|
||||
$authorizationHelper->getReachableCenters($user, 'dummy_role')->shouldNotBeCalled();
|
||||
|
||||
$centerRepository = $this->prophesize(CenterRepositoryInterface::class);
|
||||
$centerRepository->findActive()->willReturn([$centerA, $centerB])->shouldBeCalled();
|
||||
|
||||
$generator = new ExportGenerator(
|
||||
$exportManager->reveal(),
|
||||
$exportConfigNormalizer->reveal(),
|
||||
new NullLogger(),
|
||||
$authorizationHelper->reveal(),
|
||||
new CenterRegroupementResolver(),
|
||||
new ExportConfigProcessor($exportManager->reveal()),
|
||||
$this->buildParameter(false),
|
||||
$centerRepository->reveal(),
|
||||
);
|
||||
|
||||
$actual = $generator->generate('dummy', $initialData, $user);
|
||||
|
||||
self::assertEquals('export result', $actual->content);
|
||||
self::assertEquals('text/text', $actual->contentType);
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,10 @@ namespace Chill\MainBundle\Tests\Export;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Test\PrepareCenterTrait;
|
||||
@@ -30,12 +30,10 @@ use Prophecy\Prophet;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Test the export manager.
|
||||
@@ -194,176 +192,6 @@ final class ExportManagerTest extends KernelTestCase
|
||||
$this->assertNotContains($formatterBar->reveal(), $obtained);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the generation of an export.
|
||||
*/
|
||||
public function testGenerate()
|
||||
{
|
||||
$center = $this->prepareCenter(100, 'center');
|
||||
$user = $this->prepareUser([]);
|
||||
|
||||
$authorizationChecker = $this->prophet->prophesize();
|
||||
$authorizationChecker->willImplement(AuthorizationCheckerInterface::class);
|
||||
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center)
|
||||
->willReturn(true);
|
||||
$exports = [];
|
||||
$filters = [];
|
||||
$aggregators = [];
|
||||
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
|
||||
$export = $this->prophet->prophesize();
|
||||
$export->willImplement(ExportInterface::class);
|
||||
$export->initiateQuery(
|
||||
Argument::is(['foo']),
|
||||
Argument::Type('array'),
|
||||
Argument::is(['a' => 'b'])
|
||||
)
|
||||
->will(static function () use ($em) {
|
||||
$qb = $em->createQueryBuilder();
|
||||
|
||||
return $qb->addSelect('COUNT(user.id) as export')
|
||||
->from(User::class, 'user');
|
||||
});
|
||||
$export->initiateQuery(
|
||||
Argument::is(['foo']),
|
||||
Argument::Type('array'),
|
||||
Argument::is(['a' => 'b'])
|
||||
)->shouldBeCalled();
|
||||
$export->supportsModifiers()->willReturn(['foo']);
|
||||
$export->requiredRole()->willReturn('CHILL_STAT_DUMMY');
|
||||
$export->getResult(Argument::Type(QueryBuilder::class), Argument::Type('array'))->willReturn([
|
||||
[
|
||||
'aggregator' => 'cat a',
|
||||
'export' => 0,
|
||||
],
|
||||
[
|
||||
'aggregator' => 'cat b',
|
||||
'export' => 1,
|
||||
],
|
||||
]);
|
||||
$export->getLabels(
|
||||
Argument::is('export'),
|
||||
Argument::is([0, 1]),
|
||||
Argument::Type('array')
|
||||
)
|
||||
->willReturn(static function ($value) {
|
||||
switch ($value) {
|
||||
case 0:
|
||||
case 1:
|
||||
return $value;
|
||||
|
||||
case '_header':
|
||||
return 'export';
|
||||
|
||||
default: throw new \RuntimeException(sprintf('The value %s is not valid', $value));
|
||||
}
|
||||
});
|
||||
|
||||
$export->getQueryKeys(Argument::Type('array'))->willReturn(['export']);
|
||||
$export->getTitle()->willReturn('dummy title');
|
||||
$exports['dummy'] = $export->reveal();
|
||||
|
||||
$filter = $this->prophet->prophesize();
|
||||
$filter->willImplement(FilterInterface::class);
|
||||
$filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array'))
|
||||
->willReturn(null);
|
||||
$filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array'))
|
||||
->shouldBeCalled();
|
||||
$filter->addRole()->shouldBeCalled();
|
||||
$filter->addRole()->willReturn(null);
|
||||
$filter->applyOn()->willReturn('foo');
|
||||
$filter->describeAction(Argument::cetera())->willReturn('filtered string');
|
||||
$filters['filter_foo'] = $filter->reveal();
|
||||
|
||||
$aggregator = $this->prophet->prophesize();
|
||||
$aggregator->willImplement(AggregatorInterface::class);
|
||||
$aggregator->addRole()->willReturn(null);
|
||||
$aggregator->applyOn()->willReturn('foo');
|
||||
$aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array'))
|
||||
->willReturn(null);
|
||||
$aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array'))
|
||||
->shouldBeCalled();
|
||||
$aggregator->getQueryKeys(Argument::Type('array'))->willReturn(['aggregator']);
|
||||
$aggregator->getLabels(
|
||||
Argument::is('aggregator'),
|
||||
Argument::is(['cat a', 'cat b']),
|
||||
Argument::is([])
|
||||
)
|
||||
->willReturn(static fn ($value) => match ($value) {
|
||||
'_header' => 'foo_header',
|
||||
'cat a' => 'label cat a',
|
||||
'cat b' => 'label cat b',
|
||||
default => throw new \RuntimeException(sprintf('This value (%s) is not valid', $value)),
|
||||
});
|
||||
$aggregator->addRole()->willReturn(null);
|
||||
$aggregator->addRole()->shouldBeCalled();
|
||||
$aggregators['aggregator_foo'] = $aggregator->reveal();
|
||||
|
||||
$exportManager = $this->createExportManager(
|
||||
null,
|
||||
null,
|
||||
$authorizationChecker->reveal(),
|
||||
null,
|
||||
$user,
|
||||
$exports,
|
||||
$aggregators,
|
||||
$filters
|
||||
);
|
||||
|
||||
// add formatter interface
|
||||
$formatter = new \Chill\MainBundle\Export\Formatter\SpreadSheetFormatter(
|
||||
self::getContainer()->get(TranslatorInterface::class),
|
||||
$exportManager
|
||||
);
|
||||
|
||||
$exportManager->addFormatter($formatter, 'spreadsheet');
|
||||
|
||||
$response = $exportManager->generate(
|
||||
'dummy',
|
||||
[$center],
|
||||
[
|
||||
ExportType::FILTER_KEY => [
|
||||
'filter_foo' => [
|
||||
'enabled' => true,
|
||||
'form' => [],
|
||||
],
|
||||
],
|
||||
ExportType::AGGREGATOR_KEY => [
|
||||
'aggregator_foo' => [
|
||||
'enabled' => true,
|
||||
'form' => [],
|
||||
],
|
||||
],
|
||||
ExportType::PICK_FORMATTER_KEY => [
|
||||
'alias' => 'spreadsheet',
|
||||
],
|
||||
ExportType::EXPORT_KEY => [
|
||||
'a' => 'b',
|
||||
],
|
||||
],
|
||||
[
|
||||
'format' => 'csv',
|
||||
'aggregator_foo' => [
|
||||
'order' => 1,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(Response::class, $response);
|
||||
$expected = <<<'EOT'
|
||||
"dummy title",""
|
||||
"",""
|
||||
"filtered string",""
|
||||
"foo_header","export"
|
||||
"label cat a","0"
|
||||
"label cat b","1"
|
||||
|
||||
EOT;
|
||||
|
||||
$this->assertEquals($expected, $response->getContent());
|
||||
}
|
||||
|
||||
public function testIsGrantedForElementWithExportAndUserIsGranted()
|
||||
{
|
||||
$center = $this->prepareCenter(100, 'center A');
|
||||
@@ -506,6 +334,7 @@ final class ExportManagerTest extends KernelTestCase
|
||||
array $exports = [],
|
||||
array $aggregators = [],
|
||||
array $filters = [],
|
||||
array $formatters = [],
|
||||
): ExportManager {
|
||||
$localUser = $user ?? self::getContainer()->get(
|
||||
UserRepositoryInterface::class
|
||||
@@ -516,13 +345,14 @@ final class ExportManagerTest extends KernelTestCase
|
||||
$tokenStorage->setToken($token);
|
||||
|
||||
return new ExportManager(
|
||||
$logger ?? self::getContainer()->get('logger'),
|
||||
$logger ?? self::getContainer()->get(LoggerInterface::class),
|
||||
$authorizationChecker ?? self::getContainer()->get('security.authorization_checker'),
|
||||
$authorizationHelper ?? self::getContainer()->get('chill.main.security.authorization.helper'),
|
||||
$tokenStorage,
|
||||
$exports,
|
||||
$aggregators,
|
||||
$filters
|
||||
$filters,
|
||||
$formatters,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -534,19 +364,34 @@ class DummyFilterWithApplying implements FilterInterface
|
||||
private readonly string $applyOn,
|
||||
) {}
|
||||
|
||||
public function getTitle()
|
||||
public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface
|
||||
{
|
||||
return 'dummy';
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function buildForm(FormBuilderInterface $builder): void {}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array
|
||||
{
|
||||
return ['dummy filter', []];
|
||||
}
|
||||
@@ -556,9 +401,9 @@ class DummyFilterWithApplying implements FilterInterface
|
||||
return $this->role;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data) {}
|
||||
public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {}
|
||||
|
||||
public function applyOn()
|
||||
public function applyOn(): string
|
||||
{
|
||||
return $this->applyOn;
|
||||
}
|
||||
@@ -574,13 +419,28 @@ class DummyExport implements ExportInterface
|
||||
private readonly array $supportedModifiers,
|
||||
) {}
|
||||
|
||||
public function getTitle()
|
||||
public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface
|
||||
{
|
||||
return 'dummy';
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
@@ -601,24 +461,24 @@ class DummyExport implements ExportInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getQueryKeys($data)
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getResult($query, $data)
|
||||
public function getResult($query, $data, ExportGenerationContext $context): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getType()
|
||||
public function getType(): string
|
||||
{
|
||||
return 'dummy';
|
||||
}
|
||||
|
||||
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
|
||||
public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder
|
||||
{
|
||||
return null;
|
||||
throw new \RuntimeException('not implemented');
|
||||
}
|
||||
|
||||
public function requiredRole(): string
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?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\MainBundle\Tests\Export\Formatter;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\Formatter\SpreadSheetFormatter;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Translation\TranslatableMessage;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class SpreadsheetFormatterTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testGenerate(): void
|
||||
{
|
||||
$translator = $this->prophesize(\Symfony\Contracts\Translation\TranslatorInterface::class);
|
||||
$translator->getLocale()->willReturn('en');
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
|
||||
$result =
|
||||
[
|
||||
['export_count_activity' => 1, 'person_age' => 65, 'aggregator_some' => 'label0'], // row 0
|
||||
];
|
||||
$exportAlias = 'count_activity_linked_to_person';
|
||||
$formatterData =
|
||||
['format' => 'xlsx', 'person_age_aggregator' => ['order' => 1], 'aggregator2' => ['order' => 2]];
|
||||
$exportData = [];
|
||||
$filtersData =
|
||||
[
|
||||
'person_age_filter' => ['min_age' => 18, 'max_age' => 120, 'date_calc' => new RollingDate(RollingDate::T_TODAY)],
|
||||
'filter2' => [],
|
||||
];
|
||||
$aggregatorsData =
|
||||
[
|
||||
'person_age_aggregator' => ['date_age_calculation' => new RollingDate(RollingDate::T_TODAY)],
|
||||
'aggregator2' => [],
|
||||
];
|
||||
$context =
|
||||
new ExportGenerationContext($user = new User());
|
||||
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$export->getTitle()->willReturn('Count activity linked to person');
|
||||
$translator->trans('Count activity linked to person')->willReturn('Count activity linked to person');
|
||||
$export->getQueryKeys($exportData)->willReturn(['export_count_activity']);
|
||||
$export->getLabels('export_count_activity', [1], $exportData)
|
||||
->willReturn(fn (int|string $value): int|string => '_header' === $value ? 'Count activities' : $value);
|
||||
$translator->trans('Count activities')->willReturn('Count activities');
|
||||
$exportManager->getExport($exportAlias)->willReturn($export->reveal());
|
||||
|
||||
$aggregator = $this->prophesize(\Chill\MainBundle\Export\AggregatorInterface::class);
|
||||
$aggregator->getTitle()->willReturn('Person age');
|
||||
$aggregator->getQueryKeys($aggregatorsData['person_age_aggregator'])->willReturn(['person_age']);
|
||||
$aggregator->getLabels('person_age', [65], $aggregatorsData['person_age_aggregator'])
|
||||
->willReturn(fn (int|string $value): int|string => '_header' === $value ? 'Group by age' : $value);
|
||||
$translator->trans('Group by age')->willReturn('Group by age');
|
||||
$exportManager->getAggregator('person_age_aggregator')->willReturn($aggregator->reveal());
|
||||
|
||||
$aggregator2 = $this->prophesize(\Chill\MainBundle\Export\AggregatorInterface::class);
|
||||
$aggregator2->getTitle()->willReturn(new TranslatableMessage('Some'));
|
||||
$aggregator2->getQueryKeys($aggregatorsData['aggregator2'])->willReturn(['aggregator_some']);
|
||||
$aggregator2->getLabels('aggregator_some', ['label0'], $aggregatorsData['aggregator2'])
|
||||
->willReturn(fn (int|string $value): TranslatableMessage => new TranslatableMessage('_header' === $value ? 'Aggregator 2 header' : $value));
|
||||
$translator->trans('Aggregator 2 header', [], null, 'en')->willReturn('Aggregator 2 header');
|
||||
$translator->trans('label0', [], null, 'en')->willReturn('label0');
|
||||
$exportManager->getAggregator('aggregator2')->willReturn($aggregator2->reveal());
|
||||
|
||||
$filter = $this->prophesize(\Chill\MainBundle\Export\FilterInterface::class);
|
||||
$filter->getTitle()->willReturn('Person by age');
|
||||
$filter->describeAction($filtersData['person_age_filter'], $context)
|
||||
->willReturn(['Filter by age, from {{ start }} to {{ end }}', ['{{ start }}' => '18', '{{ end }}' => '120']]);
|
||||
$translator->trans('Filter by age, from {{ start }} to {{ end }}', ['{{ start }}' => '18', '{{ end }}' => '120'])
|
||||
->willReturn('Filter by age, from 18 to 120');
|
||||
$exportManager->getFilter('person_age_filter')->willReturn($filter->reveal());
|
||||
|
||||
$filter2 = $this->prophesize(\Chill\MainBundle\Export\FilterInterface::class);
|
||||
$filter2->getTitle()->willReturn(new TranslatableMessage('Some other filter'));
|
||||
$filter2->describeAction($filtersData['filter2'], $context)
|
||||
->willReturn(new TranslatableMessage('Other filter description'));
|
||||
$translator->trans('Other filter description', [], null, 'en')
|
||||
->willReturn('Some other filter description');
|
||||
$exportManager->getFilter('filter2')->willReturn($filter2->reveal());
|
||||
|
||||
|
||||
// create the formatter
|
||||
$formatter = new SpreadSheetFormatter($translator->reveal());
|
||||
$formatter->setExportManager($exportManager->reveal());
|
||||
|
||||
$result = $formatter->generate(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
$exportData,
|
||||
$filtersData,
|
||||
$aggregatorsData,
|
||||
$context,
|
||||
);
|
||||
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'test_spreadsheet_formatter_');
|
||||
file_put_contents($tempFile, $result->content);
|
||||
$spreadsheet = IOFactory::load($tempFile);
|
||||
$cells = $spreadsheet->getActiveSheet()->rangeToArray(
|
||||
'A1:G6',
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
unlink($tempFile);
|
||||
|
||||
self::assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $result->contentType);
|
||||
self::assertEquals($cells[1], ['A' => 'Count activity linked to perso…', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]);
|
||||
self::assertEquals($cells[2], ['A' => null, 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]);
|
||||
self::assertEquals($cells[3], ['A' => 'Filter by age, from 18 to 120', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]);
|
||||
self::assertEquals($cells[4], ['A' => 'Some other filter description', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]);
|
||||
self::assertEquals($cells[5], ['A' => 'Group by age', 'B' => 'Aggregator 2 header', 'C' => 'Count activities', 'D' => null, 'E' => null, 'F' => null, 'G' => null]);
|
||||
self::assertEquals($cells[6], ['A' => 65, 'B' => 'label0', 'C' => 1, 'D' => null, 'E' => null, 'F' => null, 'G' => null]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?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\MainBundle\Tests\Export\Messenger;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\Exception\ExportGenerationException;
|
||||
use Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage;
|
||||
use Chill\MainBundle\Export\Messenger\OnExportGenerationFails;
|
||||
use Chill\MainBundle\Repository\ExportGenerationRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class OnExportGenerationFailsTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testOnExportGenerationFails(): void
|
||||
{
|
||||
$exportGeneration = new ExportGeneration('dummy');
|
||||
$exportGeneration->setCreatedAt(new \DateTimeImmutable('10 seconds ago'));
|
||||
|
||||
$repository = $this->prophesize(ExportGenerationRepository::class);
|
||||
$repository->find($exportGeneration->getId())->willReturn($exportGeneration);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$user = $this->prophesize(User::class);
|
||||
$user->getId()->willReturn(1);
|
||||
|
||||
$subscriber = new OnExportGenerationFails(new NullLogger(), $repository->reveal(), $entityManager->reveal());
|
||||
|
||||
$subscriber->onMessageFailed(new WorkerMessageFailedEvent(
|
||||
new Envelope(new ExportRequestGenerationMessage($exportGeneration, $user->reveal())),
|
||||
'dummyReceiver',
|
||||
new ExportGenerationException('dummy_exception'),
|
||||
));
|
||||
|
||||
self::assertEquals(StoredObject::STATUS_FAILURE, $exportGeneration->getStoredObject()->getStatus());
|
||||
self::assertStringContainsString('dummy_exception', $exportGeneration->getStoredObject()->getGenerationErrors());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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\MainBundle\Tests\Export\Messenger;
|
||||
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage;
|
||||
use Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessageHandler;
|
||||
use Chill\MainBundle\Repository\ExportGenerationRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RemoveExportGenerationMessageHandlerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testInvokeUpdatesDeleteAtAndRemovesAndFlushes()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// 1. Create a MockClock at a fixed point in time
|
||||
$now = new \DateTimeImmutable('2024-06-01T12:00:00');
|
||||
$clock = new MockClock($now);
|
||||
|
||||
// 2. Create an ExportGeneration entity with a stored object
|
||||
$exportGeneration = new ExportGeneration('test-alias', ['foo' => 'bar']);
|
||||
$storedObject = $exportGeneration->getStoredObject();
|
||||
|
||||
// 3. Mock ExportGenerationRepository to return the ExportGeneration
|
||||
$exportGenerationRepository = $this->prophesize(ExportGenerationRepository::class);
|
||||
$exportGenerationRepository
|
||||
->find($exportGeneration->getId())
|
||||
->willReturn($exportGeneration);
|
||||
|
||||
// 4. Mock EntityManagerInterface and set expectations
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->remove($exportGeneration)->shouldBeCalled();
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
// 6. Create message
|
||||
$message = new RemoveExportGenerationMessage($exportGeneration);
|
||||
|
||||
// 7. Handler instantiation
|
||||
$handler = new RemoveExportGenerationMessageHandler(
|
||||
$exportGenerationRepository->reveal(),
|
||||
$entityManager->reveal(),
|
||||
new NullLogger(),
|
||||
$clock
|
||||
);
|
||||
|
||||
// Pre-condition: deleteAt not set.
|
||||
$this->assertNull($storedObject->getDeleteAt());
|
||||
|
||||
// Act
|
||||
$handler->__invoke($message);
|
||||
|
||||
// Assert
|
||||
$this->assertEquals($now, $storedObject->getDeleteAt(), 'deleteAt of stored object was updated');
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -12,12 +12,14 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Tests\Export;
|
||||
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Export\SortExportElement;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Contracts\Translation\TranslatableInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,10 @@ class SortExportElementTest extends KernelTestCase
|
||||
|
||||
$previousName = null;
|
||||
foreach ($filters as $filter) {
|
||||
if ($filter->getTitle() instanceof TranslatableInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $previousName) {
|
||||
$previousName = $translator->trans($filter->getTitle());
|
||||
continue;
|
||||
@@ -119,24 +125,39 @@ class SortExportElementTest extends KernelTestCase
|
||||
return new class ($title) implements AggregatorInterface {
|
||||
public function __construct(private readonly string $title) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function buildForm(FormBuilderInterface $builder): void {}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, mixed $data)
|
||||
public function getLabels($key, array $values, mixed $data): callable
|
||||
{
|
||||
return fn ($v) => $v;
|
||||
}
|
||||
|
||||
public function getQueryKeys($data)
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
public function getTitle(): string|TranslatableInterface
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
@@ -146,11 +167,11 @@ class SortExportElementTest extends KernelTestCase
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data) {}
|
||||
public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {}
|
||||
|
||||
public function applyOn()
|
||||
public function applyOn(): string
|
||||
{
|
||||
return [];
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -160,19 +181,34 @@ class SortExportElementTest extends KernelTestCase
|
||||
return new class ($title) implements FilterInterface {
|
||||
public function __construct(private readonly string $title) {}
|
||||
|
||||
public function getTitle()
|
||||
public function getTitle(): string|TranslatableInterface
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function buildForm(FormBuilderInterface $builder): void {}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
public function describeAction($data, ExportGenerationContext $context): string|TranslatableInterface|array
|
||||
{
|
||||
return ['a', []];
|
||||
}
|
||||
@@ -182,11 +218,11 @@ class SortExportElementTest extends KernelTestCase
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data) {}
|
||||
public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {}
|
||||
|
||||
public function applyOn()
|
||||
public function applyOn(): string
|
||||
{
|
||||
return [];
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user