From 441aadc43c04dc3af6da8a751e3b17a69083563f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gamez?= Date: Mon, 27 May 2024 02:05:54 +0200 Subject: [PATCH 1/3] Configure Integration Test secrets with environment variables only --- .gitignore | 2 +- composer.json | 3 +- phpunit.xml.dist | 1 + tests/.env.dist | 6 + tests/Integration/ServiceAccountTest.php | 46 ++----- tests/IntegrationTestCase.php | 155 ++--------------------- tests/bootstrap.php | 9 ++ 7 files changed, 45 insertions(+), 177 deletions(-) create mode 100644 tests/.env.dist create mode 100644 tests/bootstrap.php diff --git a/.gitignore b/.gitignore index 88676d4b..822a4d67 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ /vendor /docs/_build /docs/*.pyc -/tests/_fixtures/test_* +/tests/.env /.firebaserc /.php-cs-fixer.cache diff --git a/composer.json b/composer.json index 3dc76f39..0a6b67c9 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,8 @@ "phpunit/phpunit": "^10.5.20", "rector/rector": "^1.0.5", "shipmonk/composer-dependency-analyser": "^1.5.3", - "symfony/var-dumper": "^6.3.5 || ^7.0.7" + "symfony/var-dumper": "^6.3.5 || ^7.0.7", + "vlucas/phpdotenv": "^5.6" }, "suggest": { "google/cloud-firestore": "^1.0 to use the Firestore component" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9df31065..e0502e42 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" beStrictAboutOutputDuringTests="true" + bootstrap="tests/bootstrap.php" colors="true" > diff --git a/tests/.env.dist b/tests/.env.dist new file mode 100644 index 00000000..01e6ab70 --- /dev/null +++ b/tests/.env.dist @@ -0,0 +1,6 @@ +GOOGLE_APPLICATION_CREDENTIALS= +FIREBASE_TENANT_ID= +TEST_FIREBASE_APP_ID= +TEST_FIREBASE_RTDB_URI= +TEST_FIREBASE_TENANT_ID= +TEST_REGISTRATION_TOKENS= diff --git a/tests/Integration/ServiceAccountTest.php b/tests/Integration/ServiceAccountTest.php index 4b625cd3..1cf9bc56 100644 --- a/tests/Integration/ServiceAccountTest.php +++ b/tests/Integration/ServiceAccountTest.php @@ -4,56 +4,40 @@ namespace Kreait\Firebase\Tests\Integration; -use Beste\Json; use Kreait\Firebase\Factory; +use Kreait\Firebase\Tests\IntegrationTestCase; use Kreait\Firebase\Util; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\Attributes\Test; -use PHPUnit\Framework\TestCase; use function assert; /** * @internal */ -final class ServiceAccountTest extends TestCase +final class ServiceAccountTest extends IntegrationTestCase { /** * @var non-empty-string */ private static string $credentialsPath; - private static bool $credentialsPathIsTemporary = false; public static function setUpBeforeClass(): void { - $credentialsFromEnvironment = Util::getenv('GOOGLE_APPLICATION_CREDENTIALS'); + parent::setUpBeforeClass(); - if ($credentialsFromEnvironment !== null && str_starts_with($credentialsFromEnvironment, '{')) { - // Don't overwrite the fixtures file - $credentialsPath = __DIR__.'/test_credentials.json'; - self::$credentialsPathIsTemporary = true; - - $result = file_put_contents($credentialsPath, $credentialsFromEnvironment); - - if ($result === false) { - self::fail("Unable to write credentials to file `{$credentialsPath}`"); - } - - Util::putenv('GOOGLE_APPLICATION_CREDENTIALS', $credentialsPath); - } elseif (!file_exists($credentialsPath = __DIR__.'/../_fixtures/test_credentials.json')) { - self::markTestSkipped('The integration tests require credentials'); - } - - self::$credentialsPath = $credentialsPath; + self::$credentialsPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'test_credentials.json'; + file_put_contents(self::$credentialsPath, json_encode(self::$serviceAccount)); + Util::putenv('GOOGLE_APPLICATION_CREDENTIALS', self::$credentialsPath); } public static function tearDownAfterClass(): void { - if (self::$credentialsPathIsTemporary) { - unlink(self::$credentialsPath); - } + unlink(self::$credentialsPath); } #[Test] + #[DoesNotPerformAssertions] public function withPathToServiceAccount(): void { $factory = (new Factory())->withServiceAccount(self::$credentialsPath); @@ -62,6 +46,7 @@ public function withPathToServiceAccount(): void } #[Test] + #[DoesNotPerformAssertions] public function withJsonString(): void { $json = file_get_contents(self::$credentialsPath); @@ -73,19 +58,16 @@ public function withJsonString(): void } #[Test] + #[DoesNotPerformAssertions] public function withArray(): void { - $json = file_get_contents(self::$credentialsPath); - assert($json !== false && $json !== ''); - - $array = Json::decode($json, true); - - $factory = (new Factory())->withServiceAccount($array); + $factory = (new Factory())->withServiceAccount(self::$serviceAccount); $this->assertFunctioningConnection($factory); } #[Test] + #[DoesNotPerformAssertions] public function withGoogleApplicationCredentialsAsFilePath(): void { Util::putenv('GOOGLE_APPLICATION_CREDENTIALS', self::$credentialsPath); @@ -94,6 +76,7 @@ public function withGoogleApplicationCredentialsAsFilePath(): void } #[Test] + #[DoesNotPerformAssertions] public function withGoogleApplicationCredentialsAsJsonString(): void { $json = file_get_contents(self::$credentialsPath); @@ -111,7 +94,6 @@ private function assertFunctioningConnection(Factory $factory): void try { $user = $auth->createAnonymousUser(); - $this->addToAssertionCount(1); } finally { if ($user !== null) { $auth->deleteUser($user->uid); diff --git a/tests/IntegrationTestCase.php b/tests/IntegrationTestCase.php index 6f6a81e3..781c35b5 100644 --- a/tests/IntegrationTestCase.php +++ b/tests/IntegrationTestCase.php @@ -7,13 +7,9 @@ use Beste\Json; use Kreait\Firebase\Factory; use Kreait\Firebase\Util; -use Throwable; use function array_rand; use function bin2hex; -use function file_exists; -use function file_get_contents; -use function is_array; use function mb_strtolower; use function random_bytes; @@ -54,23 +50,19 @@ abstract class IntegrationTestCase extends FirebaseTestCase public static function setUpBeforeClass(): void { - $credentials = self::credentialsFromEnvironment() ?? self::credentialsFromFile(); + $credentials = self::credentialsFromEnvironment(); if (!$credentials) { self::markTestSkipped('The integration tests require credentials'); } - if (str_starts_with($credentials, '{')) { - self::$serviceAccount = Json::decode($credentials, true); - } else { - self::$serviceAccount = Json::decodeFile($credentials, true); - } + self::$serviceAccount = Json::decode($credentials, true); self::$factory = (new Factory())->withServiceAccount(self::$serviceAccount); - self::$registrationTokens = self::registrationTokens(); - self::$rtdbUrl = self::rtdbUrl(); - self::$tenantId = self::tenantId(); - self::$appId = self::appId(); + self::$registrationTokens = self::registrationTokensFromEnvironment(); + self::$rtdbUrl = Util::getenv('TEST_FIREBASE_RTDB_URI'); + self::$tenantId = Util::getenv('TEST_FIREBASE_TENANT_ID'); + self::$appId = Util::getenv('TEST_FIREBASE_APP_ID'); } /** @@ -112,18 +104,6 @@ protected static function databaseIsEmulated(): bool return Util::rtdbEmulatorHost() !== null; } - /** - * @return non-empty-string|null - */ - private static function credentialsFromFile(): ?string - { - $credentialsPath = self::$fixturesDir.'/test_credentials.json'; - - return file_exists($credentialsPath) - ? $credentialsPath - : null; - } - /** * @return non-empty-string|null */ @@ -137,124 +117,13 @@ private static function credentialsFromEnvironment(): ?string /** * @return list */ - private static function registrationTokens(): array - { - $registrationTokens = self::registrationTokensFromEnvironment() ?? self::registrationTokensFromFile(); - $registrationTokens = array_map(trim(...), $registrationTokens); - $registrationTokens = array_filter($registrationTokens); - - return array_values($registrationTokens); - } - - /** - * @return array - */ - private static function registrationTokensFromFile(): array - { - $tokens = []; - - $path = self::$fixturesDir.'/test_devices.json'; - - if (!file_exists($path)) { - return $tokens; - } - - try { - if ($contents = file_get_contents($path)) { - $tokens = Json::decode($contents, true); - } - } catch (Throwable) { - } - - return array_map(strval(...), $tokens); - } - - /** - * @return array|null - */ - private static function registrationTokensFromEnvironment(): ?array - { - if (!($tokens = Util::getenv('TEST_REGISTRATION_TOKENS'))) { - return null; - } - - try { - $tokens = Json::decode($tokens, true); - } catch (Throwable) { - return null; - } - - if (!is_array($tokens)) { - return null; - } - - return array_map(strval(...), $tokens); - } - - /** - * @return non-empty-string|null - */ - private static function rtdbUrl(): ?string - { - return self::setting('TEST_FIREBASE_RTDB_URI', 'test_rtdb.json'); - } - - /** - * @return non-empty-string|null - */ - private static function tenantId(): ?string - { - return self::setting('TEST_FIREBASE_TENANT_ID', 'test_tenant.json'); - } - - /** - * @return non-empty-string|null - */ - private static function appId(): ?string - { - return self::setting('TEST_FIREBASE_APP_ID', 'test_app.json'); - } - - /** - * @param non-empty-string $envName - * @param non-empty-string $envFile - * - * @return non-empty-string|null - */ - private static function setting(string $envName, string $envFile): ?string + private static function registrationTokensFromEnvironment(): array { - return self::settingFromEnv($envName) ?? self::settingFromFile($envFile); - } - - /** - * @return non-empty-string|null - */ - private static function settingFromFile(string $envFile): ?string - { - $path = self::$fixturesDir.'/'.$envFile; - - if (!file_exists($path)) { - return null; - } - - try { - if ($contents = file_get_contents($path)) { - return Json::decode($contents, true); - } - - return null; - } catch (Throwable) { - return null; - } - } + $tokens = Json::decode(Util::getenv('TEST_REGISTRATION_TOKENS') ?? '', true); + $tokens = array_map(strval(...), $tokens); + $tokens = array_map(trim(...), $tokens); + $tokens = array_filter($tokens, fn($token) => $token !== ''); - /** - * @param non-empty-string $envKey - * - * @return non-empty-string|null - */ - private static function settingFromEnv(string $envKey): ?string - { - return Util::getenv($envKey); + return array_values($tokens); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..466f80aa --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,9 @@ +safeLoad(); From 481c07f2f3aa9cb827cecfa7618554416c51c9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gamez?= Date: Tue, 28 May 2024 23:13:01 +0200 Subject: [PATCH 2/3] Restore overwritten credentials --- tests/Integration/ServiceAccountTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Integration/ServiceAccountTest.php b/tests/Integration/ServiceAccountTest.php index 1cf9bc56..038ab0ce 100644 --- a/tests/Integration/ServiceAccountTest.php +++ b/tests/Integration/ServiceAccountTest.php @@ -21,11 +21,14 @@ final class ServiceAccountTest extends IntegrationTestCase * @var non-empty-string */ private static string $credentialsPath; + private static ?string $originalCredentials; public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); + self::$originalCredentials = Util::getenv('GOOGLE_APPLICATION_CREDENTIALS'); + self::$credentialsPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'test_credentials.json'; file_put_contents(self::$credentialsPath, json_encode(self::$serviceAccount)); Util::putenv('GOOGLE_APPLICATION_CREDENTIALS', self::$credentialsPath); @@ -34,6 +37,7 @@ public static function setUpBeforeClass(): void public static function tearDownAfterClass(): void { unlink(self::$credentialsPath); + Util::putenv('GOOGLE_APPLICATION_CREDENTIALS', self::$originalCredentials); } #[Test] From d01c6a2fcbd9b1433cca793a4d41357042c73392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gamez?= Date: Tue, 28 May 2024 23:32:18 +0200 Subject: [PATCH 3/3] Add helper to reset the test Firebase project --- composer.json | 3 +++ tests/bin/reset-project | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100755 tests/bin/reset-project diff --git a/composer.json b/composer.json index 0a6b67c9..edd0d36e 100644 --- a/composer.json +++ b/composer.json @@ -104,6 +104,9 @@ "rector-fix": [ "vendor/bin/rector" ], + "reset-project": [ + "tests/bin/reset-project" + ], "test": [ "@analyze", "@test-dependencies", diff --git a/tests/bin/reset-project b/tests/bin/reset-project new file mode 100755 index 00000000..c04a7373 --- /dev/null +++ b/tests/bin/reset-project @@ -0,0 +1,40 @@ +#!/usr/bin/env php +safeLoad(); +$dotenv->required('GOOGLE_APPLICATION_CREDENTIALS'); + +$factory = (new Factory())->withDatabaseUri(Util::getenv('TEST_FIREBASE_RTDB_URI')); + +$auth = $factory->createAuth(); +$db = $factory->createDatabase(); + +echo 'Resetting database rules... '; +$factory->createDatabase()->updateRules(RuleSet::private()); +echo 'DONE'.PHP_EOL; + +echo 'Purging database... '; +$factory->createDatabase()->getReference('/')->remove(); +echo 'DONE'.PHP_EOL; + +echo 'Purging auth users... '; +do { + $users = iterator_to_array($auth->listUsers()); + $uids = array_map(fn(UserRecord $record) => $record->uid, $users); + + if (count($uids) > 0) { + $auth->deleteUsers($uids, true); + } +} while (count($uids) > 0); +echo 'DONE'.PHP_EOL;