From 49f3ea9ba18b9dab14c855c8bb640bc8d543f577 Mon Sep 17 00:00:00 2001 From: Thomas Nabord Date: Fri, 30 Aug 2024 09:39:19 +0200 Subject: [PATCH] Plus Module Sources management to current process and add tests --- classes/Task/Upgrade/UpgradeModules.php | 2 +- .../UpgradeTools/Module/ModuleDownloader.php | 99 +++++----------- .../Module/ModuleDownloaderContext.php | 23 +++- .../UpgradeTools/Module/ModuleUnzipper.php | 2 +- .../Module/ModuleUnzipperContext.php | 2 +- .../Module/Source/ModuleSource.php | 9 +- .../Module/Source/ModuleSourceAggregate.php | 5 +- .../Module/ModuleDownloaderContextTest.php | 4 +- .../Module/ModuleDownloaderTest.php | 111 ++++++++++++++++++ .../Module/ModuleUnzipperContextTest.php | 2 +- 10 files changed, 172 insertions(+), 87 deletions(-) create mode 100644 tests/unit/UpgradeTools/Module/ModuleDownloaderTest.php diff --git a/classes/Task/Upgrade/UpgradeModules.php b/classes/Task/Upgrade/UpgradeModules.php index ec7b8b992a..2ef5e1ca87 100644 --- a/classes/Task/Upgrade/UpgradeModules.php +++ b/classes/Task/Upgrade/UpgradeModules.php @@ -75,7 +75,7 @@ public function run(): int $modulesPath = $this->container->getProperty(UpgradeContainer::PS_ROOT_PATH) . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR; - $moduleDownloader = new ModuleDownloader($this->translator, $this->logger, $this->container->getState()->getInstallVersion()); + $moduleDownloader = new ModuleDownloader($this->translator, $this->logger); $moduleUnzipper = new ModuleUnzipper($this->translator, $this->container->getZipAction(), $modulesPath); $moduleMigration = new ModuleMigration($this->translator, $this->logger); diff --git a/classes/UpgradeTools/Module/ModuleDownloader.php b/classes/UpgradeTools/Module/ModuleDownloader.php index ce18cbc973..94dd5ab550 100644 --- a/classes/UpgradeTools/Module/ModuleDownloader.php +++ b/classes/UpgradeTools/Module/ModuleDownloader.php @@ -26,9 +26,9 @@ namespace PrestaShop\Module\AutoUpgrade\UpgradeTools\Module; +use Exception; use PrestaShop\Module\AutoUpgrade\Exceptions\UpgradeException; use PrestaShop\Module\AutoUpgrade\Log\Logger; -use PrestaShop\Module\AutoUpgrade\Tools14; use PrestaShop\Module\AutoUpgrade\UpgradeTools\Translator; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; @@ -41,17 +41,10 @@ class ModuleDownloader /** @var Logger */ private $logger; - /** @var string */ - private $psVersion; - - /** @var string */ - private $addonsUrl = 'api.addons.prestashop.com'; - - public function __construct(Translator $translator, Logger $logger, string $psVersion) + public function __construct(Translator $translator, Logger $logger) { $this->translator = $translator; $this->logger = $logger; - $this->psVersion = $psVersion; } /** @@ -59,82 +52,48 @@ public function __construct(Translator $translator, Logger $logger, string $psVe */ public function downloadModule(ModuleDownloaderContext $moduleDownloaderContext): void { - $localModuleUsed = false; - - if ($moduleDownloaderContext->getModuleIsLocal()) { - $localModuleUsed = $this->downloadModuleFromLocalZip($moduleDownloaderContext); - } - - if (!$localModuleUsed) { - $this->downloadModuleFromAddons($moduleDownloaderContext); - } - - if (filesize($moduleDownloaderContext->getZipFullPath()) <= 300) { - throw (new UpgradeException($this->translator->trans('[WARNING] An error occurred while downloading module %s, the received file is empty.', [$moduleDownloaderContext->getModuleName()])))->setSeverity(UpgradeException::SEVERITY_WARNING); - } - } - - private function downloadModuleFromLocalZip(ModuleDownloaderContext $moduleDownloaderContext): bool - { - try { - $localModuleZip = $this->getLocalModuleZipPath($moduleDownloaderContext->getModuleName()); - if (empty($localModuleZip)) { - return false; + for ($i = 0; $i < count($moduleDownloaderContext->getUpdateSources()); ++$i) { + try { + $this->attemptDownload($moduleDownloaderContext, $i); + } catch (Exception $e) { + $this->logger->debug($this->translator->trans('Download of source #%s has failed. %s', [$i, $e->getMessage()])); + + if ($i + 1 === count($moduleDownloaderContext->getUpdateSources())) { + throw (new UpgradeException('All download attempts have failed. Check your environment and try again.'))->setSeverity(UpgradeException::SEVERITY_ERROR); + } } - $filesystem = new Filesystem(); - $filesystem->copy($localModuleZip, $moduleDownloaderContext->getZipFullPath()); - unlink($localModuleZip); - $this->logger->notice($this->translator->trans('Local module %s successfully copied.', [$moduleDownloaderContext->getModuleName()])); - - return true; - } catch (IOException $e) { - $this->logger->notice($this->translator->trans('Can not found or copy local module %s. Trying to download it from Addons.', [$moduleDownloaderContext->getModuleName()])); } - return false; + $this->logger->notice($this->translator->trans('Module %s has been successfully downloaded.', [$moduleDownloaderContext->getModuleName()])); } /** - * @throws UpgradeException + * @throws IOException When copy fails + * @throws UpgradeException If download content is invalid */ - private function downloadModuleFromAddons(ModuleDownloaderContext $moduleDownloaderContext): void + private function attemptDownload(ModuleDownloaderContext $moduleDownloaderContext, int $index): void { - $addonsUrl = extension_loaded('openssl') - ? 'https://' . $this->addonsUrl - : 'http://' . $this->addonsUrl; - - // Make the request - $context = stream_context_create([ - 'http' => [ - 'method' => 'POST', - 'content' => 'version=' . $this->psVersion . '&method=module&id_module=' . $moduleDownloaderContext->getModuleId(), - 'header' => 'Content-type: application/x-www-form-urlencoded', - 'timeout' => 10, - ], - ]); + $moduleSource = $moduleDownloaderContext->getUpdateSources()[$index]; + $filesystem = new Filesystem(); + $filesystem->mirror($moduleSource->getPath(), $moduleDownloaderContext->getDestinationFolder()); - // file_get_contents can return false if https is not supported (or warning) - $content = Tools14::file_get_contents($addonsUrl, false, $context); - if (empty($content) || substr($content, 5) == 'translator->trans('[WARNING] No response from Addons server.')))->setSeverity(UpgradeException::SEVERITY_WARNING); + if (!$moduleSource->isUnzipable()) { + return; } - if (false === (bool) file_put_contents($moduleDownloaderContext->getZipFullPath(), $content)) { - throw (new UpgradeException($this->translator->trans('[WARNING] Unable to write module %s\'s zip file in temporary directory.', [$moduleDownloaderContext->getModuleName()])))->setSeverity(UpgradeException::SEVERITY_WARNING); - } - - $this->logger->notice($this->translator->trans('Module %s has been successfully downloaded from Addons.', [$moduleDownloaderContext->getModuleName()])); + $this->assertDownloadedFileIsCorrect($moduleDownloaderContext); } - private function getLocalModuleZipPath(string $name): ?string + private function assertDownloadedFileIsCorrect(ModuleDownloaderContext $moduleDownloaderContext) { - $autoUpgradeDir = _PS_ADMIN_DIR_ . DIRECTORY_SEPARATOR . 'autoupgrade'; - $module_zip = $autoUpgradeDir . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . $name . '.zip'; - - if (file_exists($module_zip) && is_readable($module_zip)) { - return $module_zip; + if (filesize($moduleDownloaderContext->getDestinationFolder()) <= 300) { + throw (new UpgradeException($this->translator->trans('[WARNING] An error occurred while downloading module %s, the received file is empty.', [$moduleDownloaderContext->getModuleName()])))->setSeverity(UpgradeException::SEVERITY_WARNING); } - return null; + $downloadedFile = fopen($moduleDownloaderContext->getDestinationFolder(), 'r'); + if (!$downloadedFile || fread($downloadedFile, 5) == 'translator->trans('[WARNING] No response from provider.')))->setSeverity(UpgradeException::SEVERITY_WARNING); + } + fclose($downloadedFile); } } diff --git a/classes/UpgradeTools/Module/ModuleDownloaderContext.php b/classes/UpgradeTools/Module/ModuleDownloaderContext.php index ef57e5d5a7..2366710687 100644 --- a/classes/UpgradeTools/Module/ModuleDownloaderContext.php +++ b/classes/UpgradeTools/Module/ModuleDownloaderContext.php @@ -27,9 +27,13 @@ namespace PrestaShop\Module\AutoUpgrade\UpgradeTools\Module; use LogicException; +use PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\Source\ModuleSource; class ModuleDownloaderContext { + /** @var string */ + private $destinationFolder; + /** @var string */ private $moduleName; @@ -39,10 +43,14 @@ class ModuleDownloaderContext /** @var ModuleSource[]|null */ private $updateSources; - public function __construct(string $moduleName, string $referenceVersion) + /** + * @param array{name:string, currentVersion:string} $moduleInfos + */ + public function __construct(string $destinationFolder, array $moduleInfos) { - $this->moduleName = $moduleName; - $this->referenceVersion = $referenceVersion; + $this->destinationFolder = $destinationFolder; + $this->moduleName = $moduleInfos['name']; + $this->referenceVersion = $moduleInfos['currentVersion']; } /** @@ -50,11 +58,20 @@ public function __construct(string $moduleName, string $referenceVersion) */ public function validate(): void { + if (empty($this->destinationFolder)) { + throw new LogicException('Destination path is invalid.'); + } + if (empty($this->updateSources)) { throw new LogicException('List of updates is invalid.'); } } + public function getDestinationFolder(): string + { + return $this->destinationFolder; + } + public function getModuleName(): string { return $this->moduleName; diff --git a/classes/UpgradeTools/Module/ModuleUnzipper.php b/classes/UpgradeTools/Module/ModuleUnzipper.php index f966743cd3..3b9d3439d7 100644 --- a/classes/UpgradeTools/Module/ModuleUnzipper.php +++ b/classes/UpgradeTools/Module/ModuleUnzipper.php @@ -54,7 +54,7 @@ public function __construct(Translator $translator, ZipAction $zipAction, string */ public function unzipModule(ModuleUnzipperContext $moduleUnzipperContext): void { - if (!$this->zipAction->extract($moduleUnzipperContext->getZipFullPath(), $this->modulesPath)) { + if (!$this->zipAction->extract($moduleUnzipperContext->getDestinationFilePath(), $this->modulesPath)) { throw (new UpgradeException($this->translator->trans('[WARNING] Error when trying to extract module %s.', [$moduleUnzipperContext->getModuleName()])))->setSeverity(UpgradeException::SEVERITY_WARNING); } } diff --git a/classes/UpgradeTools/Module/ModuleUnzipperContext.php b/classes/UpgradeTools/Module/ModuleUnzipperContext.php index 6ce988a263..77c59bf296 100644 --- a/classes/UpgradeTools/Module/ModuleUnzipperContext.php +++ b/classes/UpgradeTools/Module/ModuleUnzipperContext.php @@ -56,7 +56,7 @@ private function validate(): void } } - public function getZipFullPath(): string + public function getDestinationFilePath(): string { return $this->zipFullPath; } diff --git a/classes/UpgradeTools/Module/Source/ModuleSource.php b/classes/UpgradeTools/Module/Source/ModuleSource.php index 8c15445afe..b96c2f0cbd 100644 --- a/classes/UpgradeTools/Module/Source/ModuleSource.php +++ b/classes/UpgradeTools/Module/Source/ModuleSource.php @@ -4,16 +4,16 @@ class ModuleSource { - /** var string */ + /** @var string */ private $name; - /** var string */ + /** @var string */ private $newVersion; - /** var string */ + /** @var string */ private $path; - /** var bool */ + /** @var bool */ private $unzipable; public function __construct(string $name, string $newVersion, string $path, bool $unzipable) @@ -44,6 +44,7 @@ public function isUnzipable(): bool return $this->unzipable; } + /** @return array */ public function toArray(): array { return [ diff --git a/classes/UpgradeTools/Module/Source/ModuleSourceAggregate.php b/classes/UpgradeTools/Module/Source/ModuleSourceAggregate.php index 471a4b88b6..b8c297cd77 100644 --- a/classes/UpgradeTools/Module/Source/ModuleSourceAggregate.php +++ b/classes/UpgradeTools/Module/Source/ModuleSourceAggregate.php @@ -18,9 +18,6 @@ public function __construct(array $sourceProviders) $this->providers = $sourceProviders; } - /** - * @return ModuleSource[] - */ public function setSourcesIn(ModuleDownloaderContext $moduleContext): void { $updateSources = []; @@ -39,7 +36,7 @@ public function setSourcesIn(ModuleDownloaderContext $moduleContext): void } /** - * @param ModuleSource[] + * @param ModuleSource[] $sources * * @return ModuleSource[] */ diff --git a/tests/unit/UpgradeTools/Module/ModuleDownloaderContextTest.php b/tests/unit/UpgradeTools/Module/ModuleDownloaderContextTest.php index 3bd1e9c28f..03ee387828 100644 --- a/tests/unit/UpgradeTools/Module/ModuleDownloaderContextTest.php +++ b/tests/unit/UpgradeTools/Module/ModuleDownloaderContextTest.php @@ -40,7 +40,7 @@ public function testConstructWithCorrectSettings() $moduleDownloaderContext = new ModuleDownloaderContext($zipFullPath, $moduleInfos); - $this->assertEquals($zipFullPath, $moduleDownloaderContext->getZipFullPath()); + $this->assertEquals($zipFullPath, $moduleDownloaderContext->getDestinationFilePath()); $this->assertEquals('mymodule', $moduleDownloaderContext->getModuleName()); $this->assertEquals(1245, $moduleDownloaderContext->getModuleId()); $this->assertTrue($moduleDownloaderContext->getModuleIsLocal()); @@ -56,7 +56,7 @@ public function testConstructWithCorrectSettingsAndNotIsLocal() $moduleDownloaderContext = new ModuleDownloaderContext($zipFullPath, $moduleInfos); - $this->assertEquals($zipFullPath, $moduleDownloaderContext->getZipFullPath()); + $this->assertEquals($zipFullPath, $moduleDownloaderContext->getDestinationFilePath()); $this->assertEquals('mymodule', $moduleDownloaderContext->getModuleName()); $this->assertEquals(1245, $moduleDownloaderContext->getModuleId()); $this->assertFalse($moduleDownloaderContext->getModuleIsLocal()); diff --git a/tests/unit/UpgradeTools/Module/ModuleDownloaderTest.php b/tests/unit/UpgradeTools/Module/ModuleDownloaderTest.php new file mode 100644 index 0000000000..e60f67b82a --- /dev/null +++ b/tests/unit/UpgradeTools/Module/ModuleDownloaderTest.php @@ -0,0 +1,111 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ + +use PHPUnit\Framework\TestCase; +use PrestaShop\Module\AutoUpgrade\Log\Logger; +use PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader; +use PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloaderContext; +use PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\Source\ModuleSource; +use PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\Source\ModuleSourceAggregate; +use PrestaShop\Module\AutoUpgrade\UpgradeTools\Translator; + +class ModuleDownloaderTest extends TestCase +{ + /** @var ModuleDownloader */ + private $moduleDownloader; + + /** @var PHPUnit_Framework_MockObject_MockObject|Logger|(Logger&PHPUnit_Framework_MockObject_MockObject) */ + private $logger; + + public static function setUpBeforeClass() + { + require_once __DIR__ . '/Source/Provider/ModuleSourceProviderMock.php'; + @mkdir(sys_get_temp_dir() . '/fakeDownloaderDestination'); + } + + protected function setUp() + { + $translator = $this->createMock(Translator::class); + $translator->method('trans') + ->willReturnCallback(function ($message, $parameters = []) { + return vsprintf($message, $parameters); + }); + + $this->logger = $this->createMock(Logger::class); + $this->moduleDownloader = new ModuleDownloader($translator, $this->logger); + } + + public function testModuleDownloaderSucceedsOnFirstTryWithLocalFile() + { + $moduleContext = new ModuleDownloaderContext(sys_get_temp_dir() . '/fakeDownloaderDestination', ['name' => 'mymodule', 'currentVersion' => '1.0.0']); + + $dummyProvider1 = (new ModuleSourceProviderMock())->setSources([ + new ModuleSource('mymodule', '2.0.0', realpath(__DIR__ . '/../../../fixtures/mymodule'), false), + ]); + $moduleSourceList = new ModuleSourceAggregate([$dummyProvider1]); + + $moduleSourceList->setSourcesIn($moduleContext); + + $this->logger->expects($this->once()) + ->method('notice') + ->with('Module mymodule has been successfully downloaded.'); + + $this->moduleDownloader->downloadModule($moduleContext); + } + + public function testModuleDownloaderSucceedsOnFirstTryWithRemoteFile() + { + } + + public function testModuleDownloaderFails() + { + $moduleContext = new ModuleDownloaderContext(sys_get_temp_dir() . '/fakeDownloaderDestination', ['name' => 'mymodule', 'currentVersion' => '1.0.0']); + + $dummyProvider1 = (new ModuleSourceProviderMock())->setSources([ + new ModuleSource('mymodule', '2.0.0', realpath(__DIR__ . '/../../../fixtures/mymodule'), false), + ]); + $moduleSourceList = new ModuleSourceAggregate([$dummyProvider1]); + + $moduleSourceList->setSourcesIn($moduleContext); + + $this->logger->expects($this->once()) + ->method('debug') + ->with('Download of source #1 has failed. %s'); + $this->logger->expects($this->once()) + ->method('warning') + ->with('All download attempts have failed. Check your environment and try again.'); + + $this->moduleDownloader->downloadModule($moduleContext); + } + + public function testModuleDownloaderHandlesFallbacks() + { + } + + public function testDetectionOfXmlFileInDownloadedContents() + { + } +} diff --git a/tests/unit/UpgradeTools/Module/ModuleUnzipperContextTest.php b/tests/unit/UpgradeTools/Module/ModuleUnzipperContextTest.php index 8df52d0ebb..79db556da9 100644 --- a/tests/unit/UpgradeTools/Module/ModuleUnzipperContextTest.php +++ b/tests/unit/UpgradeTools/Module/ModuleUnzipperContextTest.php @@ -38,7 +38,7 @@ public function testConstructWithCorrectSettings() $moduleUnzipperContext = new ModuleUnzipperContext($zipFullPath, $moduleName); - $this->assertEquals($zipFullPath, $moduleUnzipperContext->getZipFullPath()); + $this->assertEquals($zipFullPath, $moduleUnzipperContext->getDestinationFilePath()); $this->assertEquals($moduleName, $moduleUnzipperContext->getModuleName()); }