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/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 @@ + */ 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 () { @@ -41,18 +41,32 @@ function () { throw new MetadataException("Empty target custom attribute"); } - return [$target['custom']['version'] => $target['custom']]; + $release = ReleaseData::from($target['custom']); + + return [$release->version => $release]; }); } ); + + 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 ($release) { - return $release["stability"] === "Stable"; - })->sort(function ($releaseA, $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 dae63ce..6abd959 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()); } @@ -74,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)