mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 16:14:59 +00:00
Partage d'export enregistré et génération asynchrone des exports
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<?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\Authorization;
|
||||
|
||||
use Chill\MainBundle\Controller\ExportGenerationCreateFromSavedExportController;
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExportGenerationCreateFromSavedExportControllerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testInvoke(): void
|
||||
{
|
||||
$savedExport = new SavedExport();
|
||||
$savedExport->setOptions($exportOptions = ['test' => 'content'])->setExportAlias('dummy_export_alias');
|
||||
|
||||
$user = new User();
|
||||
$reflection = new \ReflectionClass($user);
|
||||
$id = $reflection->getProperty('id');
|
||||
$id->setValue($user, 1);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted(SavedExportVoter::GENERATE, $savedExport)->shouldBeCalled()->willReturn(true);
|
||||
$security->getUser()->willReturn($user);
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(
|
||||
static fn ($arg) => $arg instanceof ExportGeneration && $arg->getOptions() === $exportOptions && $arg->getSavedExport() === $savedExport,
|
||||
))->shouldBeCalled();
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$messenger = $this->prophesize(MessageBusInterface::class);
|
||||
$messenger->dispatch(Argument::type(ExportRequestGenerationMessage::class))->shouldBeCalled()->will(
|
||||
static fn (array $args): Envelope => new Envelope($args[0]),
|
||||
);
|
||||
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->serialize(Argument::type(ExportGeneration::class), 'json', ['groups' => ['read']])->shouldBeCalled()->willReturn('{"test": "export-generation"}');
|
||||
|
||||
$controller = new ExportGenerationCreateFromSavedExportController(
|
||||
$security->reveal(),
|
||||
$entityManager->reveal(),
|
||||
$messenger->reveal(),
|
||||
new MockClock(),
|
||||
$serializer->reveal()
|
||||
);
|
||||
|
||||
$response = $controller($savedExport);
|
||||
|
||||
self::assertInstanceOf(JsonResponse::class, $response);
|
||||
self::assertEquals('{"test": "export-generation"}', $response->getContent());
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
<?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\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\MainBundle\Controller\ExportGenerationController;
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Twig\Environment;
|
||||
use function PHPUnit\Framework\assertEquals;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ExportGenerationControllerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testObjectStatus(): void
|
||||
{
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
$environment = $this->prophesize(Environment::class);
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->serialize(Argument::any(), 'json', ['groups' => ['read']])->willReturn('{}');
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
|
||||
$pending = new ExportGeneration('dummy', []);
|
||||
|
||||
$controller = new ExportGenerationController($security->reveal(), $environment->reveal(), $serializer->reveal(), $exportManager->reveal());
|
||||
|
||||
$actual = $controller->objectStatus($pending);
|
||||
self::assertEquals('{}', $actual->getContent());
|
||||
|
||||
$generated = new ExportGeneration('dummy', []);
|
||||
$generated->getStoredObject()->setStatus(StoredObject::STATUS_READY);
|
||||
|
||||
self:assertEquals('{}', $controller->objectStatus($generated)->getContent());
|
||||
}
|
||||
}
|
@@ -21,13 +21,13 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class ExportControllerTest extends WebTestCase
|
||||
final class ExportIndexControllerTest extends WebTestCase
|
||||
{
|
||||
use PrepareClientTrait;
|
||||
|
||||
public function testIndex()
|
||||
{
|
||||
$client = $this->getClientAuthenticatedAsAdmin();
|
||||
$client = $this->getClientAuthenticated();
|
||||
|
||||
$client->request('GET', '/fr/exports/');
|
||||
|
@@ -0,0 +1,52 @@
|
||||
<?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\Entity\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class SavedExportTest extends TestCase
|
||||
{
|
||||
public function testIsSharedWithUser(): void
|
||||
{
|
||||
// Create test users
|
||||
$user1 = new User();
|
||||
$user2 = new User();
|
||||
$user3 = new User();
|
||||
|
||||
// Create a test group and add user2 to the group
|
||||
$group = new UserGroup();
|
||||
$group->addUser($user2);
|
||||
|
||||
// Create a SavedExport entity
|
||||
$savedExport = new SavedExport();
|
||||
|
||||
// Share the saved export with user1
|
||||
$savedExport->addShare($user1);
|
||||
|
||||
// Share the saved export with the group
|
||||
$savedExport->addShare($group);
|
||||
|
||||
// Assertions
|
||||
$this->assertTrue($savedExport->isSharedWithUser($user1), 'User1 should have access to the saved export.');
|
||||
$this->assertTrue($savedExport->isSharedWithUser($user2), 'User2 (via group) should have access to the saved export.');
|
||||
$this->assertFalse($savedExport->isSharedWithUser($user3), 'User3 should not have access to the saved export.');
|
||||
|
||||
}
|
||||
}
|
@@ -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 '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -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\MainBundle\Tests\Security\Authorization;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class SavedExportVoterTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider voteProvider
|
||||
*/
|
||||
public function testVote(string $attribute, mixed $savedExport, User $user, $expectedResult, ?bool $isGranted = null): void
|
||||
{
|
||||
$export = $this->prophesize(ExportInterface::class);
|
||||
$exportManager = $this->prophesize(ExportManager::class);
|
||||
$exportManager->getExport('dummy_export')->willReturn($export->reveal());
|
||||
$exportManager->isGrantedForElement(Argument::any())->willReturn($isGranted);
|
||||
|
||||
$accessDecisionManager = $this->prophesize(AccessDecisionManagerInterface::class);
|
||||
|
||||
$voter = new SavedExportVoter($exportManager->reveal(), $accessDecisionManager->reveal());
|
||||
$token = new UsernamePasswordToken($user, 'default', ['ROLE_USER']);
|
||||
|
||||
self::assertEquals($expectedResult, $voter->vote($token, $savedExport, [$attribute]));
|
||||
}
|
||||
|
||||
public static function voteProvider(): iterable
|
||||
{
|
||||
$alls = [SavedExportVoter::GENERATE, SavedExportVoter::GENERATE, SavedExportVoter::EDIT, SavedExportVoter::DELETE];
|
||||
$userA = new User();
|
||||
$userB = new User();
|
||||
$userC = new User();
|
||||
$group = new UserGroup();
|
||||
$group->addUser($userC);
|
||||
|
||||
$savedExport = new SavedExport();
|
||||
$savedExport->setExportAlias('dummy_export');
|
||||
$savedExport->setUser($userA);
|
||||
|
||||
// abstain
|
||||
foreach ($alls as $attribute) {
|
||||
yield [
|
||||
$attribute,
|
||||
new \stdClass(),
|
||||
$userA,
|
||||
VoterInterface::ACCESS_ABSTAIN,
|
||||
true,
|
||||
];
|
||||
}
|
||||
|
||||
yield [
|
||||
'dummy',
|
||||
$savedExport,
|
||||
$userA,
|
||||
VoterInterface::ACCESS_ABSTAIN,
|
||||
false,
|
||||
];
|
||||
|
||||
foreach ($alls as $attribute) {
|
||||
yield [
|
||||
$attribute,
|
||||
$savedExport,
|
||||
$userA,
|
||||
VoterInterface::ACCESS_GRANTED,
|
||||
true,
|
||||
];
|
||||
}
|
||||
|
||||
yield [
|
||||
SavedExportVoter::GENERATE,
|
||||
$savedExport,
|
||||
$userA,
|
||||
VoterInterface::ACCESS_DENIED,
|
||||
false,
|
||||
];
|
||||
|
||||
foreach ($alls as $attribute) {
|
||||
yield [
|
||||
$attribute,
|
||||
$savedExport,
|
||||
$userB,
|
||||
VoterInterface::ACCESS_DENIED,
|
||||
true,
|
||||
];
|
||||
}
|
||||
|
||||
$savedExport = new SavedExport();
|
||||
$savedExport->setExportAlias('dummy_export');
|
||||
$savedExport->setUser($userA);
|
||||
$savedExport->addShare($userB);
|
||||
|
||||
yield [
|
||||
SavedExportVoter::GENERATE,
|
||||
$savedExport,
|
||||
$userB,
|
||||
VoterInterface::ACCESS_DENIED,
|
||||
false,
|
||||
];
|
||||
|
||||
yield [
|
||||
SavedExportVoter::GENERATE,
|
||||
$savedExport,
|
||||
$userB,
|
||||
VoterInterface::ACCESS_GRANTED,
|
||||
true,
|
||||
];
|
||||
|
||||
foreach ([SavedExportVoter::EDIT, SavedExportVoter::DELETE] as $attribute) {
|
||||
yield [
|
||||
$attribute,
|
||||
$savedExport,
|
||||
$userB,
|
||||
VoterInterface::ACCESS_DENIED,
|
||||
true,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
<?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\Services\Regroupement;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Regroupment;
|
||||
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class CenterRegroupementResolverTest extends TestCase
|
||||
{
|
||||
private static CenterRegroupementResolver $resolver;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
self::$resolver = new CenterRegroupementResolver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideData
|
||||
*/
|
||||
public function testResolveCenter(array $groups, array $centers, array $expected): void
|
||||
{
|
||||
$actual = self::$resolver->resolveCenters($groups, $centers);
|
||||
|
||||
self::assertEquals(count($expected), count($actual));
|
||||
|
||||
foreach ($expected as $center) {
|
||||
self::assertContains($center, $actual);
|
||||
}
|
||||
}
|
||||
|
||||
public static function provideData(): iterable
|
||||
{
|
||||
$centerA = new Center();
|
||||
$centerB = new Center();
|
||||
$centerC = new Center();
|
||||
$centerD = new Center();
|
||||
|
||||
$groupA = new Regroupment();
|
||||
$groupA->addCenter($centerA)->addCenter($centerB);
|
||||
|
||||
$groupB = new Regroupment();
|
||||
$groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC);
|
||||
|
||||
yield [
|
||||
[$groupA],
|
||||
[],
|
||||
[$centerA, $centerB],
|
||||
];
|
||||
|
||||
yield [
|
||||
[$groupA, $groupB],
|
||||
[],
|
||||
[$centerA, $centerB, $centerC],
|
||||
];
|
||||
|
||||
yield [
|
||||
[$groupA, $groupB],
|
||||
[$centerB, $centerD],
|
||||
[$centerA, $centerB, $centerC, $centerD],
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
<?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\Services\Regroupement;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Regroupment;
|
||||
use Chill\MainBundle\Service\Regroupement\RegroupementFiltering;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RegroupementFilteringTest extends TestCase
|
||||
{
|
||||
private static RegroupementFiltering $regroupementFiltering;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
self::$regroupementFiltering = new RegroupementFiltering();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataForFilterContainsAtLeastOnCenter
|
||||
*/
|
||||
public function testFilterContainsAtLeastOnCenter(array $groups, array $centers, array $expected): void
|
||||
{
|
||||
$actual = self::$regroupementFiltering->filterContainsAtLeastOneCenter($groups, $centers);
|
||||
|
||||
self::assertEquals(count($expected), count($actual));
|
||||
self::assertTrue(array_is_list($actual));
|
||||
|
||||
foreach ($expected as $center) {
|
||||
self::assertContains($center, $actual);
|
||||
}
|
||||
}
|
||||
|
||||
public static function provideDataForFilterContainsAtLeastOnCenter(): iterable
|
||||
{
|
||||
|
||||
$centerA = new Center();
|
||||
$centerB = new Center();
|
||||
$centerC = new Center();
|
||||
$centerD = new Center();
|
||||
|
||||
$groupA = new Regroupment();
|
||||
$groupA->addCenter($centerA)->addCenter($centerB);
|
||||
|
||||
$groupB = new Regroupment();
|
||||
$groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC);
|
||||
|
||||
$groupC = new Regroupment();
|
||||
$groupC->addCenter($centerA)->addCenter($centerD);
|
||||
|
||||
yield [
|
||||
[$groupA, $groupB],
|
||||
[],
|
||||
[],
|
||||
];
|
||||
|
||||
yield [
|
||||
[$groupA, $groupB],
|
||||
[$centerA, $centerB, $centerC],
|
||||
[$groupA, $groupB],
|
||||
];
|
||||
|
||||
yield [
|
||||
[$groupA, $groupC],
|
||||
[$centerD],
|
||||
[$groupC],
|
||||
];
|
||||
|
||||
yield [
|
||||
[$groupA],
|
||||
[$centerB, $centerD],
|
||||
[$groupA],
|
||||
];
|
||||
|
||||
yield [
|
||||
[$groupA],
|
||||
[new Center()],
|
||||
[],
|
||||
];
|
||||
|
||||
}
|
||||
}
|
@@ -14,6 +14,7 @@ namespace Services\RollingDate;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -22,11 +23,9 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
final class RollingDateConverterTest extends TestCase
|
||||
{
|
||||
private RollingDateConverter $converter;
|
||||
|
||||
protected function setUp(): void
|
||||
private function buildConverter(\DateTimeImmutable|string $pivot = 'now'): RollingDateConverter
|
||||
{
|
||||
$this->converter = new RollingDateConverter();
|
||||
return new RollingDateConverter(new MockClock($pivot));
|
||||
}
|
||||
|
||||
public function testConversionFixedDate()
|
||||
@@ -35,7 +34,7 @@ final class RollingDateConverterTest extends TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
'2022-01-01',
|
||||
$this->converter->convert($rollingDate)->format('Y-m-d')
|
||||
$this->buildConverter()->convert($rollingDate)->format('Y-m-d')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ final class RollingDateConverterTest extends TestCase
|
||||
{
|
||||
$rollingDate = new RollingDate(RollingDate::T_YEAR_PREVIOUS_START);
|
||||
|
||||
$actual = $this->converter->convert($rollingDate);
|
||||
$actual = $this->buildConverter()->convert($rollingDate);
|
||||
|
||||
$this->assertEquals(
|
||||
(int) (new \DateTimeImmutable('now'))->format('Y') - 1,
|
||||
@@ -63,7 +62,21 @@ final class RollingDateConverterTest extends TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
\DateTime::createFromFormat($format, $expectedDateTime),
|
||||
$this->converter->convert($rollingDate)
|
||||
$this->buildConverter()->convert($rollingDate)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateDataConversionDate
|
||||
*/
|
||||
public function testConvertOnClock(string $roll, string $expectedDateTime, string $format)
|
||||
{
|
||||
$pivot = \DateTimeImmutable::createFromFormat('Y-m-d His', '2022-11-07 000000');
|
||||
$rollingDate = new RollingDate($roll, null);
|
||||
|
||||
$this->assertEquals(
|
||||
\DateTime::createFromFormat($format, $expectedDateTime),
|
||||
$this->buildConverter($pivot)->convert($rollingDate)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,45 @@
|
||||
<?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\Services\RollingDate;
|
||||
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class RollingDateTest extends TestCase
|
||||
{
|
||||
public function testNormalizationDenormalizationProcessWithoutPivotDate(): void
|
||||
{
|
||||
$date = new RollingDate(RollingDate::T_YEAR_PREVIOUS_START);
|
||||
|
||||
$actual = RollingDate::fromNormalized($date->normalize());
|
||||
|
||||
self::assertEquals(RollingDate::T_YEAR_PREVIOUS_START, $actual->getRoll());
|
||||
self::assertNull($actual->getFixedDate());
|
||||
self::assertEquals($date->getPivotDate()?->getTimestamp(), $actual->getPivotDate()?->getTimestamp());
|
||||
}
|
||||
|
||||
public function testNormalizationDenormalizationProcessWithPivotDate(): void
|
||||
{
|
||||
$date = new RollingDate(RollingDate::T_FIXED_DATE, $fixed = new \DateTimeImmutable('now'));
|
||||
|
||||
$actual = RollingDate::fromNormalized($date->normalize());
|
||||
|
||||
self::assertEquals(RollingDate::T_FIXED_DATE, $actual->getRoll());
|
||||
self::assertEquals($fixed, $actual->getFixedDate());
|
||||
self::assertEquals($date->getPivotDate()?->getTimestamp(), $actual->getPivotDate()?->getTimestamp());
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
<?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\Services\UserGroup;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Chill\MainBundle\Service\UserGroup\UserGroupRelatedToUserJobSyncCronJob;
|
||||
use Chill\MainBundle\Service\UserGroup\UserGroupRelatedToUserJobSyncInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class UserGroupRelatedToUserJobSyncCronJobTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider canRunDataProvider
|
||||
*/
|
||||
public function testCanRun(\DateTimeImmutable $now, ?\DateTimeImmutable $lastStartExecution, bool $exected): void
|
||||
{
|
||||
$clock = new MockClock($now);
|
||||
$job = $this->prophesize(UserGroupRelatedToUserJobSyncInterface::class);
|
||||
|
||||
$cronJob = new UserGroupRelatedToUserJobSyncCronJob($clock, $job->reveal());
|
||||
|
||||
if (null !== $lastStartExecution) {
|
||||
$lastExecution = new CronJobExecution('user-group-related-to-user-job-sync');
|
||||
$lastExecution->setLastStart($lastStartExecution);
|
||||
}
|
||||
|
||||
$actual = $cronJob->canRun($lastExecution ?? null);
|
||||
|
||||
self::assertEquals($exected, $actual);
|
||||
}
|
||||
|
||||
public static function canRunDataProvider(): iterable
|
||||
{
|
||||
$now = new \DateTimeImmutable('2025-04-27T00:00:00Z');
|
||||
|
||||
yield 'never executed' => [$now, null, true];
|
||||
yield 'executed 12 hours ago' => [$now, new \DateTimeImmutable('2025-04-26T12:00:00Z'), false];
|
||||
yield 'executed more than 12 hours ago' => [$now, new \DateTimeImmutable('2025-04-25T12:00:00Z'), true];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user