diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a32a2b8..868c5d3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,13 +24,7 @@ concurrency: jobs: test: - name: > - ${{ format('PHP {0} - Sf {1} - deps {2} - stab. {3}', - matrix.php-version || 'Ø', - matrix.symfony-require || 'Ø', - matrix.dependencies || 'Ø', - matrix.stability || 'Ø' - ) }} + name: ${{ matrix.job-name }} runs-on: "ubuntu-latest" env: SYMFONY_REQUIRE: ${{matrix.symfony-require}} @@ -39,38 +33,30 @@ jobs: strategy: fail-fast: false matrix: - php-version: - - "8.1" - - "8.2" - - "8.3" - - "8.4" - - "8.5" - dependencies: - - "highest" - stability: - - "stable" - symfony-require: - - "" include: - # Test with the lowest set of dependencies - - php-version: "8.1" - dependencies: "lowest" - stability: "stable" - - # Test 6.4 LTS - - php-version: "8.5" + # Symfony 6.4 LTS on minimum supported PHP + - php-version: "8.3" symfony-require: "6.4.*" - dependencies: "highest" + job-name: "PHP 8.3 - Symfony 6.4 LTS" - # Test 7.4 LTS - - php-version: "8.5" + # Symfony 7.4 LTS on minimum supported PHP + - php-version: "8.3" symfony-require: "7.4.*" - dependencies: "highest" + job-name: "PHP 8.3 - Symfony 7.4 LTS" + + # Symfony 8 on supported PHP versions + - php-version: "8.4" + symfony-require: "8.0.*" + job-name: "PHP 8.4 - Symfony 8.0" - # Bleeding edge - php-version: "8.5" - dependencies: "highest" - stability: "dev" + symfony-require: "8.0.*" + job-name: "PHP 8.5 - Symfony 8.0" + + # Forward-compat lane for upcoming Symfony 8.x releases + - php-version: "8.5" + symfony-require: "8.*" + job-name: "PHP 8.5 - Symfony 8.x" steps: - name: "Checkout" @@ -85,15 +71,10 @@ jobs: ini-file: "development" tools: flex - - name: "Enforce using stable dependencies" - run: "composer config minimum-stability stable" - if: "${{ matrix.stability == 'stable' }}" - - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v3" with: - dependency-versions: "${{ matrix.dependencies }}" composer-options: "--prefer-dist" - - name: Run test suite on PHP ${{ matrix.php }} and Symfony ${{ matrix.symfony }} + - name: Run test suite run: ./vendor/bin/phpunit diff --git a/CHANGELOG.md b/CHANGELOG.md index 6305bb8..2d36e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,67 @@ ## [Unreleased] +* Dropped support for PHP versions older than 8.3 to match `kreait/firebase-php` 8.x +* Updated the bundle to support `kreait/firebase-php` 8.x +* Removed Dynamic Links support because it was removed from the Firebase Admin SDK +* Removed HTTP request logger configuration options because the underlying SDK hooks were removed +* Added support for per-project `http_client_options` service configuration +* Fixed project option leakage by isolating project factory instances per configured Firebase project +* Removed the container service `Kreait\Firebase\Factory` + +### Migration: HTTP request logging + +If you previously used `http_request_logger` or `http_request_debug_logger`, +migrate to a `Kreait\Firebase\Http\HttpClientOptions` service and wire your logging through Guzzle middleware. + +```yaml +# config/services.yaml +services: + App\Firebase\HttpClientOptionsFactory: ~ + + app.firebase.http_client_options: + class: Kreait\Firebase\Http\HttpClientOptions + factory: ['@App\Firebase\HttpClientOptionsFactory', 'create'] +``` + +```php +withGuzzleMiddleware($this->loggingMiddleware); + } +} +``` + +```yaml +# config/packages/firebase.yaml +kreait_firebase: + projects: + my_project: + http_client_options: 'app.firebase.http_client_options' +``` + +### Migration: custom DI integrations + +If you customized bundle internals with compiler passes or direct container lookups: + +* `Kreait\Firebase\Factory` is no longer registered as a container service. +* Project factories are now per project (`kreait_firebase..project_factory`) instead of using only `Kreait\Firebase\Symfony\Bundle\DependencyInjection\Factory\ProjectFactory`. + +If your app modified the `ProjectFactory` definition directly, update your code to target the per-project service id(s). + ## [5.7.0] * Added support for PHP 8.5 and Symfony 8 diff --git a/Makefile b/Makefile deleted file mode 100644 index b456fd6..0000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.DEFAULT_GOAL:= help -.PHONY: tests coverage view-coverage help - -tests: vendor ## Executes the test suite - vendor/bin/phpunit - -coverage: vendor ## Executes the test suite and generates code coverage reports - php -dxdebug.mode=coverage vendor/bin/phpunit -v --coverage-html=build/coverage - -view-coverage: ## Shows the code coverage report - open build/coverage/index.html - -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index b918edb..7c6a7c3 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ The following services will be available for your project: * `kreait_firebase.my_project.messaging` * `kreait_firebase.my_project.remote_config` * `kreait_firebase.my_project.storage` -* `kreait_firebase.my_project.dynamic_links` * `kreait_firebase.other_project.*` The following classes will be available for dependency injection if you have configured only one project: @@ -90,7 +89,6 @@ The following classes will be available for dependency injection if you have con * `Kreait\Firebase\Contract\Messaging` * `Kreait\Firebase\Contract\RemoteConfig` * `Kreait\Firebase\Contract\Storage` -* `Kreait\Firebase\Contract\DynamicLinks` To make it easier to use classes via dependency injection in the constructor of a class when multiple projects exist, you can do this in the constructor: @@ -101,7 +99,6 @@ To make it easier to use classes via dependency injection in the constructor of * `Kreait\Firebase\Contract\Messaging $myProjectMessaging` * `Kreait\Firebase\Contract\RemoteConfig $myProjectRemoteConfig` * `Kreait\Firebase\Contract\Storage $myProjectStorage` -* `Kreait\Firebase\Contract\DynamicLinks $myProjectDynamicLinks` ### Full @@ -133,16 +130,13 @@ kreait_firebase: database_uri: 'https://my_project.firebaseio.com' # Optional: Make the client tenant aware tenant_id: 'tenant-id' - # Optional: Default domain for Dynamic Links - default_dynamic_links_domain: 'https://my_project.page.link' # Optional: Used to cache Google's public keys. verifier_cache: null # Example: cache.app # Optional: Used to cache the authentication tokens for connecting to the Firebase servers. auth_token_cache: null # Example: cache.app - # If set, logs simple HTTP request and response statuses - http_request_logger: null # Example: monolog.logger.firebase - # If set, logs detailed HTTP request and response statuses - http_request_debug_logger: null # Example: monolog.logger.firebase_debug + # Optional: Service id of Kreait\Firebase\Http\HttpClientOptions + # used to configure the SDK's HTTP client. + http_client_options: null # Example: app.firebase.http_client_options ``` ## Documentation diff --git a/composer.json b/composer.json index 0013eef..440f07c 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ } ], "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "kreait/firebase-php": "^7.24", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0", + "kreait/firebase-php": "^8.1", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "symfony/cache": "^6.4 || ^7.4 || ^8.0", "symfony/config": "^6.4 || ^7.4 || ^8.0", @@ -21,7 +21,7 @@ "symfony/http-kernel": "^6.4 || ^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^10.5.58" + "phpunit/phpunit": "^12.5.11" }, "autoload": { "psr-4": { @@ -45,6 +45,11 @@ "scripts": { "test": [ "vendor/bin/phpunit" + ], + "test:coverage": [ + "Composer\\Config::disableProcessTimeout", + "mkdir -p build", + "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=build/coverage" ] } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b5562b8..f6abbcb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,6 +2,8 @@ @@ -11,9 +13,11 @@ - + src + + diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 50a390a..48a01dc 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -55,10 +55,6 @@ public function getConfigTreeBuilder(): TreeBuilder ->defaultNull() ->info('Make the client tenant aware') ->end() - ->scalarNode('default_dynamic_links_domain') - ->example('https://my-project.page.link') - ->info('The default domain for dynamic links') - ->end() ->scalarNode('verifier_cache') ->defaultNull() ->example('cache.app') @@ -69,15 +65,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->example('cache.app') ->info('Used to cache the authentication tokens for connecting to the Firebase servers.') ->end() - ->scalarNode('http_request_logger') - ->defaultNull() - ->example('monolog.logger.firebase') - ->info('If set, logs simple HTTP request and response statuses') - ->end() - ->scalarNode('http_request_debug_logger') + ->scalarNode('http_client_options') ->defaultNull() - ->example('monolog.logger.firebase_debug') - ->info('If set, logs detailed HTTP request and response statuses') + ->example('app.firebase.http_client_options') + ->info('Service id of a Kreait\\Firebase\\Http\\HttpClientOptions instance to configure the SDK HTTP client.') ->end() ->end() ->end() diff --git a/src/DependencyInjection/Factory/ProjectFactory.php b/src/DependencyInjection/Factory/ProjectFactory.php index b665eb7..ba1b904 100644 --- a/src/DependencyInjection/Factory/ProjectFactory.php +++ b/src/DependencyInjection/Factory/ProjectFactory.php @@ -6,8 +6,8 @@ use Kreait\Firebase; use Kreait\Firebase\Factory; +use Kreait\Firebase\Http\HttpClientOptions; use Psr\Cache\CacheItemPoolInterface; -use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Adapter\Psr16Adapter; @@ -15,8 +15,7 @@ class ProjectFactory { private ?CacheItemPoolInterface $verifierCache = null; private ?CacheItemPoolInterface $authTokenCache = null; - private ?LoggerInterface $httpRequestLogger = null; - private ?LoggerInterface $httpRequestDebugLogger = null; + private ?HttpClientOptions $httpClientOptions = null; /** * @param CacheInterface|CacheItemPoolInterface $verifierCache @@ -42,14 +41,9 @@ public function setAuthTokenCache($authTokenCache = null): void $this->authTokenCache = $authTokenCache; } - public function setHttpRequestLogger(?LoggerInterface $logger = null): void + public function setHttpClientOptions(?HttpClientOptions $httpClientOptions = null): void { - $this->httpRequestLogger = $logger; - } - - public function setHttpRequestDebugLogger(?LoggerInterface $logger = null): void - { - $this->httpRequestDebugLogger = $logger; + $this->httpClientOptions = $httpClientOptions; } public function createAuth(array $config = []): Firebase\Contract\Auth @@ -85,12 +79,8 @@ public function createFactory(array $config = []): Factory $factory = $factory->withAuthTokenCache($this->authTokenCache); } - if ($this->httpRequestLogger) { - $factory = $factory->withHttpLogger($this->httpRequestLogger); - } - - if ($this->httpRequestDebugLogger) { - $factory = $factory->withHttpDebugLogger($this->httpRequestDebugLogger); + if ($this->httpClientOptions) { + $factory = $factory->withHttpClientOptions($this->httpClientOptions); } return $factory; @@ -101,6 +91,9 @@ public function createDatabase(array $config = []): Firebase\Contract\Database return $this->createFactory($config)->createDatabase(); } + /** + * @codeCoverageIgnore Firestore needs optional runtime dependencies (incl. gRPC), which are not part of the default test setup. + */ public function createFirestore(array $config = []): Firebase\Contract\Firestore { return $this->createFactory($config)->createFirestore(); @@ -121,13 +114,6 @@ public function createStorage(array $config = []): Firebase\Contract\Storage return $this->createFactory($config)->createStorage(); } - public function createDynamicLinksService(array $config = []): Firebase\Contract\DynamicLinks - { - $defaultDynamicLinksDomain = $config['default_dynamic_links_domain'] ?? null; - - return $this->createFactory($config)->createDynamicLinksService($defaultDynamicLinksDomain); - } - public function createAppCheck(array $config = []): Firebase\Contract\AppCheck { return $this->createFactory($config)->createAppCheck(); diff --git a/src/DependencyInjection/FirebaseExtension.php b/src/DependencyInjection/FirebaseExtension.php index 1df645f..9890e6c 100644 --- a/src/DependencyInjection/FirebaseExtension.php +++ b/src/DependencyInjection/FirebaseExtension.php @@ -7,10 +7,8 @@ use Kreait\Firebase; use Kreait\Firebase\Symfony\Bundle\DependencyInjection\Factory\ProjectFactory; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; -use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; class FirebaseExtension extends Extension @@ -20,8 +18,7 @@ public function load(array $configs, ContainerBuilder $container): void $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); - $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('firebase.php'); + $container->register(ProjectFactory::class, ProjectFactory::class)->setPublic(false); $projectConfigurations = $config['projects'] ?? []; $projectConfigurationsCount = \count($projectConfigurations); @@ -39,14 +36,15 @@ public function load(array $configs, ContainerBuilder $container): void private function processProjectConfiguration(string $name, array $config, ContainerBuilder $container): void { - $this->registerService($name, 'database', $config, Firebase\Contract\Database::class, $container, 'createDatabase'); - $this->registerService($name, 'auth', $config, Firebase\Contract\Auth::class, $container, 'createAuth'); - $this->registerService($name, 'storage', $config, Firebase\Contract\Storage::class, $container, 'createStorage'); - $this->registerService($name, 'remote_config', $config, Firebase\Contract\RemoteConfig::class, $container, 'createRemoteConfig'); - $this->registerService($name, 'messaging', $config, Firebase\Contract\Messaging::class, $container, 'createMessaging'); - $this->registerService($name, 'firestore', $config, Firebase\Contract\Firestore::class, $container, 'createFirestore'); - $this->registerService($name, 'dynamic_links', $config, Firebase\Contract\DynamicLinks::class, $container, 'createDynamicLinksService'); - $this->registerService($name, 'app_check', $config, Firebase\Contract\AppCheck::class, $container, 'createAppCheck'); + $projectFactory = $this->registerProjectFactory($name, $config, $container); + + $this->registerService($name, 'database', $config, Firebase\Contract\Database::class, $projectFactory, $container, 'createDatabase'); + $this->registerService($name, 'auth', $config, Firebase\Contract\Auth::class, $projectFactory, $container, 'createAuth'); + $this->registerService($name, 'storage', $config, Firebase\Contract\Storage::class, $projectFactory, $container, 'createStorage'); + $this->registerService($name, 'remote_config', $config, Firebase\Contract\RemoteConfig::class, $projectFactory, $container, 'createRemoteConfig'); + $this->registerService($name, 'messaging', $config, Firebase\Contract\Messaging::class, $projectFactory, $container, 'createMessaging'); + $this->registerService($name, 'firestore', $config, Firebase\Contract\Firestore::class, $projectFactory, $container, 'createFirestore'); + $this->registerService($name, 'app_check', $config, Firebase\Contract\AppCheck::class, $projectFactory, $container, 'createAppCheck'); } public function getAlias(): string @@ -59,31 +57,35 @@ public function getConfiguration(array $config, ContainerBuilder $container): Co return new Configuration($this->getAlias()); } - private function registerService(string $name, string $postfix, array $config, string $contract, ContainerBuilder $container, string $method = 'create'): void + private function registerProjectFactory(string $name, array $config, ContainerBuilder $container): Reference { - $projectServiceId = \sprintf('%s.%s.%s', $this->getAlias(), $name, $postfix); - $isPublic = $config['public']; - - $factory = $container->getDefinition(ProjectFactory::class); + $projectFactoryServiceId = \sprintf('%s.%s.project_factory', $this->getAlias(), $name); + $projectFactory = clone $container->getDefinition(ProjectFactory::class); if ($config['verifier_cache'] ?? null) { - $factory->addMethodCall('setVerifierCache', [new Reference($config['verifier_cache'])]); + $projectFactory->addMethodCall('setVerifierCache', [new Reference($config['verifier_cache'])]); } if ($config['auth_token_cache'] ?? null) { - $factory->addMethodCall('setAuthTokenCache', [new Reference($config['auth_token_cache'])]); + $projectFactory->addMethodCall('setAuthTokenCache', [new Reference($config['auth_token_cache'])]); } - if ($config['http_request_logger'] ?? null) { - $factory->addMethodCall('setHttpRequestLogger', [new Reference($config['http_request_logger'])]); + if ($config['http_client_options'] ?? null) { + $projectFactory->addMethodCall('setHttpClientOptions', [new Reference($config['http_client_options'])]); } - if ($config['http_request_debug_logger'] ?? null) { - $factory->addMethodCall('setHttpRequestDebugLogger', [new Reference($config['http_request_debug_logger'])]); - } + $container->setDefinition($projectFactoryServiceId, $projectFactory); + + return new Reference($projectFactoryServiceId); + } + + private function registerService(string $name, string $postfix, array $config, string $contract, Reference $projectFactory, ContainerBuilder $container, string $method = 'create'): void + { + $projectServiceId = \sprintf('%s.%s.%s', $this->getAlias(), $name, $postfix); + $isPublic = $config['public']; $container->register($projectServiceId, $contract) - ->setFactory([$factory, $method]) + ->setFactory([$projectFactory, $method]) ->setLazy(true) ->addArgument($config) ->setPublic($isPublic); diff --git a/src/Resources/config/firebase.php b/src/Resources/config/firebase.php deleted file mode 100644 index f85ec6a..0000000 --- a/src/Resources/config/firebase.php +++ /dev/null @@ -1,20 +0,0 @@ -services(); - - // Equivalent of Kreait\Firebase\Factory - // NO need for these parameters anymore — use FQCN directly. - - $services - ->set(Factory::class) - ->public(); // optional: same as XML service id="Kreait\Firebase\Factory" - - $services - ->set(ProjectFactory::class) - ->public(false); // same as public="false" -}; diff --git a/tests/DependencyInjection/Factory/ProjectFactoryTest.php b/tests/DependencyInjection/Factory/ProjectFactoryTest.php index 2cbb7ac..09c13a6 100644 --- a/tests/DependencyInjection/Factory/ProjectFactoryTest.php +++ b/tests/DependencyInjection/Factory/ProjectFactoryTest.php @@ -4,8 +4,9 @@ namespace Kreait\Firebase\Symfony\Bundle\Tests\DependencyInjection\Factory; -use Kreait\Firebase\Factory as FirebaseFactory; +use Kreait\Firebase\Http\HttpClientOptions; use Kreait\Firebase\Symfony\Bundle\DependencyInjection\Factory\ProjectFactory; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemPoolInterface; use Psr\SimpleCache\CacheInterface; @@ -40,84 +41,122 @@ protected function setUp(): void ]; } - public function test_it_can_handle_a_custom_database_uri(): void + #[DoesNotPerformAssertions] + public function testItCanHandleACustomDatabaseUri(): void { $this->factory->createDatabase($this->defaultConfig + ['database_uri' => 'https://domain.tld']); - $this->addToAssertionCount(1); } - public function test_it_can_handle_a_credentials_path(): void + #[DoesNotPerformAssertions] + public function testItCanCreateMessaging(): void + { + $this->factory->createMessaging($this->defaultConfig); + } + + #[DoesNotPerformAssertions] + public function testItCanCreateRemoteConfig(): void + { + $this->factory->createRemoteConfig($this->defaultConfig); + } + + #[DoesNotPerformAssertions] + public function testItCanCreateStorage(): void + { + $this->factory->createStorage($this->defaultConfig); + } + + #[DoesNotPerformAssertions] + public function testItCanCreateAppCheck(): void + { + $this->factory->createAppCheck($this->defaultConfig); + } + + #[DoesNotPerformAssertions] + public function testItCanHandleACredentialsPath(): void { $this->factory->createAuth(['credentials' => __DIR__.'/../../_fixtures/valid_credentials.json']); - $this->addToAssertionCount(1); } - public function test_it_can_handle_a_credentials_string(): void + #[DoesNotPerformAssertions] + public function testItCanHandleACredentialsString(): void { $credentials = \file_get_contents(__DIR__.'/../../_fixtures/valid_credentials.json'); $this->factory->createAuth(['credentials' => $credentials]); - $this->addToAssertionCount(1); } - public function test_it_can_handle_a_credentials_array(): void + #[DoesNotPerformAssertions] + public function testItCanHandleACredentialsArray(): void { $credentials = \json_decode(\file_get_contents(__DIR__.'/../../_fixtures/valid_credentials.json'), true); $this->factory->createAuth(['credentials' => $credentials]); - $this->addToAssertionCount(1); } - public function test_it_can_handle_a_tenant_id(): void + #[DoesNotPerformAssertions] + public function testItCanHandleATenantId(): void { $this->factory->createAuth($this->defaultConfig + ['tenant_id' => 'tenant-id']); - $this->addToAssertionCount(1); } - public function test_it_can_handle_a_project_id(): void + #[DoesNotPerformAssertions] + public function testItCanHandleAProjectId(): void { - $instance = $this->factory->createAuth($this->defaultConfig + ['project_id' => 'project-b']); - $this->addToAssertionCount(1); + $this->factory->createAuth($this->defaultConfig + ['project_id' => 'project-b']); } - public function test_it_accepts_a_PSR16_verifier_cache(): void + #[DoesNotPerformAssertions] + public function testItAcceptsAPSR16VerifierCache(): void { - $cache = $this->createMock(CacheInterface::class); + $cache = $this->createStub(CacheInterface::class); $this->factory->setVerifierCache($cache); $this->factory->createAuth($this->defaultConfig); - $this->addToAssertionCount(1); } - public function test_it_accepts_a_PSR6_verifier_cache(): void + #[DoesNotPerformAssertions] + public function testItAcceptsAPSR6VerifierCache(): void { - $cache = $this->createMock(CacheItemPoolInterface::class); + $cache = $this->createStub(CacheItemPoolInterface::class); $this->factory->setVerifierCache($cache); $this->factory->createAuth($this->defaultConfig); - $this->addToAssertionCount(1); } - public function test_it_accepts_a_PSR16_auth_token_cache(): void + #[DoesNotPerformAssertions] + public function testItAcceptsAPSR16AuthTokenCache(): void { - $cache = $this->createMock(CacheInterface::class); + $cache = $this->createStub(CacheInterface::class); $this->factory->setAuthTokenCache($cache); $this->factory->createAuth($this->defaultConfig); - - $this->addToAssertionCount(1); } - /** - * @test - */ - public function it_accepts_a_PSR6_auth_token_cache(): void + #[DoesNotPerformAssertions] + public function testItAcceptsAPSR6AuthTokenCache(): void { - $cache = $this->createMock(CacheItemPoolInterface::class); + $cache = $this->createStub(CacheItemPoolInterface::class); $this->factory->setAuthTokenCache($cache); $this->factory->createAuth($this->defaultConfig); + } - $this->addToAssertionCount(1); + #[DoesNotPerformAssertions] + public function testItAcceptsHttpClientOptions(): void + { + $httpClientOptions = HttpClientOptions::default()->withTimeout(10.0); + + $this->factory->setHttpClientOptions($httpClientOptions); + $this->factory->createAuth($this->defaultConfig); + } + + #[DoesNotPerformAssertions] + public function testItCanResetHttpClientOptionsToNull(): void + { + $httpClientOptions = HttpClientOptions::default()->withTimeout(10.0); + + $this->factory->setHttpClientOptions($httpClientOptions); + $this->factory->setHttpClientOptions(null); + $this->factory->createAuth($this->defaultConfig); } } diff --git a/tests/DependencyInjection/FirebaseExtensionTest.php b/tests/DependencyInjection/FirebaseExtensionTest.php index 3183fff..695eb82 100644 --- a/tests/DependencyInjection/FirebaseExtensionTest.php +++ b/tests/DependencyInjection/FirebaseExtensionTest.php @@ -5,10 +5,10 @@ namespace Kreait\Firebase\Symfony\Bundle\Tests\DependencyInjection; use Kreait\Firebase; +use Kreait\Firebase\Http\HttpClientOptions; use Kreait\Firebase\Symfony\Bundle\DependencyInjection\FirebaseExtension; use PHPUnit\Framework\TestCase; use Psr\Cache\CacheItemPoolInterface; -use Psr\Log\LoggerInterface; use ReflectionException; use stdClass; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; @@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; use TypeError; /** @@ -31,7 +32,7 @@ protected function setUp(): void $this->extension = new FirebaseExtension(); } - public function test_a_project_is_created_with_a_service_for_each_feature(): void + public function testAProjectIsCreatedWithAServiceForEachFeature(): void { $container = $this->createContainer([ 'projects' => [ @@ -61,16 +62,12 @@ public function test_a_project_is_created_with_a_service_for_each_feature(): voi $this->assertInstanceOf(Firebase\Contract\Messaging::class, $container->get(Firebase\Contract\Messaging::class)); $this->assertInstanceOf(Firebase\Contract\Messaging::class, $container->get(Firebase\Contract\Messaging::class.' $fooMessaging')); - $this->assertInstanceOf(Firebase\Contract\DynamicLinks::class, $container->get($this->extension->getAlias().'.foo.dynamic_links')); - $this->assertInstanceOf(Firebase\Contract\DynamicLinks::class, $container->get(Firebase\Contract\DynamicLinks::class)); - $this->assertInstanceOf(Firebase\Contract\DynamicLinks::class, $container->get(Firebase\Contract\DynamicLinks::class.' $fooDynamicLinks')); - $this->assertInstanceOf(Firebase\Contract\AppCheck::class, $container->get($this->extension->getAlias().'.foo.app_check')); $this->assertInstanceOf(Firebase\Contract\AppCheck::class, $container->get(Firebase\Contract\AppCheck::class)); $this->assertInstanceOf(Firebase\Contract\AppCheck::class, $container->get(Firebase\Contract\AppCheck::class.' $fooAppCheck')); } - public function test_a_verifier_cache_can_be_used(): void + public function testAVerifierCacheCanBeUsed(): void { $cacheServiceId = 'cache.app.simple.mock'; @@ -82,14 +79,14 @@ public function test_a_verifier_cache_can_be_used(): void ], ], ]); - $cache = $this->createMock(CacheItemPoolInterface::class); + + $cache = $this->createStub(CacheItemPoolInterface::class); $container->set($cacheServiceId, $cache); - $container->get(Firebase\Contract\Auth::class); - $this->addToAssertionCount(1); + $this->assertInstanceOf(Firebase\Contract\Auth::class, $container->get(Firebase\Contract\Auth::class)); } - public function test_an_auth_token_cache_can_be_used(): void + public function testAnAuthTokenCacheCanBeUsed(): void { $cacheServiceId = 'cache.app.simple.mock'; @@ -101,84 +98,94 @@ public function test_an_auth_token_cache_can_be_used(): void ], ], ]); - $cache = $this->createMock(CacheItemPoolInterface::class); + + $cache = $this->createStub(CacheItemPoolInterface::class); $container->set($cacheServiceId, $cache); - $container->get(Firebase\Contract\Auth::class); - $this->addToAssertionCount(1); + $this->assertInstanceOf(Firebase\Contract\Auth::class, $container->get(Firebase\Contract\Auth::class)); } - public function test_a_request_logger_can_be_used(): void + public function testHttpClientOptionsCanBeUsed(): void { - $loggerServiceId = 'firebase_logger'; + $httpClientOptionsServiceId = 'firebase.http_client_options'; $container = $this->createContainer([ 'projects' => [ 'foo' => [ 'credentials' => __DIR__.'/../_fixtures/valid_credentials.json', - 'http_request_logger' => $loggerServiceId, + 'http_client_options' => $httpClientOptionsServiceId, ], ], ]); - $logger = $this->createMock(LoggerInterface::class); - $container->set($loggerServiceId, $logger); - $container->get(Firebase\Contract\Auth::class); - $this->addToAssertionCount(1); + $container->set($httpClientOptionsServiceId, HttpClientOptions::default()->withTimeout(10.0)); + + $this->assertInstanceOf(Firebase\Contract\Auth::class, $container->get(Firebase\Contract\Auth::class)); } - public function test_a_request_debug_logger_can_be_used(): void + public function testAProjectCanBePrivate(): void { - $loggerServiceId = 'firebase_debug_logger'; - $container = $this->createContainer([ 'projects' => [ 'foo' => [ 'credentials' => __DIR__.'/../_fixtures/valid_credentials.json', - 'http_request_debug_logger' => $loggerServiceId, + 'public' => false, ], ], ]); - $logger = $this->createMock(LoggerInterface::class); - $container->set($loggerServiceId, $logger); + $container->compile(); - $container->get(Firebase\Contract\Auth::class); - $this->addToAssertionCount(1); + $this->assertFalse($container->has($this->extension->getAlias().'.foo')); } - public function test_a_project_can_be_private(): void + public function testItCanProvideMultipleProjects(): void { $container = $this->createContainer([ 'projects' => [ 'foo' => [ 'credentials' => __DIR__.'/../_fixtures/valid_credentials.json', - 'public' => false, + ], + 'bar' => [ + 'credentials' => __DIR__.'/../_fixtures/valid_credentials.json', ], ], ]); - $container->compile(); - $this->assertFalse($container->has($this->extension->getAlias().'.foo')); + $this->assertTrue($container->hasDefinition($this->extension->getAlias().'.foo.auth')); + $this->assertTrue($container->hasDefinition($this->extension->getAlias().'.bar.auth')); + $this->assertTrue($container->hasDefinition($this->projectFactoryServiceId('foo'))); + $this->assertTrue($container->hasDefinition($this->projectFactoryServiceId('bar'))); } - public function test_it_can_provide_multiple_projects(): void + public function testProjectFactoryOptionsDoNotLeakBetweenProjects(): void { + $fooCacheServiceId = 'cache.foo'; + $barCacheServiceId = 'cache.bar'; + $container = $this->createContainer([ 'projects' => [ 'foo' => [ 'credentials' => __DIR__.'/../_fixtures/valid_credentials.json', + 'verifier_cache' => $fooCacheServiceId, ], 'bar' => [ 'credentials' => __DIR__.'/../_fixtures/valid_credentials.json', + 'auth_token_cache' => $barCacheServiceId, ], ], ]); - $this->assertTrue($container->hasDefinition($this->extension->getAlias().'.foo.auth')); - $this->assertTrue($container->hasDefinition($this->extension->getAlias().'.bar.auth')); + $fooCalls = $container->getDefinition($this->projectFactoryServiceId('foo'))->getMethodCalls(); + $barCalls = $container->getDefinition($this->projectFactoryServiceId('bar'))->getMethodCalls(); + + $this->assertContainsEquals(['setVerifierCache', [new Reference($fooCacheServiceId)]], $fooCalls); + $this->assertNotContainsEquals(['setAuthTokenCache', [new Reference($barCacheServiceId)]], $fooCalls); + + $this->assertContainsEquals(['setAuthTokenCache', [new Reference($barCacheServiceId)]], $barCalls); + $this->assertNotContainsEquals(['setVerifierCache', [new Reference($fooCacheServiceId)]], $barCalls); } - public function test_it_supports_specifying_credentials(): void + public function testItSupportsSpecifyingCredentials(): void { $container = $this->createContainer([ 'projects' => [ @@ -191,7 +198,7 @@ public function test_it_supports_specifying_credentials(): void $this->assertTrue($container->hasDefinition($this->extension->getAlias().'.foo.auth')); } - public function test_it_accepts_only_one_default_project(): void + public function testItAcceptsOnlyOneDefaultProject(): void { $this->expectException(InvalidConfigurationException::class); @@ -209,7 +216,7 @@ public function test_it_accepts_only_one_default_project(): void ]); } - public function test_it_has_no_default_project_if_none_could_be_determined(): void + public function testItHasNoDefaultProjectIfNoneCouldBeDetermined(): void { $container = $this->createContainer([ 'projects' => [ @@ -249,4 +256,9 @@ public function process(ContainerBuilder $container): void return $container; } + + private function projectFactoryServiceId(string $projectName): string + { + return $this->extension->getAlias().'.'.$projectName.'.project_factory'; + } } diff --git a/tests/FirebaseBundleTest.php b/tests/FirebaseBundleTest.php index 6c77a86..2f16ed0 100644 --- a/tests/FirebaseBundleTest.php +++ b/tests/FirebaseBundleTest.php @@ -13,7 +13,7 @@ */ final class FirebaseBundleTest extends TestCase { - public function test_it_uses_the_right_container_extension(): void + public function testItUsesTheRightContainerExtension(): void { $bundle = new FirebaseBundle();