Skip to content

Commit

Permalink
Plus Module Sources management to current process and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Quetzacoalt91 committed Aug 30, 2024
1 parent 84eea40 commit 49f3ea9
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 87 deletions.
2 changes: 1 addition & 1 deletion classes/Task/Upgrade/UpgradeModules.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
99 changes: 29 additions & 70 deletions classes/UpgradeTools/Module/ModuleDownloader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,100 +41,59 @@ 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;
}

/**
* @throws UpgradeException
*/
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) == '<?xml') {
throw (new UpgradeException($this->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)

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.2.5)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.3.4)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.4.4)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.5.1)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.6)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.7)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (1.7.8)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.0.0)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.

Check failure on line 87 in classes/UpgradeTools/Module/ModuleDownloader.php

View workflow job for this annotation

GitHub Actions / PHPStan (latest)

Method PrestaShop\Module\AutoUpgrade\UpgradeTools\Module\ModuleDownloader::assertDownloadedFileIsCorrect() has no return type specified.
{
$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) == '<?xml') {
throw (new UpgradeException($this->translator->trans('[WARNING] No response from provider.')))->setSeverity(UpgradeException::SEVERITY_WARNING);
}
fclose($downloadedFile);
}
}
23 changes: 20 additions & 3 deletions classes/UpgradeTools/Module/ModuleDownloaderContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -39,22 +43,35 @@ 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'];
}

/**
* @throws LogicException
*/
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;
Expand Down
2 changes: 1 addition & 1 deletion classes/UpgradeTools/Module/ModuleUnzipper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion classes/UpgradeTools/Module/ModuleUnzipperContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private function validate(): void
}
}

public function getZipFullPath(): string
public function getDestinationFilePath(): string
{
return $this->zipFullPath;
}
Expand Down
9 changes: 5 additions & 4 deletions classes/UpgradeTools/Module/Source/ModuleSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -44,6 +44,7 @@ public function isUnzipable(): bool
return $this->unzipable;
}

/** @return array<string, string|boolean> */
public function toArray(): array
{
return [
Expand Down
5 changes: 1 addition & 4 deletions classes/UpgradeTools/Module/Source/ModuleSourceAggregate.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ public function __construct(array $sourceProviders)
$this->providers = $sourceProviders;
}

/**
* @return ModuleSource[]
*/
public function setSourcesIn(ModuleDownloaderContext $moduleContext): void
{
$updateSources = [];
Expand All @@ -39,7 +36,7 @@ public function setSourcesIn(ModuleDownloaderContext $moduleContext): void
}

/**
* @param ModuleSource[]
* @param ModuleSource[] $sources
*
* @return ModuleSource[]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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());
Expand Down
111 changes: 111 additions & 0 deletions tests/unit/UpgradeTools/Module/ModuleDownloaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License 3.0 (AFL-3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <[email protected]>
* @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()
{
}
}
Loading

0 comments on commit 49f3ea9

Please sign in to comment.