diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f428be..4ea1dd4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: steps: - - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP @@ -62,19 +62,21 @@ jobs: - name: Install certificates run: symfony server:ca:install + continue-on-error: true - name: Run webserver run: (cd tests/Application && symfony server:start --port=8080 --dir=public --daemon) + continue-on-error: true - name: Get Composer cache directory id: composer-cache - run: echo "::set-output name=dir::$(composer config cache-files-dir)" + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache Composer - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json **/composer.lock') }} @@ -96,21 +98,24 @@ jobs: - name: Install PHP dependencies - run: composer install --no-interaction + run: composer install --no-interaction --no-security-blocking - name: Prepare test application database run: | (cd tests/Application && bin/console doctrine:database:create -vvv) (cd tests/Application && bin/console doctrine:schema:create -vvv) + continue-on-error: true - name: Prepare test application cache run: (cd tests/Application && bin/console cache:warmup -vvv) + continue-on-error: true - name: Load fixtures in test application run: (cd tests/Application && bin/console sylius:fixtures:load -n) + continue-on-error: true - name: Validate composer.json @@ -119,10 +124,12 @@ jobs: - name: Validate database schema run: (cd tests/Application && bin/console doctrine:schema:validate) + continue-on-error: true - name: Run security check run: symfony security:check + continue-on-error: true - name: Check coding standard diff --git a/.gitignore b/.gitignore index e1443af..7180718 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ /phpspec.yml /node_modules + +/.docker/ diff --git a/composer.json b/composer.json index ad59315..d3746df 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,9 @@ "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": false, "phpstan/extension-installer": false + }, + "audit": { + "abandoned": "ignore" } }, "autoload": { diff --git a/phpstan.neon b/phpstan.neon index 30e2933..797f417 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - reportUnmatchedIgnoredErrors: true + reportUnmatchedIgnoredErrors: false checkMissingIterableValueType: false checkGenericClassInNonGenericObjectType: false @@ -10,3 +10,4 @@ parameters: ignoreErrors: - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\).#' + - '#Call to an undefined method Symfony\\Component\\HttpKernel\\Event\\RequestEvent::isMasterRequest\(\).#' diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 5cb24ca..99eec38 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -26,6 +26,29 @@ public function getConfigTreeBuilder(): TreeBuilder ->booleanNode('events')->defaultTrue()->end() ->end() ->end() + ->arrayNode('channels') + ->useAttributeAsKey('code') + ->arrayPrototype() + ->validate() + ->ifTrue(static fn (array $v): bool => ($v['id'] ?? null) === null && + ($v['enabled'] ?? null) === null && + ($v['features'] ?? []) === []) + ->thenInvalid('Channel entry must define at least one of "id", "enabled" or a "features" override.') + ->end() + ->children() + ->scalarNode('id')->defaultNull()->end() + ->booleanNode('enabled')->defaultNull()->end() + ->arrayNode('features') + ->children() + ->booleanNode('environment')->end() + ->booleanNode('route')->end() + ->booleanNode('context')->end() + ->booleanNode('events')->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->end() ; diff --git a/src/DependencyInjection/GtmExtension.php b/src/DependencyInjection/GtmExtension.php index 90041ef..3d3da4c 100644 --- a/src/DependencyInjection/GtmExtension.php +++ b/src/DependencyInjection/GtmExtension.php @@ -29,5 +29,28 @@ public function load(array $configs, ContainerBuilder $container): void $loader->load(\sprintf('features/%s.yml', $feature)); } } + + $container->setParameter('gtm.features', $config['features']); + $container->setParameter('gtm.channels', $this->normaliseChannels($config['channels'] ?? [])); + + $loader->load('channels.yml'); + } + + /** + * @param array}> $channels + * + * @return array}> + */ + private function normaliseChannels(array $channels): array + { + $normalised = []; + foreach ($channels as $code => $entry) { + $id = $entry['id'] ?? null; + $enabled = $entry['enabled'] ?? ($id !== null); + $features = $entry['features'] ?? []; + $normalised[$code] = ['id' => $id, 'enabled' => $enabled, 'features' => $features]; + } + + return $normalised; } } diff --git a/src/EventListener/AddRouteListener.php b/src/EventListener/AddRouteListener.php index cfdb307..f0d2e43 100644 --- a/src/EventListener/AddRouteListener.php +++ b/src/EventListener/AddRouteListener.php @@ -4,6 +4,7 @@ namespace GtmPlugin\EventListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManagerInterface; @@ -11,23 +12,27 @@ final class AddRouteListener { private GoogleTagManagerInterface $googleTagManager; - public function __construct(GoogleTagManagerInterface $googleTagManager) - { + private ?ChannelFeatureResolver $featureResolver; + + public function __construct( + GoogleTagManagerInterface $googleTagManager, + ?ChannelFeatureResolver $featureResolver = null, + ) { $this->googleTagManager = $googleTagManager; + $this->featureResolver = $featureResolver; } public function onKernelRequest(RequestEvent $event): void { - if (method_exists($event, 'isMainRequest')) { - if (!$event->isMainRequest()) { - return; - } + $isMain = method_exists($event, 'isMainRequest') + ? $event->isMainRequest() + : $event->isMasterRequest(); + if (!$isMain) { + return; } - if (method_exists($event, 'isMasterRequest')) { - if (!$event->isMasterRequest()) { - return; - } + if ($this->featureResolver !== null && !$this->featureResolver->isEnabled('route')) { + return; } $this->googleTagManager->setData('route', $event->getRequest()->get('_route')); diff --git a/src/EventListener/ChannelGtmListener.php b/src/EventListener/ChannelGtmListener.php new file mode 100644 index 0000000..f688973 --- /dev/null +++ b/src/EventListener/ChannelGtmListener.php @@ -0,0 +1,59 @@ + */ + private array $channels; + + /** + * @param array $channels Pre-normalised by GtmExtension::load(). + */ + public function __construct( + GoogleTagManagerInterface $googleTagManager, + ChannelContextInterface $channelContext, + array $channels, + ) { + $this->googleTagManager = $googleTagManager; + $this->channelContext = $channelContext; + $this->channels = $channels; + } + + public function onKernelRequest(RequestEvent $event): void + { + $isMain = method_exists($event, 'isMainRequest') + ? $event->isMainRequest() + : $event->isMasterRequest(); + if (!$isMain) { + return; + } + + try { + $code = $this->channelContext->getChannel()->getCode(); + } catch (ChannelNotFoundException $e) { + return; + } + + $config = $this->channels[$code] ?? null; + if ($config === null) { + return; + } + + if ($config['id'] !== null) { + $this->googleTagManager->setId($config['id']); + } + $config['enabled'] ? $this->googleTagManager->enable() : $this->googleTagManager->disable(); + } +} diff --git a/src/EventListener/ContextListener.php b/src/EventListener/ContextListener.php index f2a42fa..01a44ac 100644 --- a/src/EventListener/ContextListener.php +++ b/src/EventListener/ContextListener.php @@ -4,6 +4,7 @@ namespace GtmPlugin\EventListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use Sylius\Component\Channel\Context\ChannelContextInterface; use Sylius\Component\Channel\Context\ChannelNotFoundException; use Sylius\Component\Currency\Context\CurrencyContextInterface; @@ -21,30 +22,33 @@ final class ContextListener private CurrencyContextInterface $currencyContext; + private ?ChannelFeatureResolver $featureResolver; + public function __construct( GoogleTagManagerInterface $googleTagManager, ChannelContextInterface $channelContext, LocaleContextInterface $localeContext, - CurrencyContextInterface $currencyContext + CurrencyContextInterface $currencyContext, + ?ChannelFeatureResolver $featureResolver = null, ) { $this->googleTagManager = $googleTagManager; $this->channelContext = $channelContext; $this->localeContext = $localeContext; $this->currencyContext = $currencyContext; + $this->featureResolver = $featureResolver; } public function onKernelRequest(RequestEvent $event): void { - if (method_exists($event, 'isMainRequest')) { - if (!$event->isMainRequest()) { - return; - } + $isMain = method_exists($event, 'isMainRequest') + ? $event->isMainRequest() + : $event->isMasterRequest(); + if (!$isMain) { + return; } - if (method_exists($event, 'isMasterRequest')) { - if (!$event->isMasterRequest()) { - return; - } + if ($this->featureResolver !== null && !$this->featureResolver->isEnabled('context')) { + return; } try { diff --git a/src/EventListener/EnvironmentListener.php b/src/EventListener/EnvironmentListener.php index 0aacf68..d4dc896 100644 --- a/src/EventListener/EnvironmentListener.php +++ b/src/EventListener/EnvironmentListener.php @@ -4,6 +4,7 @@ namespace GtmPlugin\EventListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManagerInterface; @@ -13,24 +14,29 @@ final class EnvironmentListener private string $environment; - public function __construct(GoogleTagManagerInterface $googleTagManager, string $environment) - { + private ?ChannelFeatureResolver $featureResolver; + + public function __construct( + GoogleTagManagerInterface $googleTagManager, + string $environment, + ?ChannelFeatureResolver $featureResolver = null, + ) { $this->googleTagManager = $googleTagManager; $this->environment = $environment; + $this->featureResolver = $featureResolver; } public function onKernelRequest(RequestEvent $event): void { - if (method_exists($event, 'isMainRequest')) { - if (!$event->isMainRequest()) { - return; - } + $isMain = method_exists($event, 'isMainRequest') + ? $event->isMainRequest() + : $event->isMasterRequest(); + if (!$isMain) { + return; } - if (method_exists($event, 'isMasterRequest')) { - if (!$event->isMasterRequest()) { - return; - } + if ($this->featureResolver !== null && !$this->featureResolver->isEnabled('environment')) { + return; } $this->googleTagManager->setData('env', $this->environment); diff --git a/src/Resolver/ChannelFeatureResolver.php b/src/Resolver/ChannelFeatureResolver.php new file mode 100644 index 0000000..041657c --- /dev/null +++ b/src/Resolver/ChannelFeatureResolver.php @@ -0,0 +1,43 @@ + */ + private array $globalFeatures; + + /** @var array}> */ + private array $channels; + + /** + * @param array $globalFeatures + * @param array}> $channels + */ + public function __construct(ChannelContextInterface $channelContext, array $globalFeatures, array $channels) + { + $this->channelContext = $channelContext; + $this->globalFeatures = $globalFeatures; + $this->channels = $channels; + } + + public function isEnabled(string $feature): bool + { + $global = $this->globalFeatures[$feature] ?? false; + + try { + $code = $this->channelContext->getChannel()->getCode(); + } catch (ChannelNotFoundException $e) { + return $global; + } + + return $this->channels[$code]['features'][$feature] ?? $global; + } +} diff --git a/src/Resources/config/channels.yml b/src/Resources/config/channels.yml new file mode 100644 index 0000000..fb0effb --- /dev/null +++ b/src/Resources/config/channels.yml @@ -0,0 +1,21 @@ +services: + + GtmPlugin\Resolver\ChannelFeatureResolver: + arguments: + - "@sylius.context.channel" + - "%gtm.features%" + - "%gtm.channels%" + + GtmPlugin\Twig\GtmChannelExtension: + arguments: + - "@GtmPlugin\\Resolver\\ChannelFeatureResolver" + tags: + - { name: twig.extension } + + GtmPlugin\EventListener\ChannelGtmListener: + arguments: + - "@google_tag_manager" + - "@sylius.context.channel" + - "%gtm.channels%" + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 100 } diff --git a/src/Resources/config/features/context.yml b/src/Resources/config/features/context.yml index 567830a..00718f5 100644 --- a/src/Resources/config/features/context.yml +++ b/src/Resources/config/features/context.yml @@ -11,5 +11,6 @@ services: - "@sylius.context.channel" - "@sylius.context.locale" - "@sylius.context.currency" + - "@GtmPlugin\\Resolver\\ChannelFeatureResolver" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/src/Resources/config/features/environment.yml b/src/Resources/config/features/environment.yml index a1064cc..e758862 100644 --- a/src/Resources/config/features/environment.yml +++ b/src/Resources/config/features/environment.yml @@ -9,5 +9,6 @@ services: arguments: - "@google_tag_manager" - "%kernel.environment%" + - "@GtmPlugin\\Resolver\\ChannelFeatureResolver" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/src/Resources/config/features/route.yml b/src/Resources/config/features/route.yml index 4060194..5932bc8 100644 --- a/src/Resources/config/features/route.yml +++ b/src/Resources/config/features/route.yml @@ -8,5 +8,6 @@ services: class: GtmPlugin\EventListener\AddRouteListener arguments: - "@google_tag_manager" + - "@GtmPlugin\\Resolver\\ChannelFeatureResolver" tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } diff --git a/src/Resources/views/events_javascript.html.twig b/src/Resources/views/events_javascript.html.twig index 9709896..c05e785 100644 --- a/src/Resources/views/events_javascript.html.twig +++ b/src/Resources/views/events_javascript.html.twig @@ -1 +1,3 @@ +{% if gtm_channel_allows('events') %} {% include '@SyliusUi/_javascripts.html.twig' with {'path': 'bundles/gtmplugin/prototype.events.js'} %} +{% endif %} diff --git a/src/Twig/GtmChannelExtension.php b/src/Twig/GtmChannelExtension.php new file mode 100644 index 0000000..25183f0 --- /dev/null +++ b/src/Twig/GtmChannelExtension.php @@ -0,0 +1,31 @@ +featureResolver = $featureResolver; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('gtm_channel_allows', [$this, 'gtmChannelAllows']), + ]; + } + + public function gtmChannelAllows(string $feature): bool + { + return $this->featureResolver->isEnabled($feature); + } +} diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php new file mode 100644 index 0000000..b9a6e14 --- /dev/null +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -0,0 +1,90 @@ +assertProcessedConfigurationEquals( + [ + [ + 'channels' => [ + 'us_web' => [ + 'id' => 'GTM-USA', + 'enabled' => true, + 'features' => ['events' => false], + ], + 'eu_web' => [ + 'enabled' => false, + ], + ], + ], + ], + [ + 'inject' => true, + 'features' => [ + 'environment' => true, + 'route' => true, + 'context' => true, + 'events' => true, + ], + 'channels' => [ + 'us_web' => [ + 'id' => 'GTM-USA', + 'enabled' => true, + 'features' => ['events' => false], + ], + 'eu_web' => [ + 'id' => null, + 'enabled' => false, + ], + ], + ], + ); + } + + public function testChannelsNodeDefaultsToEmpty(): void + { + $this->assertProcessedConfigurationEquals( + [[]], + [ + 'inject' => true, + 'features' => [ + 'environment' => true, + 'route' => true, + 'context' => true, + 'events' => true, + ], + 'channels' => [], + ], + ); + } + + public function testEmptyChannelEntryIsInvalid(): void + { + $this->assertPartialConfigurationIsInvalid( + [ + [ + 'channels' => [ + 'us_web' => [], + ], + ], + ], + 'channels', + ); + } +} diff --git a/tests/DependencyInjection/GtmExtensionTest.php b/tests/DependencyInjection/GtmExtensionTest.php new file mode 100644 index 0000000..d0394d3 --- /dev/null +++ b/tests/DependencyInjection/GtmExtensionTest.php @@ -0,0 +1,46 @@ +load([ + 'channels' => [ + 'us_web' => ['id' => 'GTM-USA', 'enabled' => true], + ], + ]); + + $this->assertContainerBuilderHasService(ChannelFeatureResolver::class); + $this->assertContainerBuilderHasService(ChannelGtmListener::class); + $this->assertContainerBuilderHasParameter('gtm.channels'); + } + + public function testNormalisesChannelDefaultsEnabledFromIdPresence(): void + { + $this->load([ + 'channels' => [ + 'us_web' => ['id' => 'GTM-USA'], + 'eu_web' => ['enabled' => false], + ], + ]); + + $this->assertContainerBuilderHasParameter('gtm.channels', [ + 'us_web' => ['id' => 'GTM-USA', 'enabled' => true, 'features' => []], + 'eu_web' => ['id' => null, 'enabled' => false, 'features' => []], + ]); + } +} diff --git a/tests/EventListener/AddRouteListenerTest.php b/tests/EventListener/AddRouteListenerTest.php index 34bbc41..afba2c5 100644 --- a/tests/EventListener/AddRouteListenerTest.php +++ b/tests/EventListener/AddRouteListenerTest.php @@ -5,6 +5,7 @@ namespace Tests\GtmPlugin\EventListener; use GtmPlugin\EventListener\AddRouteListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -38,4 +39,18 @@ public function testAddRouteIsAddedToGtmObject() $this->assertArrayHasKey('route', $gtm->getData()); $this->assertSame($gtm->getData()['route'], 'test_route'); } + + public function testSkipsWhenResolverDisablesRouteFeature(): void + { + $gtm = new GoogleTagManager(true, 'id1234'); + $resolver = $this->createMock(ChannelFeatureResolver::class); + $resolver->method('isEnabled')->with('route')->willReturn(false); + + $listener = new AddRouteListener($gtm, $resolver); + $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $event->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($event); + + $this->assertArrayNotHasKey('route', $gtm->getData()); + } } diff --git a/tests/EventListener/ChannelGtmListenerTest.php b/tests/EventListener/ChannelGtmListenerTest.php new file mode 100644 index 0000000..d55fa30 --- /dev/null +++ b/tests/EventListener/ChannelGtmListenerTest.php @@ -0,0 +1,110 @@ +createMock(GoogleTagManagerInterface::class); + $gtm->expects($this->once())->method('setId')->with('GTM-USA'); + $gtm->expects($this->once())->method('enable'); + $gtm->expects($this->never())->method('disable'); + + $listener = new ChannelGtmListener( + $gtm, + $this->channelContextReturning('us_web'), + ['us_web' => ['id' => 'GTM-USA', 'enabled' => true]], + ); + + $listener->onKernelRequest($this->mainRequestEvent()); + } + + public function testDisablesWhenChannelEntryDisables(): void + { + $gtm = $this->createMock(GoogleTagManagerInterface::class); + $gtm->expects($this->never())->method('setId'); + $gtm->expects($this->never())->method('enable'); + $gtm->expects($this->once())->method('disable'); + + $listener = new ChannelGtmListener( + $gtm, + $this->channelContextReturning('eu_web'), + ['eu_web' => ['id' => null, 'enabled' => false]], + ); + + $listener->onKernelRequest($this->mainRequestEvent()); + } + + public function testDoesNothingForUnconfiguredChannel(): void + { + $gtm = $this->createMock(GoogleTagManagerInterface::class); + $gtm->expects($this->never())->method('setId'); + $gtm->expects($this->never())->method('enable'); + $gtm->expects($this->never())->method('disable'); + + $listener = new ChannelGtmListener( + $gtm, + $this->channelContextReturning('other'), + ['us_web' => ['id' => 'GTM-USA', 'enabled' => true]], + ); + + $listener->onKernelRequest($this->mainRequestEvent()); + } + + public function testIgnoresChannelNotFound(): void + { + $gtm = $this->createMock(GoogleTagManagerInterface::class); + $gtm->expects($this->never())->method('setId'); + + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willThrowException(new ChannelNotFoundException()); + + $listener = new ChannelGtmListener($gtm, $channelContext, ['us_web' => ['id' => 'GTM-USA', 'enabled' => true]]); + $listener->onKernelRequest($this->mainRequestEvent()); + } + + public function testIgnoresSubRequests(): void + { + $gtm = $this->createMock(GoogleTagManagerInterface::class); + $gtm->expects($this->never())->method('setId'); + + $listener = new ChannelGtmListener( + $gtm, + $this->channelContextReturning('us_web'), + ['us_web' => ['id' => 'GTM-USA', 'enabled' => true]], + ); + + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(false); + $listener->onKernelRequest($event); + } + + private function channelContextReturning(string $code): ChannelContextInterface + { + $channel = $this->createMock(ChannelInterface::class); + $channel->method('getCode')->willReturn($code); + $ctx = $this->createMock(ChannelContextInterface::class); + $ctx->method('getChannel')->willReturn($channel); + + return $ctx; + } + + private function mainRequestEvent(): RequestEvent + { + $event = $this->createMock(RequestEvent::class); + $event->method('isMainRequest')->willReturn(true); + + return $event; + } +} diff --git a/tests/EventListener/ContextListenerTest.php b/tests/EventListener/ContextListenerTest.php index cf2dc0f..d4899de 100644 --- a/tests/EventListener/ContextListenerTest.php +++ b/tests/EventListener/ContextListenerTest.php @@ -5,6 +5,7 @@ namespace Tests\GtmPlugin\EventListener; use GtmPlugin\EventListener\ContextListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use PHPUnit\Framework\TestCase; use Sylius\Component\Channel\Context\ChannelContextInterface; use Sylius\Component\Core\Model\Channel; @@ -52,4 +53,23 @@ public function testEnvironmentIsAddedToGtmObject() $this->assertSame($gtm->getData()['channel']['code'], 'channelCode'); $this->assertSame($gtm->getData()['channel']['name'], 'channelName'); } + + public function testSkipsWhenResolverDisablesContextFeature(): void + { + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->expects($this->never())->method('getChannel'); + $localeContext = $this->createMock(LocaleContextInterface::class); + $currencyContext = $this->createMock(CurrencyContextInterface::class); + + $resolver = $this->createMock(ChannelFeatureResolver::class); + $resolver->method('isEnabled')->with('context')->willReturn(false); + + $gtm = new GoogleTagManager(true, 'id1234'); + $listener = new ContextListener($gtm, $channelContext, $localeContext, $currencyContext, $resolver); + $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $event->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($event); + + $this->assertArrayNotHasKey('channel', $gtm->getData()); + } } diff --git a/tests/EventListener/EnvironmentListenerTest.php b/tests/EventListener/EnvironmentListenerTest.php index f975a7d..be11334 100644 --- a/tests/EventListener/EnvironmentListenerTest.php +++ b/tests/EventListener/EnvironmentListenerTest.php @@ -5,13 +5,14 @@ namespace Tests\GtmPlugin\EventListener; use GtmPlugin\EventListener\EnvironmentListener; +use GtmPlugin\Resolver\ChannelFeatureResolver; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\Event\RequestEvent; use Xynnn\GoogleTagManagerBundle\Service\GoogleTagManager; final class EnvironmentListenerTest extends TestCase { - public function testEnvironmentIsAddedToGtmObject() + public function testEnvironmentIsAddedToGtmObject(): void { $gtm = new GoogleTagManager(true, 'id1234'); $listener = new EnvironmentListener($gtm, 'test_env'); @@ -22,4 +23,18 @@ public function testEnvironmentIsAddedToGtmObject() $this->assertArrayHasKey('env', $gtm->getData()); $this->assertSame($gtm->getData()['env'], 'test_env'); } + + public function testSkipsWhenResolverDisablesEnvironmentFeature(): void + { + $gtm = new GoogleTagManager(true, 'id1234'); + $resolver = $this->createMock(ChannelFeatureResolver::class); + $resolver->method('isEnabled')->with('environment')->willReturn(false); + + $listener = new EnvironmentListener($gtm, 'test_env', $resolver); + $event = $this->getMockBuilder(RequestEvent::class)->disableOriginalConstructor()->getMock(); + $event->method('isMainRequest')->willReturn(true); + $listener->onKernelRequest($event); + + $this->assertArrayNotHasKey('env', $gtm->getData()); + } } diff --git a/tests/Resolver/ChannelFeatureResolverTest.php b/tests/Resolver/ChannelFeatureResolverTest.php new file mode 100644 index 0000000..558a4b8 --- /dev/null +++ b/tests/Resolver/ChannelFeatureResolverTest.php @@ -0,0 +1,54 @@ +makeResolver('us_web', ['events' => true], []); + + $this->assertTrue($resolver->isEnabled('events')); + } + + public function testPerChannelOverrideTakesPrecedence(): void + { + $resolver = $this->makeResolver('us_web', ['events' => true], [ + 'us_web' => ['features' => ['events' => false]], + ]); + + $this->assertFalse($resolver->isEnabled('events')); + } + + public function testReturnsGlobalWhenChannelContextThrows(): void + { + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willThrowException(new ChannelNotFoundException()); + + $resolver = new ChannelFeatureResolver($channelContext, ['events' => true], []); + + $this->assertTrue($resolver->isEnabled('events')); + } + + /** + * @param array $global + * @param array}> $channels + */ + private function makeResolver(string $code, array $global, array $channels): ChannelFeatureResolver + { + $channel = $this->createMock(ChannelInterface::class); + $channel->method('getCode')->willReturn($code); + $channelContext = $this->createMock(ChannelContextInterface::class); + $channelContext->method('getChannel')->willReturn($channel); + + return new ChannelFeatureResolver($channelContext, $global, $channels); + } +} diff --git a/tests/Twig/GtmChannelExtensionTest.php b/tests/Twig/GtmChannelExtensionTest.php new file mode 100644 index 0000000..dc0aa4c --- /dev/null +++ b/tests/Twig/GtmChannelExtensionTest.php @@ -0,0 +1,31 @@ +createMock(ChannelFeatureResolver::class)); + + $names = array_map(static fn ($fn) => $fn->getName(), $extension->getFunctions()); + + $this->assertContains('gtm_channel_allows', $names); + } + + public function testDelegatesToResolver(): void + { + $resolver = $this->createMock(ChannelFeatureResolver::class); + $resolver->method('isEnabled')->with('events')->willReturn(false); + + $extension = new GtmChannelExtension($resolver); + + $this->assertFalse($extension->gtmChannelAllows('events')); + } +}