diff --git a/composer.json b/composer.json index f9f9caae..05fd2f8f 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento-cloud-patches", "description": "Provides critical fixes for Magento 2 Enterprise Edition", "type": "magento2-component", - "version": "1.1.10", + "version": "1.0.27", "license": "OSL-3.0", "repositories": { "repo.magento.com": { @@ -11,18 +11,17 @@ } }, "require": { - "php": "^8.0", + "php": "^7.2 || ^8.0", "ext-json": "*", - "composer/composer": "^1.9 || ^2.8 || !=2.2.16", + "composer/composer": "^1.4 || ^2.0", "composer/semver": "@stable", - "monolog/monolog": "^2.3 || ^2.7 || ^3.6", - "symfony/config": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", - "symfony/console": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", - "symfony/dependency-injection": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", - "symfony/process": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", + "symfony/config": "^3.3||^4.4||^5.0||^6.0", + "symfony/console": "^2.6||^4.0||^5.0||^6.0", + "symfony/dependency-injection": "^3.3||^4.3||^5.0||^6.0", + "symfony/process": "^2.1 || ^4.1 || ^5.1 || ^5.4 || ^6.4", "symfony/proxy-manager-bridge": "^3.3||^4.3||^5.0||^6.0", - "symfony/yaml": "^4.4 || ^5.1 || ^5.4 || ^6.4 || ^7.2", - "magento/quality-patches": "^1.1.0" + "symfony/yaml": "^3.3||^4.0||^5.0||^6.0", + "monolog/monolog": "^1.25||^2.3||^2.7" }, "require-dev": { "codeception/codeception": "^4.1 || ^5.1", @@ -30,9 +29,9 @@ "codeception/module-db": "^1.0 || ^3.0", "codeception/module-phpbrowser": "^1.0 || ^3.0", "codeception/module-rest": "^1.2 || ^3.0", - "consolidation/robo": "^1.2 || ^3.0 || ^4.0 || ^5.0", + "consolidation/robo": "^1.2 || ^3.0", "phpmd/phpmd": "@stable", - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^8.5 || ^9.5", "squizlabs/php_codesniffer": "^3.0" }, "bin": [ diff --git a/patches.json b/patches.json index c321c13e..ea81ffbb 100644 --- a/patches.json +++ b/patches.json @@ -280,33 +280,6 @@ }, "Enhanced Layout Cache Efficiency (memory usage reduced)": { ">=2.4.4 <2.4.7": "MCLOUD-11514__enhanced_layout_cache_efficiency__2.4.6-p3.patch" - }, - "Patch for CVE-2024-34102 - CosmicSting": { - ">=2.4.4 <2.4.4-p8": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch", - ">=2.4.5 <2.4.5-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch", - ">=2.4.6 <2.4.6-p5": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch", - "2.4.7": "MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch" - }, - "Patch for CVE-2024-34102 - KeyRotation": { - ">=2.4.4 <2.4.4-p10": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch", - ">=2.4.5 <2.4.5-p9": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch", - ">=2.4.6 <2.4.6-p7": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch", - ">=2.4.7 <2.4.7-p2": "MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch" - }, - "Patch for CVE-2025-24434 - Improve-web-api-async": { - ">=2.4.4 <2.4.4-p12": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch", - ">=2.4.5 <2.4.5-p11": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch", - ">=2.4.6 <2.4.6-p9": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch", - ">=2.4.7 <2.4.7-p4": "MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch" - }, - "Patch - Improve-web-api-async-performance": { - ">=2.4.4 <2.4.4-p13 || >=2.4.5 <2.4.5-p12 || >=2.4.6 <2.4.6-p10 || >=2.4.7 <2.4.7-p5 || 2.4.8": "MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch" - }, - "Patch for CVE-2025-47109 - Improve-category-view": { - "2.4.8": "MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch" - }, - "Patch for CVE-2025-47110 - Improve-admin-cache-efficiency": { - ">=2.4.4 <2.4.4-p14 || >=2.4.5 <2.4.5-p13 || >=2.4.6 <2.4.6-p11 || >=2.4.7 <2.4.7-p6 || 2.4.8": "MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch" } }, "magento/module-paypal": { @@ -397,15 +370,6 @@ "magento/magento2-b2b-base": { "Layered navigation filter is present only when product is present on the listing page with enabled Shared catalog": { ">=1.1.5 <1.3.1": "MCLOUD-6923__layered_navigation_filter_is_present_only_when_product_is_present_on_the_listing_page_with_enabled_shared_catalog__2.3.5.patch" - }, - "Fields hydration on company account create request": { - ">=1.3.3 <1.3.3-p11 || >=1.3.4 <1.3.4-p10 || >=1.3.5 <1.3.5-p8 || >=1.4.2 <1.4.2-p3": "B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch" - }, - "Fixes the issue where the file generated after Requisition List export is not removed from the var/ directory": { - ">=1.3.1 <1.3.6": "MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch" - }, - "Fixes the issue where an SQL syntax error occurs due to the non-existence of the REGEXP_LIKE function when attempting to update the company_structure table.": { - "1.5.2": "MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch" } }, "magento/magento2-ee-base": { @@ -446,10 +410,5 @@ "Fix regexp cache tag validation": { ">=103.0.6 <103.0.7": "MCLOUD-10226__fix_regexp_cache_tag_validation__2.4.6.patch" } - }, - "magento/module-catalog-graph-ql": { - "AttributeReader should use Factory for Collection": { - ">=100.4.7 <100.4.8": "ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch" - } } } diff --git a/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch b/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch deleted file mode 100644 index fae4769f..00000000 --- a/patches/ACPT-1876__attribute_reader_should_use_factory_for_collection__2.4.7.patch +++ /dev/null @@ -1,75 +0,0 @@ -From c964bc3248811dc63df6205a1246d383ad4c6e4a Mon Sep 17 00:00:00 2001 -From: Jacob Brown -Date: Wed, 3 Apr 2024 14:07:21 -0500 -Subject: [PATCH] ACPT-1854: AttributeReader should use Factory for Collection - ---- - .../Model/Config/AttributeReader.php | 18 +++++++++++------- - 1 file changed, 11 insertions(+), 7 deletions(-) - -diff --git a/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php b/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php -index ecd83bf61ef0..05acb97e4bd7 100644 ---- a/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php -+++ b/vendor/magento/module-catalog-graph-ql/Model/Config/AttributeReader.php -@@ -9,8 +9,10 @@ - use Magento\Catalog\Model\Product; - use Magento\Catalog\Model\ResourceModel\Eav\Attribute; - use Magento\CatalogGraphQl\Model\Resolver\Products\Attributes\Collection; -+use Magento\CatalogGraphQl\Model\Resolver\Products\Attributes\CollectionFactory; - use Magento\EavGraphQl\Model\Resolver\Query\Type; - use Magento\Framework\App\Config\ScopeConfigInterface; -+use Magento\Framework\App\ObjectManager; - use Magento\Framework\Config\ReaderInterface; - use Magento\Framework\GraphQl\Exception\GraphQlInputException; - use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; -@@ -36,9 +38,9 @@ class AttributeReader implements ReaderInterface - private Type $typeLocator; - - /** -- * @var Collection -+ * @var CollectionFactory - */ -- private Collection $collection; -+ private CollectionFactory $collectionFactory; - - /** - * @var ScopeConfigInterface -@@ -48,18 +50,21 @@ class AttributeReader implements ReaderInterface - /** - * @param MapperInterface $mapper - * @param Type $typeLocator -- * @param Collection $collection -+ * @param Collection $collection @deprecated @see $collectionFactory - * @param ScopeConfigInterface $config -+ * @param CollectionFactory|null $collectionFactory -+ * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function __construct( - MapperInterface $mapper, - Type $typeLocator, - Collection $collection, -- ScopeConfigInterface $config -+ ScopeConfigInterface $config, -+ CollectionFactory $collectionFactory = null, - ) { - $this->mapper = $mapper; - $this->typeLocator = $typeLocator; -- $this->collection = $collection; -+ $this->collectionFactory = $collectionFactory ?? ObjectManager::getInstance()->get(CollectionFactory::class); - $this->config = $config; - } - -@@ -74,12 +79,11 @@ public function __construct( - public function read($scope = null) : array - { - $config = []; -- - if ($this->config->isSetFlag(self::XML_PATH_INCLUDE_DYNAMIC_ATTRIBUTES, ScopeInterface::SCOPE_STORE)) { - $typeNames = $this->mapper->getMappedTypes(Product::ENTITY); - - /** @var Attribute $attribute */ -- foreach ($this->collection->getAttributes() as $attribute) { -+ foreach ($this->collectionFactory->create()->getAttributes() as $attribute) { - $attributeCode = $attribute->getAttributeCode(); - $locatedType = $this->typeLocator->getType($attributeCode, Product::ENTITY) ?: 'String'; - $locatedType = TypeProcessor::NORMALIZED_ANY_TYPE === $locatedType ? 'String' : ucfirst($locatedType); diff --git a/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch b/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch deleted file mode 100644 index c17eab6c..00000000 --- a/patches/B2B-4051__fields_hydration_company_account_create_request__1.3.3.patch +++ /dev/null @@ -1,144 +0,0 @@ -new file mode 100644 ---- /dev/null -+++ b/vendor/magento/module-company/Model/Customer/AccountManagement/CompanyRequestHydrator.php -@@ -0,0 +1,66 @@ -+request = $request; -+ } -+ -+ /** -+ * Get and hydrate company data from HTTP request. -+ * -+ * @return array -+ */ -+ public function getCompanyDataFromRequest(): array -+ { -+ $result = []; -+ $companyData = $this->request->getPost('company', []); -+ foreach ($this->fieldsToSave as $item) { -+ if (isset($companyData[$item])) { -+ $result[$item] = $companyData[$item]; -+ } -+ } -+ -+ return $result; -+ } -+} ---- a/vendor/magento/module-company/Plugin/Customer/Api/AccountManagement.php -+++ b/vendor/magento/module-company/Plugin/Customer/Api/AccountManagement.php -@@ -11,17 +11,13 @@ - use Magento\Customer\Api\CustomerRepositoryInterface; - use Magento\Company\Api\CompanyManagementInterface; - use Magento\Framework\Exception\NoSuchEntityException; -+use Magento\Company\Model\Customer\AccountManagement\CompanyRequestHydrator; - - /** - * Plugin for AccountManagement. Processing company data. - */ - class AccountManagement - { -- /** -- * @var \Magento\Framework\App\Request\Http -- */ -- private $request; -- - /** - * @var \Magento\Company\Model\Email\Sender - */ -@@ -47,30 +43,35 @@ - */ - private $customerRepository; - -+ /** -+ * @var CompanyRequestHydrator -+ */ -+ private $companyRequestHydrator; -+ - /** - * AccountManagement constructor - * -- * @param \Magento\Framework\App\Request\Http $request - * @param \Magento\Company\Model\Email\Sender $companyEmailSender - * @param \Magento\Backend\Model\UrlInterface $urlBuilder - * @param \Magento\Company\Model\Customer\Company $customerCompany - * @param CompanyManagementInterface $companyManagement - * @param CustomerRepositoryInterface $customerRepository -+ * @param CompanyRequestHydrator $companyRequestHydrator - */ - public function __construct( -- \Magento\Framework\App\Request\Http $request, - \Magento\Company\Model\Email\Sender $companyEmailSender, - \Magento\Backend\Model\UrlInterface $urlBuilder, - \Magento\Company\Model\Customer\Company $customerCompany, - CompanyManagementInterface $companyManagement, -- CustomerRepositoryInterface $customerRepository -+ CustomerRepositoryInterface $customerRepository, -+ CompanyRequestHydrator $companyRequestHydrator - ) { -- $this->request = $request; - $this->companyEmailSender = $companyEmailSender; - $this->urlBuilder = $urlBuilder; - $this->customerCompany = $customerCompany; - $this->companyManagement = $companyManagement; - $this->customerRepository = $customerRepository; -+ $this->companyRequestHydrator = $companyRequestHydrator; - } - - /** -@@ -127,11 +128,7 @@ - \Magento\Customer\Api\AccountManagementInterface $subject, - \Magento\Customer\Api\Data\CustomerInterface $result - ) { -- $companyData = $this->request->getPost('company', []); -- if (isset($companyData['status'])) { -- unset($companyData['status']); -- } -- -+ $companyData = $this->companyRequestHydrator->getCompanyDataFromRequest(); - if (is_array($companyData) && !empty($companyData)) { - $jobTitle = $companyData['job_title'] ?? null; - $companyDataObject = $this->customerCompany->createCompany($result, $companyData, $jobTitle); diff --git a/patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch b/patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch deleted file mode 100644 index bcd29915..00000000 --- a/patches/MCLOUD-11623__requisition_list_exports_saved_to_var_directory__2.4.5-p1.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/vendor/magento/module-requisition-list/Controller/Requisition/Export.php b/vendor/magento/module-requisition-list/Controller/Requisition/Export.php -index e8332a7f1091..7eee2f51b7f4 100644 ---- a/vendor/magento/module-requisition-list/Controller/Requisition/Export.php -+++ b/vendor/magento/module-requisition-list/Controller/Requisition/Export.php -@@ -101,9 +101,15 @@ public function execute() - - $fileName = "{$requisitionList->getName()}.{$writer->getFileExtension()}"; - -+ $content = [ -+ 'type' => "string", -+ 'value' => $this->requisitionListExport->export(), -+ 'rm' => true, -+ ]; -+ - return $this->fileFactory->create( - $fileName, -- $this->requisitionListExport->export(), -+ $content, - DirectoryList::VAR_DIR, - $writer->getContentType() - ); - diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch deleted file mode 100644 index 5175edb0..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.4.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index a491a567a37..5e8bef787d2 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -4,3 +4,4 @@ Summary,Summary - Menu,Menu - Account,Account - Settings,Settings -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index 7bf9e0afaf0..00493cc05ba 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -54,3 +54,4 @@ Footer,Footer - "Update to your %store_name shipment","Update to your %store_name shipment" - "Address Book","Address Book" - "Account Information","Account Information" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index 908a4e70140..cc019845b58 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @return \Magento\Framework\Reflection\NameFinder - * - * @deprecated 100.1.0 -+ * @see nothing - */ - private function getNameFinder() - { -@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @throws \Exception - * @throws SerializationException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) -+ * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function _createFromArray($className, $data) - { -@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch deleted file mode 100644 index e5740c26..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.5.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index a491a567a37..5e8bef787d2 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -4,3 +4,4 @@ Summary,Summary - Menu,Menu - Account,Account - Settings,Settings -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index 7bf9e0afaf0..00493cc05ba 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -54,3 +54,4 @@ Footer,Footer - "Update to your %store_name shipment","Update to your %store_name shipment" - "Address Book","Address Book" - "Account Information","Account Information" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index a5e881f4be5..a60f1dd7ba1 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -153,6 +153,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @return \Magento\Framework\Reflection\NameFinder - * - * @deprecated 100.1.0 -+ * @see nothing - */ - private function getNameFinder() - { -@@ -261,6 +262,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - * @throws \Exception - * @throws SerializationException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) -+ * @SuppressWarnings(PHPMD.NPathComplexity) - */ - protected function _createFromArray($className, $data) - { -@@ -268,6 +270,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch deleted file mode 100644 index 1296cc09..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.6.patch +++ /dev/null @@ -1,46 +0,0 @@ -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index a491a567a37..5e8bef787d2 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -4,3 +4,4 @@ Summary,Summary - Menu,Menu - Account,Account - Settings,Settings -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index 7bf9e0afaf0..00493cc05ba 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -54,3 +54,4 @@ Footer,Footer - "Update to your %store_name shipment","Update to your %store_name shipment" - "Address Book","Address Book" - "Account Information","Account Information" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index 9d7fd443508..65987772c23 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -275,6 +275,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch deleted file mode 100644 index 4f23fb7f..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_CosmicSting__2.4.7.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv b/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv -index 2708988e731..885d0056d4b 100644 ---- a/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv -+++ b/vendor/magento/theme-adminhtml-backend/i18n/en_US.csv -@@ -547,3 +547,4 @@ Dashboard,Dashboard - "Web Section","Web Section" - "Store Email Addresses Section","Store Email Addresses Section" - "Email to a Friend","Email to a Friend" -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-blank/i18n/en_US.csv b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -index 025866f654d..cc02ab5ac90 100644 ---- a/vendor/magento/theme-frontend-blank/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-blank/i18n/en_US.csv -@@ -439,3 +439,4 @@ Summary,Summary - Test,Test - test,test - Two,Two -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/theme-frontend-luma/i18n/en_US.csv b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -index e80cb58e679..3d0e8ab2650 100644 ---- a/vendor/magento/theme-frontend-luma/i18n/en_US.csv -+++ b/vendor/magento/theme-frontend-luma/i18n/en_US.csv -@@ -489,3 +489,4 @@ Remove,Remove - Test,Test - test,test - Two,Two -+"Invalid data type","Invalid data type" -diff --git a/vendor/magento/framework/Webapi/ServiceInputProcessor.php b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -index cd7960409e1..df31058ff32 100644 ---- a/vendor/magento/framework/Webapi/ServiceInputProcessor.php -+++ b/vendor/magento/framework/Webapi/ServiceInputProcessor.php -@@ -278,6 +278,12 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface, ResetAf - // convert to string directly to avoid situations when $className is object - // which implements __toString method like \ReflectionObject - $className = (string) $className; -+ if (is_subclass_of($className, \SimpleXMLElement::class) -+ || is_subclass_of($className, \DOMElement::class)) { -+ throw new SerializationException( -+ new Phrase('Invalid data type') -+ ); -+ } - $class = new ClassReflection($className); - if (is_subclass_of($className, self::EXTENSION_ATTRIBUTES_TYPE)) { - $className = substr($className, 0, -strlen('Interface')); -diff --git a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php ---- a/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 022e64b08a88658667bc2d6b922eada2b7910965) -+++ b/vendor/magento/module-jwt-user-token/Model/SecretBasedJwksFactory.php (revision 8d2b0c1c6b421cdcd7f62a48a5edc9b0211d92a2) -@@ -35,6 +35,7 @@ - public function __construct(DeploymentConfig $deploymentConfig, JwkFactory $jwkFactory) - { - $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get('crypt/key'))); -+ $this->keys = [end($this->keys)]; - //Making sure keys are large enough. - foreach ($this->keys as &$key) { - $key = str_pad($key, 2048, '&', STR_PAD_BOTH); diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch deleted file mode 100644 index 94adc617..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.4.patch +++ /dev/null @@ -1,163 +0,0 @@ -diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -new file mode 100644 -index 0000000000000..8777f99139edc ---- /dev/null -+++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -@@ -0,0 +1,141 @@ -+encryptor = $encryptor; -+ $this->cache = $cache; -+ $this->writer = $writer; -+ $this->random = $random; -+ -+ parent::__construct(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function configure() -+ { -+ $this->setName('encryption:key:change'); -+ $this->setDescription('Change the encryption key inside the env.php file.'); -+ $this->addOption( -+ 'key', -+ 'k', -+ InputOption::VALUE_OPTIONAL, -+ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' -+ ); -+ -+ parent::configure(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function execute(InputInterface $input, OutputInterface $output) -+ { -+ try { -+ $key = $input->getOption('key'); -+ -+ if (!empty($key)) { -+ $this->encryptor->validateKey($key); -+ } -+ -+ $this->updateEncryptionKey($key); -+ $this->cache->clean(); -+ -+ $output->writeln('Encryption key has been updated successfully.'); -+ -+ return Cli::RETURN_SUCCESS; -+ } catch (\Exception $e) { -+ $output->writeln('' . $e->getMessage() . ''); -+ return Cli::RETURN_FAILURE; -+ } -+ } -+ -+ /** -+ * Update encryption key -+ * -+ * @param string|null $key -+ * @return void -+ * @throws FileSystemException -+ */ -+ private function updateEncryptionKey(string $key = null): void -+ { -+ // prepare new key, encryptor and new configuration segment -+ if (!$this->writer->checkIfWritable()) { -+ throw new FileSystemException(__('Deployment configuration file is not writable.')); -+ } -+ -+ if (null === $key) { -+ // md5() here is not for cryptographic use. It used for generate encryption key itself -+ // and do not encrypt any passwords -+ // phpcs:ignore Magento2.Security.InsecureFunction -+ $key = md5($this->random->getRandomString(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE)); -+ } -+ -+ $this->encryptor->setNewKey($key); -+ -+ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); -+ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); -+ -+ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; -+ -+ $this->writer->saveConfig($configData); -+ } -+} -diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml -index b4e471f4e40ef..495234759a7f8 100644 ---- a/vendor/magento/module-encryption-key/etc/di.xml -+++ b/vendor/magento/module-encryption-key/etc/di.xml -@@ -11,4 +11,11 @@ - Magento\Config\Model\Config\Structure\Proxy - - -+ -+ -+ -+ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand -+ -+ -+ - diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch deleted file mode 100644 index 29adfc38..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.5.patch +++ /dev/null @@ -1,162 +0,0 @@ -diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -new file mode 100644 -index 0000000000000..351379552e104 ---- /dev/null -+++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -@@ -0,0 +1,140 @@ -+encryptor = $encryptor; -+ $this->cache = $cache; -+ $this->writer = $writer; -+ $this->random = $random; -+ -+ parent::__construct(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function configure() -+ { -+ $this->setName('encryption:key:change'); -+ $this->setDescription('Change the encryption key inside the env.php file.'); -+ $this->addOption( -+ 'key', -+ 'k', -+ InputOption::VALUE_OPTIONAL, -+ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' -+ ); -+ -+ parent::configure(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function execute(InputInterface $input, OutputInterface $output) -+ { -+ try { -+ $key = $input->getOption('key'); -+ -+ if (!empty($key)) { -+ $this->encryptor->validateKey($key); -+ } -+ -+ $this->updateEncryptionKey($key); -+ $this->cache->clean(); -+ -+ $output->writeln('Encryption key has been updated successfully.'); -+ -+ return Cli::RETURN_SUCCESS; -+ } catch (\Exception $e) { -+ $output->writeln('' . $e->getMessage() . ''); -+ return Cli::RETURN_FAILURE; -+ } -+ } -+ -+ /** -+ * Update encryption key -+ * -+ * @param string|null $key -+ * @return void -+ * @throws FileSystemException -+ */ -+ private function updateEncryptionKey(string $key = null): void -+ { -+ // prepare new key, encryptor and new configuration segment -+ if (!$this->writer->checkIfWritable()) { -+ throw new FileSystemException(__('Deployment configuration file is not writable.')); -+ } -+ -+ if (null === $key) { -+ // md5() here is not for cryptographic use. It used for generate encryption key itself -+ // and do not encrypt any passwords -+ // phpcs:ignore Magento2.Security.InsecureFunction -+ $key = md5($this->random->getRandomString(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE)); -+ } -+ $this->encryptor->setNewKey($key); -+ -+ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); -+ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); -+ -+ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; -+ -+ $this->writer->saveConfig($configData); -+ } -+} -diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml -index b4e471f4e40ef..495234759a7f8 100644 ---- a/vendor/magento/module-encryption-key/etc/di.xml -+++ b/vendor/magento/module-encryption-key/etc/di.xml -@@ -11,4 +11,11 @@ - Magento\Config\Model\Config\Structure\Proxy - - -+ -+ -+ -+ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand -+ -+ -+ - diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch deleted file mode 100644 index c5ba4386..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.6.patch +++ /dev/null @@ -1,159 +0,0 @@ -diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -new file mode 100644 -index 0000000000000..0e4995b847893 ---- /dev/null -+++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -@@ -0,0 +1,137 @@ -+encryptor = $encryptor; -+ $this->cache = $cache; -+ $this->writer = $writer; -+ $this->random = $random; -+ -+ parent::__construct(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function configure() -+ { -+ $this->setName('encryption:key:change'); -+ $this->setDescription('Change the encryption key inside the env.php file.'); -+ $this->addOption( -+ 'key', -+ 'k', -+ InputOption::VALUE_OPTIONAL, -+ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' -+ ); -+ -+ parent::configure(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function execute(InputInterface $input, OutputInterface $output) -+ { -+ try { -+ $key = $input->getOption('key'); -+ -+ if (!empty($key)) { -+ $this->encryptor->validateKey($key); -+ } -+ -+ $this->updateEncryptionKey($key); -+ $this->cache->clean(); -+ -+ $output->writeln('Encryption key has been updated successfully.'); -+ -+ return Command::SUCCESS; -+ } catch (\Exception $e) { -+ $output->writeln('' . $e->getMessage() . ''); -+ return Command::FAILURE; -+ } -+ } -+ -+ /** -+ * Update encryption key -+ * -+ * @param string|null $key -+ * @return void -+ * @throws FileSystemException -+ */ -+ private function updateEncryptionKey(string $key = null): void -+ { -+ // prepare new key, encryptor and new configuration segment -+ if (!$this->writer->checkIfWritable()) { -+ throw new FileSystemException(__('Deployment configuration file is not writable.')); -+ } -+ -+ if (null === $key) { -+ // md5() here is not for cryptographic use. It used for generate encryption key itself -+ // and do not encrypt any passwords -+ // phpcs:ignore Magento2.Security.InsecureFunction -+ $key = md5($this->random->getRandomString(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE)); -+ } -+ -+ $this->encryptor->setNewKey($key); -+ -+ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); -+ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); -+ -+ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; -+ -+ $this->writer->saveConfig($configData); -+ } -+} -diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml -index b4e471f4e40ef..495234759a7f8 100644 ---- a/vendor/magento/module-encryption-key/etc/di.xml -+++ b/vendor/magento/module-encryption-key/etc/di.xml -@@ -11,4 +11,11 @@ - Magento\Config\Model\Config\Structure\Proxy - - -+ -+ -+ -+ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand -+ -+ -+ - diff --git a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch b/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch deleted file mode 100644 index 0c8e1fca..00000000 --- a/patches/MCLOUD-12969__Patch_for_CVE_2024_34102_KeyRotation__2.4.7.patch +++ /dev/null @@ -1,157 +0,0 @@ -diff --git a/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -new file mode 100644 -index 0000000000000..cd6ffb4323163 ---- /dev/null -+++ b/vendor/magento/module-encryption-key/Console/Command/UpdateEncryptionKeyCommand.php -@@ -0,0 +1,135 @@ -+encryptor = $encryptor; -+ $this->cache = $cache; -+ $this->writer = $writer; -+ $this->random = $random; -+ -+ parent::__construct(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function configure() -+ { -+ $this->setName('encryption:key:change'); -+ $this->setDescription('Change the encryption key inside the env.php file.'); -+ $this->addOption( -+ 'key', -+ 'k', -+ InputOption::VALUE_OPTIONAL, -+ 'Key has to be a 32 characters long string. If not provided, a random key will be generated.' -+ ); -+ -+ parent::configure(); -+ } -+ -+ /** -+ * @inheritDoc -+ */ -+ protected function execute(InputInterface $input, OutputInterface $output) -+ { -+ try { -+ $key = $input->getOption('key'); -+ -+ if (!empty($key)) { -+ $this->encryptor->validateKey($key); -+ } -+ -+ $this->updateEncryptionKey($key); -+ $this->cache->clean(); -+ -+ $output->writeln('Encryption key has been updated successfully.'); -+ -+ return Command::SUCCESS; -+ } catch (\Exception $e) { -+ $output->writeln('' . $e->getMessage() . ''); -+ return Command::FAILURE; -+ } -+ } -+ -+ /** -+ * Update encryption key -+ * -+ * @param string|null $key -+ * @return void -+ * @throws FileSystemException -+ */ -+ private function updateEncryptionKey(string $key = null): void -+ { -+ // prepare new key, encryptor and new configuration segment -+ if (!$this->writer->checkIfWritable()) { -+ throw new FileSystemException(__('Deployment configuration file is not writable.')); -+ } -+ -+ if (null === $key) { -+ $key = ConfigOptionsListConstants::STORE_KEY_ENCODED_RANDOM_STRING_PREFIX . -+ $this->random->getRandomBytes(ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE); -+ } -+ -+ $this->encryptor->setNewKey($key); -+ -+ $encryptSegment = new ConfigData(ConfigFilePool::APP_ENV); -+ $encryptSegment->set(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY, $this->encryptor->exportKeys()); -+ -+ $configData = [$encryptSegment->getFileKey() => $encryptSegment->getData()]; -+ -+ $this->writer->saveConfig($configData); -+ } -+} -diff --git a/vendor/magento/module-encryption-key/etc/di.xml b/vendor/magento/module-encryption-key/etc/di.xml -index b4e471f4e40ef..495234759a7f8 100644 ---- a/vendor/magento/module-encryption-key/etc/di.xml -+++ b/vendor/magento/module-encryption-key/etc/di.xml -@@ -11,4 +11,11 @@ - Magento\Config\Model\Config\Structure\Proxy - - -+ -+ -+ -+ Magento\EncryptionKey\Console\Command\UpdateEncryptionKeyCommand -+ -+ -+ - diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch deleted file mode 100644 index fe8f0d19..00000000 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.4.patch +++ /dev/null @@ -1,1096 +0,0 @@ -diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php -index 6e0aac11d8e9..27c2bf4051cc 100644 ---- a/vendor/magento/module-customer/Model/AccountManagement.php -+++ b/vendor/magento/module-customer/Model/AccountManagement.php -@@ -876,11 +876,6 @@ public function getConfirmationStatus($customerId) - */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') - { -- $groupId = $customer->getGroupId(); -- if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { -- $customer->setGroupId(null); -- } -- - if ($password !== null) { - $this->checkPasswordStrength($password); - $customerEmail = $customer->getEmail(); -diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php -index 02a05705b57e..8b4f78ab26c7 100644 ---- a/vendor/magento/module-customer/Model/AccountManagementApi.php -+++ b/vendor/magento/module-customer/Model/AccountManagementApi.php -@@ -6,16 +6,127 @@ - - namespace Magento\Customer\Model; - -+use Magento\Customer\Api\AddressRepositoryInterface; -+use Magento\Customer\Api\CustomerMetadataInterface; -+use Magento\Customer\Api\CustomerRepositoryInterface; - use Magento\Customer\Api\Data\CustomerInterface; -+use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory; -+use Magento\Customer\Helper\View as CustomerViewHelper; -+use Magento\Customer\Model\Config\Share as ConfigShare; -+use Magento\Customer\Model\Customer as CustomerModel; -+use Magento\Customer\Model\Metadata\Validator; -+use Magento\Framework\Api\ExtensibleDataObjectConverter; -+use Magento\Framework\App\Config\ScopeConfigInterface; -+use Magento\Framework\AuthorizationInterface; -+use Magento\Framework\DataObjectFactory as ObjectFactory; -+use Magento\Framework\Encryption\EncryptorInterface as Encryptor; -+use Magento\Framework\Event\ManagerInterface; -+use Magento\Framework\Exception\AuthorizationException; -+use Magento\Framework\Mail\Template\TransportBuilder; -+use Magento\Framework\Math\Random; -+use Magento\Framework\Reflection\DataObjectProcessor; -+use Magento\Framework\Registry; -+use Magento\Framework\Stdlib\DateTime; -+use Magento\Framework\Stdlib\StringUtils as StringHelper; -+use Magento\Store\Model\StoreManagerInterface; -+use Psr\Log\LoggerInterface as PsrLogger; - - /** - * Account Management service implementation for external API access. -+ * - * Handle various customer account actions. - * - * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class AccountManagementApi extends AccountManagement - { -+ /** -+ * @var AuthorizationInterface -+ */ -+ private $authorization; -+ -+ /** -+ * @param CustomerFactory $customerFactory -+ * @param ManagerInterface $eventManager -+ * @param StoreManagerInterface $storeManager -+ * @param Random $mathRandom -+ * @param Validator $validator -+ * @param ValidationResultsInterfaceFactory $validationResultsDataFactory -+ * @param AddressRepositoryInterface $addressRepository -+ * @param CustomerMetadataInterface $customerMetadataService -+ * @param CustomerRegistry $customerRegistry -+ * @param PsrLogger $logger -+ * @param Encryptor $encryptor -+ * @param ConfigShare $configShare -+ * @param StringHelper $stringHelper -+ * @param CustomerRepositoryInterface $customerRepository -+ * @param ScopeConfigInterface $scopeConfig -+ * @param TransportBuilder $transportBuilder -+ * @param DataObjectProcessor $dataProcessor -+ * @param Registry $registry -+ * @param CustomerViewHelper $customerViewHelper -+ * @param DateTime $dateTime -+ * @param CustomerModel $customerModel -+ * @param ObjectFactory $objectFactory -+ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter -+ * @param AuthorizationInterface $authorization -+ * @SuppressWarnings(PHPMD.ExcessiveParameterList) -+ */ -+ public function __construct( -+ CustomerFactory $customerFactory, -+ ManagerInterface $eventManager, -+ StoreManagerInterface $storeManager, -+ Random $mathRandom, -+ Validator $validator, -+ ValidationResultsInterfaceFactory $validationResultsDataFactory, -+ AddressRepositoryInterface $addressRepository, -+ CustomerMetadataInterface $customerMetadataService, -+ CustomerRegistry $customerRegistry, -+ PsrLogger $logger, -+ Encryptor $encryptor, -+ ConfigShare $configShare, -+ StringHelper $stringHelper, -+ CustomerRepositoryInterface $customerRepository, -+ ScopeConfigInterface $scopeConfig, -+ TransportBuilder $transportBuilder, -+ DataObjectProcessor $dataProcessor, -+ Registry $registry, -+ CustomerViewHelper $customerViewHelper, -+ DateTime $dateTime, -+ CustomerModel $customerModel, -+ ObjectFactory $objectFactory, -+ ExtensibleDataObjectConverter $extensibleDataObjectConverter, -+ AuthorizationInterface $authorization -+ ) { -+ $this->authorization = $authorization; -+ parent::__construct( -+ $customerFactory, -+ $eventManager, -+ $storeManager, -+ $mathRandom, -+ $validator, -+ $validationResultsDataFactory, -+ $addressRepository, -+ $customerMetadataService, -+ $customerRegistry, -+ $logger, -+ $encryptor, -+ $configShare, -+ $stringHelper, -+ $customerRepository, -+ $scopeConfig, -+ $transportBuilder, -+ $dataProcessor, -+ $registry, -+ $customerViewHelper, -+ $dateTime, -+ $customerModel, -+ $objectFactory, -+ $extensibleDataObjectConverter -+ ); -+ } -+ - /** - * @inheritDoc - * -@@ -23,9 +134,30 @@ class AccountManagementApi extends AccountManagement - */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') - { -+ $this->validateCustomerRequest($customer); - $customer = parent::createAccount($customer, $password, $redirectUrl); - $customer->setConfirmation(null); - - return $customer; - } -+ -+ /** -+ * Validate anonymous request -+ * -+ * @param CustomerInterface $customer -+ * @return void -+ * @throws AuthorizationException -+ */ -+ private function validateCustomerRequest(CustomerInterface $customer): void -+ { -+ $groupId = $customer->getGroupId(); -+ if (isset($groupId) && -+ !$this->authorization->isAllowed(self::ADMIN_RESOURCE) -+ ) { -+ $params = ['resources' => self::ADMIN_RESOURCE]; -+ throw new AuthorizationException( -+ __("The consumer isn't authorized to access %resources.", $params) -+ ); -+ } -+ } - } -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -new file mode 100644 -index 000000000000..295b33d2db14 ---- /dev/null -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -0,0 +1,89 @@ -+authorization = $authorization; -+ } -+ -+ /** -+ * Validate groupId for anonymous request -+ * -+ * @param MassSchedule $massSchedule -+ * @param string $topic -+ * @param array $entitiesArray -+ * @param string|null $groupId -+ * @param string|null $userId -+ * @return null -+ * @throws AuthorizationException -+ * @SuppressWarnings(PHPMD.UnusedFormalParameter) -+ */ -+ public function beforePublishMass( -+ MassSchedule $massSchedule, -+ string $topic, -+ array $entitiesArray, -+ string $groupId = null, -+ string $userId = null -+ ) { -+ // only apply the plugin on account create. -+ if ($topic !== self::TOPIC_NAME) { -+ return; -+ } -+ -+ foreach ($entitiesArray as $entityParams) { -+ foreach ($entityParams as $entity) { -+ if ($entity instanceof CustomerInterface) { -+ $groupId = $entity->getGroupId(); -+ if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { -+ $params = ['resources' => self::ADMIN_RESOURCE]; -+ throw new AuthorizationException( -+ __("The consumer isn't authorized to access %resources.", $params) -+ ); -+ } -+ } -+ } -+ } -+ return null; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php -new file mode 100644 -index 000000000000..074d40021a18 ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php -@@ -0,0 +1,421 @@ -+customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); -+ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); -+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); -+ $this->random = $this->createMock(Random::class); -+ $this->validator = $this->createMock(Validator::class); -+ $this->validationResultsInterfaceFactory = $this->createMock( -+ ValidationResultsInterfaceFactory::class -+ ); -+ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); -+ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); -+ $this->customerRegistry = $this->createMock(CustomerRegistry::class); -+ -+ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); -+ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); -+ $this->share = $this->createMock(Share::class); -+ $this->string = $this->createMock(StringUtils::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->transportBuilder = $this->createMock(TransportBuilder::class); -+ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); -+ $this->registry = $this->createMock(Registry::class); -+ $this->customerViewHelper = $this->createMock(View::class); -+ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); -+ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); -+ $this->objectFactory = $this->createMock(DataObjectFactory::class); -+ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); -+ $this->extensibleDataObjectConverter = $this->createMock( -+ ExtensibleDataObjectConverter::class -+ ); -+ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); -+ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) -+ ->onlyMethods(['addData', 'setData']) -+ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); -+ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); -+ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); -+ -+ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) -+ ->disableOriginalConstructor() -+ ->onlyMethods(['create']) -+ ->getMock(); -+ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->authorizationMock = $this->createMock(Authorization::class); -+ $this->objectManagerHelper = new ObjectManagerHelper($this); -+ $this->accountManagement = $this->objectManagerHelper->getObject( -+ AccountManagementApi::class, -+ [ -+ 'customerFactory' => $this->customerFactory, -+ 'eventManager' => $this->manager, -+ 'storeManager' => $this->storeManager, -+ 'mathRandom' => $this->random, -+ 'validator' => $this->validator, -+ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, -+ 'addressRepository' => $this->addressRepository, -+ 'customerMetadataService' => $this->customerMetadata, -+ 'customerRegistry' => $this->customerRegistry, -+ 'logger' => $this->logger, -+ 'encryptor' => $this->encryptor, -+ 'configShare' => $this->share, -+ 'stringHelper' => $this->string, -+ 'customerRepository' => $this->customerRepository, -+ 'scopeConfig' => $this->scopeConfig, -+ 'transportBuilder' => $this->transportBuilder, -+ 'dataProcessor' => $this->dataObjectProcessor, -+ 'registry' => $this->registry, -+ 'customerViewHelper' => $this->customerViewHelper, -+ 'dateTime' => $this->dateTime, -+ 'customerModel' => $this->customer, -+ 'objectFactory' => $this->objectFactory, -+ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, -+ 'dateTimeFactory' => $this->dateTimeFactory, -+ 'accountConfirmation' => $this->accountConfirmation, -+ 'sessionManager' => $this->sessionManager, -+ 'saveHandler' => $this->saveHandler, -+ 'visitorCollectionFactory' => $this->visitorCollectionFactory, -+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, -+ 'addressRegistry' => $this->addressRegistryMock, -+ 'allowedCountriesReader' => $this->allowedCountriesReader, -+ 'authorization' => $this->authorizationMock -+ ] -+ ); -+ $this->accountManagementMock = $this->createMock(AccountManagement::class); -+ -+ $this->storeMock = $this->getMockBuilder( -+ StoreInterface::class -+ )->disableOriginalConstructor() -+ ->getMock(); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @throws LocalizedException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforeCreateAccount( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ -+ $customer = $this->getMockBuilder(CustomerInterface::class) -+ ->addMethods(['setData']) -+ ->getMockForAbstractClass(); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $customer->method('getWebsiteId')->willReturn(2); -+ $customer->method('getStoreId')->willReturn(1); -+ $customer->method('setData')->willReturn(1); -+ -+ $this->customerRepository->method('get')->willReturn($customer); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $this->customerRepository->method('save')->willReturn($customer); -+ -+ if (!$willThrowException) { -+ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); -+ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); -+ $this->random->method('getUniqueHash')->willReturn('testabc'); -+ $date = $this->getMockBuilder(\DateTime::class) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory->expects(static::once()) -+ ->method('create') -+ ->willReturn($date); -+ $date->expects(static::once()) -+ ->method('format') -+ ->with('Y-m-d H:i:s') -+ ->willReturn('2015-01-01 00:00:00'); -+ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); -+ $this->storeManager->method('getStores') -+ ->willReturn([$this->storeMock]); -+ } -+ $this->accountManagement->createAccount($customer); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -index 8ff6a8585212..cbe0a18e4b17 100644 ---- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - $defaultGroupId = 1; -- $requestedGroupId = 3; - - $datetime = $this->prepareDateTimeFactory(); - -@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void - return null; - } - })); -- $customer->expects($this->atLeastOnce()) -- ->method('getGroupId') -- ->willReturn($requestedGroupId); - $customer - ->method('setGroupId') - ->willReturnOnConsecutiveCalls(null, $defaultGroupId); -diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -new file mode 100644 -index 000000000000..107df2c2863e ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -@@ -0,0 +1,112 @@ -+authorizationMock = $this->createMock(Authorization::class); -+ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ -+ 'authorization' => $this->authorizationMock -+ ]); -+ $this->massScheduleMock = $this->createMock(MassSchedule::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforePublishMass( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $customer = $this->getMockForAbstractClass(CustomerInterface::class); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $entitiesArray = [ -+ [$customer, 'Password1', ''] -+ ]; -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ $this->plugin->beforePublishMass( -+ $this->massScheduleMock, -+ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', -+ $entitiesArray, -+ '', -+ '' -+ ); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json -index 2d76da56bff7..ff34d423c2da 100644 ---- a/vendor/magento/module-customer/composer.json -+++ b/vendor/magento/module-customer/composer.json -@@ -29,5 +29,6 @@ - "suggest": { - "magento/module-cookie": "100.4.*", - "magento/module-customer-sample-data": "Sample Data version: 100.4.*", -- "magento/module-webapi": "100.4.*" -+ "magento/module-webapi": "100.4.*", -+ "magento/module-asynchronous-operations": "100.4.*" - }, -diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index 156986b7b4a3..120a8dda8aec 100644 ---- a/vendor/magento/module-customer/etc/di.xml -+++ b/vendor/magento/module-customer/etc/di.xml -@@ -560,4 +560,9 @@ - - - -+ -+ -+ - -diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea19..a7cce5b03a26 100644 ---- a/vendor/magento/module-quote/etc/webapi.xml -+++ b/vendor/magento/module-quote/etc/webapi.xml -@@ -98,6 +98,9 @@ - - - -+ -+ %customer_id% -+ - - - -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 8601e5011bda..93555559ac9a 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -8,10 +8,12 @@ - - namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - -+use Magento\Framework\Api\SimpleDataObjectConverter; - use Magento\Framework\App\ObjectManager; - use Magento\Framework\Exception\AuthorizationException; - use Magento\Framework\Exception\InputException; - use Magento\Framework\Exception\LocalizedException; -+use Magento\Framework\Reflection\MethodsMap; - use Magento\Framework\Webapi\Exception; - use Magento\Framework\Webapi\Rest\Request as RestRequest; - use Magento\Framework\Webapi\ServiceInputProcessor; -@@ -24,6 +26,8 @@ - - /** - * This class is responsible for retrieving resolved input data -+ * -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class InputParamsResolver - { -@@ -61,6 +65,11 @@ class InputParamsResolver - */ - private $inputArraySizeLimitValue; - -+ /** -+ * @var MethodsMap -+ */ -+ private $methodsMap; -+ - /** - * Initialize dependencies. - * -@@ -72,6 +81,7 @@ class InputParamsResolver - * @param WebapiInputParamsResolver $inputParamsResolver - * @param bool $isBulk - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue -+ * @param MethodsMap|null $methodsMap - */ - public function __construct( - RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( - RequestValidator $requestValidator, - WebapiInputParamsResolver $inputParamsResolver, - bool $isBulk = false, -- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null -+ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, -+ ?MethodsMap $methodsMap = null - ) { - $this->request = $request; - $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( - $this->isBulk = $isBulk; - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); -+ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() -+ ->get(MethodsMap::class); - } - - /** -@@ -113,12 +126,19 @@ public function resolve() - - $this->requestValidator->validate(); - $webapiResolvedParams = []; -+ $inputData = $this->getInputData(); - $route = $this->getRoute(); - $routeServiceClass = $route->getServiceClass(); - $routeServiceMethod = $route->getServiceMethod(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - -- foreach ($this->getInputData() as $key => $singleEntityParams) { -+ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); -+ -+ foreach ($inputData as $key => $singleEntityParams) { -+ if (!is_array($singleEntityParams)) { -+ continue; -+ } -+ - $webapiResolvedParams[$key] = $this->resolveBulkItemParams( - $singleEntityParams, - $routeServiceClass, -@@ -142,11 +162,22 @@ public function getInputData() - $inputData = $this->request->getRequestData(); - - $httpMethod = $this->request->getHttpMethod(); -- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { -+ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { - $requestBodyParams = $this->request->getBodyParams(); - $inputData = array_merge($requestBodyParams, $inputData); - } -- return $inputData; -+ -+ return array_map(function ($singleEntityParams) { -+ if (is_array($singleEntityParams)) { -+ $singleEntityParams = $this->filterInputData($singleEntityParams); -+ $singleEntityParams = $this->paramsOverrider->override( -+ $singleEntityParams, -+ $this->getRoute()->getParameters() -+ ); -+ } -+ -+ return $singleEntityParams; -+ }, $inputData); - } - - /** -@@ -179,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s - { - return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); - } -+ -+ /** -+ * Validates InputData -+ * -+ * @param array $inputData -+ * @return array -+ */ -+ private function filterInputData(array $inputData): array -+ { -+ $result = []; -+ -+ $data = array_filter($inputData, function ($k) use (&$result) { -+ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; -+ return !isset($result[$key]) && ($result[$key] = true); -+ }, ARRAY_FILTER_USE_KEY); -+ -+ return array_map(function ($value) { -+ return is_array($value) ? $this->filterInputData($value) : $value; -+ }, $data); -+ } -+ -+ /** -+ * Validate that parameters are really used in the current request. -+ * -+ * @param string $serviceClassName -+ * @param string $serviceMethodName -+ * @param array $paramOverriders -+ */ -+ private function validateParameters( -+ string $serviceClassName, -+ string $serviceMethodName, -+ array $paramOverriders -+ ): void { -+ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); -+ foreach ($paramOverriders as $key => $param) { -+ $arrayKeys = explode('.', $param ?? ''); -+ $value = array_shift($arrayKeys); -+ -+ foreach ($methodParams as $serviceMethodParam) { -+ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; -+ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; -+ -+ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); -+ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { -+ if (count($arrayKeys) > 0) { -+ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); -+ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); -+ } -+ unset($paramOverriders[$key]); -+ break; -+ } -+ } -+ } -+ -+ if (!empty($paramOverriders)) { -+ $message = 'The current request does not expect the next parameters: ' -+ . implode(', ', $paramOverriders); -+ throw new \UnexpectedValueException(__($message)->__toString()); -+ } -+ } - } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index ce9e4ee94178..e08fe0388cfb 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -10,10 +10,13 @@ - - class GuestCartManagementTest extends WebapiAbstract - { -- const SERVICE_VERSION = 'V1'; -- const SERVICE_NAME = 'quoteGuestCartManagementV1'; -- const RESOURCE_PATH = '/V1/guest-carts/'; -+ public const SERVICE_VERSION = 'V1'; -+ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; -+ public const RESOURCE_PATH = '/V1/guest-carts/'; - -+ /** -+ * @var array List of created quotes -+ */ - protected $createdQuotes = []; - - /** -@@ -339,7 +342,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index efc7e669b360..18ffe842c794 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -109,3 +109,4 @@ app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/ - app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php - app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php - app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch deleted file mode 100644 index 53e4e8ea..00000000 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.5.patch +++ /dev/null @@ -1,1092 +0,0 @@ -diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php -index d70058aef445..513c0b0717e8 100644 ---- a/vendor/magento/module-customer/Model/AccountManagement.php -+++ b/vendor/magento/module-customer/Model/AccountManagement.php -@@ -879,11 +879,6 @@ public function getConfirmationStatus($customerId) - */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') - { -- $groupId = $customer->getGroupId(); -- if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { -- $customer->setGroupId(null); -- } -- - if ($password !== null) { - $this->checkPasswordStrength($password); - $customerEmail = $customer->getEmail(); -diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php -index 02a05705b57e..8b4f78ab26c7 100644 ---- a/vendor/magento/module-customer/Model/AccountManagementApi.php -+++ b/vendor/magento/module-customer/Model/AccountManagementApi.php -@@ -6,16 +6,127 @@ - - namespace Magento\Customer\Model; - -+use Magento\Customer\Api\AddressRepositoryInterface; -+use Magento\Customer\Api\CustomerMetadataInterface; -+use Magento\Customer\Api\CustomerRepositoryInterface; - use Magento\Customer\Api\Data\CustomerInterface; -+use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory; -+use Magento\Customer\Helper\View as CustomerViewHelper; -+use Magento\Customer\Model\Config\Share as ConfigShare; -+use Magento\Customer\Model\Customer as CustomerModel; -+use Magento\Customer\Model\Metadata\Validator; -+use Magento\Framework\Api\ExtensibleDataObjectConverter; -+use Magento\Framework\App\Config\ScopeConfigInterface; -+use Magento\Framework\AuthorizationInterface; -+use Magento\Framework\DataObjectFactory as ObjectFactory; -+use Magento\Framework\Encryption\EncryptorInterface as Encryptor; -+use Magento\Framework\Event\ManagerInterface; -+use Magento\Framework\Exception\AuthorizationException; -+use Magento\Framework\Mail\Template\TransportBuilder; -+use Magento\Framework\Math\Random; -+use Magento\Framework\Reflection\DataObjectProcessor; -+use Magento\Framework\Registry; -+use Magento\Framework\Stdlib\DateTime; -+use Magento\Framework\Stdlib\StringUtils as StringHelper; -+use Magento\Store\Model\StoreManagerInterface; -+use Psr\Log\LoggerInterface as PsrLogger; - - /** - * Account Management service implementation for external API access. -+ * - * Handle various customer account actions. - * - * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class AccountManagementApi extends AccountManagement - { -+ /** -+ * @var AuthorizationInterface -+ */ -+ private $authorization; -+ -+ /** -+ * @param CustomerFactory $customerFactory -+ * @param ManagerInterface $eventManager -+ * @param StoreManagerInterface $storeManager -+ * @param Random $mathRandom -+ * @param Validator $validator -+ * @param ValidationResultsInterfaceFactory $validationResultsDataFactory -+ * @param AddressRepositoryInterface $addressRepository -+ * @param CustomerMetadataInterface $customerMetadataService -+ * @param CustomerRegistry $customerRegistry -+ * @param PsrLogger $logger -+ * @param Encryptor $encryptor -+ * @param ConfigShare $configShare -+ * @param StringHelper $stringHelper -+ * @param CustomerRepositoryInterface $customerRepository -+ * @param ScopeConfigInterface $scopeConfig -+ * @param TransportBuilder $transportBuilder -+ * @param DataObjectProcessor $dataProcessor -+ * @param Registry $registry -+ * @param CustomerViewHelper $customerViewHelper -+ * @param DateTime $dateTime -+ * @param CustomerModel $customerModel -+ * @param ObjectFactory $objectFactory -+ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter -+ * @param AuthorizationInterface $authorization -+ * @SuppressWarnings(PHPMD.ExcessiveParameterList) -+ */ -+ public function __construct( -+ CustomerFactory $customerFactory, -+ ManagerInterface $eventManager, -+ StoreManagerInterface $storeManager, -+ Random $mathRandom, -+ Validator $validator, -+ ValidationResultsInterfaceFactory $validationResultsDataFactory, -+ AddressRepositoryInterface $addressRepository, -+ CustomerMetadataInterface $customerMetadataService, -+ CustomerRegistry $customerRegistry, -+ PsrLogger $logger, -+ Encryptor $encryptor, -+ ConfigShare $configShare, -+ StringHelper $stringHelper, -+ CustomerRepositoryInterface $customerRepository, -+ ScopeConfigInterface $scopeConfig, -+ TransportBuilder $transportBuilder, -+ DataObjectProcessor $dataProcessor, -+ Registry $registry, -+ CustomerViewHelper $customerViewHelper, -+ DateTime $dateTime, -+ CustomerModel $customerModel, -+ ObjectFactory $objectFactory, -+ ExtensibleDataObjectConverter $extensibleDataObjectConverter, -+ AuthorizationInterface $authorization -+ ) { -+ $this->authorization = $authorization; -+ parent::__construct( -+ $customerFactory, -+ $eventManager, -+ $storeManager, -+ $mathRandom, -+ $validator, -+ $validationResultsDataFactory, -+ $addressRepository, -+ $customerMetadataService, -+ $customerRegistry, -+ $logger, -+ $encryptor, -+ $configShare, -+ $stringHelper, -+ $customerRepository, -+ $scopeConfig, -+ $transportBuilder, -+ $dataProcessor, -+ $registry, -+ $customerViewHelper, -+ $dateTime, -+ $customerModel, -+ $objectFactory, -+ $extensibleDataObjectConverter -+ ); -+ } -+ - /** - * @inheritDoc - * -@@ -23,9 +134,30 @@ class AccountManagementApi extends AccountManagement - */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') - { -+ $this->validateCustomerRequest($customer); - $customer = parent::createAccount($customer, $password, $redirectUrl); - $customer->setConfirmation(null); - - return $customer; - } -+ -+ /** -+ * Validate anonymous request -+ * -+ * @param CustomerInterface $customer -+ * @return void -+ * @throws AuthorizationException -+ */ -+ private function validateCustomerRequest(CustomerInterface $customer): void -+ { -+ $groupId = $customer->getGroupId(); -+ if (isset($groupId) && -+ !$this->authorization->isAllowed(self::ADMIN_RESOURCE) -+ ) { -+ $params = ['resources' => self::ADMIN_RESOURCE]; -+ throw new AuthorizationException( -+ __("The consumer isn't authorized to access %resources.", $params) -+ ); -+ } -+ } - } -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -new file mode 100644 -index 000000000000..295b33d2db14 ---- /dev/null -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -0,0 +1,89 @@ -+authorization = $authorization; -+ } -+ -+ /** -+ * Validate groupId for anonymous request -+ * -+ * @param MassSchedule $massSchedule -+ * @param string $topic -+ * @param array $entitiesArray -+ * @param string|null $groupId -+ * @param string|null $userId -+ * @return null -+ * @throws AuthorizationException -+ * @SuppressWarnings(PHPMD.UnusedFormalParameter) -+ */ -+ public function beforePublishMass( -+ MassSchedule $massSchedule, -+ string $topic, -+ array $entitiesArray, -+ string $groupId = null, -+ string $userId = null -+ ) { -+ // only apply the plugin on account create. -+ if ($topic !== self::TOPIC_NAME) { -+ return; -+ } -+ -+ foreach ($entitiesArray as $entityParams) { -+ foreach ($entityParams as $entity) { -+ if ($entity instanceof CustomerInterface) { -+ $groupId = $entity->getGroupId(); -+ if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { -+ $params = ['resources' => self::ADMIN_RESOURCE]; -+ throw new AuthorizationException( -+ __("The consumer isn't authorized to access %resources.", $params) -+ ); -+ } -+ } -+ } -+ } -+ return null; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php -new file mode 100644 -index 000000000000..074d40021a18 ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php -@@ -0,0 +1,421 @@ -+customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); -+ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); -+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); -+ $this->random = $this->createMock(Random::class); -+ $this->validator = $this->createMock(Validator::class); -+ $this->validationResultsInterfaceFactory = $this->createMock( -+ ValidationResultsInterfaceFactory::class -+ ); -+ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); -+ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); -+ $this->customerRegistry = $this->createMock(CustomerRegistry::class); -+ -+ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); -+ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); -+ $this->share = $this->createMock(Share::class); -+ $this->string = $this->createMock(StringUtils::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->transportBuilder = $this->createMock(TransportBuilder::class); -+ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); -+ $this->registry = $this->createMock(Registry::class); -+ $this->customerViewHelper = $this->createMock(View::class); -+ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); -+ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); -+ $this->objectFactory = $this->createMock(DataObjectFactory::class); -+ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); -+ $this->extensibleDataObjectConverter = $this->createMock( -+ ExtensibleDataObjectConverter::class -+ ); -+ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); -+ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) -+ ->onlyMethods(['addData', 'setData']) -+ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); -+ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); -+ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); -+ -+ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) -+ ->disableOriginalConstructor() -+ ->onlyMethods(['create']) -+ ->getMock(); -+ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->authorizationMock = $this->createMock(Authorization::class); -+ $this->objectManagerHelper = new ObjectManagerHelper($this); -+ $this->accountManagement = $this->objectManagerHelper->getObject( -+ AccountManagementApi::class, -+ [ -+ 'customerFactory' => $this->customerFactory, -+ 'eventManager' => $this->manager, -+ 'storeManager' => $this->storeManager, -+ 'mathRandom' => $this->random, -+ 'validator' => $this->validator, -+ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, -+ 'addressRepository' => $this->addressRepository, -+ 'customerMetadataService' => $this->customerMetadata, -+ 'customerRegistry' => $this->customerRegistry, -+ 'logger' => $this->logger, -+ 'encryptor' => $this->encryptor, -+ 'configShare' => $this->share, -+ 'stringHelper' => $this->string, -+ 'customerRepository' => $this->customerRepository, -+ 'scopeConfig' => $this->scopeConfig, -+ 'transportBuilder' => $this->transportBuilder, -+ 'dataProcessor' => $this->dataObjectProcessor, -+ 'registry' => $this->registry, -+ 'customerViewHelper' => $this->customerViewHelper, -+ 'dateTime' => $this->dateTime, -+ 'customerModel' => $this->customer, -+ 'objectFactory' => $this->objectFactory, -+ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, -+ 'dateTimeFactory' => $this->dateTimeFactory, -+ 'accountConfirmation' => $this->accountConfirmation, -+ 'sessionManager' => $this->sessionManager, -+ 'saveHandler' => $this->saveHandler, -+ 'visitorCollectionFactory' => $this->visitorCollectionFactory, -+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, -+ 'addressRegistry' => $this->addressRegistryMock, -+ 'allowedCountriesReader' => $this->allowedCountriesReader, -+ 'authorization' => $this->authorizationMock -+ ] -+ ); -+ $this->accountManagementMock = $this->createMock(AccountManagement::class); -+ -+ $this->storeMock = $this->getMockBuilder( -+ StoreInterface::class -+ )->disableOriginalConstructor() -+ ->getMock(); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @throws LocalizedException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforeCreateAccount( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ -+ $customer = $this->getMockBuilder(CustomerInterface::class) -+ ->addMethods(['setData']) -+ ->getMockForAbstractClass(); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $customer->method('getWebsiteId')->willReturn(2); -+ $customer->method('getStoreId')->willReturn(1); -+ $customer->method('setData')->willReturn(1); -+ -+ $this->customerRepository->method('get')->willReturn($customer); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $this->customerRepository->method('save')->willReturn($customer); -+ -+ if (!$willThrowException) { -+ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); -+ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); -+ $this->random->method('getUniqueHash')->willReturn('testabc'); -+ $date = $this->getMockBuilder(\DateTime::class) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory->expects(static::once()) -+ ->method('create') -+ ->willReturn($date); -+ $date->expects(static::once()) -+ ->method('format') -+ ->with('Y-m-d H:i:s') -+ ->willReturn('2015-01-01 00:00:00'); -+ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); -+ $this->storeManager->method('getStores') -+ ->willReturn([$this->storeMock]); -+ } -+ $this->accountManagement->createAccount($customer); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -index 8ff6a8585212..cbe0a18e4b17 100644 ---- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - $defaultGroupId = 1; -- $requestedGroupId = 3; - - $datetime = $this->prepareDateTimeFactory(); - -@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void - return null; - } - })); -- $customer->expects($this->atLeastOnce()) -- ->method('getGroupId') -- ->willReturn($requestedGroupId); - $customer - ->method('setGroupId') - ->willReturnOnConsecutiveCalls(null, $defaultGroupId); -diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -new file mode 100644 -index 000000000000..107df2c2863e ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -@@ -0,0 +1,112 @@ -+authorizationMock = $this->createMock(Authorization::class); -+ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ -+ 'authorization' => $this->authorizationMock -+ ]); -+ $this->massScheduleMock = $this->createMock(MassSchedule::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforePublishMass( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $customer = $this->getMockForAbstractClass(CustomerInterface::class); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $entitiesArray = [ -+ [$customer, 'Password1', ''] -+ ]; -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ $this->plugin->beforePublishMass( -+ $this->massScheduleMock, -+ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', -+ $entitiesArray, -+ '', -+ '' -+ ); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json -index 2d76da56bff7..ff34d423c2da 100644 ---- a/vendor/magento/module-customer/composer.json -+++ b/vendor/magento/module-customer/composer.json -@@ -35,5 +35,6 @@ - "suggest": { - "magento/module-cookie": "100.4.*", - "magento/module-customer-sample-data": "Sample Data version: 100.4.*", -- "magento/module-webapi": "100.4.*" -+ "magento/module-webapi": "100.4.*", -+ "magento/module-asynchronous-operations": "100.4.*" - }, -diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index 31b79935ad9a..4cda16e121c9 100644 ---- a/vendor/magento/module-customer/etc/di.xml -+++ b/vendor/magento/module-customer/etc/di.xml -@@ -567,4 +567,9 @@ - - - -+ -+ -+ - -diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea19..a7cce5b03a26 100644 ---- a/vendor/magento/module-quote/etc/webapi.xml -+++ b/vendor/magento/module-quote/etc/webapi.xml -@@ -98,6 +98,9 @@ - - - -+ -+ %customer_id% -+ - - - -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc..93555559ac9a 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -8,10 +8,12 @@ - - namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - -+use Magento\Framework\Api\SimpleDataObjectConverter; - use Magento\Framework\App\ObjectManager; - use Magento\Framework\Exception\AuthorizationException; - use Magento\Framework\Exception\InputException; - use Magento\Framework\Exception\LocalizedException; -+use Magento\Framework\Reflection\MethodsMap; - use Magento\Framework\Webapi\Exception; - use Magento\Framework\Webapi\Rest\Request as RestRequest; - use Magento\Framework\Webapi\ServiceInputProcessor; -@@ -24,6 +26,8 @@ - - /** - * This class is responsible for retrieving resolved input data -+ * -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class InputParamsResolver - { -@@ -61,6 +65,11 @@ class InputParamsResolver - */ - private $inputArraySizeLimitValue; - -+ /** -+ * @var MethodsMap -+ */ -+ private $methodsMap; -+ - /** - * Initialize dependencies. - * -@@ -72,6 +81,7 @@ class InputParamsResolver - * @param WebapiInputParamsResolver $inputParamsResolver - * @param bool $isBulk - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue -+ * @param MethodsMap|null $methodsMap - */ - public function __construct( - RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( - RequestValidator $requestValidator, - WebapiInputParamsResolver $inputParamsResolver, - bool $isBulk = false, -- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null -+ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, -+ ?MethodsMap $methodsMap = null - ) { - $this->request = $request; - $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( - $this->isBulk = $isBulk; - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); -+ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() -+ ->get(MethodsMap::class); - } - - /** -@@ -119,7 +132,13 @@ public function resolve() - $routeServiceMethod = $route->getServiceMethod(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - -+ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); -+ - foreach ($inputData as $key => $singleEntityParams) { -+ if (!is_array($singleEntityParams)) { -+ continue; -+ } -+ - $webapiResolvedParams[$key] = $this->resolveBulkItemParams( - $singleEntityParams, - $routeServiceClass, -@@ -143,11 +162,22 @@ public function getInputData() - $inputData = $this->request->getRequestData(); - - $httpMethod = $this->request->getHttpMethod(); -- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { -+ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { - $requestBodyParams = $this->request->getBodyParams(); - $inputData = array_merge($requestBodyParams, $inputData); - } -- return $inputData; -+ -+ return array_map(function ($singleEntityParams) { -+ if (is_array($singleEntityParams)) { -+ $singleEntityParams = $this->filterInputData($singleEntityParams); -+ $singleEntityParams = $this->paramsOverrider->override( -+ $singleEntityParams, -+ $this->getRoute()->getParameters() -+ ); -+ } -+ -+ return $singleEntityParams; -+ }, $inputData); - } - - /** -@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s - { - return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); - } -+ -+ /** -+ * Validates InputData -+ * -+ * @param array $inputData -+ * @return array -+ */ -+ private function filterInputData(array $inputData): array -+ { -+ $result = []; -+ -+ $data = array_filter($inputData, function ($k) use (&$result) { -+ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; -+ return !isset($result[$key]) && ($result[$key] = true); -+ }, ARRAY_FILTER_USE_KEY); -+ -+ return array_map(function ($value) { -+ return is_array($value) ? $this->filterInputData($value) : $value; -+ }, $data); -+ } -+ -+ /** -+ * Validate that parameters are really used in the current request. -+ * -+ * @param string $serviceClassName -+ * @param string $serviceMethodName -+ * @param array $paramOverriders -+ */ -+ private function validateParameters( -+ string $serviceClassName, -+ string $serviceMethodName, -+ array $paramOverriders -+ ): void { -+ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); -+ foreach ($paramOverriders as $key => $param) { -+ $arrayKeys = explode('.', $param ?? ''); -+ $value = array_shift($arrayKeys); -+ -+ foreach ($methodParams as $serviceMethodParam) { -+ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; -+ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; -+ -+ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); -+ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { -+ if (count($arrayKeys) > 0) { -+ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); -+ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); -+ } -+ unset($paramOverriders[$key]); -+ break; -+ } -+ } -+ } -+ -+ if (!empty($paramOverriders)) { -+ $message = 'The current request does not expect the next parameters: ' -+ . implode(', ', $paramOverriders); -+ throw new \UnexpectedValueException(__($message)->__toString()); -+ } -+ } - } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index ce9e4ee94178..e08fe0388cfb 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -10,10 +10,13 @@ - - class GuestCartManagementTest extends WebapiAbstract - { -- const SERVICE_VERSION = 'V1'; -- const SERVICE_NAME = 'quoteGuestCartManagementV1'; -- const RESOURCE_PATH = '/V1/guest-carts/'; -+ public const SERVICE_VERSION = 'V1'; -+ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; -+ public const RESOURCE_PATH = '/V1/guest-carts/'; - -+ /** -+ * @var array List of created quotes -+ */ - protected $createdQuotes = []; - - /** -@@ -339,7 +342,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index 9991dd4e05fe..dc98670bd000 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -110,4 +110,5 @@ app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php - app/code/Magento/Newsletter/Model/Queue/TransportBuilder.php - app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/attribute/steps/bulk.phtml - app/code/Magento/GoogleGtag --app/code/Magento/AdminAdobeIms/Observer/AuthObserver -\ No newline at end of file -+app/code/Magento/AdminAdobeIms/Observer/AuthObserver -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch deleted file mode 100644 index d18b1e8b..00000000 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.6.patch +++ /dev/null @@ -1,1090 +0,0 @@ -diff --git a/vendor/magento/module-customer/Model/AccountManagement.php b/vendor/magento/module-customer/Model/AccountManagement.php -index 702b6aeb6865..bfebafeb0330 100644 ---- a/vendor/magento/module-customer/Model/AccountManagement.php -+++ b/vendor/magento/module-customer/Model/AccountManagement.php -@@ -882,11 +882,6 @@ public function getConfirmationStatus($customerId) - */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') - { -- $groupId = $customer->getGroupId(); -- if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { -- $customer->setGroupId(null); -- } -- - if ($password !== null) { - $this->checkPasswordStrength($password); - $customerEmail = $customer->getEmail(); -diff --git a/vendor/magento/module-customer/Model/AccountManagementApi.php b/vendor/magento/module-customer/Model/AccountManagementApi.php -index 02a05705b57e..8b4f78ab26c7 100644 ---- a/vendor/magento/module-customer/Model/AccountManagementApi.php -+++ b/vendor/magento/module-customer/Model/AccountManagementApi.php -@@ -6,16 +6,127 @@ - - namespace Magento\Customer\Model; - -+use Magento\Customer\Api\AddressRepositoryInterface; -+use Magento\Customer\Api\CustomerMetadataInterface; -+use Magento\Customer\Api\CustomerRepositoryInterface; - use Magento\Customer\Api\Data\CustomerInterface; -+use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory; -+use Magento\Customer\Helper\View as CustomerViewHelper; -+use Magento\Customer\Model\Config\Share as ConfigShare; -+use Magento\Customer\Model\Customer as CustomerModel; -+use Magento\Customer\Model\Metadata\Validator; -+use Magento\Framework\Api\ExtensibleDataObjectConverter; -+use Magento\Framework\App\Config\ScopeConfigInterface; -+use Magento\Framework\AuthorizationInterface; -+use Magento\Framework\DataObjectFactory as ObjectFactory; -+use Magento\Framework\Encryption\EncryptorInterface as Encryptor; -+use Magento\Framework\Event\ManagerInterface; -+use Magento\Framework\Exception\AuthorizationException; -+use Magento\Framework\Mail\Template\TransportBuilder; -+use Magento\Framework\Math\Random; -+use Magento\Framework\Reflection\DataObjectProcessor; -+use Magento\Framework\Registry; -+use Magento\Framework\Stdlib\DateTime; -+use Magento\Framework\Stdlib\StringUtils as StringHelper; -+use Magento\Store\Model\StoreManagerInterface; -+use Psr\Log\LoggerInterface as PsrLogger; - - /** - * Account Management service implementation for external API access. -+ * - * Handle various customer account actions. - * - * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class AccountManagementApi extends AccountManagement - { -+ /** -+ * @var AuthorizationInterface -+ */ -+ private $authorization; -+ -+ /** -+ * @param CustomerFactory $customerFactory -+ * @param ManagerInterface $eventManager -+ * @param StoreManagerInterface $storeManager -+ * @param Random $mathRandom -+ * @param Validator $validator -+ * @param ValidationResultsInterfaceFactory $validationResultsDataFactory -+ * @param AddressRepositoryInterface $addressRepository -+ * @param CustomerMetadataInterface $customerMetadataService -+ * @param CustomerRegistry $customerRegistry -+ * @param PsrLogger $logger -+ * @param Encryptor $encryptor -+ * @param ConfigShare $configShare -+ * @param StringHelper $stringHelper -+ * @param CustomerRepositoryInterface $customerRepository -+ * @param ScopeConfigInterface $scopeConfig -+ * @param TransportBuilder $transportBuilder -+ * @param DataObjectProcessor $dataProcessor -+ * @param Registry $registry -+ * @param CustomerViewHelper $customerViewHelper -+ * @param DateTime $dateTime -+ * @param CustomerModel $customerModel -+ * @param ObjectFactory $objectFactory -+ * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter -+ * @param AuthorizationInterface $authorization -+ * @SuppressWarnings(PHPMD.ExcessiveParameterList) -+ */ -+ public function __construct( -+ CustomerFactory $customerFactory, -+ ManagerInterface $eventManager, -+ StoreManagerInterface $storeManager, -+ Random $mathRandom, -+ Validator $validator, -+ ValidationResultsInterfaceFactory $validationResultsDataFactory, -+ AddressRepositoryInterface $addressRepository, -+ CustomerMetadataInterface $customerMetadataService, -+ CustomerRegistry $customerRegistry, -+ PsrLogger $logger, -+ Encryptor $encryptor, -+ ConfigShare $configShare, -+ StringHelper $stringHelper, -+ CustomerRepositoryInterface $customerRepository, -+ ScopeConfigInterface $scopeConfig, -+ TransportBuilder $transportBuilder, -+ DataObjectProcessor $dataProcessor, -+ Registry $registry, -+ CustomerViewHelper $customerViewHelper, -+ DateTime $dateTime, -+ CustomerModel $customerModel, -+ ObjectFactory $objectFactory, -+ ExtensibleDataObjectConverter $extensibleDataObjectConverter, -+ AuthorizationInterface $authorization -+ ) { -+ $this->authorization = $authorization; -+ parent::__construct( -+ $customerFactory, -+ $eventManager, -+ $storeManager, -+ $mathRandom, -+ $validator, -+ $validationResultsDataFactory, -+ $addressRepository, -+ $customerMetadataService, -+ $customerRegistry, -+ $logger, -+ $encryptor, -+ $configShare, -+ $stringHelper, -+ $customerRepository, -+ $scopeConfig, -+ $transportBuilder, -+ $dataProcessor, -+ $registry, -+ $customerViewHelper, -+ $dateTime, -+ $customerModel, -+ $objectFactory, -+ $extensibleDataObjectConverter -+ ); -+ } -+ - /** - * @inheritDoc - * -@@ -23,9 +134,30 @@ class AccountManagementApi extends AccountManagement - */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') - { -+ $this->validateCustomerRequest($customer); - $customer = parent::createAccount($customer, $password, $redirectUrl); - $customer->setConfirmation(null); - - return $customer; - } -+ -+ /** -+ * Validate anonymous request -+ * -+ * @param CustomerInterface $customer -+ * @return void -+ * @throws AuthorizationException -+ */ -+ private function validateCustomerRequest(CustomerInterface $customer): void -+ { -+ $groupId = $customer->getGroupId(); -+ if (isset($groupId) && -+ !$this->authorization->isAllowed(self::ADMIN_RESOURCE) -+ ) { -+ $params = ['resources' => self::ADMIN_RESOURCE]; -+ throw new AuthorizationException( -+ __("The consumer isn't authorized to access %resources.", $params) -+ ); -+ } -+ } - } -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -new file mode 100644 -index 000000000000..cdda3016694c ---- /dev/null -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -0,0 +1,90 @@ -+authorization = $authorization; -+ } -+ -+ /** -+ * Validate groupId for anonymous request -+ * -+ * @param MassSchedule $massSchedule -+ * @param string $topic -+ * @param array $entitiesArray -+ * @param string|null $groupId -+ * @param string|null $userId -+ * @return null -+ * @throws AuthorizationException -+ * @SuppressWarnings(PHPMD.UnusedFormalParameter) -+ */ -+ public function beforePublishMass( -+ MassSchedule $massSchedule, -+ string $topic, -+ array $entitiesArray, -+ string $groupId = null, -+ string $userId = null -+ ) { -+ // only apply the plugin on account create. -+ if ($topic !== self::TOPIC_NAME) { -+ return; -+ } -+ -+ foreach ($entitiesArray as $entityParams) { -+ foreach ($entityParams as $entity) { -+ if ($entity instanceof CustomerInterface) { -+ $groupId = $entity->getGroupId(); -+ if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) { -+ $params = ['resources' => self::ADMIN_RESOURCE]; -+ throw new AuthorizationException( -+ __("The consumer isn't authorized to access %resources.", $params) -+ ); -+ } -+ } -+ } -+ } -+ return null; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php -new file mode 100644 -index 000000000000..074d40021a18 ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementApiTest.php -@@ -0,0 +1,421 @@ -+customerFactory = $this->createPartialMock(CustomerFactory::class, ['create']); -+ $this->manager = $this->getMockForAbstractClass(ManagerInterface::class); -+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); -+ $this->random = $this->createMock(Random::class); -+ $this->validator = $this->createMock(Validator::class); -+ $this->validationResultsInterfaceFactory = $this->createMock( -+ ValidationResultsInterfaceFactory::class -+ ); -+ $this->addressRepository = $this->getMockForAbstractClass(AddressRepositoryInterface::class); -+ $this->customerMetadata = $this->getMockForAbstractClass(CustomerMetadataInterface::class); -+ $this->customerRegistry = $this->createMock(CustomerRegistry::class); -+ -+ $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); -+ $this->encryptor = $this->getMockForAbstractClass(EncryptorInterface::class); -+ $this->share = $this->createMock(Share::class); -+ $this->string = $this->createMock(StringUtils::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->transportBuilder = $this->createMock(TransportBuilder::class); -+ $this->dataObjectProcessor = $this->createMock(DataObjectProcessor::class); -+ $this->registry = $this->createMock(Registry::class); -+ $this->customerViewHelper = $this->createMock(View::class); -+ $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); -+ $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); -+ $this->objectFactory = $this->createMock(DataObjectFactory::class); -+ $this->addressRegistryMock = $this->createMock(AddressRegistry::class); -+ $this->extensibleDataObjectConverter = $this->createMock( -+ ExtensibleDataObjectConverter::class -+ ); -+ $this->allowedCountriesReader = $this->createMock(AllowedCountries::class); -+ $this->customerSecure = $this->getMockBuilder(CustomerSecure::class) -+ ->onlyMethods(['addData', 'setData']) -+ ->addMethods(['setRpToken', 'setRpTokenCreatedAt']) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); -+ $this->accountConfirmation = $this->createMock(AccountConfirmation::class); -+ $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); -+ -+ $this->visitorCollectionFactory = $this->getMockBuilder(CollectionFactory::class) -+ ->disableOriginalConstructor() -+ ->onlyMethods(['create']) -+ ->getMock(); -+ $this->sessionManager = $this->getMockBuilder(SessionManagerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->saveHandler = $this->getMockBuilder(SaveHandlerInterface::class) -+ ->disableOriginalConstructor() -+ ->getMockForAbstractClass(); -+ $this->authorizationMock = $this->createMock(Authorization::class); -+ $this->objectManagerHelper = new ObjectManagerHelper($this); -+ $this->accountManagement = $this->objectManagerHelper->getObject( -+ AccountManagementApi::class, -+ [ -+ 'customerFactory' => $this->customerFactory, -+ 'eventManager' => $this->manager, -+ 'storeManager' => $this->storeManager, -+ 'mathRandom' => $this->random, -+ 'validator' => $this->validator, -+ 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, -+ 'addressRepository' => $this->addressRepository, -+ 'customerMetadataService' => $this->customerMetadata, -+ 'customerRegistry' => $this->customerRegistry, -+ 'logger' => $this->logger, -+ 'encryptor' => $this->encryptor, -+ 'configShare' => $this->share, -+ 'stringHelper' => $this->string, -+ 'customerRepository' => $this->customerRepository, -+ 'scopeConfig' => $this->scopeConfig, -+ 'transportBuilder' => $this->transportBuilder, -+ 'dataProcessor' => $this->dataObjectProcessor, -+ 'registry' => $this->registry, -+ 'customerViewHelper' => $this->customerViewHelper, -+ 'dateTime' => $this->dateTime, -+ 'customerModel' => $this->customer, -+ 'objectFactory' => $this->objectFactory, -+ 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, -+ 'dateTimeFactory' => $this->dateTimeFactory, -+ 'accountConfirmation' => $this->accountConfirmation, -+ 'sessionManager' => $this->sessionManager, -+ 'saveHandler' => $this->saveHandler, -+ 'visitorCollectionFactory' => $this->visitorCollectionFactory, -+ 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, -+ 'addressRegistry' => $this->addressRegistryMock, -+ 'allowedCountriesReader' => $this->allowedCountriesReader, -+ 'authorization' => $this->authorizationMock -+ ] -+ ); -+ $this->accountManagementMock = $this->createMock(AccountManagement::class); -+ -+ $this->storeMock = $this->getMockBuilder( -+ StoreInterface::class -+ )->disableOriginalConstructor() -+ ->getMock(); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @throws LocalizedException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforeCreateAccount( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ -+ $customer = $this->getMockBuilder(CustomerInterface::class) -+ ->addMethods(['setData']) -+ ->getMockForAbstractClass(); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $customer->method('getWebsiteId')->willReturn(2); -+ $customer->method('getStoreId')->willReturn(1); -+ $customer->method('setData')->willReturn(1); -+ -+ $this->customerRepository->method('get')->willReturn($customer); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $this->customerRepository->method('save')->willReturn($customer); -+ -+ if (!$willThrowException) { -+ $this->accountManagementMock->method('createAccountWithPasswordHash')->willReturn($customer); -+ $this->storeMock->expects($this->any())->method('getId')->willReturnOnConsecutiveCalls(2, 1); -+ $this->random->method('getUniqueHash')->willReturn('testabc'); -+ $date = $this->getMockBuilder(\DateTime::class) -+ ->disableOriginalConstructor() -+ ->getMock(); -+ $this->dateTimeFactory->expects(static::once()) -+ ->method('create') -+ ->willReturn($date); -+ $date->expects(static::once()) -+ ->method('format') -+ ->with('Y-m-d H:i:s') -+ ->willReturn('2015-01-01 00:00:00'); -+ $this->customerRegistry->method('retrieveSecureData')->willReturn($this->customerSecure); -+ $this->storeManager->method('getStores') -+ ->willReturn([$this->storeMock]); -+ } -+ $this->accountManagement->createAccount($customer); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -index 9e68d53fd594..e2b507f6fe37 100644 ---- a/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -+++ b/vendor/magento/module-customer/Test/Unit/Model/AccountManagementTest.php -@@ -1222,7 +1222,6 @@ public function testCreateAccountWithGroupId(): void - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - $defaultGroupId = 1; -- $requestedGroupId = 3; - - $datetime = $this->prepareDateTimeFactory(); - -@@ -1299,9 +1298,6 @@ public function testCreateAccountWithGroupId(): void - return null; - } - })); -- $customer->expects($this->atLeastOnce()) -- ->method('getGroupId') -- ->willReturn($requestedGroupId); - $customer - ->method('setGroupId') - ->willReturnOnConsecutiveCalls(null, $defaultGroupId); -diff --git a/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -new file mode 100644 -index 000000000000..107df2c2863e ---- /dev/null -+++ b/vendor/magento/module-customer/Test/Unit/Plugin/AsyncRequestCustomerGroupAuthorizationTest.php -@@ -0,0 +1,112 @@ -+authorizationMock = $this->createMock(Authorization::class); -+ $this->plugin = $objectManager->getObject(AsyncRequestCustomerGroupAuthorization::class, [ -+ 'authorization' => $this->authorizationMock -+ ]); -+ $this->massScheduleMock = $this->createMock(MassSchedule::class); -+ $this->customerRepository = $this->getMockForAbstractClass(CustomerRepositoryInterface::class); -+ } -+ -+ /** -+ * Verify that only authorized request will be able to change groupId -+ * -+ * @param int $groupId -+ * @param int $customerId -+ * @param bool $isAllowed -+ * @param int $willThrowException -+ * @return void -+ * @throws AuthorizationException -+ * @dataProvider customerDataProvider -+ */ -+ public function testBeforePublishMass( -+ int $groupId, -+ int $customerId, -+ bool $isAllowed, -+ int $willThrowException -+ ): void { -+ if ($willThrowException) { -+ $this->expectException(AuthorizationException::class); -+ } else { -+ $this->expectNotToPerformAssertions(); -+ } -+ $customer = $this->getMockForAbstractClass(CustomerInterface::class); -+ $customer->method('getGroupId')->willReturn($groupId); -+ $customer->method('getId')->willReturn($customerId); -+ $this->customerRepository->method('getById')->with($customerId)->willReturn($customer); -+ $entitiesArray = [ -+ [$customer, 'Password1', ''] -+ ]; -+ $this->authorizationMock -+ ->expects($this->once()) -+ ->method('isAllowed') -+ ->with('Magento_Customer::manage') -+ ->willReturn($isAllowed); -+ $this->plugin->beforePublishMass( -+ $this->massScheduleMock, -+ 'async.magento.customer.api.accountmanagementinterface.createaccount.post', -+ $entitiesArray, -+ '', -+ '' -+ ); -+ } -+ -+ /** -+ * @return array -+ */ -+ public function customerDataProvider(): array -+ { -+ return [ -+ [3, 1, false, 1], -+ [3, 1, true, 0] -+ ]; -+ } -+} -diff --git a/vendor/magento/module-customer/composer.json b/vendor/magento/module-customer/composer.json -index ef2047644759..39c82c20f2ec 100644 ---- a/vendor/magento/module-customer/composer.json -+++ b/vendor/magento/module-customer/composer.json -@@ -29,5 +29,6 @@ - "suggest": { - "magento/module-cookie": "100.4.*", - "magento/module-customer-sample-data": "Sample Data version: 100.4.*", -- "magento/module-webapi": "100.4.*" -+ "magento/module-webapi": "100.4.*", -+ "magento/module-asynchronous-operations": "100.4.*" - }, -diff --git a/vendor/magento/module-customer/etc/di.xml b/vendor/magento/module-customer/etc/di.xml -index b178f51f8919..96fd4b86be70 100644 ---- a/vendor/magento/module-customer/etc/di.xml -+++ b/vendor/magento/module-customer/etc/di.xml -@@ -585,4 +585,9 @@ - - - -+ -+ -+ - -diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea19..a7cce5b03a26 100644 ---- a/vendor/magento/module-quote/etc/webapi.xml -+++ b/vendor/magento/module-quote/etc/webapi.xml -@@ -98,6 +98,9 @@ - - - -+ -+ %customer_id% -+ - - - -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc..93555559ac9a 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -8,10 +8,12 @@ - - namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - -+use Magento\Framework\Api\SimpleDataObjectConverter; - use Magento\Framework\App\ObjectManager; - use Magento\Framework\Exception\AuthorizationException; - use Magento\Framework\Exception\InputException; - use Magento\Framework\Exception\LocalizedException; -+use Magento\Framework\Reflection\MethodsMap; - use Magento\Framework\Webapi\Exception; - use Magento\Framework\Webapi\Rest\Request as RestRequest; - use Magento\Framework\Webapi\ServiceInputProcessor; -@@ -24,6 +26,8 @@ - - /** - * This class is responsible for retrieving resolved input data -+ * -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class InputParamsResolver - { -@@ -61,6 +65,11 @@ class InputParamsResolver - */ - private $inputArraySizeLimitValue; - -+ /** -+ * @var MethodsMap -+ */ -+ private $methodsMap; -+ - /** - * Initialize dependencies. - * -@@ -72,6 +81,7 @@ class InputParamsResolver - * @param WebapiInputParamsResolver $inputParamsResolver - * @param bool $isBulk - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue -+ * @param MethodsMap|null $methodsMap - */ - public function __construct( - RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( - RequestValidator $requestValidator, - WebapiInputParamsResolver $inputParamsResolver, - bool $isBulk = false, -- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null -+ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, -+ ?MethodsMap $methodsMap = null - ) { - $this->request = $request; - $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( - $this->isBulk = $isBulk; - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); -+ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() -+ ->get(MethodsMap::class); - } - - /** -@@ -119,7 +132,13 @@ public function resolve() - $routeServiceMethod = $route->getServiceMethod(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - -+ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); -+ - foreach ($inputData as $key => $singleEntityParams) { -+ if (!is_array($singleEntityParams)) { -+ continue; -+ } -+ - $webapiResolvedParams[$key] = $this->resolveBulkItemParams( - $singleEntityParams, - $routeServiceClass, -@@ -143,11 +162,22 @@ public function getInputData() - $inputData = $this->request->getRequestData(); - - $httpMethod = $this->request->getHttpMethod(); -- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { -+ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { - $requestBodyParams = $this->request->getBodyParams(); - $inputData = array_merge($requestBodyParams, $inputData); - } -- return $inputData; -+ -+ return array_map(function ($singleEntityParams) { -+ if (is_array($singleEntityParams)) { -+ $singleEntityParams = $this->filterInputData($singleEntityParams); -+ $singleEntityParams = $this->paramsOverrider->override( -+ $singleEntityParams, -+ $this->getRoute()->getParameters() -+ ); -+ } -+ -+ return $singleEntityParams; -+ }, $inputData); - } - - /** -@@ -180,4 +210,64 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s - { - return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); - } -+ -+ /** -+ * Validates InputData -+ * -+ * @param array $inputData -+ * @return array -+ */ -+ private function filterInputData(array $inputData): array -+ { -+ $result = []; -+ -+ $data = array_filter($inputData, function ($k) use (&$result) { -+ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; -+ return !isset($result[$key]) && ($result[$key] = true); -+ }, ARRAY_FILTER_USE_KEY); -+ -+ return array_map(function ($value) { -+ return is_array($value) ? $this->filterInputData($value) : $value; -+ }, $data); -+ } -+ -+ /** -+ * Validate that parameters are really used in the current request. -+ * -+ * @param string $serviceClassName -+ * @param string $serviceMethodName -+ * @param array $paramOverriders -+ */ -+ private function validateParameters( -+ string $serviceClassName, -+ string $serviceMethodName, -+ array $paramOverriders -+ ): void { -+ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); -+ foreach ($paramOverriders as $key => $param) { -+ $arrayKeys = explode('.', $param ?? ''); -+ $value = array_shift($arrayKeys); -+ -+ foreach ($methodParams as $serviceMethodParam) { -+ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; -+ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; -+ -+ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); -+ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { -+ if (count($arrayKeys) > 0) { -+ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); -+ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); -+ } -+ unset($paramOverriders[$key]); -+ break; -+ } -+ } -+ } -+ -+ if (!empty($paramOverriders)) { -+ $message = 'The current request does not expect the next parameters: ' -+ . implode(', ', $paramOverriders); -+ throw new \UnexpectedValueException(__($message)->__toString()); -+ } -+ } - } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index ce9e4ee94178..e08fe0388cfb 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -10,10 +10,13 @@ - - class GuestCartManagementTest extends WebapiAbstract - { -- const SERVICE_VERSION = 'V1'; -- const SERVICE_NAME = 'quoteGuestCartManagementV1'; -- const RESOURCE_PATH = '/V1/guest-carts/'; -+ public const SERVICE_VERSION = 'V1'; -+ public const SERVICE_NAME = 'quoteGuestCartManagementV1'; -+ public const RESOURCE_PATH = '/V1/guest-carts/'; - -+ /** -+ * @var array List of created quotes -+ */ - protected $createdQuotes = []; - - /** -@@ -339,7 +342,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index 80fe4ec247a6..50285d7492e8 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed - app/code/Magento/GoogleGtag - app/code/Magento/AdminAdobeIms/Observer/AuthObserver - app/code/Magento/OpenSearch/SearchAdapter/Adapter -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php diff --git a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch b/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch deleted file mode 100644 index 038b6c83..00000000 --- a/patches/MCLOUD-13240__Patch_for_CVE_2025_24434_improve_web_api_async__2.4.7.patch +++ /dev/null @@ -1,245 +0,0 @@ -diff --git a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -index 5b5c8ce1fc0c..295b33d2db14 100644 ---- a/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -+++ b/vendor/magento/module-customer/Plugin/AsyncRequestCustomerGroupAuthorization.php -@@ -9,7 +9,6 @@ - namespace Magento\Customer\Plugin; - - use Magento\Customer\Api\Data\CustomerInterface; --use Magento\Framework\App\ObjectManager; - use Magento\Framework\AuthorizationInterface; - use Magento\Framework\Exception\AuthorizationException; - use Magento\AsynchronousOperations\Model\MassSchedule; -@@ -26,6 +25,13 @@ class AsyncRequestCustomerGroupAuthorization - */ - public const ADMIN_RESOURCE = 'Magento_Customer::manage'; - -+ /** -+ * account create topic name -+ * -+ * @var string -+ */ -+ private const TOPIC_NAME = 'async.magento.customer.api.accountmanagementinterface.createaccount.post'; -+ - /** - * @var AuthorizationInterface - */ -@@ -60,6 +66,11 @@ public function beforePublishMass( - string $groupId = null, - string $userId = null - ) { -+ // only apply the plugin on account create. -+ if ($topic !== self::TOPIC_NAME) { -+ return; -+ } -+ - foreach ($entitiesArray as $entityParams) { - foreach ($entityParams as $entity) { - if ($entity instanceof CustomerInterface) { -diff --git a/vendor/magento/module-quote/etc/webapi.xml b/vendor/magento/module-quote/etc/webapi.xml -index 79d98968ea19..a7cce5b03a26 100644 ---- a/vendor/magento/module-quote/etc/webapi.xml -+++ b/vendor/magento/module-quote/etc/webapi.xml -@@ -98,6 +98,9 @@ - - - -+ -+ %customer_id% -+ - - - -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 6718087888bc..6e159eaddf16 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -8,10 +8,12 @@ - - namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; - -+use Magento\Framework\Api\SimpleDataObjectConverter; - use Magento\Framework\App\ObjectManager; - use Magento\Framework\Exception\AuthorizationException; - use Magento\Framework\Exception\InputException; - use Magento\Framework\Exception\LocalizedException; -+use Magento\Framework\Reflection\MethodsMap; - use Magento\Framework\Webapi\Exception; - use Magento\Framework\Webapi\Rest\Request as RestRequest; - use Magento\Framework\Webapi\ServiceInputProcessor; -@@ -24,6 +26,8 @@ - - /** - * This class is responsible for retrieving resolved input data -+ * -+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ - class InputParamsResolver - { -@@ -61,6 +65,11 @@ class InputParamsResolver - */ - private $inputArraySizeLimitValue; - -+ /** -+ * @var MethodsMap -+ */ -+ private $methodsMap; -+ - /** - * Initialize dependencies. - * -@@ -72,6 +81,7 @@ class InputParamsResolver - * @param WebapiInputParamsResolver $inputParamsResolver - * @param bool $isBulk - * @param InputArraySizeLimitValue|null $inputArraySizeLimitValue -+ * @param MethodsMap|null $methodsMap - */ - public function __construct( - RestRequest $request, -@@ -81,7 +91,8 @@ public function __construct( - RequestValidator $requestValidator, - WebapiInputParamsResolver $inputParamsResolver, - bool $isBulk = false, -- ?InputArraySizeLimitValue $inputArraySizeLimitValue = null -+ ?InputArraySizeLimitValue $inputArraySizeLimitValue = null, -+ ?MethodsMap $methodsMap = null - ) { - $this->request = $request; - $this->paramsOverrider = $paramsOverrider; -@@ -92,6 +103,8 @@ public function __construct( - $this->isBulk = $isBulk; - $this->inputArraySizeLimitValue = $inputArraySizeLimitValue ?? ObjectManager::getInstance() - ->get(InputArraySizeLimitValue::class); -+ $this->methodsMap = $methodsMap ?? ObjectManager::getInstance() -+ ->get(MethodsMap::class); - } - - /** -@@ -119,7 +132,13 @@ public function resolve() - $routeServiceMethod = $route->getServiceMethod(); - $this->inputArraySizeLimitValue->set($route->getInputArraySizeLimit()); - -+ $this->validateParameters($routeServiceClass, $routeServiceMethod, array_keys($route->getParameters())); -+ - foreach ($inputData as $key => $singleEntityParams) { -+ if (!is_array($singleEntityParams)) { -+ continue; -+ } -+ - $webapiResolvedParams[$key] = $this->resolveBulkItemParams( - $singleEntityParams, - $routeServiceClass, -@@ -143,11 +162,22 @@ public function getInputData() - $inputData = $this->request->getRequestData(); - - $httpMethod = $this->request->getHttpMethod(); -- if ($httpMethod == RestRequest::HTTP_METHOD_DELETE) { -+ if ($httpMethod === RestRequest::HTTP_METHOD_DELETE) { - $requestBodyParams = $this->request->getBodyParams(); - $inputData = array_merge($requestBodyParams, $inputData); - } -- return $inputData; -+ -+ return array_map(function ($singleEntityParams) { -+ if (is_array($singleEntityParams)) { -+ $singleEntityParams = $this->filterInputData($singleEntityParams); -+ $singleEntityParams = $this->paramsOverrider->override( -+ $singleEntityParams, -+ $this->getRoute()->getParameters() -+ ); -+ } -+ -+ return $singleEntityParams; -+ }, $inputData); - } - - /** -@@ -180,4 +210,65 @@ private function resolveBulkItemParams(array $inputData, string $serviceClass, s - { - return $this->serviceInputProcessor->process($serviceClass, $serviceMethod, $inputData); - } -+ -+ /** -+ * Validates InputData -+ * -+ * @param array $inputData -+ * @return array -+ */ -+ private function filterInputData(array $inputData): array -+ { -+ $result = []; -+ -+ $data = array_filter($inputData, function ($k) use (&$result) { -+ $key = is_string($k) ? strtolower(str_replace('_', "", $k)) : $k; -+ return !isset($result[$key]) && ($result[$key] = true); -+ }, ARRAY_FILTER_USE_KEY); -+ -+ return array_map(function ($value) { -+ return is_array($value) ? $this->filterInputData($value) : $value; -+ }, $data); -+ } -+ -+ /** -+ * Validate that parameters are really used in the current request. -+ * -+ * @param string $serviceClassName -+ * @param string $serviceMethodName -+ * @param array $paramOverriders -+ */ -+ private function validateParameters( -+ string $serviceClassName, -+ string $serviceMethodName, -+ array $paramOverriders -+ ): void { -+ //phpcs:ignore CopyPaste -+ $methodParams = $this->methodsMap->getMethodParams($serviceClassName, $serviceMethodName); -+ foreach ($paramOverriders as $key => $param) { -+ $arrayKeys = explode('.', $param ?? ''); -+ $value = array_shift($arrayKeys); -+ -+ foreach ($methodParams as $serviceMethodParam) { -+ $serviceMethodParamName = $serviceMethodParam[MethodsMap::METHOD_META_NAME]; -+ $serviceMethodType = $serviceMethodParam[MethodsMap::METHOD_META_TYPE]; -+ -+ $camelCaseValue = SimpleDataObjectConverter::snakeCaseToCamelCase($value); -+ if ($serviceMethodParamName === $value || $serviceMethodParamName === $camelCaseValue) { -+ if (count($arrayKeys) > 0) { -+ $camelCaseKey = SimpleDataObjectConverter::snakeCaseToCamelCase('set_' . $arrayKeys[0]); -+ $this->validateParameters($serviceMethodType, $camelCaseKey, [implode('.', $arrayKeys)]); -+ } -+ unset($paramOverriders[$key]); -+ break; -+ } -+ } -+ } -+ -+ if (!empty($paramOverriders)) { -+ $message = 'The current request does not expect the next parameters: ' -+ . implode(', ', $paramOverriders); -+ throw new \UnexpectedValueException(__($message)->__toString()); -+ } -+ } - } -diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -index 68cc2c2b2315..6f08b21f3812 100644 ---- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -+++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php -@@ -354,7 +354,7 @@ public function testPlaceOrder() - public function testAssignCustomerByGuestUser() - { - $this->expectException(\Exception::class); -- $this->expectExceptionMessage('You don\'t have the correct permissions to assign the customer to the cart.'); -+ $this->expectExceptionMessage('Enter and try again.'); - - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class)->load('test01', 'reserved_order_id'); -diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -index 08ba4bba28c6..c9d07aa2abed 100644 ---- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt -@@ -111,3 +111,4 @@ app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/ed - app/code/Magento/GoogleGtag - app/code/Magento/AdminAdobeIms/Observer/AuthObserver - app/code/Magento/OpenSearch/SearchAdapter/Adapter -+app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php - diff --git a/patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch b/patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch deleted file mode 100644 index 33e5683a..00000000 --- a/patches/MCLOUD-13605__B2B_SQL_syntax_error_due_to_the_REGEXP_LIKE_function__1.5.2.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php b/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php -index 4dcb3dbcb5b3..7ba339a8703e 100644 ---- a/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php -+++ b/vendor/magento/module-company/Setup/Patch/Data/SetCompanyForStructure.php -@@ -71,7 +71,7 @@ public function apply() - $this->moduleDataSetup->getConnection()->update( - $this->moduleDataSetup->getTable('company_structure'), - ['company_id' => $company['entity_id']], -- ['REGEXP_LIKE(path, ?)' => -+ ['path REGEXP ?' => - '^' . $adminStructureIds[$company['super_user_id']]['structure_id'] . '(/.+)?$'] - ); - } - diff --git a/patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch b/patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch deleted file mode 100644 index 3073c351..00000000 --- a/patches/MCLOUD-13619__Improve_web_api_async_performance__2.4.x.patch +++ /dev/null @@ -1,50 +0,0 @@ -diff --git a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -index 93555559ac9a1..2f46685c6b117 100644 ---- a/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -+++ b/vendor/magento/module-webapi-async/Controller/Rest/Asynchronous/InputParamsResolver.php -@@ -70,6 +70,11 @@ class InputParamsResolver - */ - private $methodsMap; - -+ /** -+ * @var array -+ */ -+ private array $inputData = []; -+ - /** - * Initialize dependencies. - * -@@ -156,8 +161,14 @@ public function resolve() - */ - public function getInputData() - { -+ if (!empty($this->inputData)) { -+ return $this->inputData; -+ } -+ - if ($this->isBulk === false) { -- return [$this->inputParamsResolver->getInputData()]; -+ $this->inputData = [$this->inputParamsResolver->getInputData()]; -+ -+ return $this->inputData; - } - $inputData = $this->request->getRequestData(); - -@@ -167,7 +178,7 @@ public function getInputData() - $inputData = array_merge($requestBodyParams, $inputData); - } - -- return array_map(function ($singleEntityParams) { -+ $this->inputData = array_map(function ($singleEntityParams) { - if (is_array($singleEntityParams)) { - $singleEntityParams = $this->filterInputData($singleEntityParams); - $singleEntityParams = $this->paramsOverrider->override( -@@ -178,6 +189,8 @@ public function getInputData() - - return $singleEntityParams; - }, $inputData); -+ -+ return $this->inputData; - } - - /** diff --git a/patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch b/patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch deleted file mode 100644 index e042d0ba..00000000 --- a/patches/MCLOUD-13752__Patch_for_CVE-2025-47109_Improve_category_view__2.4.8.patch +++ /dev/null @@ -1,59 +0,0 @@ -diff --git a/vendor/magento/module-catalog/Helper/Category.php b/vendor/magento/module-catalog/Helper/Category.php -index fe511d40e9caa..761dc6f62adda 100644 ---- a/vendor/magento/module-catalog/Helper/Category.php -+++ b/vendor/magento/module-catalog/Helper/Category.php -@@ -10,8 +10,10 @@ - use Magento\Catalog\Model\CategoryFactory; - use Magento\Framework\App\Helper\AbstractHelper; - use Magento\Framework\App\Helper\Context; -+use Magento\Framework\App\ObjectManager; - use Magento\Framework\Data\CollectionFactory; - use Magento\Framework\Data\Tree\Node\Collection; -+use Magento\Framework\Escaper; - use Magento\Framework\Exception\NoSuchEntityException; - use Magento\Framework\ObjectManager\ResetAfterRequestInterface; - use Magento\Store\Model\ScopeInterface; -@@ -63,24 +65,33 @@ class Category extends AbstractHelper implements ResetAfterRequestInterface - */ - protected $categoryRepository; - -+ /** -+ * @var Escaper|null -+ */ -+ private ?Escaper $escaper; -+ - /** - * @param Context $context - * @param CategoryFactory $categoryFactory - * @param StoreManagerInterface $storeManager - * @param CollectionFactory $dataCollectionFactory - * @param CategoryRepositoryInterface $categoryRepository -+ * @param Escaper|null $escaper - */ - public function __construct( - Context $context, - CategoryFactory $categoryFactory, - StoreManagerInterface $storeManager, - CollectionFactory $dataCollectionFactory, -- CategoryRepositoryInterface $categoryRepository -+ CategoryRepositoryInterface $categoryRepository, -+ ?Escaper $escaper = null - ) { - $this->_categoryFactory = $categoryFactory; - $this->_storeManager = $storeManager; - $this->_dataCollectionFactory = $dataCollectionFactory; - $this->categoryRepository = $categoryRepository; -+ $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); -+ - parent::__construct($context); - } - -@@ -204,6 +215,7 @@ public function getCanonicalUrl(string $categoryUrl): string - if ($params && isset($params['p'])) { - $categoryUrl = $categoryUrl . '?p=' . $params['p']; - } -- return $categoryUrl; -+ -+ return $this->escaper->escapeUrl($categoryUrl); - } - } diff --git a/patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch b/patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch deleted file mode 100644 index b9af7660..00000000 --- a/patches/MCLOUD-13753__Patch_for_CVE-2025-47110_improve-admin-cache-efficiency__2.4.x.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff --git a/vendor/magento/module-email/Model/Template/Filter.php b/vendor/magento/module-email/Model/Template/Filter.php -index f5b69484285cc..f86deacad3b4e 100644 ---- a/vendor/magento/module-email/Model/Template/Filter.php -+++ b/vendor/magento/module-email/Model/Template/Filter.php -@@ -1,7 +1,7 @@ - setDataUsingMethod($k, $v); - } - -+ if (!$block->hasData('cache_key')) { -+ $block->setDataUsingMethod('cache_key', $block->getCacheKey()); -+ } -+ - if (isset($blockParameters['output'])) { - $method = $blockParameters['output']; - } diff --git a/src/App/GenericException.php b/src/App/GenericException.php index 60fbd9f4..06955e80 100644 --- a/src/App/GenericException.php +++ b/src/App/GenericException.php @@ -19,7 +19,7 @@ class GenericException extends \Exception * @param int $code * @param Throwable|null $previous */ - public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null) + public function __construct(string $message, int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); } diff --git a/src/Command/Apply.php b/src/Command/Apply.php index 2fb110cf..47b1692e 100644 --- a/src/Command/Apply.php +++ b/src/Command/Apply.php @@ -81,7 +81,7 @@ protected function configure() /** * @inheritDoc */ - public function execute(InputInterface $input, OutputInterface $output): int + public function execute(InputInterface $input, OutputInterface $output) { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Ece/Apply.php b/src/Command/Ece/Apply.php index 6749962c..7a1b513e 100644 --- a/src/Command/Ece/Apply.php +++ b/src/Command/Ece/Apply.php @@ -89,7 +89,7 @@ protected function configure() /** * @inheritDoc */ - public function execute(InputInterface $input, OutputInterface $output): int + public function execute(InputInterface $input, OutputInterface $output) { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Ece/Revert.php b/src/Command/Ece/Revert.php index 5065008a..82446359 100644 --- a/src/Command/Ece/Revert.php +++ b/src/Command/Ece/Revert.php @@ -71,7 +71,7 @@ protected function configure() /** * {@inheritDoc} */ - public function execute(InputInterface $input, OutputInterface $output): int + public function execute(InputInterface $input, OutputInterface $output) { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Revert.php b/src/Command/Revert.php index 70412826..d0bc7e40 100644 --- a/src/Command/Revert.php +++ b/src/Command/Revert.php @@ -94,7 +94,7 @@ protected function configure() /** * {@inheritDoc} */ - public function execute(InputInterface $input, OutputInterface $output): int + public function execute(InputInterface $input, OutputInterface $output) { $this->logger->info($this->magentoVersion->get()); diff --git a/src/Command/Status.php b/src/Command/Status.php index 3f70ed97..72df3a05 100644 --- a/src/Command/Status.php +++ b/src/Command/Status.php @@ -61,7 +61,7 @@ protected function configure() /** * {@inheritDoc} */ - public function execute(InputInterface $input, OutputInterface $output): int + public function execute(InputInterface $input, OutputInterface $output) { try { $this->showStatus->run($input, $output); diff --git a/src/Console/QuestionFactory.php b/src/Console/QuestionFactory.php index d45f7417..9a21dfd4 100644 --- a/src/Console/QuestionFactory.php +++ b/src/Console/QuestionFactory.php @@ -22,7 +22,7 @@ class QuestionFactory * * @return Question */ - public function create(string $question, ?string $default = null): Question + public function create(string $question, string $default = null): Question { return new Question($question, $default); } diff --git a/src/Shell/ProcessFactory.php b/src/Shell/ProcessFactory.php index 4cb7a3fc..5184bea2 100644 --- a/src/Shell/ProcessFactory.php +++ b/src/Shell/ProcessFactory.php @@ -49,7 +49,7 @@ public function __construct(DirectoryList $directoryList, Composer $composer) * @return Process * @throws PackageNotFoundException */ - public function create(array $cmd, ?string $input = null): Process + public function create(array $cmd, string $input = null): Process { return new Process( $this->processSupportsArrayParam() ? $cmd : implode(' ', $cmd), diff --git a/src/Test/Functional/Acceptance/AbstractCest.php b/src/Test/Functional/Acceptance/AbstractCest.php index 07c9c213..d3374bbc 100644 --- a/src/Test/Functional/Acceptance/AbstractCest.php +++ b/src/Test/Functional/Acceptance/AbstractCest.php @@ -17,123 +17,6 @@ class AbstractCest */ protected $edition = 'EE'; - /** - * @var array - */ - private $dependencyListFor244 = [ - "magento/module-re-captcha-admin-ui" => "1.1.2", - "magento/module-re-captcha-checkout" => "1.1.2", - "magento/module-re-captcha-contact" => "1.1.1", - "magento/module-re-captcha-customer" => "1.1.2", - "magento/module-re-captcha-frontend-ui" => "1.1.2", - "magento/module-re-captcha-migration" => "1.1.2", - "magento/module-re-captcha-newsletter" => "1.1.2", - "magento/module-re-captcha-paypal" => "1.1.2", - "magento/module-re-captcha-review" => "1.1.2", - "magento/module-re-captcha-send-friend" => "1.1.2", - "magento/module-re-captcha-store-pickup" => "1.0.1", - "magento/module-re-captcha-ui" => "1.1.2", - "magento/module-re-captcha-user" => "1.1.2", - "magento/module-re-captcha-validation" => "1.1.1", - "magento/module-re-captcha-validation-api" => "1.1.1", - "magento/module-re-captcha-version-2-checkbox" => "2.0.2", - "magento/module-re-captcha-version-2-invisible" => "2.0.2", - "magento/module-re-captcha-version-3-invisible" => "2.0.2", - "magento/module-re-captcha-webapi-api" => "1.0.1", - "magento/module-re-captcha-webapi-rest" => "1.0.1", - "magento/module-re-captcha-webapi-graph-ql" => "1.0.1", - "magento/module-re-captcha-webapi-ui" => "1.0.1", - "magento/module-securitytxt" => "1.1.1", - "magento/module-two-factor-auth" => "1.1.3", - "magento/module-re-captcha-checkout-sales-rule" => "1.1.0", - "magento/inventory-composer-installer" => "1.2.0", - "magento/module-inventory" => "1.2.2", - "magento/module-inventory-admin-ui" => "1.2.2", - "magento/module-inventory-advanced-checkout" => "1.2.1", - "magento/module-inventory-api" => "1.2.2", - "magento/module-inventory-bundle-product" => "1.2.1", - "magento/module-inventory-bundle-product-admin-ui" => "1.2.2", - "magento/module-inventory-bundle-product-indexer" => "1.1.1", - "magento/module-inventory-bundle-import-export" => "1.1.1", - "magento/module-inventory-cache" => "1.2.2", - "magento/module-inventory-catalog" => "1.2.2", - "magento/module-inventory-catalog-admin-ui" => "1.2.2", - "magento/module-inventory-catalog-api" => "1.3.2", - "magento/module-inventory-catalog-search" => "1.2.2", - "magento/module-inventory-configurable-product" => "1.2.2", - "magento/module-inventory-configurable-product-admin-ui" => "1.2.2", - "magento/module-inventory-configurable-product-indexer" => "1.2.2", - "magento/module-inventory-configuration" => "1.2.2", - "magento/module-inventory-configuration-api" => "1.2.1", - "magento/module-inventory-distance-based-source-selection" => "1.2.2", - "magento/module-inventory-distance-based-source-selection-admin-ui" => "1.2.1", - "magento/module-inventory-distance-based-source-selection-api" => "1.2.1", - "magento/module-inventory-elasticsearch" => "1.2.1", - "magento/module-inventory-export-stock" => "1.2.1", - "magento/module-inventory-export-stock-api" => "1.2.1", - "magento/module-inventory-graph-ql" => "1.2.1", - "magento/module-inventory-grouped-product" => "1.2.2", - "magento/module-inventory-grouped-product-admin-ui" => "1.2.2", - "magento/module-inventory-grouped-product-indexer" => "1.2.2", - "magento/module-inventory-import-export" => "1.2.2", - "magento/module-inventory-indexer" => "2.1.2", - "magento/module-inventory-in-store-pickup" => "1.1.1", - "magento/module-inventory-in-store-pickup-admin-ui" => "1.1.1", - "magento/module-inventory-in-store-pickup-api" => "1.1.1", - "magento/module-inventory-in-store-pickup-frontend" => "1.1.2", - "magento/module-inventory-in-store-pickup-graph-ql" => "1.1.1", - "magento/module-inventory-in-store-pickup-multishipping" => "1.1.1", - "magento/module-inventory-in-store-pickup-quote" => "1.1.1", - "magento/module-inventory-in-store-pickup-quote-graph-ql" => "1.1.1", - "magento/module-inventory-in-store-pickup-sales" => "1.1.1", - "magento/module-inventory-in-store-pickup-sales-admin-ui" => "1.1.2", - "magento/module-inventory-in-store-pickup-sales-api" => "1.1.1", - "magento/module-inventory-in-store-pickup-shipping" => "1.1.1", - "magento/module-inventory-in-store-pickup-shipping-admin-ui" => "1.1.1", - "magento/module-inventory-in-store-pickup-shipping-api" => "1.1.1", - "magento/module-inventory-in-store-pickup-webapi-extension" => "1.1.1", - "magento/module-inventory-low-quantity-notification" => "1.2.1", - "magento/module-inventory-low-quantity-notification-admin-ui" => "1.2.2", - "magento/module-inventory-low-quantity-notification-api" => "1.2.1", - "magento/module-inventory-multi-dimensional-indexer-api" => "1.2.1", - "magento/module-inventory-product-alert" => "1.2.2", - "magento/module-inventory-quote-graph-ql" => "1.0.1", - "magento/module-inventory-requisition-list" => "1.2.2", - "magento/module-inventory-reservation-cli" => "1.2.2", - "magento/module-inventory-reservations" => "1.2.1", - "magento/module-inventory-reservations-api" => "1.2.1", - "magento/module-inventory-sales" => "1.2.2", - "magento/module-inventory-sales-admin-ui" => "1.2.2", - "magento/module-inventory-sales-api" => "1.2.1", - "magento/module-inventory-sales-frontend-ui" => "1.2.2", - "magento/module-inventory-setup-fixture-generator" => "1.2.1", - "magento/module-inventory-shipping" => "1.2.2", - "magento/module-inventory-shipping-admin-ui" => "1.2.2", - "magento/module-inventory-source-deduction-api" => "1.2.2", - "magento/module-inventory-source-selection" => "1.2.1", - "magento/module-inventory-source-selection-api" => "1.4.1", - "magento/module-inventory-visual-merchandiser" => "1.1.2", - "magento/module-inventory-swatches-frontend-ui" => "1.0.1", - "magento/module-inventory-catalog-frontend-ui" => "1.0.2", - "magento/module-inventory-configurable-product-frontend-ui" => "1.0.2", - "magento/module-inventory-wishlist" => "1.0.1", - "magento/module-inventory-catalog-search-bundle-product" => "1.0.1", - "magento/module-inventory-catalog-search-configurable-product" => "1.0.1", - "magento/module-page-builder" => "2.2.2", - "magento/module-page-builder-analytics" => "1.6.2", - "magento/module-cms-page-builder-analytics" => "1.6.2", - "magento/module-page-builder-admin-analytics" => "1.1.2", - "magento/module-catalog-page-builder-analytics" => "1.6.2", - "magento/module-aws-s3-page-builder" => "1.0.2", - "magento/module-banner-page-builder" => "2.2.2", - "magento/module-banner-page-builder-analytics" => "1.7.1", - "magento/module-catalog-staging-page-builder" => "1.7.1", - "magento/module-staging-page-builder" => "2.2.2", - "magento/module-cms-page-builder-analytics-staging" => "1.7.1", - "magento/module-catalog-page-builder-analytics-staging" => "1.7.1", - "magento/module-page-builder-admin-gws-admin-ui" => "1.7.1" - ]; - /** * @param \CliTester $I */ @@ -146,7 +29,6 @@ public function _before(\CliTester $I): void * @param \CliTester $I * @param string $templateVersion * @param string $magentoVersion - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function prepareTemplate(\CliTester $I, string $templateVersion, string $magentoVersion = null): void { @@ -176,15 +58,6 @@ protected function prepareTemplate(\CliTester $I, string $templateVersion, strin ); } - if ($magentoVersion === '2.4.4') { - foreach ($this->dependencyListFor244 as $package => $version) { - $I->assertTrue( - $I->addDependencyToComposer($package, $version), - "Can not override dependency {$package} with version {$version} for Adobe Commerce 2.4.4" - ); - } - } - if ($this->edition === 'CE' || $magentoVersion) { $version = $magentoVersion ?: $this->getVersionRangeForMagento($I); $I->removeDependencyFromComposer('magento/magento-cloud-metapackage'); diff --git a/src/Test/Functional/Acceptance/Acceptance81Cest.php b/src/Test/Functional/Acceptance/Acceptance81Cest.php index bbd08f33..49ffd695 100644 --- a/src/Test/Functional/Acceptance/Acceptance81Cest.php +++ b/src/Test/Functional/Acceptance/Acceptance81Cest.php @@ -18,22 +18,14 @@ class Acceptance81Cest extends AcceptanceCest protected function patchesDataProvider(): array { return [ - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p1'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p2'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p3'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p4'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p5'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p6'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p7'], - ['templateVersion' => '2.4.4-p1-p8', 'magentoVersion' => '2.4.4-p8'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p1'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p2'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p3'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p4'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p5'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p6'], - ['templateVersion' => '2.4.5-p1-p7', 'magentoVersion' => '2.4.5-p7'], + ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4'], + ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p1'], + ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p2'], + ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p3'], + ['templateVersion' => '2.4.4', 'magentoVersion' => '2.4.4-p4'], + ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5'], + ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5-p1'], + ['templateVersion' => '2.4.5', 'magentoVersion' => '2.4.5-p2'], ]; } } diff --git a/src/Test/Functional/Acceptance/Acceptance84Cest.php b/src/Test/Functional/Acceptance/Acceptance84Cest.php deleted file mode 100644 index fac30028..00000000 --- a/src/Test/Functional/Acceptance/Acceptance84Cest.php +++ /dev/null @@ -1,24 +0,0 @@ - '2.4.8', 'magentoVersion' => '2.4.8'], - ]; - } -} diff --git a/src/Test/Functional/Acceptance/PatchApplier83Cest.php b/src/Test/Functional/Acceptance/PatchApplier83Cest.php deleted file mode 100644 index 49e3e4b9..00000000 --- a/src/Test/Functional/Acceptance/PatchApplier83Cest.php +++ /dev/null @@ -1,24 +0,0 @@ - '2.4.7', 'magentoVersion' => '2.4.7'], - ]; - } -} diff --git a/src/Test/Functional/Acceptance/PatchApplier84Cest.php b/src/Test/Functional/Acceptance/PatchApplier84Cest.php deleted file mode 100644 index c14a280c..00000000 --- a/src/Test/Functional/Acceptance/PatchApplier84Cest.php +++ /dev/null @@ -1,24 +0,0 @@ - '2.4.8', 'magentoVersion' => '2.4.8'], - ]; - } -} diff --git a/src/Test/Functional/Acceptance/PatchApplierCest.php b/src/Test/Functional/Acceptance/PatchApplierCest.php index 46b526d6..05a0990e 100644 --- a/src/Test/Functional/Acceptance/PatchApplierCest.php +++ b/src/Test/Functional/Acceptance/PatchApplierCest.php @@ -10,43 +10,28 @@ use Magento\CloudDocker\Test\Functional\Codeception\Docker; /** - * Abstract PatchApplierCest - * - * @abstract + * @group php83 */ -abstract class PatchApplierCest extends AbstractCest +class PatchApplierCest extends AbstractCest { /** - * Prepares the test environment before each test. - * - * @param \CliTester $I The CLI tester instance. - * @throws \Robo\Exception\TaskException + * @param \CliTester $I */ public function _before(\CliTester $I): void { parent::_before($I); + + $this->prepareTemplate($I, '2.4.7'); + $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); } /** - * Tests applying an existing patch to a target file. - * * @param \CliTester $I - * @param \Codeception\Example $data The example data for the test. - * Expected structure: - * [ - * 'templateVersion' => string, - * 'magentoVersion' => string|null (optional) - * ] * @throws \Robo\Exception\TaskException - * @dataProvider patchesDataProvider */ - public function testApplyingPatch(\CliTester $I, \Codeception\Example $data): void + public function testApplyingPatch(\CliTester $I): void { - $this->prepareTemplate($I, $data['templateVersion'], $data['magentoVersion'] ?? null); - $I->generateDockerCompose('--mode=production'); - - $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); $I->copyFileToWorkDir('files/patches/target_file.md', 'target_file.md'); $I->copyFileToWorkDir('files/patches/patch.patch', 'm2-hotfixes/patch.patch'); @@ -57,30 +42,17 @@ public function testApplyingPatch(\CliTester $I, \Codeception\Example $data): vo $targetFile = $I->grabFileContent('/target_file.md', Docker::BUILD_CONTAINER); $I->assertStringContainsString('# Hello Magento', $targetFile); $I->assertStringContainsString('## Additional Info', $targetFile); - $log = $I->grabFileContent('/init/var/log/cloud.log', Docker::BUILD_CONTAINER); + $log = $I->grabFileContent('/var/log/cloud.log', Docker::BUILD_CONTAINER); $I->assertStringContainsString('Patch ../m2-hotfixes/patch.patch has been applied', $log); } /** - * Tests that an existing patch is not applied again. - * * @param \CliTester $I - * @param \Codeception\Example $data The example data for the test. - * Expected structure: - * [ - * 'templateVersion' => string, - * 'magentoVersion' => string|null (optional) - * ] * @throws \Robo\Exception\TaskException - * @dataProvider patchesDataProvider */ - public function testApplyingExistingPatch(\CliTester $I, \Codeception\Example $data): void + public function testApplyingExistingPatch(\CliTester $I): void { - $this->prepareTemplate($I, $data['templateVersion'], $data['magentoVersion'] ?? null); - $I->generateDockerCompose('--mode=production'); - - $I->copyFileToWorkDir('files/debug_logging/.magento.env.yaml', '.magento.env.yaml'); $I->copyFileToWorkDir('files/patches/target_file_applied_patch.md', 'target_file.md'); $I->copyFileToWorkDir('files/patches/patch.patch', 'm2-hotfixes/patch.patch'); @@ -93,13 +65,7 @@ public function testApplyingExistingPatch(\CliTester $I, \Codeception\Example $d $I->assertStringContainsString('## Additional Info', $targetFile); $I->assertStringContainsString( 'Patch ../m2-hotfixes/patch.patch was already applied', - $I->grabFileContent('/init/var/log/cloud.log', Docker::BUILD_CONTAINER) + $I->grabFileContent('/var/log/cloud.log', Docker::BUILD_CONTAINER) ); } - - /** - * Returns the data provider for patches. - * @return array - */ - abstract protected function patchesDataProvider(): array; } diff --git a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php index 6c8ae3fd..eb0c62ff 100644 --- a/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ApplyOptionalActionTest.php @@ -108,12 +108,7 @@ public function testExecuteSuccessful() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patch1, $patch2, $patch3]); $this->applier->method('apply') @@ -122,27 +117,18 @@ public function testExecuteSuccessful() [$patch2->getPath(), $patch2->getId(), 'Patch ' . $patch2->getId() .' has been applied'], [$patch3->getPath(), $patch3->getId(), 'Patch ' . $patch3->getId() .' has been applied'], ]); + $this->renderer->expects($this->exactly(3)) - ->method('printPatchInfo') - ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { - static $callCount = 0; - $expectedPatches = [$patch1, $patch2, $patch3]; - $expectedMessages = [ - 'Patch ' . $patch1->getId() . ' has been applied', - 'Patch ' . $patch2->getId() . ' has been applied', - 'Patch ' . $patch3->getId() . ' has been applied' - ]; - - if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { - $callCount++; - return true; - } - - return false; - }); + ->method('printPatchInfo') + ->withConsecutive( + [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied'], + [$outputMock, $patch2, 'Patch ' . $patch2->getId() .' has been applied'], + [$outputMock, $patch3, 'Patch ' . $patch3->getId() .' has been applied'] + ); + $this->action->execute($inputMock, $outputMock, $patchFilter); } - + /** * Tests successful optional patches applying. * @@ -163,13 +149,9 @@ public function testApplyAlreadyAppliedPatch() $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patch1]); + $this->applier->expects($this->never()) ->method('apply'); $this->renderer->expects($this->never()) @@ -177,10 +159,12 @@ public function testApplyAlreadyAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') - ->with( - $this->stringContains( - 'Patch ' . $patch1->getId() .' (' . $patch1->getFilename() . ') was already applied' - ) + ->withConsecutive( + [ + $this->stringContains( + 'Patch ' . $patch1->getId() .' (' . $patch1->getFilename() . ') was already applied' + ) + ] ); $this->action->execute($inputMock, $outputMock, $patchFilter); @@ -219,10 +203,8 @@ public function testApplyingAllPatchesAndSkipDeprecated() $this->renderer->expects($this->once()) ->method('printPatchInfo') - ->with( - $outputMock, - $patch1, - 'Patch ' . $patch1->getId() .' has been applied' + ->withConsecutive( + [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied'] ); $this->action->execute($inputMock, $outputMock, $patchFilter); @@ -250,35 +232,27 @@ public function testApplyWithException() ->willReturn([$patch1, $patch2]); $this->applier->method('apply') - ->willReturnCallback(function ($path, $id) use ($patch1, $patch2) { - if ($id === 'MC-22222') { - throw new ApplierException('Applier error message'); + ->willReturnMap([ + [$patch1->getPath(), $patch1->getId()], + [$patch2->getPath(), $patch2->getId()] + ])->willReturnCallback( + function ($path, $id) { + if ($id === 'MC-22222') { + throw new ApplierException('Applier error message'); + } + + return "Patch {$path} {$id} has been applied"; } - // Return success message for the first patch - return "Patch {$path} {$id} has been applied"; - }); + ); + $this->conflictProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { - static $callCount = 0; - $expectedPatches = [$patch1, $patch2, $patch3]; - $expectedMessages = [ - 'Patch ' . $patch1->getId() . ' has been applied', - 'Patch ' . $patch2->getId() . ' has been applied', - 'Patch ' . $patch3->getId() . ' has been applied' - ]; - - if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { - $callCount++; - return true; - } - - return false; - }) + ->withConsecutive([$outputMock, $patch2, [$patch1], 'Applier error message']) ->willThrowException(new RuntimeException('Error message')); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); + $this->action->execute($inputMock, $outputMock, $patchFilter); } diff --git a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php index aecea225..2d374ca9 100644 --- a/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ConfirmRequiredActionTest.php @@ -86,20 +86,15 @@ public function testAskConfirmationForNotAppliedPatches() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patch1, $patch2, $patch3]); - $aggregatedPatch = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatch = $this->getMockForAbstractClass(AggregatedPatchInterface::class); $this->aggregator->expects($this->once()) ->method('aggregate') ->with([$patch1, $patch2]) @@ -107,13 +102,8 @@ public function testAskConfirmationForNotAppliedPatches() $this->renderer->expects($this->once()) ->method('printTable') - ->with($outputMock, [$aggregatedPatch]) - ->willReturnCallback(function ($output) use ($outputMock, $aggregatedPatch) { - if ($output === $outputMock && $aggregatedPatch === [$aggregatedPatch]) { - throw new RuntimeException('Error message'); - } - return null; - }); + ->withConsecutive([$outputMock, [$aggregatedPatch]]); + $this->renderer->expects($this->once()) ->method('printQuestion') ->willReturn(true); @@ -129,12 +119,12 @@ public function testPatchNotFoundException() $patchFilter = ['unknown id']; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); - $this->optionalPool->expects($this->once()) + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); + $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->with($patchFilter) + ->withConsecutive([$patchFilter]) ->willThrowException(new PatchNotFoundException('')); $this->expectException(RuntimeException::class); @@ -154,29 +144,23 @@ public function testConfirmationRejected() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getAdditionalRequiredPatches') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patch1]); - $aggregatedPatch = $this->createMock(AggregatedPatchInterface::class); + $aggregatedPatch = $this->getMockForAbstractClass(AggregatedPatchInterface::class); $this->aggregator->expects($this->once()) ->method('aggregate') ->with([$patch1]) ->willReturn([$aggregatedPatch]); - $this->optionalPool->expects($this->once()) - ->method('getAdditionalRequiredPatches') - ->with($patchFilter) - ->willReturn([$patch1]); + $this->renderer->expects($this->once()) + ->method('printTable') + ->withConsecutive([$outputMock, [$aggregatedPatch]]); $this->renderer->expects($this->once()) ->method('printQuestion') @@ -197,7 +181,7 @@ public function testConfirmationRejected() */ private function createPatch(string $path, string $id, bool $isDeprecated = false) { - $patch = $this->createMock(PatchInterface::class); + $patch = $this->getMockForAbstractClass(PatchInterface::class); $patch->method('getPath')->willReturn($path); $patch->method('getFilename')->willReturn('filename.patch'); $patch->method('getId')->willReturn($id); diff --git a/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php b/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php index 70b6cdf7..441a0722 100644 --- a/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ProcessDeprecatedActionTest.php @@ -105,21 +105,11 @@ public function testProcessDeprecationSuccessful() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patchMock]); $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->willReturnCallback(function ($patchId) use ($patchFilter, $patch1) { - if ($patchId === $patch1->getId()) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patch1->getId()]) ->willReturn([]); $this->aggregator->expects($this->once()) @@ -128,12 +118,8 @@ public function testProcessDeprecationSuccessful() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function ($patchId) use ($patchFilter) { - if ($patchId === $expectedMessage) { - $this->stringContains($expectedMessage); - } - return []; - }); + ->withConsecutive([$this->stringContains($expectedMessage)]); + $this->renderer->expects($this->once()) ->method('printQuestion') ->willReturn(true); @@ -157,12 +143,7 @@ public function testProcessDeprecationException() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -204,12 +185,7 @@ public function testProcessReplacementSuccessful() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -218,22 +194,12 @@ public function testProcessReplacementSuccessful() $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patch1->getId()]) ->willReturn($requireReplacement); $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function ($patchId) use ($patchFilter) { - if ($patchId === $expectedMessage) { - $this->stringContains($expectedMessage); - } - return []; - }); + ->withConsecutive([$this->stringContains($expectedMessage)]); $this->renderer->expects($this->once()) ->method('printQuestion') @@ -263,12 +229,7 @@ public function testSkippingReplacementProcessForAppliedPatch() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -303,12 +264,7 @@ public function testProcessReplacementException() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patchMock]); $this->aggregator->expects($this->once()) @@ -317,12 +273,7 @@ public function testProcessReplacementException() $this->optionalPool->expects($this->once()) ->method('getReplacedBy') - ->willReturnCallback(function ($patchId) use ($patchFilter, $patch1) { - if ($patchId === $patch1->getId()) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patch1->getId()]) ->willReturn($requireReplacement); $this->renderer->expects($this->once()) diff --git a/src/Test/Unit/Command/Process/Action/RevertActionTest.php b/src/Test/Unit/Command/Process/Action/RevertActionTest.php index 469f0d75..b8f515e0 100644 --- a/src/Test/Unit/Command/Process/Action/RevertActionTest.php +++ b/src/Test/Unit/Command/Process/Action/RevertActionTest.php @@ -56,19 +56,12 @@ class RevertActionTest extends TestCase * @var RevertValidator|MockObject */ private $revertValidator; - - /** - * @var revertAction|MockObject - */ - protected $revertAction; /** * @inheritdoc */ protected function setUp(): void { - // Initialize the $revertAction property - $this->revertAction = $this->createMock(RevertAction::class); $this->applier = $this->createMock(Applier::class); $this->revertValidator = $this->createMock(RevertValidator::class); $this->statusPool = $this->createMock(StatusPool::class); @@ -105,17 +98,12 @@ public function testExecuteSuccessful() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter, false]) ->willReturn([$patch1, $patch2]); $this->applier->method('revert') @@ -126,21 +114,10 @@ public function testExecuteSuccessful() $this->renderer->expects($this->exactly(2)) ->method('printPatchInfo') - ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2) { - static $callCount = 0; - $expectedPatches = [$patch1, $patch2]; - $expectedMessages = [ - 'Patch ' . $patch1->getId() . ' has been applied', - 'Patch ' . $patch2->getId() . ' has been applied' - ]; - - if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { - $callCount++; - return true; - } - - return false; - }); + ->withConsecutive( + [$outputMock, $patch2, 'Patch ' . $patch2->getId() .' has been reverted'], + [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been reverted'] + ); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -160,17 +137,12 @@ public function testRevertNotAppliedPatch() ]); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchFilter, $patch1) { - if ($filter === $patchFilter) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchFilter]) ->willReturn([$patch1]); $this->applier->expects($this->never()) @@ -180,14 +152,14 @@ public function testRevertNotAppliedPatch() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function ($patchId) use ($patchFilter, $patch1) { - if ($patchId === $patch1->getId()) { + ->withConsecutive( + [ $this->stringContains( 'Patch ' . $patch1->getId() . ' (' . $patch1->getFilename() . ') is not applied' - ); - } - return []; - }); + ) + ] + ); + $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -203,9 +175,9 @@ public function testRevertWithException() $errorMessage = sprintf('Reverting patch %s (%s) failed.', $patch1->getId(), $patch1->getPath()); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->method('getList') ->willReturn([$patch1]); @@ -214,12 +186,10 @@ public function testRevertWithException() $outputMock->expects($this->once()) ->method('writeln') - ->willReturnCallback(function ($patchId) use ($patchFilter) { - if ($patchId === $errorMessage) { - $this->stringContains($errorMessage); - } - return []; - }); + ->withConsecutive( + [$this->stringContains($errorMessage)] + ); + $this->expectException(RuntimeException::class); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -232,12 +202,12 @@ public function testPatchNotFoundException() $patchFilter = ['unknown id']; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->optionalPool->expects($this->once()) ->method('getList') - ->with($patchFilter) + ->withConsecutive([$patchFilter]) ->willThrowException(new PatchNotFoundException('')); $this->expectException(RuntimeException::class); @@ -252,15 +222,14 @@ public function testValidationFailedException() $patchFilter = ['MC-11111']; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->revertValidator->expects($this->once()) ->method('validate') - ->with($patchFilter) + ->withConsecutive([$patchFilter]) ->willThrowException(new RuntimeException('Error')); - $this->optionalPool->expects($this->never()) ->method('getList'); @@ -278,7 +247,7 @@ public function testValidationFailedException() */ private function createPatch(string $path, string $id) { - $patch = $this->createMock(PatchInterface::class); + $patch = $this->getMockForAbstractClass(PatchInterface::class); $patch->method('getPath')->willReturn($path); $patch->method('getId')->willReturn($id); diff --git a/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php b/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php index 328383fc..b793c373 100644 --- a/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php +++ b/src/Test/Unit/Command/Process/Action/ReviewAppliedActionTest.php @@ -54,7 +54,7 @@ class ReviewAppliedActionTest extends TestCase */ protected function setUp(): void { - $this->logger = $this->createMock(LoggerInterface::class); + $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); $this->statusPool = $this->createMock(StatusPool::class); $this->optionalPool = $this->createMock(OptionalPool::class); @@ -82,9 +82,9 @@ public function testAppliedPatchesExceedsLimit() } /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->statusPool->method('isApplied') ->willReturn(true); @@ -95,7 +95,7 @@ public function testAppliedPatchesExceedsLimit() $outputMock->expects($this->once()) ->method('writeln') - ->with($this->stringContains('error')); + ->withConsecutive([$this->stringContains('error')]); $this->action->execute($inputMock, $outputMock, $patchFilter); } @@ -111,9 +111,9 @@ public function testAppliedPatchesNotExceedLimit() } /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->statusPool->method('isApplied') ->willReturn(true); @@ -137,7 +137,7 @@ public function testAppliedPatchesNotExceedLimit() */ private function createPatch(string $id) { - $patch = $this->createMock(PatchInterface::class); + $patch = $this->getMockForAbstractClass(PatchInterface::class); $patch->method('getId')->willReturn($id); return $patch; diff --git a/src/Test/Unit/Command/Process/ApplyLocalTest.php b/src/Test/Unit/Command/Process/ApplyLocalTest.php index 742e109a..d2212697 100644 --- a/src/Test/Unit/Command/Process/ApplyLocalTest.php +++ b/src/Test/Unit/Command/Process/ApplyLocalTest.php @@ -62,7 +62,7 @@ class ApplyLocalTest extends TestCase protected function setUp(): void { $this->applier = $this->createMock(Applier::class); - $this->logger = $this->createMock(LoggerInterface::class); + $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); $this->localPool = $this->createMock(LocalPool::class); $this->renderer = $this->createMock(Renderer::class); $this->rollbackProcessor = $this->createMock(RollbackProcessor::class); @@ -86,9 +86,9 @@ public function testExecuteLocalPatchesNotFound() $expectedMessage = 'Hot-fixes were not found. Skipping'; /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->localPool->method('getList') ->willReturn([]); $outputMock->expects($this->once()) @@ -125,23 +125,12 @@ public function testApplySuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { - static $callCount = 0; - $expectedPatches = [$patch1, $patch2, $patch3]; - $expectedMessages = [ - $this->anything(), - 'Patch ' . $patch1->getTitle() . ' has been applied', - 'Patch ' . $patch2->getTitle() . ' has been applied', - 'Patch ' . $patch3->getTitle() . ' has been applied' - ]; - - if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { - $callCount++; - return true; - } - - return false; - }); + ->withConsecutive( + [$this->anything()], + [$this->stringContains('Patch ' . $patch1->getTitle() .' has been applied')], + [$this->stringContains('Patch ' . $patch2->getTitle() .' has been applied')], + [$this->stringContains('Patch ' . $patch3->getTitle() .' has been applied')] + ); $this->manager->run($inputMock, $outputMock); } @@ -180,12 +169,7 @@ function ($path, $title) { $this->rollbackProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function ($filter) use ($patch1) { - if ($filter === $patch1) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([[$patch1]]) ->willReturn($rollbackMessages); $this->expectException(RuntimeException::class); diff --git a/src/Test/Unit/Command/Process/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/ApplyOptionalTest.php index 0825517e..0b947307 100644 --- a/src/Test/Unit/Command/Process/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/ApplyOptionalTest.php @@ -82,7 +82,8 @@ public function testApplyWithPatchArgumentProvided() $this->actionPool->expects($this->once()) ->method('execute') - ->with($inputMock, $outputMock, $cliPatchArgument); + ->withConsecutive([$inputMock, $outputMock, $cliPatchArgument]); + $this->applyOptional->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/ApplyRequiredTest.php b/src/Test/Unit/Command/Process/ApplyRequiredTest.php index de335c7f..45b794ac 100644 --- a/src/Test/Unit/Command/Process/ApplyRequiredTest.php +++ b/src/Test/Unit/Command/Process/ApplyRequiredTest.php @@ -62,7 +62,7 @@ class ApplyRequiredTest extends TestCase protected function setUp(): void { $this->applier = $this->createMock(Applier::class); - $this->logger = $this->createMock(LoggerInterface::class); + $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); $this->requiredPool = $this->createMock(RequiredPool::class); $this->renderer = $this->createMock(Renderer::class); $this->conflictProcessor = $this->createMock(ConflictProcessor::class); @@ -88,9 +88,9 @@ public function testApplySuccessful() $patch3 = $this->createPatch('/path/patch3.patch', 'MC-33333'); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->requiredPool->method('getList') ->willReturn([$patch1, $patch2, $patch3]); @@ -103,22 +103,12 @@ public function testApplySuccessful() $this->renderer->expects($this->exactly(3)) ->method('printPatchInfo') - ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2, $patch3) { - static $callCount = 0; - $expectedPatches = [$patch1, $patch2, $patch3]; - $expectedMessages = [ - 'Patch ' . $patch1->getId() . ' has been applied', - 'Patch ' . $patch2->getId() . ' has been applied', - 'Patch ' . $patch3->getId() . ' has been applied' - ]; - - if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { - $callCount++; - return true; - } - - return false; - }); + ->withConsecutive( + [$outputMock, $patch1, 'Patch ' . $patch1->getId() .' has been applied'], + [$outputMock, $patch2, 'Patch ' . $patch2->getId() .' has been applied'], + [$outputMock, $patch3, 'Patch ' . $patch3->getId() .' has been applied'] + ); + $this->manager->run($inputMock, $outputMock); } @@ -132,29 +122,19 @@ public function testApplyWithException() $patch = $this->createPatch('/path/patch.patch', 'MC-11111'); /** @var InputInterface|MockObject $inputMock */ - $inputMock = $this->createMock(InputInterface::class); + $inputMock = $this->getMockForAbstractClass(InputInterface::class); /** @var OutputInterface|MockObject $outputMock */ - $outputMock = $this->createMock(OutputInterface::class); + $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $this->requiredPool->method('getList') ->willReturn([$patch]); $this->applier->method('apply') - ->with( - $this->logicalOr($this->equalTo($patch->getPath()), $this->equalTo($patch->getId())) - ) + ->withConsecutive([$patch->getPath(), $patch->getId()]) ->willThrowException(new ApplierException('Applier error message')); - $this->conflictProcessor->expects($this->once()) ->method('process') - ->with( - $this->logicalOr( - $this->equalTo($outputMock), - $this->equalTo($patch), - $this->equalTo([]), - $this->equalTo('Applier error message') - ) - ) - ->willThrowException(new RuntimeException('Error message')); + ->withConsecutive([$outputMock, $patch, [], 'Applier error message']) + ->willThrowException(new RuntimeException('Error message')); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Error message'); @@ -172,7 +152,7 @@ public function testApplyWithException() */ private function createPatch(string $path, string $id) { - $patch = $this->createMock(PatchInterface::class); + $patch = $this->getMockForAbstractClass(PatchInterface::class); $patch->method('getPath')->willReturn($path); $patch->method('getId')->willReturn($id); diff --git a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php index 560081a5..4f547b8d 100644 --- a/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php +++ b/src/Test/Unit/Command/Process/Ece/ApplyOptionalTest.php @@ -88,14 +88,8 @@ public function testApplyWithPatchEnvVariableProvided() $this->actionPool->expects($this->once()) ->method('execute') - ->with($inputMock, $outputMock, $configQualityPatches) - ->willReturnCallback(function ($input, $output, $config) - use ($inputMock, $outputMock, $configQualityPatches) { - if ($input === $inputMock && $output === $outputMock && $config === $configQualityPatches) { - return true; - } - return null; - }); + ->withConsecutive([$inputMock, $outputMock, $configQualityPatches]); + $this->applyOptionalEce->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/Ece/RevertTest.php b/src/Test/Unit/Command/Process/Ece/RevertTest.php index 6663b319..cbba07ac 100644 --- a/src/Test/Unit/Command/Process/Ece/RevertTest.php +++ b/src/Test/Unit/Command/Process/Ece/RevertTest.php @@ -116,31 +116,16 @@ public function testRevertSuccessful() $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function ($patch, $message) use ($patch1, $patch2) { - static $callCount = 0; - $expectedPatches = [$patch1, $patch2, $patch3]; - $expectedMessages = [ - $this->anything(), - 'Patch ' . $patch1->getTitle() . ' has been reverted', - 'Patch ' . $patch2->getTitle() . ' has been reverted', - ]; - - if ($patch === $expectedPatches[$callCount] && $message === $expectedMessages[$callCount]) { - $callCount++; - return true; - } + ->withConsecutive( + [$this->anything()], + [$this->stringContains('Patch ' . $patch2->getTitle() .' has been reverted')], + [$this->stringContains('Patch ' . $patch1->getTitle() .' has been reverted')] + ); - return false; - }); $this->revertAction->expects($this->once()) ->method('execute') - ->with($inputMock, $outputMock, []) - ->willReturnCallback(function ($input, $output) use ($inputMock, $outputMock) { - if ($output === $outputMock && $input === $inputMock && $patch === []) { - return true; - } - return false; - }); + ->withConsecutive([$inputMock, $outputMock, []]); + $this->revertEce->run($inputMock, $outputMock); } @@ -182,13 +167,7 @@ function ($path, $title) { $this->revertAction->expects($this->once()) ->method('execute') - ->with($inputMock, $outputMock) - ->willReturnCallback(function ($input, $output) use ($inputMock, $outputMock) { - if ($output === $outputMock && $input === $inputMock) { - return true; - } - return false; - }); + ->withConsecutive([$inputMock, $outputMock, []]); $this->revertEce->run($inputMock, $outputMock); } diff --git a/src/Test/Unit/Command/Process/RendererTest.php b/src/Test/Unit/Command/Process/RendererTest.php index 6e65b5e0..b7b1f66d 100644 --- a/src/Test/Unit/Command/Process/RendererTest.php +++ b/src/Test/Unit/Command/Process/RendererTest.php @@ -76,12 +76,7 @@ public function testPrintPatchInfo(PatchInterface $patch, string $prependedMessa $outputMock = $this->getMockForAbstractClass(OutputInterface::class); $outputMock->expects($this->atLeastOnce()) ->method('writeln') - ->willReturnCallback(function ($filter) use ($expectedArray) { - if ($filter === $expectedArray) { - return $expectedArray; - } - return []; - }); + ->withConsecutive([$expectedArray]); $this->renderer->printPatchInfo($outputMock, $patch, $prependedMessage); } diff --git a/src/Test/Unit/Command/Process/RevertTest.php b/src/Test/Unit/Command/Process/RevertTest.php index 4cef6a4c..d2d00a4d 100644 --- a/src/Test/Unit/Command/Process/RevertTest.php +++ b/src/Test/Unit/Command/Process/RevertTest.php @@ -83,25 +83,13 @@ public function testRevertWithPatchArgumentProvided() ->with(RevertCommand::OPT_ALL) ->willReturn($cliOptAll); $this->filterFactory->method('createRevertFilter') - ->with($cliOptAll, $cliPatchArgument) - ->willReturnCallback(function ($patches) use ($cliOptAll, $cliPatchArgument) { - if ($patches === $cliOptAll && $patches === $cliPatchArgument) { - return true; - } - return false; - }) + ->withConsecutive([$cliOptAll, $cliPatchArgument]) ->willReturn($cliPatchArgument); $this->revertAction->expects($this->once()) ->method('execute') - ->with($inputMock, $outputMock, $cliPatchArgument) - ->willReturnCallback(function ($input, $output, $cliPatch) - use ($inputMock, $outputMock, $cliPatchArgument) { - if ($input === $inputMock && $output === $outputMock && $cliPatch === $cliPatchArgument) { - return true; - } - return false; - }); + ->withConsecutive([$inputMock, $outputMock, $cliPatchArgument]); + $this->manager->run($inputMock, $outputMock); } @@ -129,13 +117,7 @@ public function testRevertWithEmptyPatchArgument() ->with(RevertCommand::OPT_ALL) ->willReturn($cliOptAll); $this->filterFactory->method('createRevertFilter') - ->with($cliOptAll, $cliPatchArgument) - ->willReturnCallback(function ($cliOpt, $cliPatch) use ($cliOptAll, $cliPatchArgument) { - if ($cliOpt === $cliOptAll && $cliPatch === $cliPatchArgument) { - return true; - } - return false; - }) + ->withConsecutive([$cliOptAll, $cliPatchArgument]) ->willReturn(null); $this->revertAction->expects($this->never()) diff --git a/src/Test/Unit/Command/Process/ShowStatusTest.php b/src/Test/Unit/Command/Process/ShowStatusTest.php index 8d3eca2e..6caf965c 100644 --- a/src/Test/Unit/Command/Process/ShowStatusTest.php +++ b/src/Test/Unit/Command/Process/ShowStatusTest.php @@ -140,12 +140,7 @@ public function testShowStatus() $this->reviewAppliedAction->expects($this->once()) ->method('execute') - ->willReturnCallback(function ($input, $output, $patches) use ($inputMock, $outputMock) { - if ($input === $outputMock && $output === $outputMock) { - return true; - } - return false; - }); + ->withConsecutive([$inputMock, $outputMock, []]); $this->optionalPool->method('getList') ->willReturn([$patchMock]); $this->localPool->method('getList') @@ -158,26 +153,16 @@ public function testShowStatus() // Show warning message about patch deprecation $outputMock->expects($this->exactly(4)) ->method('writeln') - ->willReturnCallback(function ($filter) use ($patch1) { - if ($filter === $patch1->getId()) { - $this->anything(); - $this->stringContains('Deprecated patch ' . $patch1->getId() . ' is currently applied'); - } - return false; - }); + ->withConsecutive( + [$this->anything()], + [$this->stringContains('Deprecated patch ' . $patch1->getId() . ' is currently applied')] + ); // Show patches in the table $this->renderer->expects($this->once()) ->method('printTable') - ->with($outputMock, [$patch1, $patch2, $patch5]) - ->willReturnCallback(function ($output, $patches) - use ($outputMock, $patch, $patch2, $patch5) { - if ($output === $outputMock && $patches === [$patch2] - && $patches === [$patch2] && $patches === [$patch2]) { - return true; - } - return false; - }); + ->withConsecutive([$outputMock, [$patch1, $patch2, $patch5]]); + $this->manager->run($inputMock, $outputMock); } @@ -196,6 +181,9 @@ private function createPatch(string $id, bool $isDeprecated, string $replacedWit $patch->method('isDeprecated')->willReturn($isDeprecated); $patch->method('getReplacedWith')->willReturn($replacedWith); + // To make mock object unique for assertions and array operations. + $patch->id = microtime(); + return $patch; } } diff --git a/src/Test/Unit/Patch/AggregatorTest.php b/src/Test/Unit/Patch/AggregatorTest.php index 3ab331af..b438c516 100644 --- a/src/Test/Unit/Patch/AggregatorTest.php +++ b/src/Test/Unit/Patch/AggregatorTest.php @@ -8,7 +8,6 @@ namespace Magento\CloudPatches\Test\Unit\Patch; use Magento\CloudPatches\Patch\AggregatedPatchFactory; -use Magento\CloudPatches\Patch\Data\AggregatedPatchInterface; use Magento\CloudPatches\Patch\Aggregator; use Magento\CloudPatches\Patch\Data\Patch; use PHPUnit\Framework\MockObject\MockObject; @@ -38,9 +37,9 @@ protected function setUp(): void $this->aggregator = new Aggregator($this->aggregatedPatchFactory); } - /** - * Tests patch aggregation. - */ + /** + * Tests patch aggregation. + */ public function testAggregate() { $patch1CE = $this->createPatch('MC-1', 'Patch1 CE'); @@ -50,30 +49,21 @@ public function testAggregate() $patch2EE = $this->createPatch('MC-2', 'Patch2 EE'); $patch3 = $this->createPatch('MC-3', 'Patch3'); - // Mock AggregatedPatchInterface to return the patches when getPatches is called - $aggregatedPatchMock1 = $this->createMock(AggregatedPatchInterface::class); - $aggregatedPatchMock1->method('getRequire')->willReturn([$patch1CE, $patch1EE, $patch1B2B]); - - $aggregatedPatchMock2 = $this->createMock(AggregatedPatchInterface::class); - $aggregatedPatchMock2->method('getRequire')->willReturn([$patch2CE, $patch2EE]); - - $aggregatedPatchMock3 = $this->createMock(AggregatedPatchInterface::class); - $aggregatedPatchMock3->method('getRequire')->willReturn([$patch3]); - - // Setting up the factory mock to return AggregatedPatchInterface mocks $this->aggregatedPatchFactory->expects($this->exactly(3)) - ->method('create') - ->willReturnOnConsecutiveCalls( - $aggregatedPatchMock1, // First call returns this AggregatedPatchInterface mock - $aggregatedPatchMock2, // Second call returns this AggregatedPatchInterface mock - $aggregatedPatchMock3 // Third call returns this AggregatedPatchInterface mock - ); + ->method('create') + ->withConsecutive( + [[$patch1CE, $patch1EE, $patch1B2B]], + [[$patch2CE, $patch2EE]], + [[$patch3]] + ); - $result = $this->aggregator->aggregate( - [$patch1CE, $patch1EE, $patch1B2B, $patch2CE, $patch2EE, $patch3] + $this->assertTrue( + is_array( + $this->aggregator->aggregate( + [$patch1CE, $patch1EE, $patch1B2B, $patch2CE, $patch2EE, $patch3] + ) + ) ); - - $this->assertTrue(is_array($result)); } /** @@ -88,7 +78,10 @@ private function createPatch(string $id, string $title) $patch = $this->createMock(Patch::class); $patch->method('getId')->willReturn($id); $patch->method('getTitle')->willReturn($title); - $patch->method('__toString')->willReturn(microtime()); + + // To make mock object unique for assertions and array operations. + $patch->id = microtime(); + $patch->method('__toString')->willReturn($patch->id); return $patch; } diff --git a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php index 850e0be2..b0a3ff2e 100644 --- a/src/Test/Unit/Patch/Collector/CloudCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/CloudCollectorTest.php @@ -105,76 +105,49 @@ public function testCollectSuccessful(bool $isCloud, string $expectedType) $this->patchBuilder->expects($this->exactly(3)) ->method('setId') - ->willReturnCallback(function ($args) { - static $series = [ - 'MDVA-2470', 'MDVA-2470', 'MAGECLOUD-2033' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->withConsecutive(['MDVA-2470'], ['MDVA-2470'], ['MAGECLOUD-2033']); $this->patchBuilder->expects($this->exactly(3)) ->method('setTitle') - ->willReturnCallback(function ($args) { - static $series = [ - 'Fix asset locker race condition when using Redis', - 'Fix asset locker race condition when using Redis EE', - 'Allow DB dumps done with the support module to complete' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->withConsecutive( + ['Fix asset locker race condition when using Redis'], + ['Fix asset locker race condition when using Redis EE'], + ['Allow DB dumps done with the support module to complete'] + ); $this->patchBuilder->expects($this->exactly(3)) ->method('setFilename') - ->willReturnCallback(function ($args) { - static $series = [ - 'MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', - 'MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', - 'MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->withConsecutive( + ['MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], + ['MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], + ['MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] + ); $this->patchBuilder->expects($this->exactly(3)) ->method('setPath') - ->willReturnCallback(function ($args) { - static $series = [ - self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch', - self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch', - self::CLOUD_PATCH_DIR . '/MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); - + ->withConsecutive( + [self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], + [self::CLOUD_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], + [self::CLOUD_PATCH_DIR . '/MAGECLOUD-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] + ); $this->patchBuilder->expects($this->exactly(3)) ->method('setType') - ->with($this->logicalOr( - $this->equalTo($expectedType), - $this->equalTo($expectedType), - $this->equalTo($expectedType) - )); + ->withConsecutive( + [$expectedType], + [$expectedType], + [$expectedType] + ); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageName') - ->willReturnCallback(function ($args) { - static $series = [ - 'magento/magento2-base', - 'magento/magento2-ee-base', - 'magento/magento2-ee-base' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->withConsecutive( + ['magento/magento2-base'], + ['magento/magento2-ee-base'], + ['magento/magento2-ee-base'] + ); $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageConstraint') - ->willReturnCallback(function ($args) { - static $series = [ - '2.2.0 - 2.2.5', - '2.2.0 - 2.2.5', - '2.2.0 - 2.2.5' - ]; - $expectedArgs = array_shift($series); - $this->assertSame($expectedArgs, $args); - }); + ->withConsecutive( + ['2.2.0 - 2.2.5'], + ['2.2.0 - 2.2.5'], + ['2.2.0 - 2.2.5'] + ); $this->patchBuilder->expects($this->exactly(3)) ->method('build') ->willReturn($this->createMock(Patch::class)); @@ -185,7 +158,7 @@ public function testCollectSuccessful(bool $isCloud, string $expectedType) /** * @return array */ - public static function collectDataProvider(): array + public function collectDataProvider(): array { return [ ['isCloud' => false, 'expectedType' => PatchInterface::TYPE_OPTIONAL], @@ -215,13 +188,13 @@ public function testInvalidConfigurationPatchFilename(array $invalidConfig) /** * @return array */ - public static function invalidPatchFilenameDataProvider(): array + public function invalidPatchFilenameDataProvider(): array { return [ - [self::createConfig('fix_asset_locking_race_condition__2.1.4.patch')], - [self::createConfig('MDVA-2470__fix_asset_locking_race_condition.patch')], - [self::createConfig('MDVA-2470_fix_asset_locking_race_condition__2.1.4.patch')], - [self::createConfig('MDVA-2470__fix_asset_locking_race_condition_2.1.4.patch')], + [$this->createConfig('fix_asset_locking_race_condition__2.1.4.patch')], + [$this->createConfig('MDVA-2470__fix_asset_locking_race_condition.patch')], + [$this->createConfig('MDVA-2470_fix_asset_locking_race_condition__2.1.4.patch')], + [$this->createConfig('MDVA-2470__fix_asset_locking_race_condition_2.1.4.patch')], ]; } @@ -231,7 +204,7 @@ public static function invalidPatchFilenameDataProvider(): array * @param string $filename * @return array */ - private static function createConfig(string $filename): array + private function createConfig(string $filename): array { return [ 'magento/magento2-base' => [ @@ -264,7 +237,7 @@ public function testInvalidConfigurationTitleSection(array $config) /** * @return array */ - public static function invalidTitleSectionDataProvider(): array + public function invalidTitleSectionDataProvider(): array { return [ [ diff --git a/src/Test/Unit/Patch/Collector/LocalCollectorTest.php b/src/Test/Unit/Patch/Collector/LocalCollectorTest.php index 8fdfcdfa..7787d081 100644 --- a/src/Test/Unit/Patch/Collector/LocalCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/LocalCollectorTest.php @@ -65,47 +65,22 @@ public function testCollect() $this->patchBuilder->expects($this->exactly(2)) ->method('setId') - ->with( - $this->logicalOr($this->equalTo($shortPath1), $this->equalTo($shortPath2)) - ); + ->withConsecutive([$shortPath1], [$shortPath2]); $this->patchBuilder->expects($this->exactly(2)) ->method('setTitle') - ->with( - $this->logicalOr($this->equalTo($shortPath1), $this->equalTo($shortPath2)) + ->withConsecutive( + [$shortPath1], + [$shortPath2] ); - $this->patchBuilder->expects($this->exactly(2)) ->method('setFilename') - ->willReturnCallback(function ($service) { - static $services = [ - 'patch1.patch', 'patch2.patch' - ]; - - $expectedService = array_shift($services); - $this->assertSame($expectedService, $service); - }); + ->withConsecutive(['patch1.patch'], ['patch2.patch']); $this->patchBuilder->expects($this->exactly(2)) ->method('setPath') - ->willReturnCallback(function () use (&$callCount, $file1, $file2) { - $callCount++; - if ($callCount === 1) { - return $file1; - } elseif ($callCount === 2) { - return $file2; - } - }); - + ->withConsecutive([$file1], [$file2]); $this->patchBuilder->expects($this->exactly(2)) ->method('setType') - ->willReturnCallback(function ($service) { - static $services = [ - PatchInterface::TYPE_CUSTOM, - PatchInterface::TYPE_CUSTOM - ]; - - $expectedService = array_shift($services); - $this->assertSame($expectedService, $service); - }); + ->withConsecutive([PatchInterface::TYPE_CUSTOM], [PatchInterface::TYPE_CUSTOM]); $this->patchBuilder->expects($this->exactly(2)) ->method('build') ->willReturn($this->createMock(Patch::class)); diff --git a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php index ec71c386..9cf82dc7 100644 --- a/src/Test/Unit/Patch/Collector/QualityCollectorTest.php +++ b/src/Test/Unit/Patch/Collector/QualityCollectorTest.php @@ -86,6 +86,13 @@ public function testCollectSuccessful() $this->qualityPackage->method('getPatchesDirectoryPath') ->willReturn(self::QUALITY_PATCH_DIR); + $this->package->method('matchConstraint') + ->willReturnMap([ + ['magento/magento2-base', '2.1.4 - 2.1.14', false], + ['magento/magento2-base', '2.2.0 - 2.2.5', true], + ['magento/magento2-ee-base', '2.2.0 - 2.2.5', true], + ]); + $this->package->method('matchConstraint') ->willReturnMap([ ['magento/magento2-base', '2.1.4 - 2.1.14', false], @@ -93,126 +100,66 @@ public function testCollectSuccessful() ['magento/magento2-ee-base', '2.2.0 - 2.2.5', true], ]); - // Replacing withConsecutive with with() and logicalOr $this->patchBuilder->expects($this->exactly(3)) ->method('setId') - ->with( - $this->logicalOr( - $this->equalTo('MDVA-2470'), - $this->equalTo('MDVA-2470'), - $this->equalTo('MDVA-2033') - ) - ); - + ->withConsecutive(['MDVA-2470'], ['MDVA-2470'], ['MDVA-2033']); $this->patchBuilder->expects($this->exactly(3)) ->method('setTitle') - ->with( - $this->logicalOr( - $this->equalTo('Fix asset locker race condition when using Redis'), - $this->equalTo('Fix asset locker race condition when using Redis'), - $this->equalTo('Allow DB dumps done with the support module to complete') - ) + ->withConsecutive( + ['Fix asset locker race condition when using Redis'], + ['Fix asset locker race condition when using Redis'], + ['Allow DB dumps done with the support module to complete'] ); - $this->patchBuilder->expects($this->exactly(3)) ->method('setFilename') - ->with( - $this->logicalOr( - $this->equalTo('MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'), - $this->equalTo('MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'), - $this->equalTo('MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch') - ) + ->withConsecutive( + ['MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], + ['MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], + ['MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] ); - $this->patchBuilder->expects($this->exactly(3)) ->method('setPath') - ->with( - $this->logicalOr( - $this->equalTo( - self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch' - ), - $this->equalTo( - self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch' - ), - $this->equalTo( - self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch' - ) - ) + ->withConsecutive( + [self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0.patch'], + [self::QUALITY_PATCH_DIR . '/MDVA-2470__fix_asset_locking_race_condition__2.2.0_ee.patch'], + [self::QUALITY_PATCH_DIR . '/MDVA-2033__prevent_deadlock_during_db_dump__2.2.0.patch'] ); - - $this->PatchBuildertest(); - - $this->patchBuilder->expects($this->exactly(3)) - ->method('build') - ->willReturn($this->createMock(Patch::class)); - - $this->assertTrue(is_array($this->collector->collect())); - } - - /** - * patchBuilder function - */ - public function patchBuilderTest() - { $this->patchBuilder->expects($this->exactly(3)) ->method('setType') - ->with( - $this->logicalOr( - $this->equalTo(PatchInterface::TYPE_OPTIONAL), - $this->equalTo(PatchInterface::TYPE_OPTIONAL), - $this->equalTo(PatchInterface::TYPE_OPTIONAL) - ) + ->withConsecutive( + [PatchInterface::TYPE_OPTIONAL], + [PatchInterface::TYPE_OPTIONAL], + [PatchInterface::TYPE_OPTIONAL] ); - $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageName') - ->with( - $this->logicalOr( - $this->equalTo('magento/magento2-base'), - $this->equalTo('magento/magento2-ee-base'), - $this->equalTo('magento/magento2-ee-base') - ) + ->withConsecutive( + ['magento/magento2-base'], + ['magento/magento2-ee-base'], + ['magento/magento2-ee-base'] ); - $this->patchBuilder->expects($this->exactly(3)) ->method('setPackageConstraint') - ->with( - $this->logicalOr( - $this->equalTo('2.2.0 - 2.2.5'), - $this->equalTo('2.2.0 - 2.2.5'), - $this->equalTo('2.2.0 - 2.2.5') - ) + ->withConsecutive( + ['2.2.0 - 2.2.5'], + ['2.2.0 - 2.2.5'], + ['2.2.0 - 2.2.5'] ); - $this->patchBuilder->expects($this->exactly(3)) ->method('setRequire') - ->with( - $this->logicalOr( - $this->equalTo([]), - $this->equalTo([]), - $this->equalTo(['MC-11111', 'MC-22222']) - ) - ); + ->withConsecutive([[]], [[]], [['MC-11111', 'MC-22222']]); $this->patchBuilder->expects($this->exactly(3)) ->method('setReplacedWith') - ->with( - $this->logicalOr( - $this->equalTo(''), - $this->equalTo(''), - $this->equalTo('MC-33333') - ) - ); - + ->withConsecutive([''], [''], ['MC-33333']); $this->patchBuilder->expects($this->exactly(3)) ->method('setDeprecated') - ->with( - $this->logicalOr( - $this->equalTo(false), - $this->equalTo(false), - $this->equalTo(true) - ) - ); + ->withConsecutive([false], [false], [true]); + $this->patchBuilder->expects($this->exactly(3)) + ->method('build') + ->willReturn($this->createMock(Patch::class)); + + $this->assertTrue(is_array($this->collector->collect())); } /** diff --git a/src/Test/Unit/Patch/Conflict/AnalyzerTest.php b/src/Test/Unit/Patch/Conflict/AnalyzerTest.php index 0d3daec5..b3c00ff7 100644 --- a/src/Test/Unit/Patch/Conflict/AnalyzerTest.php +++ b/src/Test/Unit/Patch/Conflict/AnalyzerTest.php @@ -107,7 +107,7 @@ public function testAnalyze(array $checkApplyMap, string $expectedMessage) /** * @return array */ - public static function analyzeDataProvider(): array + public function analyzeDataProvider(): array { return [ [ diff --git a/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php b/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php index 37ffaebd..fcbecaed 100644 --- a/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php +++ b/src/Test/Unit/Patch/Conflict/ApplyCheckerTest.php @@ -68,12 +68,7 @@ public function testCheck() $this->optionalPool->expects($this->once()) ->method('getList') - ->willReturnCallback(function ($filter) use ($patchIds, $patch1) { - if ($filter === $patchIds) { - return [$patch1]; - } - return []; - }) + ->withConsecutive([$patchIds]) ->willReturn([$patch1, $patch2, $patch3]); $this->filesystem->expects($this->exactly(3)) ->method('get') diff --git a/src/Test/Unit/Patch/Conflict/ProcessorTest.php b/src/Test/Unit/Patch/Conflict/ProcessorTest.php index 8656c85d..20dacd06 100644 --- a/src/Test/Unit/Patch/Conflict/ProcessorTest.php +++ b/src/Test/Unit/Patch/Conflict/ProcessorTest.php @@ -83,30 +83,18 @@ public function testProcess() $this->rollbackProcessor->expects($this->once()) ->method('process') - ->willReturnCallback(function ($patch) use ($appliedPatch1, $appliedPatch2) { - static $callCount = 0; - $expectedPatches = [$appliedPatch1, $appliedPatch2]; - if ($patch === $expectedPatches[$callCount]) { - $callCount++; - return true; - } - - return false; - }) + ->withConsecutive([[$appliedPatch1, $appliedPatch2]]) ->willReturn($rollbackMessages); $this->conflictAnalyzer->expects($this->once()) ->method('analyze') - ->with($failedPatch) + ->withConsecutive([$failedPatch]) ->willReturn($conflictDetails); $outputMock->expects($this->exactly(2)) ->method('writeln') - ->willReturnCallback(function ($filter) use ($failedPatch) { - if ($filter === $failedPatch->getId() && $filter === $rollbackMessages) { - $this->stringContains('Error: patch ' . $failedPatch->getId() . ' can\'t be applied'); - $rollbackMessages; - } - return []; - }); + ->withConsecutive( + [$this->stringContains('Error: patch ' . $failedPatch->getId() . ' can\'t be applied')], + [$rollbackMessages] + ); $expectedErrorMessage = sprintf( 'Applying patch %s (%s) failed.%s%s', diff --git a/src/Test/Unit/Patch/FilterFactoryTest.php b/src/Test/Unit/Patch/FilterFactoryTest.php index 9039a678..dbe63fe7 100644 --- a/src/Test/Unit/Patch/FilterFactoryTest.php +++ b/src/Test/Unit/Patch/FilterFactoryTest.php @@ -46,7 +46,7 @@ public function testCreateApplyFilter(array $inputArgument, $expectedValue) /** * @return array */ - public static function createApplyFilterDataProvider(): array + public function createApplyFilterDataProvider(): array { return [ ['inputArgument' => [], 'expectedValue' => null], @@ -75,7 +75,7 @@ public function testCreateRevertFilter(array $inputArgument, bool $optAll, $expe /** * @return array */ - public static function createRevertFilterDataProvider(): array + public function createRevertFilterDataProvider(): array { return [ ['inputArgument' => [], 'optAll' => false, 'expectedValue' => null], diff --git a/src/Test/Unit/Patch/GitConverterTest.php b/src/Test/Unit/Patch/GitConverterTest.php index b29b2517..bbeeda58 100644 --- a/src/Test/Unit/Patch/GitConverterTest.php +++ b/src/Test/Unit/Patch/GitConverterTest.php @@ -47,7 +47,7 @@ public function testConvert(string $composerContent, string $expectedContent) * phpcs:disable * @return array */ - public static function convertDataProvider() + public function convertDataProvider() { return [ [ diff --git a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php index b6cc39c6..9796426a 100644 --- a/src/Test/Unit/Patch/Pool/OptionalPoolTest.php +++ b/src/Test/Unit/Patch/Pool/OptionalPoolTest.php @@ -319,15 +319,15 @@ private function caseReturnPatchListUnique(): array */ private function createPatch(string $id, array $require = [], string $replacedWith = '') { - $patch = $this->createMock(Patch::class); $patch->method('getId')->willReturn($id); $patch->method('getRequire')->willReturn($require); $patch->method('getReplacedWith')->willReturn($replacedWith); $patch->method('getOrigin')->willReturn(SupportCollector::ORIGIN); - // To avoid dynamically adding properties, use __toString method instead - $patch->method('__toString')->willReturn($id); + // To make mock object unique for assertions and array operations. + $patch->id = microtime(); + $patch->method('__toString')->willReturn($patch->id); return $patch; } diff --git a/src/Test/Unit/Patch/RevertValidatorTest.php b/src/Test/Unit/Patch/RevertValidatorTest.php index 9eac161e..7c2e6eca 100644 --- a/src/Test/Unit/Patch/RevertValidatorTest.php +++ b/src/Test/Unit/Patch/RevertValidatorTest.php @@ -112,7 +112,8 @@ public function testValidateWithNoDependents() ->with('MC-1') ->willReturn([]); - $this->statusPool->method('isApplied')->willReturn(false); + $this->statusPool->expects($this->never()) + ->method('isApplied'); $this->revertValidator->validate($patchFilter); } diff --git a/src/Test/Unit/Patch/Status/OptionalResolverTest.php b/src/Test/Unit/Patch/Status/OptionalResolverTest.php index d59bb219..a8902b47 100644 --- a/src/Test/Unit/Patch/Status/OptionalResolverTest.php +++ b/src/Test/Unit/Patch/Status/OptionalResolverTest.php @@ -248,6 +248,9 @@ private function createPatch(string $id, array $require = []) $aggregatedPatch->method('getRequire')->willReturn($require); $aggregatedPatch->method('getItems')->willReturn([$patch]); + // To make mock object unique for assertions and array operations. + $aggregatedPatch->id = microtime(); + return $aggregatedPatch; } } diff --git a/src/Test/Unit/Shell/Command/PatchDriverTest.php b/src/Test/Unit/Shell/Command/PatchDriverTest.php index 2e9d7a9f..02ac4ae5 100644 --- a/src/Test/Unit/Shell/Command/PatchDriverTest.php +++ b/src/Test/Unit/Shell/Command/PatchDriverTest.php @@ -1,56 +1,62 @@ baseDir = dirname(__DIR__, 5) . '/tests/unit/'; $this->cwd = $this->baseDir . 'var/'; - $this->processFactoryMock = $this->createMock(ProcessFactory::class); + $processFactory = $this->createMock(ProcessFactory::class); + $processFactory->method('create') + ->willReturnCallback( + function (array $cmd, string $input = null) { + return new Process( + $cmd, + $this->cwd, + null, + $input + ); + } + ); + $this->command = new PatchDriver( + $processFactory + ); } /** - * Clean up files after tests. - * - * @return void + * @inheritDoc */ protected function tearDown(): void { @@ -63,101 +69,81 @@ protected function tearDown(): void } /** - * Test successful patch apply. - * - * @return void + * Tests that patch is applied */ - public function testApply(): void + public function testApply() { $this->copyFileToWorkingDir($this->getFixtureFile('file1.md')); $patchContent = $this->getFileContent($this->getFixtureFile('file1.patch')); - - $this->processFactoryMock->method('create')->willReturnCallback( - function (array $cmd, ?string $input = null) { - return new Process($cmd, $this->cwd, null, $input); - } - ); - - $command = new PatchDriver($this->processFactoryMock); - $command->apply($patchContent); - + $this->command->apply($patchContent); $expected = $this->getFileContent($this->getFixtureFile('file1_applied_patch.md')); $actual = $this->getFileContent($this->getVarFile('file1.md')); - $this->assertEquals($expected, $actual); } /** - * Test patch apply failure handling. - * - * @return void + * Tests that patch is not applied to any target files if an error occurs */ - public function testApplyFailure(): void + public function testApplyFailure() { $this->copyFileToWorkingDir($this->getFixtureFile('file1.md')); $this->copyFileToWorkingDir($this->getFixtureFile('file2_applied_patch.md'), 'file2.md'); $patchContent = $this->getFileContent($this->getFixtureFile('file1_and_file2.patch')); - - $processMock = $this->createMock(Process::class); - $processMock->method('mustRun')->willThrowException(new ProcessFailedException($processMock)); - - $this->processFactoryMock->method('create')->willReturn($processMock); - $command = new PatchDriver($this->processFactoryMock); - - $this->expectException(DriverException::class); - $command->apply($patchContent); + $exception = null; + try { + $this->command->apply($patchContent); + } catch (PatchCommandException $e) { + $exception = $e; + } + $this->assertNotNull($exception); + $expected = $this->getFileContent($this->getFixtureFile('file1.md')); + $actual = $this->getFileContent($this->getVarFile('file1.md')); + $this->assertEquals($expected, $actual); + $expected = $this->getFileContent($this->getFixtureFile('file2_applied_patch.md')); + $actual = $this->getFileContent($this->getVarFile('file2.md')); + $this->assertEquals($expected, $actual); } /** - * Test successful patch revert. - * - * @return void + * Tests that patch is reverted */ - public function testRevert(): void + public function testRevert() { $this->copyFileToWorkingDir($this->getFixtureFile('file1_applied_patch.md'), 'file1.md'); $patchContent = $this->getFileContent($this->getFixtureFile('file1.patch')); - - $this->processFactoryMock->method('create')->willReturnCallback( - function (array $cmd, ?string $input = null) { - return new Process($cmd, $this->cwd, null, $input); - } - ); - - $command = new PatchDriver($this->processFactoryMock); - $command->revert($patchContent); - + $this->command->revert($patchContent); $expected = $this->getFileContent($this->getFixtureFile('file1.md')); $actual = $this->getFileContent($this->getVarFile('file1.md')); - $this->assertEquals($expected, $actual); } /** - * Test patch revert failure handling - * - * @return void + * Tests that patch is not reverted in any target files if an error occurs */ - public function testRevertFailure(): void + public function testRevertFailure() { $this->copyFileToWorkingDir($this->getFixtureFile('file1_applied_patch.md'), 'file1.md'); $this->copyFileToWorkingDir($this->getFixtureFile('file2.md')); $patchContent = $this->getFileContent($this->getFixtureFile('file1_and_file2.patch')); - - $processMock = $this->createMock(Process::class); - $processMock->method('mustRun')->willThrowException(new ProcessFailedException($processMock)); - - $this->processFactoryMock->method('create')->willReturn($processMock); - $command = new PatchDriver($this->processFactoryMock); - - $this->expectException(DriverException::class); - $command->revert($patchContent); + $exception = null; + try { + $this->command->revert($patchContent); + } catch (PatchCommandException $e) { + $exception = $e; + } + $this->assertNotNull($exception); + $expected = $this->getFileContent($this->getFixtureFile('file1_applied_patch.md')); + $actual = $this->getFileContent($this->getVarFile('file1.md')); + $this->assertEquals($expected, $actual); + $expected = $this->getFileContent($this->getFixtureFile('file2.md')); + $actual = $this->getFileContent($this->getVarFile('file2.md')); + $this->assertEquals($expected, $actual); } /** - * Get full path to a file in the test working directory. + * Get file path in var directory * - * @param string $name + * @param string $name * @return string */ private function getVarFile(string $name): string @@ -166,9 +152,9 @@ private function getVarFile(string $name): string } /** - * Get full path to a fixture file. + * Get file path in files directory * - * @param string $name + * @param string $name * @return string */ private function getFixtureFile(string $name): string @@ -177,9 +163,9 @@ private function getFixtureFile(string $name): string } /** - * Get content from a file. + * Get the file content * - * @param string $path + * @param string $path * @return string */ private function getFileContent(string $path): string @@ -188,13 +174,12 @@ private function getFileContent(string $path): string } /** - * Copy a file to the test working directory. + * Copy file to working directory * - * @param string $path - * @param string|null $name - * @return void + * @param string $path + * @param string|null $name */ - private function copyFileToWorkingDir(string $path, ?string $name = null): void + private function copyFileToWorkingDir(string $path, string $name = null) { $name = $name ?? basename($path); copy($path, $this->getVarFile($name)); diff --git a/tests/unit/phpunit.xml.dist b/tests/unit/phpunit.xml.dist index 9f032a93..de58085e 100644 --- a/tests/unit/phpunit.xml.dist +++ b/tests/unit/phpunit.xml.dist @@ -1,22 +1,26 @@ ../../src/Test/Unit - - - ../../src - - - + + + ../../src + + ../../src/Test + + + +