diff --git a/src/ExpoPushConnector.php b/src/ExpoPushConnector.php index 959d208..d2261e7 100644 --- a/src/ExpoPushConnector.php +++ b/src/ExpoPushConnector.php @@ -25,6 +25,8 @@ class ExpoPushConnector extends Connector public const MAX_CONCURRENT_REQUESTS = 6; + private static ?string $sdkVersion = null; + public function __construct( protected ?string $authToken = null, ?RateLimitStore $rateLimitStore = null, @@ -83,17 +85,29 @@ protected function resolveRateLimitStore(): RateLimitStore */ public function sdkVersion(): string { - static $version = null; + if (self::$sdkVersion === null) { + self::$sdkVersion = self::resolveSdkVersion(dirname(__DIR__) . '/composer.json'); + } - if (!$version) { - $composer = json_decode( - file_get_contents(dirname(__DIR__) . '/composer.json'), - ); + return self::$sdkVersion; + } - $version = InstalledVersions::getPrettyVersion($composer->name); + /** + * Resolve the installed version from composer.json, falling back to 'unknown' if it can't be determined + */ + private static function resolveSdkVersion(string $composerJsonPath): string + { + if (!is_readable($composerJsonPath)) { + return 'unknown'; } - return (string) $version; + try { + $composer = json_decode(file_get_contents($composerJsonPath), flags: JSON_THROW_ON_ERROR); + + return InstalledVersions::getPrettyVersion($composer->name) ?? 'unknown'; + } catch (Throwable) { + return 'unknown'; + } } // Internals ---- diff --git a/tests/Unit/ExpoPushConnectorTest.php b/tests/Unit/ExpoPushConnectorTest.php index 354ef7b..77c09d9 100644 --- a/tests/Unit/ExpoPushConnectorTest.php +++ b/tests/Unit/ExpoPushConnectorTest.php @@ -4,10 +4,13 @@ namespace Dru1x\ExpoPush\Tests\Unit; +use Composer\InstalledVersions; use Dru1x\ExpoPush\Config\RetryConfig; use Dru1x\ExpoPush\ExpoPushConnector; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; +use ReflectionMethod; +use ReflectionProperty; use Saloon\Http\Auth\TokenAuthenticator; use Saloon\RateLimitPlugin\Exceptions\LimitException; use Saloon\RateLimitPlugin\Limit; @@ -15,6 +18,31 @@ class ExpoPushConnectorTest extends TestCase { + private ?string $originalSdkVersion = null; + + /** @var list */ + private array $tempFiles = []; + + protected function setUp(): void + { + parent::setUp(); + + $this->originalSdkVersion = (new ReflectionProperty(ExpoPushConnector::class, 'sdkVersion'))->getValue(); + } + + protected function tearDown(): void + { + (new ReflectionProperty(ExpoPushConnector::class, 'sdkVersion'))->setValue(null, $this->originalSdkVersion); + + foreach ($this->tempFiles as $tempFile) { + is_file($tempFile) && unlink($tempFile); + } + + $this->tempFiles = []; + + parent::tearDown(); + } + #[Test] public function resolves_the_expo_push_api_base_url(): void { @@ -113,8 +141,89 @@ public function custom_retry_configuration_is_used_when_supplied(): void $this->assertFalse($connector->throwOnMaxTries); } + // SDK Version ---- + + #[Test] + public function sdk_version_resolves(): void + { + $connector = new ExpoPushConnector(); + + $this->assertNotSame('unknown', $connector->sdkVersion()); + } + + #[Test] + public function sdk_version_resolves_the_real_installed_version(): void + { + $composerJsonPath = dirname(__DIR__, 2) . '/composer.json'; + $composer = json_decode(file_get_contents($composerJsonPath)); + + $this->assertSame( + InstalledVersions::getPrettyVersion($composer->name), + $this->resolveSdkVersion($composerJsonPath), + ); + } + + #[Test] + public function sdk_version_falls_back_to_unknown_when_composer_json_is_missing(): void + { + $this->assertSame('unknown', $this->resolveSdkVersion('/nonexistent/composer.json')); + } + + #[Test] + public function sdk_version_falls_back_to_unknown_when_composer_json_is_malformed(): void + { + $path = $this->writeTempFile('{not valid json'); + + $this->assertSame('unknown', $this->resolveSdkVersion($path)); + } + + #[Test] + public function sdk_version_falls_back_to_unknown_when_the_package_is_not_installed(): void + { + $path = $this->writeTempFile(json_encode(['name' => 'not/a-real-package'])); + + $this->assertSame('unknown', $this->resolveSdkVersion($path)); + } + + #[Test] + public function sdk_version_does_not_recompute_once_resolved(): void + { + (new ReflectionProperty(ExpoPushConnector::class, 'sdkVersion'))->setValue(null, 'cached-test-value'); + + $connector = new ExpoPushConnector(); + + $this->assertSame('cached-test-value', $connector->sdkVersion()); + } + + #[Test] + public function sdk_version_caches_a_falsy_resolved_value_instead_of_recomputing_it(): void + { + (new ReflectionProperty(ExpoPushConnector::class, 'sdkVersion'))->setValue(null, ''); + + $connector = new ExpoPushConnector(); + + $this->assertSame('', $connector->sdkVersion()); + } + // Internals ---- + private function resolveSdkVersion(string $composerJsonPath): string + { + return (new ReflectionMethod(ExpoPushConnector::class, 'resolveSdkVersion')) + ->invoke(null, $composerJsonPath); + } + + private function writeTempFile(string $contents): string + { + $path = tempnam(sys_get_temp_dir(), 'expo-push-sdk-version-test-'); + + file_put_contents($path, $contents); + + $this->tempFiles[] = $path; + + return $path; + } + private function findLimit(ExpoPushConnector $connector, string $name): Limit { try {