mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
639 lines
21 KiB
PHP
639 lines
21 KiB
PHP
<?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\User;
|
|
use Chill\MainBundle\Export\AggregatorInterface;
|
|
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;
|
|
use Chill\MainBundle\Test\PrepareScopeTrait;
|
|
use Chill\MainBundle\Test\PrepareUserTrait;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\QueryBuilder;
|
|
use Prophecy\Argument;
|
|
use Prophecy\Prophet;
|
|
use Psr\Log\LoggerInterface;
|
|
use RuntimeException;
|
|
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\Role\Role;
|
|
use Symfony\Component\Security\Core\User\UserInterface;
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
use function count;
|
|
|
|
/**
|
|
* Test the export manager.
|
|
*
|
|
* @internal
|
|
* @coversNothing
|
|
*/
|
|
final class ExportManagerTest extends KernelTestCase
|
|
{
|
|
use PrepareCenterTrait;
|
|
|
|
use PrepareScopeTrait;
|
|
|
|
use PrepareUserTrait;
|
|
|
|
private Prophet $prophet;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
self::bootKernel();
|
|
|
|
$this->prophet = new \Prophecy\Prophet();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->prophet->checkPredictions();
|
|
}
|
|
|
|
public function testAggregatorsApplyingOn()
|
|
{
|
|
$centers = [$center = new Center()];
|
|
$user = $this->prepareUser([]);
|
|
|
|
$authorizationChecker = $this->prophet->prophesize();
|
|
$authorizationChecker->willImplement(AuthorizationCheckerInterface::class);
|
|
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center)
|
|
->willReturn(true);
|
|
|
|
$exports = [];
|
|
$aggregators = [];
|
|
$filters = [];
|
|
|
|
$exports[] = $exportFooBar = new DummyExport('CHILL_STAT_DUMMY', ['foo', 'bar']);
|
|
|
|
$aggregatorBar = $this->prophet->prophesize();
|
|
$aggregatorBar->willImplement(AggregatorInterface::class);
|
|
$aggregatorBar->applyOn()->willReturn('bar');
|
|
$aggregatorBar->addRole()->willReturn(null);
|
|
$aggregators['bar'] = $aggregatorBar->reveal();
|
|
|
|
$exports[] = $exportBar = new DummyExport('CHILL_STAT_DUMMY', ['bar']);
|
|
|
|
$aggregatorFoo = $this->prophet->prophesize();
|
|
$aggregatorFoo->willImplement(AggregatorInterface::class);
|
|
$aggregatorFoo->applyOn()->willReturn('foo');
|
|
$aggregatorFoo->addRole()->willReturn(null);
|
|
$aggregators['foo'] = $aggregatorFoo->reveal();
|
|
|
|
$exports[] = $exportFoo = new DummyExport('CHILL_STAT_DUMMY', ['foo']);
|
|
|
|
$exportManager = $this->createExportManager(
|
|
null,
|
|
null,
|
|
$authorizationChecker->reveal(),
|
|
null,
|
|
$user,
|
|
$exports,
|
|
$aggregators,
|
|
$filters
|
|
);
|
|
|
|
|
|
$obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFoo, $centers));
|
|
$this->assertEquals(1, count($obtained));
|
|
$this->assertContains('foo', array_keys($obtained));
|
|
|
|
$obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportBar, $centers));
|
|
$this->assertEquals(1, count($obtained));
|
|
$this->assertContains('bar', array_keys($obtained));
|
|
|
|
$obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFooBar, $centers));
|
|
$this->assertEquals(2, count($obtained));
|
|
$this->assertContains('bar', array_keys($obtained));
|
|
$this->assertContains('foo', array_keys($obtained));
|
|
|
|
// test with empty centers
|
|
$obtained = iterator_to_array($exportManager->getAggregatorsApplyingOn($exportFooBar, []));
|
|
$this->assertEquals(0, count($obtained));
|
|
}
|
|
|
|
public function testFiltersApplyingOn()
|
|
{
|
|
$centers = [$center = new Center()];
|
|
$user = $this->prepareUser([]);
|
|
|
|
$authorizationChecker = $this->prophet->prophesize();
|
|
$authorizationChecker->willImplement(AuthorizationCheckerInterface::class);
|
|
$authorizationChecker->isGranted(Argument::type('string'), $center)
|
|
->willReturn(true);
|
|
|
|
$filters = [];
|
|
$exports = [];
|
|
|
|
$exports['fooBar'] = $exportFooBar = new DummyExport('CHILL_STAT_DUMMY', ['foo', 'bar']);
|
|
$filters['bar'] = $filterBar = new DummyFilterWithApplying(null, 'bar');
|
|
$exports['bar'] = $exportBar = new DummyExport('CHILL_STAT_DUMMY', ['bar']);
|
|
$filters['foo'] = $filterFoo = new DummyFilterWithApplying(null, 'foo');
|
|
$exports['foo'] = $exportFoo = new DummyExport('CHILL_STAT_DUMMY', ['foo']);
|
|
|
|
$exportManager = $this->createExportManager(
|
|
null,
|
|
null,
|
|
$authorizationChecker->reveal(),
|
|
null,
|
|
$user,
|
|
$exports,
|
|
[],
|
|
$filters
|
|
);
|
|
|
|
$obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFoo, $centers));
|
|
$this->assertEquals(1, count($obtained));
|
|
$this->assertContains('foo', array_keys($obtained));
|
|
|
|
$obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportBar, $centers));
|
|
$this->assertEquals(1, count($obtained));
|
|
$this->assertContains('bar', array_keys($obtained));
|
|
|
|
$obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFooBar, $centers));
|
|
$this->assertEquals(2, count($obtained));
|
|
$this->assertContains('bar', array_keys($obtained));
|
|
$this->assertContains('foo', array_keys($obtained));
|
|
|
|
$obtained = iterator_to_array($exportManager->getFiltersApplyingOn($exportFooBar, []));
|
|
$this->assertEquals(0, count($obtained));
|
|
}
|
|
|
|
public function testFormattersByTypes()
|
|
{
|
|
$exportManager = $this->createExportManager();
|
|
|
|
//create a formatter
|
|
$formatterFoo = $this->prophet->prophesize();
|
|
$formatterFoo->willImplement(\Chill\MainBundle\Export\FormatterInterface::class);
|
|
$formatterFoo->getType()->willReturn('foo');
|
|
$formatterBar = $this->prophet->prophesize();
|
|
$formatterBar->willImplement(\Chill\MainBundle\Export\FormatterInterface::class);
|
|
$formatterBar->getType()->willReturn('bar');
|
|
$exportManager->addFormatter($formatterFoo->reveal(), 'foo');
|
|
$exportManager->addFormatter($formatterBar->reveal(), 'bar');
|
|
|
|
$obtained = $exportManager->getFormattersByTypes(['foo']);
|
|
|
|
$this->assertContains($formatterFoo->reveal(), $obtained);
|
|
$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::$container->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::$container->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');
|
|
$user = $this->prepareUser([]);
|
|
|
|
$authorizationChecker = $this->prophet->prophesize();
|
|
$authorizationChecker->willImplement(\Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface::class);
|
|
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center)
|
|
->willReturn(true);
|
|
|
|
$export = $this->prophet->prophesize();
|
|
$export->willImplement(ExportInterface::class);
|
|
$export->requiredRole()->willReturn('CHILL_STAT_DUMMY');
|
|
|
|
$exportManager = $this->createExportManager(
|
|
null,
|
|
null,
|
|
$authorizationChecker->reveal(),
|
|
null,
|
|
$user,
|
|
['dummy_export' => $export->reveal()],
|
|
);
|
|
|
|
$result = $exportManager->isGrantedForElement($export->reveal(), null, [$center]);
|
|
|
|
$this->assertTrue($result);
|
|
}
|
|
|
|
public function testIsGrantedForElementWithExportAndUserIsGrantedNotForAllCenters()
|
|
{
|
|
$center = $this->prepareCenter(100, 'center A');
|
|
$centerB = $this->prepareCenter(102, 'center B');
|
|
$user = $this->prepareUser([]);
|
|
|
|
$authorizationChecker = $this->prophet->prophesize();
|
|
$authorizationChecker->willImplement(\Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface::class);
|
|
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center)
|
|
->willReturn(true);
|
|
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $centerB)
|
|
->willReturn(false);
|
|
|
|
$exportManager = $this->createExportManager(
|
|
null,
|
|
null,
|
|
$authorizationChecker->reveal(),
|
|
null,
|
|
$user
|
|
);
|
|
|
|
$export = $this->prophet->prophesize();
|
|
$export->willImplement(ExportInterface::class);
|
|
$export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY'));
|
|
|
|
$result = $exportManager->isGrantedForElement($export->reveal(), null, [$center, $centerB]);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
public function testIsGrantedForElementWithExportEmptyCenters()
|
|
{
|
|
$user = $this->prepareUser([]);
|
|
|
|
$exportManager = $this->createExportManager(
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
$user
|
|
);
|
|
|
|
$export = $this->prophet->prophesize();
|
|
$export->willImplement(\Chill\MainBundle\Export\ExportInterface::class);
|
|
$export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY'));
|
|
|
|
$result = $exportManager->isGrantedForElement($export->reveal(), null, []);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
public function testIsGrantedForElementWithModifierFallbackToExport()
|
|
{
|
|
$center = $this->prepareCenter(100, 'center A');
|
|
$centerB = $this->prepareCenter(102, 'center B');
|
|
$user = $this->prepareUser([]);
|
|
|
|
$authorizationChecker = $this->prophet->prophesize();
|
|
$authorizationChecker->willImplement(AuthorizationCheckerInterface::class);
|
|
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center)
|
|
->willReturn(true);
|
|
$authorizationChecker->isGranted('CHILL_STAT_DUMMY', $centerB)
|
|
->willReturn(false);
|
|
|
|
$exportManager = $this->createExportManager(
|
|
null,
|
|
null,
|
|
$authorizationChecker->reveal(),
|
|
null,
|
|
$user
|
|
);
|
|
|
|
$modifier = $this->prophet->prophesize();
|
|
$modifier->willImplement(\Chill\MainBundle\Export\ModifierInterface::class);
|
|
$modifier->addRole()->willReturn(null);
|
|
|
|
$export = $this->prophet->prophesize();
|
|
$export->willImplement(ExportInterface::class);
|
|
$export->requiredRole()->willReturn(new Role('CHILL_STAT_DUMMY'));
|
|
|
|
$result = $exportManager->isGrantedForElement(
|
|
$modifier->reveal(),
|
|
$export->reveal(),
|
|
[$center, $centerB]
|
|
);
|
|
|
|
$this->assertFalse($result);
|
|
}
|
|
|
|
public function testNonExistingFormatter()
|
|
{
|
|
$this->expectException(RuntimeException::class);
|
|
|
|
$exportManager = $this->createExportManager();
|
|
|
|
$exportManager->getFormatter('non existing');
|
|
}
|
|
|
|
/**
|
|
* Create an ExportManager where every element may be replaced by a double.
|
|
*
|
|
* If null is provided for an element, this is replaced by the equivalent
|
|
* from the container; if the user provided is null, this is replaced by the
|
|
* user 'center a_social' from database.
|
|
*/
|
|
protected function createExportManager(
|
|
?LoggerInterface $logger = null,
|
|
?EntityManagerInterface $em = null,
|
|
?AuthorizationCheckerInterface $authorizationChecker = null,
|
|
?AuthorizationHelper $authorizationHelper = null,
|
|
?UserInterface $user = null,
|
|
array $exports = [],
|
|
array $aggregators = [],
|
|
array $filters = [],
|
|
): ExportManager {
|
|
$localUser = $user ?? self::$container->get(
|
|
UserRepositoryInterface::class
|
|
)
|
|
->findOneBy(['username' => 'center a_social']);
|
|
$token = new UsernamePasswordToken($localUser, 'password', 'provider');
|
|
$tokenStorage = new TokenStorage();
|
|
$tokenStorage->setToken($token);
|
|
|
|
return new ExportManager(
|
|
$logger ?? self::$container->get('logger'),
|
|
$authorizationChecker ?? self::$container->get('security.authorization_checker'),
|
|
$authorizationHelper ?? self::$container->get('chill.main.security.authorization.helper'),
|
|
$tokenStorage,
|
|
$exports,
|
|
$aggregators,
|
|
$filters
|
|
);
|
|
}
|
|
}
|
|
|
|
class DummyFilterWithApplying implements FilterInterface
|
|
{
|
|
public function __construct(
|
|
private ?string $role,
|
|
private string $applyOn
|
|
) {
|
|
}
|
|
|
|
public function getTitle()
|
|
{
|
|
return 'dummy';
|
|
}
|
|
public function buildForm(FormBuilderInterface $builder)
|
|
{
|
|
}
|
|
public function getFormDefaultData(): array
|
|
{
|
|
return [];
|
|
}
|
|
public function describeAction($data, $format = 'string')
|
|
{
|
|
return ['dummy filter', []];
|
|
}
|
|
public function addRole(): ?string
|
|
{
|
|
return $this->role;
|
|
}
|
|
public function alterQuery(QueryBuilder $qb, $data)
|
|
{
|
|
}
|
|
public function applyOn()
|
|
{
|
|
return $this->applyOn;
|
|
}
|
|
}
|
|
|
|
class DummyExport implements ExportInterface
|
|
{
|
|
public function __construct(
|
|
private string $role,
|
|
/**
|
|
* @var array<string>
|
|
*/
|
|
private array $supportedModifiers,
|
|
) {
|
|
}
|
|
|
|
public function getTitle()
|
|
{
|
|
return 'dummy';
|
|
}
|
|
|
|
public function buildForm(FormBuilderInterface $builder)
|
|
{
|
|
}
|
|
|
|
public function getFormDefaultData(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function getAllowedFormattersTypes()
|
|
{
|
|
return ['test'];
|
|
}
|
|
|
|
public function getDescription()
|
|
{
|
|
return 'dummy export';
|
|
}
|
|
|
|
public function getLabels($key, array $values, mixed $data)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function getQueryKeys($data)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function getResult($query, $data)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function getType()
|
|
{
|
|
return 'dummy';
|
|
}
|
|
|
|
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function requiredRole(): string
|
|
{
|
|
return $this->role;
|
|
}
|
|
|
|
public function supportsModifiers()
|
|
{
|
|
return $this->supportedModifiers;
|
|
}
|
|
}
|