From 3d7ca421408bc1f08580d58d777ee89f4b1cca8f Mon Sep 17 00:00:00 2001 From: David Jardin Date: Sat, 18 Jan 2025 11:57:00 +0100 Subject: [PATCH 1/4] refactor release data to DTO for proper type hinting #20 --- app/Jobs/UpdateSite.php | 1 + app/TUF/ReleaseData.php | 14 ++++++++++++++ app/TUF/TufFetcher.php | 12 +++++++----- tests/Unit/TUF/TufFetcherTest.php | 21 +++++++++++++-------- 4 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 app/TUF/ReleaseData.php diff --git a/app/Jobs/UpdateSite.php b/app/Jobs/UpdateSite.php index 3cfcff4..6accb60 100644 --- a/app/Jobs/UpdateSite.php +++ b/app/Jobs/UpdateSite.php @@ -6,6 +6,7 @@ use App\Models\Site; use App\RemoteSite\Connection; +use App\RemoteSite\Responses\HealthCheck; use App\RemoteSite\Responses\PrepareUpdate; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; diff --git a/app/TUF/ReleaseData.php b/app/TUF/ReleaseData.php new file mode 100644 index 0000000..9d80230 --- /dev/null +++ b/app/TUF/ReleaseData.php @@ -0,0 +1,14 @@ + $target['custom']]; + $release = ReleaseData::from($target['custom']); + + return [$release->version => $release]; }); } ); @@ -49,10 +51,10 @@ function () { public function getLatestVersionForBranch(int $branch): string { - return $this->getReleases()->filter(function ($release) { - return $release["stability"] === "Stable"; - })->sort(function ($releaseA, $releaseB) { - return version_compare($releaseA["version"], $releaseB["version"], '<'); + return $this->getReleases()->filter(function (ReleaseData $release) { + return $release->stability === "Stable"; + })->sort(function (ReleaseData $releaseA, ReleaseData $releaseB) { + return version_compare($releaseA->version, $releaseB->version, '<'); })->pluck('version')->first(); } } diff --git a/tests/Unit/TUF/TufFetcherTest.php b/tests/Unit/TUF/TufFetcherTest.php index dae63ce..bf8269b 100644 --- a/tests/Unit/TUF/TufFetcherTest.php +++ b/tests/Unit/TUF/TufFetcherTest.php @@ -3,6 +3,7 @@ namespace Tests\Unit\TUF; use App\TUF\EloquentModelStorage; +use App\TUF\ReleaseData; use App\TUF\TufFetcher; use Illuminate\Support\Facades\App; use Tests\TestCase; @@ -24,13 +25,15 @@ public function testGetReleasesConvertsLegitResponse() "Joomla_5.1.2-Stable-Upgrade_Package.zip" => [ "custom" => [ "description" => "Joomla! 5.1.2 Release", - "version" => "5.1.2" + "version" => "5.1.2", + "stability" => "stable", ] ], "Joomla_5.2.1-Stable-Upgrade_Package.zip" => [ "custom" => [ "description" => "Joomla! 5.2.1 Release", - "version" => "5.2.1" + "version" => "5.2.1", + "stability" => "stable", ] ] ])); @@ -39,14 +42,16 @@ public function testGetReleasesConvertsLegitResponse() $result = $object->getReleases(); $this->assertEquals([ - "5.1.2" => [ + "5.1.2" => ReleaseData::from([ "description" => "Joomla! 5.1.2 Release", - "version" => "5.1.2" - ], - "5.2.1" => [ + "version" => "5.1.2", + "stability" => "stable", + ]), + "5.2.1" => ReleaseData::from([ "description" => "Joomla! 5.2.1 Release", - "version" => "5.2.1" - ], + "version" => "5.2.1", + "stability" => "stable", + ]), ], $result->toArray()); } From 95296517f3fb9451cbd80c3d6d376e5f55458efe Mon Sep 17 00:00:00 2001 From: David Jardin Date: Sat, 18 Jan 2025 11:58:41 +0100 Subject: [PATCH 2/4] cs fix --- app/Jobs/UpdateSite.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Jobs/UpdateSite.php b/app/Jobs/UpdateSite.php index 6accb60..3cfcff4 100644 --- a/app/Jobs/UpdateSite.php +++ b/app/Jobs/UpdateSite.php @@ -6,7 +6,6 @@ use App\Models\Site; use App\RemoteSite\Connection; -use App\RemoteSite\Responses\HealthCheck; use App\RemoteSite\Responses\PrepareUpdate; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; From 5083abc2ee1287ea2c0c6a2d190e96628adfd661 Mon Sep 17 00:00:00 2001 From: David Jardin Date: Sat, 18 Jan 2025 12:28:06 +0100 Subject: [PATCH 3/4] cs fix --- app/Jobs/CheckSiteHealth.php | 5 +++ app/TUF/TufFetcher.php | 28 ++++++++---- tests/Unit/TUF/TufFetcherTest.php | 75 +++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/app/Jobs/CheckSiteHealth.php b/app/Jobs/CheckSiteHealth.php index 16d072e..b2fd54d 100644 --- a/app/Jobs/CheckSiteHealth.php +++ b/app/Jobs/CheckSiteHealth.php @@ -47,6 +47,11 @@ public function handle(): void $tufFetcher = App::make(TufFetcher::class); $latestVersion = $tufFetcher->getLatestVersionForBranch((int) $this->site->cms_version[0]); + // No latest version for branch available, unsupported branch - return + if (!$latestVersion) { + return; + } + // Available version is not newer, exit if (!version_compare($latestVersion, $this->site->cms_version, ">")) { return; diff --git a/app/TUF/TufFetcher.php b/app/TUF/TufFetcher.php index 4729fbd..da1daea 100644 --- a/app/TUF/TufFetcher.php +++ b/app/TUF/TufFetcher.php @@ -18,12 +18,12 @@ public function __construct() } /** - * @return Collection + * @return Collection */ public function getReleases(): Collection { // Cache response to avoid to make constant calls on the fly - return Cache::remember( + $releases = Cache::remember( 'cms_targets', (int) config('autoupdates.tuf_repo_cachetime') * 60, // @phpstan-ignore-line function () { @@ -47,14 +47,26 @@ function () { }); } ); + + if (!$releases instanceof Collection) { + throw new MetadataException("Invalid release list"); + } + + return $releases; } - public function getLatestVersionForBranch(int $branch): string + public function getLatestVersionForBranch(int $branch): ?string { - return $this->getReleases()->filter(function (ReleaseData $release) { - return $release->stability === "Stable"; - })->sort(function (ReleaseData $releaseA, ReleaseData $releaseB) { - return version_compare($releaseA->version, $releaseB->version, '<'); - })->pluck('version')->first(); + $versionMatch = $this->getReleases()->filter(function (ReleaseData $release) use ($branch): bool { + return $release->stability === "stable" && $release->version[0] === (string) $branch; + })->sort(function (ReleaseData $releaseA, ReleaseData $releaseB): int { + return version_compare($releaseA->version, $releaseB->version); + })->last(); + + if (!$versionMatch instanceof ReleaseData) { + return null; + } + + return $versionMatch->version; } } diff --git a/tests/Unit/TUF/TufFetcherTest.php b/tests/Unit/TUF/TufFetcherTest.php index bf8269b..28cbca2 100644 --- a/tests/Unit/TUF/TufFetcherTest.php +++ b/tests/Unit/TUF/TufFetcherTest.php @@ -79,6 +79,81 @@ public function testGetReleasesThrowsExceptionOnMissingCustom() $object->getReleases(); } + public function testGetLatestVersionForBranchReturnsNullForMissingBranch() + { + App::bind(StorageInterface::class, fn() => $this->getStorageMock([ + "Joomla_5.2.1-Stable-Upgrade_Package.zip" => [ + "custom" => [ + "description" => "Joomla! 5.2.1 Release", + "version" => "5.2.1", + "stability" => "stable", + ] + ] + ])); + + $object = new TufFetcher(); + $result = $object->getLatestVersionForBranch(6); + + $this->assertNull($result); + } + + public function testGetLatestVersionForBranchChecksBranch() + { + App::bind(StorageInterface::class, fn() => $this->getStorageMock([ + "Joomla_5.2.1-Stable-Upgrade_Package.zip" => [ + "custom" => [ + "description" => "Joomla! 5.2.1 Release", + "version" => "5.2.1", + "stability" => "stable", + ] + ], + "Joomla_4.2.1-Stable-Upgrade_Package.zip" => [ + "custom" => [ + "description" => "Joomla! 4.2.1 Release", + "version" => "4.1.2", + "stability" => "stable", + ] + ] + ])); + + $object = new TufFetcher(); + $result = $object->getLatestVersionForBranch(4); + + $this->assertEquals("4.1.2", $result); + } + + public function testGetLatestVersionForBranchChecksOrdering() + { + App::bind(StorageInterface::class, fn() => $this->getStorageMock([ + "Joomla_5.2.3-Stable-Upgrade_Package.zip" => [ + "custom" => [ + "description" => "Joomla! 5.2.3 Release", + "version" => "5.2.3", + "stability" => "stable", + ] + ], + "Joomla_5.2.1-Stable-Upgrade_Package.zip" => [ + "custom" => [ + "description" => "Joomla! 5.2.1 Release", + "version" => "5.2.1", + "stability" => "stable", + ] + ], + "Joomla_5.2.2-Stable-Upgrade_Package.zip" => [ + "custom" => [ + "description" => "Joomla! 5.2.2 Release", + "version" => "5.2.2", + "stability" => "stable", + ] + ] + ])); + + $object = new TufFetcher(); + $result = $object->getLatestVersionForBranch(5); + + $this->assertEquals("5.2.3", $result); + } + protected function getStorageMock(array $targets) { $targetsMock = $this->getMockBuilder(TargetsMetadata::class) From 2caffc98f3fa281cc9736a70575fdd435bc775ff Mon Sep 17 00:00:00 2001 From: David Jardin Date: Sat, 18 Jan 2025 12:30:15 +0100 Subject: [PATCH 4/4] cs fix --- tests/Unit/TUF/TufFetcherTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Unit/TUF/TufFetcherTest.php b/tests/Unit/TUF/TufFetcherTest.php index 28cbca2..6abd959 100644 --- a/tests/Unit/TUF/TufFetcherTest.php +++ b/tests/Unit/TUF/TufFetcherTest.php @@ -81,7 +81,7 @@ public function testGetReleasesThrowsExceptionOnMissingCustom() public function testGetLatestVersionForBranchReturnsNullForMissingBranch() { - App::bind(StorageInterface::class, fn() => $this->getStorageMock([ + App::bind(StorageInterface::class, fn () => $this->getStorageMock([ "Joomla_5.2.1-Stable-Upgrade_Package.zip" => [ "custom" => [ "description" => "Joomla! 5.2.1 Release", @@ -99,7 +99,7 @@ public function testGetLatestVersionForBranchReturnsNullForMissingBranch() public function testGetLatestVersionForBranchChecksBranch() { - App::bind(StorageInterface::class, fn() => $this->getStorageMock([ + App::bind(StorageInterface::class, fn () => $this->getStorageMock([ "Joomla_5.2.1-Stable-Upgrade_Package.zip" => [ "custom" => [ "description" => "Joomla! 5.2.1 Release", @@ -124,7 +124,7 @@ public function testGetLatestVersionForBranchChecksBranch() public function testGetLatestVersionForBranchChecksOrdering() { - App::bind(StorageInterface::class, fn() => $this->getStorageMock([ + App::bind(StorageInterface::class, fn () => $this->getStorageMock([ "Joomla_5.2.3-Stable-Upgrade_Package.zip" => [ "custom" => [ "description" => "Joomla! 5.2.3 Release",