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 */ 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; } }