From 1dc2d0c3f44e9eff35bbefbacbe0fefeb75f174b Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Mon, 20 Jan 2025 11:01:25 +0100 Subject: [PATCH] feat(endpoint): add a new "/latest" endpoint Allows to retrieve latest version number for a specific channel Signed-off-by: Benjamin Gaussorgues --- .htaccess | 3 +- latest.php | 81 +++++++++++++++++++ .../features/bootstrap/FeatureContext.php | 81 ++++++++++++++++++- tests/integration/features/latest.feature | 52 ++++++++++++ 4 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 latest.php create mode 100644 tests/integration/features/latest.feature diff --git a/.htaccess b/.htaccess index 66b07d14..eae37bd8 100755 --- a/.htaccess +++ b/.htaccess @@ -1,6 +1,7 @@ -# Rewrite all requests to "index.php" +# Rewrite all requests to "index.php" except for latest DirectoryIndex index.php RewriteEngine On +RewriteRule ^latest(?|$) latest.php [L] RewriteRule ^ index.php [L] diff --git a/latest.php b/latest.php new file mode 100644 index 00000000..2076fad5 --- /dev/null +++ b/latest.php @@ -0,0 +1,81 @@ + 'Invalid PHP version provided']); + exit; +} + +if (!in_array($channel, ['stable', 'beta', 'enterprise'])) { + http_response_code(400); + echo json_encode(['error' => 'Invalid channel provided']); + exit; +} + +// Filter suitable major versions +$majorVersions = loadJson('major_versions'); +try { + $enterpriseMajorVersions = loadJson('enterprise_major_versions'); + foreach($majorVersions as $name => $version) { + if (isset($enterpriseMajorVersions[$name])) { + $majorVersions[$name] = array_merge($version, $enterpriseMajorVersions[$name]); + } + } + $majorVersions = array_replace_recursive($majorVersions + $enterpriseMajorVersions); +} catch (\Exception) { + if ($channel === 'enterprise') { + http_response_code(400); + echo json_encode(['error' => 'Invalid channel provided']); + exit; + } +} + +if ($phpVersion) { + $majorVersions = array_filter($majorVersions, static function ($release) use ($phpVersion) { + return version_compare($release['minPHP'], $phpVersion, '<='); + }); +} + +if (empty($majorVersions)) { + http_response_code(400); + echo json_encode(['error' => 'No major version found for your version of PHP']); + exit; +} +$suitableMajors = array_keys($majorVersions); + +// Filter suitable releases +$allowedChannels = [$channel]; +if ($channel === 'beta') { + $allowedChannels[] = 'stable'; +} +$releases = loadJson($channel === 'enterprise' ? 'enterprise_releases' : 'releases'); +$releases = array_filter($releases, function ($name) use ($allowedChannels, $suitableMajors) { + return in_array(getStabilityFromName($name), $allowedChannels) + && preg_match('/^('.implode('|', $suitableMajors).')\./', $name); +}, ARRAY_FILTER_USE_KEY); +uksort($releases, static fn($a, $b) => version_compare($a, $b, '<')); +if (empty($releases)) { + http_response_code(400); + echo json_encode(['error' => 'No version found for your version of PHP']); + exit; +} +// Show latest +$release = key($releases); +$info = current($releases); +echo json_encode([ + 'version' => $release, + 'url' => buildDownloadUrl($release, $info, $majorVersions[explode('.', $release)[0]]), +], JSON_UNESCAPED_SLASHES); + diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php index 9b442e55..6ff39437 100755 --- a/tests/integration/features/bootstrap/FeatureContext.php +++ b/tests/integration/features/bootstrap/FeatureContext.php @@ -39,6 +39,7 @@ class FeatureContext implements Context, SnippetAcceptingContext { private $result = ''; /** @var array */ private $resultArray = []; + private ?string $phpVersion = null; /** * @Given There is a release with channel :arg1 @@ -197,6 +198,16 @@ public function theResponseIsNonEmpty() { throw new \Exception('Response contains not between 6 or 8 array elements.'); } } + /** + * @Then The JSON response is non-empty + */ + public function theJsonResponseIsNonEmpty() { + if(empty($this->result)) { + throw new \Exception('Response is empty'); + } + + $this->resultArray = json_decode($this->result, true); + } /** * @Then Update to version :arg1 is available @@ -260,7 +271,7 @@ public function theResponseIsEmpty() { } /** - * @Then Autoupdater is set to :arg1 + * @Then Autoupdater is set to :autoupdaterValue */ public function autoupdaterIsSetTo($autoupdaterValue) { $autoupdater = $this->resultArray['autoupdater']; @@ -270,7 +281,7 @@ public function autoupdaterIsSetTo($autoupdaterValue) { } /** - * @Then EOL is set to :arg1 + * @Then EOL is set to :eolValue */ public function eolIsSetTo($eolValue) { $eol = $this->resultArray['eol']; @@ -278,4 +289,70 @@ public function eolIsSetTo($eolValue) { throw new \Exception("Expected eol $eolValue does not equals $eol"); } } + + /** + * @Given I want to know the latest :channel release + */ + public function iWantToKnowTheLatestRelease(string $channel): void + { + $this->channel = $channel; + } + + /** + * @Given I use PHP ":phpVersion" + */ + public function iUsePhp($phpVersion): void + { + $this->phpVersion = $phpVersion; + } + + /** + * @When I send a request latest.php + */ + public function iSendARequestLatestPhp(): void + { + $ch = curl_init(); + $params = [ + 'channel' => $this->channel, + ]; + if ($this->phpVersion !== null) { + $params['php'] = $this->phpVersion; + } + $optArray = array( + CURLOPT_URL => 'http://localhost:8888/latest.php?'.http_build_query($params), + CURLOPT_RETURNTRANSFER => true + ); + curl_setopt_array($ch, $optArray); + $this->result = curl_exec($ch); + curl_close($ch); + } + + /** + * @Then Version :expectedVersion is the latest release + */ + public function versionIsTheLatestRelease($expectedVersion): void + { + if (($this->resultArray['version'] ?? '') === '') { + throw new \Exception("Version number is empty"); + } + $foundVersion = $this->resultArray['version']; + if ($foundVersion !== $expectedVersion) { + throw new \Exception("Version number $foundVersion is different from expected version $expectedVersion"); + } + } + + /** + * @Then I get error ":expectedError" + */ + public function iGetError($expectedError): void + { + if (($this->resultArray['error'] ?? '') === '') { + throw new \Exception("Error message is empty"); + } + $foundError = $this->resultArray['error']; + if ($foundError !== $expectedError) { + throw new \Exception("Error message $foundError is different from expected error $expectedError"); + } + + } } diff --git a/tests/integration/features/latest.feature b/tests/integration/features/latest.feature new file mode 100644 index 00000000..b73152a7 --- /dev/null +++ b/tests/integration/features/latest.feature @@ -0,0 +1,52 @@ +Feature: Testing the latest endpoint + + Scenario: Get latest stable version + Given I want to know the latest stable release + When I send a request latest.php + Then The JSON response is non-empty + And Version "30.0.5" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-30.0.5.zip" + + Scenario: Get latest beta version + Given I want to know the latest beta release + When I send a request latest.php + Then The JSON response is non-empty + And Version "31.0.0 beta 5" is the latest release + And URL to download is "https://download.nextcloud.com/server/prereleases/nextcloud-31.0.0beta5.zip" + + Scenario: Get latest stable version with PHP 8.0 + Given I want to know the latest stable release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.11" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.11.zip" + + Scenario: Get latest beta version with PHP 8.0 + Given I want to know the latest beta release + And I use PHP "8.0" + When I send a request latest.php + Then The JSON response is non-empty + And Version "29.0.11" is the latest release + And URL to download is "https://download.nextcloud.com/server/releases/nextcloud-29.0.11.zip" + + Scenario: Get latest version with invalid PHP version + Given I want to know the latest beta release + And I use PHP "test" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid PHP version provided" + + Scenario: Get latest version with too old PHP version + Given I want to know the latest beta release + And I use PHP "5.0" + When I send a request latest.php + Then The JSON response is non-empty + And I get error "No major version found for your version of PHP" + + Scenario: Get latest version with invalid channel + Given I want to know the latest whatever release + When I send a request latest.php + Then The JSON response is non-empty + And I get error "Invalid channel provided" +