diff --git a/CHANGELOG.md b/CHANGELOG.md index 7237ebfacf5e1..e17c4e14ffc7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -785,7 +785,7 @@ Tests: * Refactored controller actions in the Product area * Moved commands cache.php, indexer.php, log.php, test.php, compiler.php, singletenant\_compiler.php, generator.php, pack.php, deploy.php and file\_assembler.php to the new bin/magento CLI framework * Data Migration Tool - * The Data Migraiton Tool is published in the separate [repository](https://github.com/magento/data-migration-tool-ce "Data Migration Tool repository") + * The Data Migration Tool is published in the separate [repository](https://github.com/magento/data-migration-tool-ce "Data Migration Tool repository") * Fixed bugs * Fixed an issue where error appeared during placing order with virtual product * Fixed an issue where billing and shipping sections didn't contain address information on order print diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml index 505e178f49f43..e660a2eb8d428 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml @@ -13,6 +13,9 @@ <description value="Test log in to AdvancedReporting and tests AdvancedReportingButtonTest"/> <testCaseId value="MC-14800"/> + <skip> + <issueId value="MC-14800" /> + </skip> <severity value="CRITICAL"/> <group value="analytics"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml new file mode 100644 index 0000000000000..844f58c789a15 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminSuccessLoginActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminSuccessLoginActionGroup"> + <waitForElementVisible selector="{{AdminHeaderSection.adminUserAccountText}}" stepKey="waitForAdminAccountTextVisible" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml new file mode 100644 index 0000000000000..ccc7cd24350c5 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertMessageOnAdminLoginActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageOnAdminLoginActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later." /> + <argument name="messageType" type="string" defaultValue="error" /> + </arguments> + <see userInput="{{message}}" selector="{{AdminLoginMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml new file mode 100644 index 0000000000000..6aaa612b249b6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAdminWithCredentialsActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="LoginAdminWithCredentialsActionGroup"> + <arguments> + <argument name="adminUser" type="string" /> + <argument name="adminPassword" type="string" /> + </arguments> + <amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminPassword}}" stepKey="fillPassword"/> + <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> + <closeAdminNotification stepKey="closeAdminNotification"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml index 1070bc409962a..b2fbadcbe38e2 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginAsAdminActionGroup.xml @@ -10,11 +10,11 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="LoginAsAdmin"> <arguments> - <argument name="adminUser" defaultValue="_ENV"/> + <argument name="adminUser" type="entity" defaultValue="DefaultAdminUser"/> </arguments> <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> + <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{adminUser.username}}" stepKey="fillUsername"/> + <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{adminUser.password}}" stepKey="fillPassword"/> <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> <closeAdminNotification stepKey="closeAdminNotification"/> </actionGroup> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetWebsiteCountryOptionsToDefaultActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetWebsiteCountryOptionsToDefaultActionGroup.xml new file mode 100644 index 0000000000000..4519648eb1d1b --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetWebsiteCountryOptionsToDefaultActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SetWebsiteCountryOptionsToDefaultActionGroup"> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation3"/> + <waitForElementVisible selector="{{CountryOptionsSection.topDestinations}}" stepKey="waitCheckboxToBeVisible3"/> + <checkOption selector="{{CountryOptionsSection.generalCountryAllowInherit}}" stepKey="setToDefault1"/> + <checkOption selector="{{CountryOptionsSection.generalCountryDefaultInherit}}" stepKey="setToDefault2"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveDefaultConfig"/> + <waitForPageLoad stepKey="waitForSavingSystemConfiguration"/> + <see userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml index 441ce886f117b..5b517c7be8a79 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminHeaderSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminHeaderSection"> <element name="pageTitle" type="text" selector=".page-header h1.page-title"/> + <element name="adminUserAccountText" type="text" selector=".page-header .admin-user-account-text" /> </section> </sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginMessagesSection.xml new file mode 100644 index 0000000000000..f6ada50ada357 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminLoginMessagesSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminLoginMessagesSection"> + <element name="messageByType" type="block" selector=".login-content .messages .message-{{messageType}}" parameterized="true" /> + </section> +</sections> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/CountryOptionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/CountryOptionsSection.xml new file mode 100644 index 0000000000000..2e2e5aec35ecd --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/CountryOptionsSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CountryOptionsSection"> + <element name="allowedCountries" type="select" selector="#general_country_allow"/> + <element name="notAllowedCountry" type="button" selector="#general_country_allow option:not([selected])"/> + <element name="generalCountryAllowInherit" type="checkbox" selector="#general_country_allow_inherit"/> + <element name="generalCountryDefaultInherit" type="checkbox" selector="#general_country_default_inherit"/> + <element name="generalCountryDefault" type="select" selector="#general_country_default"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php new file mode 100644 index 0000000000000..a6c1b088400a7 --- /dev/null +++ b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php @@ -0,0 +1,177 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Model\Multishipping; + +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Braintree\Observer\DataAssignObserver; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider as PaypalConfigProvider; +use Magento\Multishipping\Model\Checkout\Type\Multishipping\PlaceOrderInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterface; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Sales\Api\Data\OrderPaymentInterface; +use Magento\Sales\Api\OrderManagementInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; + +/** + * Order payments processing for multishipping checkout flow. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class PlaceOrder implements PlaceOrderInterface +{ + /** + * @var OrderManagementInterface + */ + private $orderManagement; + + /** + * @var OrderPaymentExtensionInterfaceFactory + */ + private $paymentExtensionFactory; + + /** + * @var GetPaymentNonceCommand + */ + private $getPaymentNonceCommand; + + /** + * @param OrderManagementInterface $orderManagement + * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory + * @param GetPaymentNonceCommand $getPaymentNonceCommand + */ + public function __construct( + OrderManagementInterface $orderManagement, + OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, + GetPaymentNonceCommand $getPaymentNonceCommand + ) { + $this->orderManagement = $orderManagement; + $this->paymentExtensionFactory = $paymentExtensionFactory; + $this->getPaymentNonceCommand = $getPaymentNonceCommand; + } + + /** + * @inheritdoc + */ + public function place(array $orderList): array + { + if (empty($orderList)) { + return []; + } + + $errorList = []; + $firstOrder = $this->orderManagement->place(array_shift($orderList)); + // get payment token from first placed order + $paymentToken = $this->getPaymentToken($firstOrder); + + foreach ($orderList as $order) { + try { + /** @var OrderInterface $order */ + $orderPayment = $order->getPayment(); + $this->setVaultPayment($orderPayment, $paymentToken); + $this->orderManagement->place($order); + } catch (\Exception $e) { + $incrementId = $order->getIncrementId(); + $errorList[$incrementId] = $e; + } + } + + return $errorList; + } + + /** + * Sets vault payment method. + * + * @param OrderPaymentInterface $orderPayment + * @param PaymentTokenInterface $paymentToken + * @return void + */ + private function setVaultPayment(OrderPaymentInterface $orderPayment, PaymentTokenInterface $paymentToken): void + { + $vaultMethod = $this->getVaultPaymentMethod( + $orderPayment->getMethod() + ); + $orderPayment->setMethod($vaultMethod); + + $publicHash = $paymentToken->getPublicHash(); + $customerId = $paymentToken->getCustomerId(); + $result = $this->getPaymentNonceCommand->execute( + ['public_hash' => $publicHash, 'customer_id' => $customerId] + ) + ->get(); + + $orderPayment->setAdditionalInformation( + DataAssignObserver::PAYMENT_METHOD_NONCE, + $result['paymentMethodNonce'] + ); + $orderPayment->setAdditionalInformation( + PaymentTokenInterface::PUBLIC_HASH, + $publicHash + ); + $orderPayment->setAdditionalInformation( + PaymentTokenInterface::CUSTOMER_ID, + $customerId + ); + } + + /** + * Returns vault payment method. + * + * For placing sequence of orders, we need to replace the original method on the vault method. + * + * @param string $method + * @return string + */ + private function getVaultPaymentMethod(string $method): string + { + $vaultPaymentMap = [ + ConfigProvider::CODE => ConfigProvider::CC_VAULT_CODE, + PaypalConfigProvider::PAYPAL_CODE => PaypalConfigProvider::PAYPAL_VAULT_CODE + ]; + + return $vaultPaymentMap[$method] ?? $method; + } + + /** + * Returns payment token. + * + * @param OrderInterface $order + * @return PaymentTokenInterface + * @throws \BadMethodCallException + */ + private function getPaymentToken(OrderInterface $order): PaymentTokenInterface + { + $orderPayment = $order->getPayment(); + $extensionAttributes = $this->getExtensionAttributes($orderPayment); + $paymentToken = $extensionAttributes->getVaultPaymentToken(); + + if ($paymentToken === null) { + throw new \BadMethodCallException('Vault Payment Token should be defined for placed order payment.'); + } + + return $paymentToken; + } + + /** + * Gets payment extension attributes. + * + * @param OrderPaymentInterface $payment + * @return OrderPaymentExtensionInterface + */ + private function getExtensionAttributes(OrderPaymentInterface $payment): OrderPaymentExtensionInterface + { + $extensionAttributes = $payment->getExtensionAttributes(); + if (null === $extensionAttributes) { + $extensionAttributes = $this->paymentExtensionFactory->create(); + $payment->setExtensionAttributes($extensionAttributes); + } + + return $extensionAttributes; + } +} diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 5af56a2afd3fe..2f956076f3846 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -21,7 +21,8 @@ "magento/module-quote": "*", "magento/module-sales": "*", "magento/module-ui": "*", - "magento/module-vault": "*" + "magento/module-vault": "*", + "magento/module-multishipping": "*" }, "suggest": { "magento/module-checkout-agreements": "*", diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index 9de4773af023a..fe4cfab9c0e30 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -42,7 +42,7 @@ <paymentInfoKeys>cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision</paymentInfoKeys> <avs_ems_adapter>Magento\Braintree\Model\AvsEmsCodeMapper</avs_ems_adapter> <cvv_ems_adapter>Magento\Braintree\Model\CvvEmsCodeMapper</cvv_ems_adapter> - <group>braintree</group> + <group>braintree_group</group> </braintree> <braintree_paypal> <model>BraintreePayPalFacade</model> @@ -68,7 +68,7 @@ <privateInfoKeys>processorResponseCode,processorResponseText,paymentId</privateInfoKeys> <paymentInfoKeys>processorResponseCode,processorResponseText,paymentId,payerEmail</paymentInfoKeys> <supported_locales>en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL,no_NO,pl_PL,es_ES,sv_SE,tr_TR,pt_BR,ja_JP,id_ID,ko_KR,pt_PT,ru_RU,th_TH,zh_CN,zh_TW</supported_locales> - <group>braintree</group> + <group>braintree_group</group> </braintree_paypal> <braintree_cc_vault> <model>BraintreeCreditCardVaultFacade</model> @@ -78,7 +78,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> - <group>braintree</group> + <group>braintree_group</group> </braintree_cc_vault> <braintree_paypal_vault> <model>BraintreePayPalVaultFacade</model> @@ -88,7 +88,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\PayPal\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> - <group>braintree</group> + <group>braintree_group</group> </braintree_paypal_vault> </payment> </default> diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml index ea417c407dffd..d8d3a93b71dc3 100644 --- a/app/code/Magento/Braintree/etc/frontend/di.xml +++ b/app/code/Magento/Braintree/etc/frontend/di.xml @@ -61,4 +61,12 @@ <argument name="resolver" xsi:type="object">Magento\Braintree\Model\LocaleResolver</argument> </arguments> </type> + <type name="Magento\Multishipping\Model\Checkout\Type\Multishipping\PlaceOrderPool"> + <arguments> + <argument name="services" xsi:type="array"> + <item name="braintree" xsi:type="string">Magento\Braintree\Model\Multishipping\PlaceOrder</item> + <item name="braintree_paypal" xsi:type="string">Magento\Braintree\Model\Multishipping\PlaceOrder</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Braintree/etc/payment.xml b/app/code/Magento/Braintree/etc/payment.xml index dbabd91151022..4cae049aaf5a9 100644 --- a/app/code/Magento/Braintree/etc/payment.xml +++ b/app/code/Magento/Braintree/etc/payment.xml @@ -8,8 +8,16 @@ <payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/payment.xsd"> <groups> - <group id="braintree"> + <group id="braintree_group"> <label>Braintree</label> </group> </groups> + <methods> + <method name="braintree"> + <allow_multiple_address>1</allow_multiple_address> + </method> + <method name="braintree_paypal"> + <allow_multiple_address>1</allow_multiple_address> + </method> + </methods> </payment> diff --git a/app/code/Magento/Braintree/view/frontend/layout/multishipping_checkout_billing.xml b/app/code/Magento/Braintree/view/frontend/layout/multishipping_checkout_billing.xml new file mode 100644 index 0000000000000..06390d403e63d --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/layout/multishipping_checkout_billing.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="checkout_billing"> + <arguments> + <argument name="form_templates" xsi:type="array"> + <item name="braintree" xsi:type="string">Magento_Braintree::multishipping/form.phtml</item> + <item name="braintree_paypal" xsi:type="string">Magento_Braintree::multishipping/form_paypal.phtml</item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml new file mode 100644 index 0000000000000..bf8aa8dd09c2c --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form.phtml @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + 'uiLayout', + 'jquery' + ], function (layout, $) { + $(function () { + var paymentMethodData = { + method: 'braintree' + }; + layout([ + { + component: 'Magento_Braintree/js/view/payment/method-renderer/multishipping/hosted-fields', + name: 'payment_method_braintree', + method: paymentMethodData.method, + item: paymentMethodData + } + ]); + + $('body').trigger('contentUpdated'); + }) + }) +</script> +<!-- ko template: getTemplate() --><!-- /ko --> diff --git a/app/code/Magento/Braintree/view/frontend/templates/multishipping/form_paypal.phtml b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form_paypal.phtml new file mode 100644 index 0000000000000..ea3eb2214c2d8 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/templates/multishipping/form_paypal.phtml @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + 'uiLayout', + 'jquery' + ], function (layout, $) { + $(function () { + var paymentMethodData = { + method: 'braintree_paypal' + }; + layout([ + { + component: 'Magento_Braintree/js/view/payment/method-renderer/multishipping/paypal', + name: 'payment_method_braintree_paypal', + method: paymentMethodData.method, + item: paymentMethodData + } + ]); + + $('body').trigger('contentUpdated'); + }) + }) +</script> +<!-- ko template: getTemplate() --><!-- /ko --> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js new file mode 100644 index 0000000000000..1ceebc8e66282 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/hosted-fields.js @@ -0,0 +1,102 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ + +define([ + 'jquery', + 'Magento_Braintree/js/view/payment/method-renderer/hosted-fields', + 'Magento_Braintree/js/validator', + 'Magento_Ui/js/model/messageList', + 'mage/translate', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/model/payment/additional-validators' +], function ( + $, + Component, + validator, + messageList, + $t, + fullScreenLoader, + setPaymentInformationAction, + additionalValidators +) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Magento_Braintree/payment/multishipping/form' + }, + + /** + * Get list of available CC types + * + * @returns {Object} + */ + getCcAvailableTypes: function () { + var availableTypes = validator.getAvailableCardTypes(), + billingCountryId; + + billingCountryId = $('#multishipping_billing_country_id').val(); + + if (billingCountryId && validator.getCountrySpecificCardTypes(billingCountryId)) { + return validator.collectTypes( + availableTypes, validator.getCountrySpecificCardTypes(billingCountryId) + ); + } + + return availableTypes; + }, + + /** + * @override + */ + placeOrder: function () { + var self = this; + + this.validatorManager.validate(self, function () { + return self.setPaymentInformation(); + }); + }, + + /** + * @override + */ + setPaymentInformation: function () { + if (additionalValidators.validate()) { + + fullScreenLoader.startLoader(); + + $.when( + setPaymentInformationAction( + this.messageContainer, + this.getData() + ) + ).done(this.done.bind(this)) + .fail(this.fail.bind(this)); + } + }, + + /** + * {Function} + */ + fail: function () { + fullScreenLoader.stopLoader(); + + return this; + }, + + /** + * {Function} + */ + done: function () { + fullScreenLoader.stopLoader(); + $('#multishipping-billing-form').submit(); + + return this; + } + }); +}); diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js new file mode 100644 index 0000000000000..6702e58d1214b --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/multishipping/paypal.js @@ -0,0 +1,143 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/*browser:true*/ +/*global define*/ +define([ + 'jquery', + 'underscore', + 'Magento_Braintree/js/view/payment/method-renderer/paypal', + 'Magento_Checkout/js/action/set-payment-information', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Checkout/js/model/full-screen-loader', + 'mage/translate' +], function ( + $, + _, + Component, + setPaymentInformationAction, + additionalValidators, + fullScreenLoader, + $t +) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Magento_Braintree/payment/multishipping/paypal', + submitButtonSelector: '#payment-continue span' + }, + + /** + * @override + */ + onActiveChange: function (isActive) { + this.updateSubmitButtonTitle(isActive); + + this._super(isActive); + }, + + /** + * @override + */ + beforePlaceOrder: function (data) { + this._super(data); + + this.updateSubmitButtonTitle(true); + }, + + /** + * @override + */ + getShippingAddress: function () { + return {}; + }, + + /** + * @override + */ + getData: function () { + var data = this._super(); + + data['additional_data']['is_active_payment_token_enabler'] = true; + + return data; + }, + + /** + * @override + */ + isActiveVault: function () { + return true; + }, + + /** + * Skipping order review step on checkout with multiple addresses is not allowed. + * + * @returns {Boolean} + */ + isSkipOrderReview: function () { + return false; + }, + + /** + * Checks if payment method nonce is already received. + * + * @returns {Boolean} + */ + isPaymentMethodNonceReceived: function () { + return this.paymentMethodNonce !== null; + }, + + /** + * Updates submit button title on multi-addresses checkout billing form. + * + * @param {Boolean} isActive + */ + updateSubmitButtonTitle: function (isActive) { + var title = this.isPaymentMethodNonceReceived() || !isActive ? + $t('Go to Review Your Order') : $t('Continue to PayPal'); + + $(this.submitButtonSelector).html(title); + }, + + /** + * @override + */ + placeOrder: function () { + if (!this.isPaymentMethodNonceReceived()) { + this.payWithPayPal(); + } else { + fullScreenLoader.startLoader(); + + $.when( + setPaymentInformationAction( + this.messageContainer, + this.getData() + ) + ).done(this.done.bind(this)) + .fail(this.fail.bind(this)); + } + }, + + /** + * {Function} + */ + fail: function () { + fullScreenLoader.stopLoader(); + + return this; + }, + + /** + * {Function} + */ + done: function () { + fullScreenLoader.stopLoader(); + $('#multishipping-billing-form').submit(); + + return this; + } + }); +}); diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html new file mode 100644 index 0000000000000..964e15df166d3 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/form.html @@ -0,0 +1,106 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div data-bind="attr: {class: 'payment-method payment-method-' + getCode()}, css: {'_active': isActive()}"> + <div> + <form id="co-transparent-form-braintree" class="form" data-bind="" method="post" action="#" novalidate="novalidate"> + <fieldset data-bind="attr: {class: 'fieldset payment items ccard ' + getCode(), id: 'payment_form_' + getCode()}"> + <legend class="legend"> + <span><!-- ko i18n: 'Credit Card Information'--><!-- /ko --></span> + </legend> + <br> + <div class="field type"> + <div class="control"> + <ul class="credit-card-types"> + <!-- ko foreach: {data: getCcAvailableTypes(), as: 'item'} --> + <li class="item" data-bind="css: { + _active: $parent.selectedCardType() == item, + _inactive: $parent.selectedCardType() != null && $parent.selectedCardType() != item + } "> + <!--ko if: $parent.getIcons(item) --> + <img data-bind="attr: { + 'src': $parent.getIcons(item).url, + 'width': $parent.getIcons(item).width, + 'height': $parent.getIcons(item).height + }"> + <!--/ko--> + </li> + <!--/ko--> + </ul> + <input type="hidden" + name="payment[cc_type]" + class="input-text" + value="" + data-bind="attr: {id: getCode() + '_cc_type', 'data-container': getCode() + '-cc-type'}, + value: creditCardType + "> + </div> + </div> + <div class="field number required"> + <label data-bind="attr: {for: getCode() + '_cc_number'}" class="label"> + <span><!-- ko i18n: 'Credit Card Number'--><!-- /ko --></span> + </label> + <div class="control"> + <div data-bind="attr: {id: getCode() + '_cc_number'}" class="hosted-control"></div> + <div class="hosted-error"><!-- ko i18n: 'Please, enter valid Credit Card Number'--><!-- /ko --></div> + </div> + </div> + <div class="field number required"> + <label data-bind="attr: {for: getCode() + '_expiration'}" class="label"> + <span><!-- ko i18n: 'Expiration Date'--><!-- /ko --></span> + </label> + <div class="control"> + <div class="hosted-date-wrap"> + <div data-bind="attr: {id: getCode() + '_expirationMonth'}" + class="hosted-control hosted-date"></div> + + <div data-bind="attr: {id: getCode() + '_expirationYear'}" + class="hosted-control hosted-date"></div> + + <div class="hosted-error"><!-- ko i18n: 'Please, enter valid Expiration Date'--><!-- /ko --></div> + </div> + </div> + </div> + <!-- ko if: (hasVerification())--> + <div class="field cvv required" data-bind="attr: {id: getCode() + '_cc_type_cvv_div'}"> + <label data-bind="attr: {for: getCode() + '_cc_cid'}" class="label"> + <span><!-- ko i18n: 'Card Verification Number'--><!-- /ko --></span> + </label> + <div class="control _with-tooltip"> + <div data-bind="attr: {id: getCode() + '_cc_cid'}" class="hosted-control hosted-cid"></div> + <div class="hosted-error"><!-- ko i18n: 'Please, enter valid Card Verification Number'--><!-- /ko --></div> + + <div class="field-tooltip toggle"> + <span class="field-tooltip-action action-cvv" + tabindex="0" + data-toggle="dropdown" + data-bind="attr: {title: $t('What is this?')}, mageInit: {'dropdown':{'activeClass': '_active'}}"> + <span><!-- ko i18n: 'What is this?'--><!-- /ko --></span> + </span> + <div class="field-tooltip-content" + data-target="dropdown" + data-bind="html: getCvvImageHtml()"></div> + </div> + </div> + </div> + <!-- /ko --> + </fieldset> + <input type="submit" id="braintree_submit" style="display:none" /> + </form> + + <div class="actions-toolbar no-display"> + <div class="primary"> + <button data-role="review-save" + type="submit" + data-bind="{click: placeOrderClick}" + class="action primary checkout"> + <span data-bind="i18n: 'Place Order'"></span> + </button> + </div> + </div> + </div> +</div> \ No newline at end of file diff --git a/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html new file mode 100644 index 0000000000000..722989e41f98f --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/template/payment/multishipping/paypal.html @@ -0,0 +1,40 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="payment-method" data-bind="css: {'_active': isActive()}"> + <div class="payment-method-title field choice"> + <label class="label" data-bind="attr: {'for': getCode()}"> + <!-- PayPal Logo --> + <img data-bind="attr: {src: getPaymentAcceptanceMarkSrc(), alt: $t('Acceptance Mark'), title: $t('Acceptance Mark')}" + class="payment-icon"/> + <!-- PayPal Logo --> + <span text="getTitle()"></span> + </label> + </div> + + <div class="payment-method-content"> + <each args="getRegion('messages')" render=""></each> + <fieldset class="braintree-paypal-fieldset" data-bind='attr: {id: "payment_form_" + getCode()}'> + <div id="paypal-container"></div> + </fieldset> + <div class="actions-toolbar braintree-paypal-actions" data-bind="visible: isReviewRequired()"> + <div class="payment-method-item braintree-paypal-account"> + <span class="payment-method-type">PayPal</span> + <span class="payment-method-description" text="customerEmail()"></span> + </div> + <div class="actions-toolbar no-display"> + <div class="primary"> + <button data-button="paypal-place" data-role="review-save" + type="submit" + data-bind="{click: placeOrder}" + class="action primary checkout"> + <span data-bind="i18n: 'Place Order'"></span> + </button> + </div> + </div> + </div> + </div> +</div> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..07329e2659876 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminLoginWithCaptchaActionGroup" extends="LoginAsAdmin"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillPassword" userInput="{{captcha}}" selector="{{AdminLoginFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..a371f177e3552 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup"> + <waitForPageLoad stepKey="waitForPageLoaded" /> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="dontSeeCaptchaField"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="dontSeeCaptchaImage"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="dontSeeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="dontSeeCaptchaFieldAfterPageReload"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="dontSeeCaptchaImageAfterPageReload"/> + <dontSee selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="dontSeeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml new file mode 100644 index 0000000000000..aa02588000d2b --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnAdminLoginFormActionGroup"> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaField}}" stepKey="seeCaptchaField"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaImg}}" stepKey="seeCaptchaImage"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaReload}}" stepKey="seeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaField}}" stepKey="seeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaImg}}" stepKey="seeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{AdminLoginFormSection.captchaReload}}" stepKey="seeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..d800c65cabb60 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnContactUsFormActionGroup"> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaField}}" stepKey="waitToSeeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaField}}" stepKey="waitToSeeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontContactUsFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml new file mode 100644 index 0000000000000..6c09d1d49381f --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup"> + <waitForElementVisible selector="{{StorefrontCustomerCreateFormSection.captchaField}}" stepKey="waitForCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerCreateFormSection.captchaImg}}" stepKey="waitForCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerCreateFormSection.captchaReload}}" stepKey="waitForCaptchaReloadButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml new file mode 100644 index 0000000000000..c68ffbfb5be4b --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnCustomerAccountInfoActionGroup"> + <checkOption selector="{{StorefrontCustomerAccountInformationSection.changeEmail}}" stepKey="clickChangeEmailCheckbox" /> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaField}}" stepKey="seeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaImg}}" stepKey="seeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaReload}}" stepKey="seeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <checkOption selector="{{StorefrontCustomerAccountInformationSection.changeEmail}}" stepKey="clickChangeEmailCheckboxAfterPageReload" /> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaField}}" stepKey="seeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaImg}}" stepKey="seeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerAccountInformationSection.captchaReload}}" stepKey="seeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..5616b099c026d --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCaptchaVisibleOnCustomerLoginFormActionGroup"> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="waitToSeeCaptchaField"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImage"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButton"/> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReloaded" /> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaField}}" stepKey="waitToSeeCaptchaFieldAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaImg}}" stepKey="waitToSeeCaptchaImageAfterPageReload"/> + <waitForElementVisible selector="{{StorefrontCustomerSignInFormSection.captchaReload}}" stepKey="waitToSeeCaptchaReloadButtonAfterPageReload"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml index beb2c2bffa135..f3b6eb1d9af84 100644 --- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CaptchaFormsDisplayingActionGroup"> <click selector="{{CaptchaFormsDisplayingSection.store}}" stepKey="ClickToGoStores"/> <waitForPageLoad stepKey="waitForStoresLoaded"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..8aff3d5482f2c --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerChangeEmailWithCaptchaActionGroup" extends="StorefrontCustomerChangeEmailActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + + <fillField selector="{{StorefrontCustomerAccountInformationSection.captchaField}}" userInput="{{captcha}}" stepKey="fillCaptchaField" after="fillCurrentPassword" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..3546fa2e57a33 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillContactUsFormWithCaptchaActionGroup" extends="StorefrontFillContactUsFormActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillComment" userInput="{{captcha}}" selector="{{StorefrontContactUsFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..d67ebc1a00768 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" extends="StorefrontFillCustomerAccountCreationFormActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillConfirmPassword" userInput="{{captcha}}" selector="{{StorefrontCustomerCreateFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml new file mode 100644 index 0000000000000..5ad727a8fe99d --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" extends="StorefrontFillCustomerLoginFormActionGroup"> + <arguments> + <argument name="captcha" type="string" /> + </arguments> + <fillField stepKey="fillCaptchaField" after="fillPassword" userInput="{{captcha}}" selector="{{StorefrontCustomerSignInFormSection.captchaField}}" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml new file mode 100644 index 0000000000000..90f48c320f2ac --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="StorefrontCustomerCaptchaEnableConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontCustomerCaptchaDisableConfigData"> + <data key="path">customer/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerCreateFormConfigData"> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Create user</data> + <data key="value">user_create</data> + </entity> + <entity name="StorefrontCaptchaOnContactUsFormConfigData"> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Contact Us</data> + <data key="value">contact_us</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerLoginConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Login</data> + <data key="value">user_login</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerChangePasswordConfigData"> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Change password</data> + <data key="value">user_edit</data> + </entity> + <entity name="StorefrontCaptchaOnCustomerForgotPasswordConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/forms</data> + <data key="scope_id">0</data> + <data key="label">Forgot password</data> + <data key="value">user_forgotpassword</data> + </entity> + <entity name="StorefrontCustomerCaptchaModeAlwaysConfigData"> + <data key="path">customer/captcha/mode</data> + <data key="scope_id">0</data> + <data key="label">Always</data> + <data key="value">always</data> + </entity> + <entity name="StorefrontCustomerCaptchaModeAfterFailConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/mode</data> + <data key="scope_id">0</data> + <data key="label">After number of attempts to login</data> + <data key="value">after_fail</data> + </entity> + <entity name="StorefrontCustomerCaptchaLength3ConfigData"> + <data key="path">customer/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">3</data> + <data key="value">3</data> + </entity> + <entity name="StorefrontCustomerCaptchaSymbols1ConfigData"> + <data key="path">customer/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">1</data> + <data key="value">1</data> + </entity> + <entity name="StorefrontCustomerCaptchaDefaultLengthConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">4-5</data> + <data key="value">4-5</data> + </entity> + <entity name="StorefrontCustomerCaptchaDefaultSymbolsConfigData"> + <!-- Magento default value --> + <data key="path">customer/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + <data key="value">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + </entity> + <entity name="AdminCaptchaEnableConfigData"> + <!-- Magento default value --> + <data key="path">admin/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="AdminCaptchaDisableConfigData"> + <data key="path">admin/captcha/enable</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> + <entity name="AdminCaptchaLength3ConfigData"> + <data key="path">admin/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">3</data> + <data key="value">3</data> + </entity> + <entity name="AdminCaptchaSymbols1ConfigData"> + <data key="path">admin/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">1</data> + <data key="value">1</data> + </entity> + <entity name="AdminCaptchaDefaultLengthConfigData"> + <!-- Magento default value --> + <data key="path">admin/captcha/length</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">4-5</data> + <data key="value">4-5</data> + </entity> + <entity name="AdminCaptchaDefaultSymbolsConfigData"> + <!-- Magento default value --> + <data key="path">admin/captcha/symbols</data> + <data key="scope">admin</data> + <data key="scope_id">1</data> + <data key="label">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + <data key="value">ABCDEFGHJKMnpqrstuvwxyz23456789</data> + </entity> +</entities> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml new file mode 100644 index 0000000000000..d8fb206b8111c --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="WrongCaptcha"> + <data key="value" unique="suffix">WrongCAPTCHA</data> + </entity> + + <!-- This CAPTCHA will only work if "StorefrontCustomerCaptchaLength3ConfigData" and "StorefrontCustomerCaptchaSymbols1ConfigData" config is set. --> + <entity name="PreconfiguredCaptcha"> + <data key="value">111</data> + </entity> +</entities> diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml index 9db8110c0f64b..57a09219fe4db 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CaptchaData"> <data key="createUser">Create user</data> <data key="login">Login</data> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml new file mode 100644 index 0000000000000..2bcc6fc542d82 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminLoginFormSection"> + <element name="captchaField" type="input" selector="#login-form input[name='captcha[backend_login]']" /> + <element name="captchaImg" type="block" selector="#login-form img#backend_login"/> + <element name="captchaReload" type="block" selector="#login-form img#captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml new file mode 100644 index 0000000000000..f587812576ff1 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsCaptchaSection"> + <element name="captchaField" type="input" selector="#captcha_contact_us"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml new file mode 100644 index 0000000000000..60cf961ba7e8c --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsFormSection"> + <element name="captchaField" type="input" selector="#contact-form input[name='captcha[contact_us]']" /> + <element name="captchaImg" type="block" selector="#contact-form img.captcha-img"/> + <element name="captchaReload" type="block" selector="#contact-form button.captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml new file mode 100644 index 0000000000000..a273c8d4abd28 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerAccountInformationSection"> + <element name="captchaField" type="input" selector="#captcha_user_edit"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml new file mode 100644 index 0000000000000..f48e6124cb214 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerCreateFormSection"> + <element name="captchaField" type="input" selector="#captcha_user_create"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index 7a0557c4a2744..54aa36d1ca267 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -8,7 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="StorefrontCustomerSignInPopupFormSection"> + <section name="StorefrontCustomerSignInFormSection"> <element name="captchaField" type="input" selector="#captcha_user_login"/> <element name="captchaImg" type="block" selector=".captcha-img"/> <element name="captchaReload" type="block" selector=".captcha-reload"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml new file mode 100644 index 0000000000000..7a0557c4a2744 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSignInPopupFormSection"> + <element name="captchaField" type="input" selector="#captcha_user_login"/> + <element name="captchaImg" type="block" selector=".captcha-img"/> + <element name="captchaReload" type="block" selector=".captcha-reload"/> + </section> +</sections> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml new file mode 100644 index 0000000000000..e5ee55910df65 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminLoginWithCaptchaTest"> + <annotations> + <features value="Captcha"/> + <stories value="Admin login + Captcha"/> + <title value="Captcha on Admin login form"/> + <description value="Test creation for admin login with captcha."/> + <testCaseId value="MC-14012" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI command="config:set {{AdminCaptchaLength3ConfigData.path}} {{AdminCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{AdminCaptchaSymbols1ConfigData.path}} {{AdminCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </before> + <after> + <magentoCLI command="config:set {{AdminCaptchaDefaultLengthConfigData.path}} {{AdminCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{AdminCaptchaDefaultSymbolsConfigData.path}} {{AdminCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </after> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdminWithWrongCredentialsFirstAttempt"> + <argument name="adminUser" value="AdminUserWrongCredentials" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeFirstLoginErrorMessage" /> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdminWithWrongCredentialsSecondAttempt"> + <argument name="adminUser" value="AdminUserWrongCredentials" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeSecondLoginErrorMessage" /> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdminWithWrongCredentialsThirdAttempt"> + <argument name="adminUser" value="AdminUserWrongCredentials" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeThirdLoginErrorMessage" /> + + <!-- Check captcha visibility on admin login page --> + <actionGroup ref="AssertCaptchaVisibleOnAdminLoginFormActionGroup" stepKey="assertCaptchaVisible" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="AdminLoginWithCaptchaActionGroup" stepKey="loginAsAdminWithIncorrectCaptcha"> + <argument name="adminUser" value="DefaultAdminUser" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="seeIncorrectCaptchaErrorMessage"> + <argument name="message" value="Incorrect CAPTCHA." /> + </actionGroup> + <actionGroup ref="AssertCaptchaVisibleOnAdminLoginFormActionGroup" stepKey="assertCaptchaVisibleAfterIncorrectCaptcha" /> + + <actionGroup ref="AdminLoginWithCaptchaActionGroup" stepKey="loginAsAdminWithCorrectCaptcha"> + <argument name="adminUser" value="DefaultAdminUser" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="verifyAdminLoggedIn" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml new file mode 100644 index 0000000000000..54237087227d8 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaEditCustomerEmailTest"> + <annotations> + <features value="Captcha"/> + <stories value="Customer Account Info Edit + Captcha"/> + <title value="Test for checking captcha on the customer account edit page."/> + <description value="Test for checking captcha on the customer account edit page and customer is locked."/> + <testCaseId value="MC-14013" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Setup CAPTCHA for testing --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerChangePasswordConfigData.path}} {{StorefrontCaptchaOnCustomerChangePasswordConfigData.value}}" stepKey="enableUserEditCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + + <createData entity="Simple_US_Customer" stepKey="customer"/> + <!-- Sign in as customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + </before> + <after> + <!-- Revert Captcha forms configurations --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <!-- Open Customer edit page --> + <actionGroup ref="StorefrontOpenCustomerAccountInfoEditPageActionGroup" stepKey="goToCustomerEditPage" /> + + <!-- Update email with incorrect password 3 times. --> + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailFirstAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageFirstAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailSecondAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageSecondAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="StorefrontCustomerChangeEmailActionGroup" stepKey="changeEmailThirdAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageThirdAttempt"> + <argument name="message" value="The password doesn't match this account. Verify the password and try again." /> + <argument name="messageType" value="error" /> + </actionGroup> + + <!-- Check captcha visibility after incorrect password submit form --> + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountInfoActionGroup" stepKey="assertCaptchaVisible" /> + + <!-- Try to submit form with incorrect captcha --> + <actionGroup ref="StorefrontCustomerChangeEmailWithCaptchaActionGroup" stepKey="changeEmailWithIncorrectCaptcha"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="{{Colorado_US_Customer.password}}" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageAfterIncorrectCaptcha"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + + <!-- Update customer email correct password and CAPTCHA --> + <actionGroup ref="StorefrontCustomerChangeEmailWithCaptchaActionGroup" stepKey="changeEmailCorrectAttempt"> + <argument name="email" value="$$customer.email$$" /> + <argument name="password" value="$$customer.password$$" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="AssertMessageCustomerChangeAccountInfoActionGroup" stepKey="assertAccountMessageCorrectAttempt" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml new file mode 100644 index 0000000000000..0c6a3f31c1df2 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaOnContactUsTest"> + <annotations> + <features value="Captcha"/> + <stories value="Submit Contact us form + Captcha"/> + <title value="Captcha on contact us form test"/> + <description value="Test creation for send comment using the contact us form with captcha."/> + <testCaseId value="MC-14103" /> + <severity value="MAJOR"/> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="config:set {{StorefrontCaptchaOnContactUsFormConfigData.path}} {{StorefrontCaptchaOnContactUsFormConfigData.value}}" stepKey="enableUserEditCaptcha"/> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </after> + + <!-- Open storefront contact us form --> + <actionGroup ref="StorefrontOpenContactUsPageActionGroup" stepKey="goToContactUsPage" /> + + <!-- Check Captcha items --> + <actionGroup ref="AssertCaptchaVisibleOnContactUsFormActionGroup" stepKey="seeCaptchaOnContactUsForm" /> + + <!-- Submit Contact Us form --> + <actionGroup ref="StorefrontFillContactUsFormWithCaptchaActionGroup" stepKey="fillContactUsFormWithWrongCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="contactUsData" value="DefaultContactUsData" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontSubmitContactUsFormActionGroup" stepKey="submitContactUsFormWithWrongCaptcha" /> + + <!-- Check Captcha items after form reload --> + <actionGroup ref="AssertMessageContactUsFormActionGroup" stepKey="verifyErrorMessage"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + <actionGroup ref="AssertCaptchaVisibleOnContactUsFormActionGroup" stepKey="seeCaptchaOnContactUsFormAfterWrongCaptcha" /> + + <actionGroup ref="StorefrontFillContactUsFormWithCaptchaActionGroup" stepKey="fillContactUsFormWithCorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="contactUsData" value="DefaultContactUsData" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontSubmitContactUsFormActionGroup" stepKey="submitContactUsFormWithCorrectCaptcha" /> + <actionGroup ref="AssertMessageContactUsFormActionGroup" stepKey="verifySuccessMessage" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml new file mode 100644 index 0000000000000..5a1be68d3f251 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaOnCustomerLoginTest"> + <annotations> + <features value="Captcha"/> + <stories value="Login with Customer Account + Captcha"/> + <title value="Captcha customer login page test"/> + <description value="Check CAPTCHA on Storefront Login Page."/> + <severity value="MAJOR"/> + <testCaseId value="MC-14010" /> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + + <!-- Open storefront login form --> + <actionGroup ref="StorefrontOpenCustomerLoginPageActionGroup" stepKey="goToSignInPage" /> + + <!-- Login with wrong credentials 3 times --> + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormFirstAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonFirstAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterFirstAttempt" /> + <actionGroup ref="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup" stepKey="dontSeeCaptchaAfterFirstAttempt" /> + + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormSecondAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonSecondAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterSecondAttempt" /> + <actionGroup ref="AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup" stepKey="dontSeeCaptchaAfterSecondAttempt" /> + + <actionGroup ref="StorefrontFillCustomerLoginFormActionGroup" stepKey="fillLoginFormThirdAttempt"> + <argument name="customer" value="Colorado_US_Customer" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonThirdAttempt" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterThirdAttempt" /> + <actionGroup ref="AssertCaptchaVisibleOnCustomerLoginFormActionGroup" stepKey="seeCaptchaAfterThirdAttempt" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" stepKey="fillLoginFormCorrectAccountIncorrectCaptcha"> + <argument name="customer" value="$$customer$$" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonCorrectAccountIncorrectCaptcha" /> + <actionGroup ref="AssertMessageCustomerLoginActionGroup" stepKey="seeErrorMessageAfterIncorrectCaptcha"> + <argument name="message" value="Incorrect CAPTCHA" /> + </actionGroup> + + <actionGroup ref="StorefrontFillCustomerLoginFormWithCaptchaActionGroup" stepKey="fillLoginFormCorrectAccountCorrectCaptcha"> + <argument name="customer" value="$$customer$$" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickSignOnCustomerLoginFormActionGroup" stepKey="clickSignInAccountButtonCorrectAccountCorrectCaptcha" /> + <actionGroup ref="AssertCustomerWelcomeMessageActionGroup" stepKey="assertCustomerLoggedIn"> + <argument name="customerFullName" value="$$customer.firstname$$ $$customer.lastname$$" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml new file mode 100644 index 0000000000000..2c331f958e467 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCaptchaRegisterNewCustomerTest"> + <annotations> + <features value="Captcha"/> + <stories value="Create New Customer Account + Captcha"/> + <title value="Test creation for customer register with captcha on storefront."/> + <description value="Test creation for customer register with captcha on storefront."/> + <severity value="MAJOR"/> + <testCaseId value="MC-14805" /> + <group value="captcha"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!-- Enable captcha for customer. --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerCreateFormConfigData.path}} {{StorefrontCaptchaOnCustomerCreateFormConfigData.value}}" stepKey="enableUserRegistrationCaptcha" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAlwaysConfigData.path}} {{StorefrontCustomerCaptchaModeAlwaysConfigData.value}}" stepKey="alwaysEnableCaptcha" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaLength3ConfigData.path}} {{StorefrontCustomerCaptchaLength3ConfigData.value}}" stepKey="setCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaSymbols1ConfigData.path}} {{StorefrontCustomerCaptchaSymbols1ConfigData.value}}" stepKey="setCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </before> + <after> + <!-- Set default configuration. --> + <magentoCLI command="config:set {{StorefrontCaptchaOnCustomerLoginConfigData.path}} {{StorefrontCaptchaOnCustomerLoginConfigData.value}},{{StorefrontCaptchaOnCustomerForgotPasswordConfigData.value}}" stepKey="enableCaptchaOnDefaultForms" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaModeAfterFailConfigData.path}} {{StorefrontCustomerCaptchaModeAfterFailConfigData.value}}" stepKey="defaultCaptchaMode" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultLengthConfigData.path}} {{StorefrontCustomerCaptchaDefaultLengthConfigData.value}}" stepKey="setDefaultCaptchaLength" /> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.path}} {{StorefrontCustomerCaptchaDefaultSymbolsConfigData.value}}" stepKey="setDefaultCaptchaSymbols" /> + <magentoCLI command="cache:clean config full_page" stepKey="cleanInvalidatedCaches"/> + </after> + + <!-- Open Customer registration page --> + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="goToCustomerAccountCreatePage" /> + + <!-- Check captcha visibility registration page load --> + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup" stepKey="verifyCaptchaVisible" /> + + <!-- Submit form with incorrect captcha --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" stepKey="fillNewCustomerAccountFormWithIncorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="captcha" value="{{WrongCaptcha.value}}" /> + </actionGroup> + + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="clickCreateAnAccountButton" /> + + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="assertMessage"> + <argument name="message" value="Incorrect CAPTCHA" /> + <argument name="messageType" value="error" /> + </actionGroup> + + <actionGroup ref="AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup" stepKey="verifyCaptchaVisibleAfterFail" /> + + <!-- Submit form with correct captcha --> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup" stepKey="fillNewCustomerAccountFormWithCorrectCaptcha"> + <argument name="customer" value="Simple_US_Customer" /> + <argument name="captcha" value="{{PreconfiguredCaptcha.value}}" /> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="clickCreateAnAccountButtonAfterCorrectCaptcha" /> + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="assertSuccessMessage" /> + </test> +</tests> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml new file mode 100644 index 0000000000000..36d7989b9acc1 --- /dev/null +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontResetCustomerPasswordFailedTest"> + <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> + </before> + <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php index a67f55235b6df..83ec501592489 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php @@ -71,7 +71,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -80,7 +80,7 @@ protected function _construct() } /** - * @return $this + * @inheritdoc */ protected function _prepareLayout() { @@ -182,6 +182,8 @@ public function getSuggestedCategoriesJson($namePart) } /** + * Get add root button html + * * @return string */ public function getAddRootButtonHtml() @@ -190,6 +192,8 @@ public function getAddRootButtonHtml() } /** + * Get add sub button html + * * @return string */ public function getAddSubButtonHtml() @@ -198,6 +202,8 @@ public function getAddSubButtonHtml() } /** + * Get expand button html + * * @return string */ public function getExpandButtonHtml() @@ -206,6 +212,8 @@ public function getExpandButtonHtml() } /** + * Get collapse button html + * * @return string */ public function getCollapseButtonHtml() @@ -214,6 +222,8 @@ public function getCollapseButtonHtml() } /** + * Get store switcher + * * @return string */ public function getStoreSwitcherHtml() @@ -222,6 +232,8 @@ public function getStoreSwitcherHtml() } /** + * Get loader tree url + * * @param bool|null $expanded * @return string */ @@ -235,6 +247,8 @@ public function getLoadTreeUrl($expanded = null) } /** + * Get nodes url + * * @return string */ public function getNodesUrl() @@ -243,6 +257,8 @@ public function getNodesUrl() } /** + * Get switcher tree url + * * @return string */ public function getSwitchTreeUrl() @@ -254,6 +270,8 @@ public function getSwitchTreeUrl() } /** + * Get is was expanded + * * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ @@ -263,6 +281,8 @@ public function getIsWasExpanded() } /** + * Get move url + * * @return string */ public function getMoveUrl() @@ -271,6 +291,8 @@ public function getMoveUrl() } /** + * Get tree + * * @param mixed|null $parenNodeCategory * @return array */ @@ -282,6 +304,8 @@ public function getTree($parenNodeCategory = null) } /** + * Get tree json + * * @param mixed|null $parenNodeCategory * @return string */ @@ -367,7 +391,7 @@ protected function _getNodeJson($node, $level = 0) } } - if ($isParent || $node->getLevel() < 2) { + if ($isParent || $node->getLevel() < 1) { $item['expanded'] = true; } @@ -390,6 +414,8 @@ public function buildNodeName($node) } /** + * Is category movable + * * @param Node|array $node * @return bool */ @@ -403,6 +429,8 @@ protected function _isCategoryMoveable($node) } /** + * Is parent selected category + * * @param Node|array $node * @return bool */ diff --git a/app/code/Magento/Catalog/Block/Product/View/Gallery.php b/app/code/Magento/Catalog/Block/Product/View/Gallery.php index 706d9b83b9711..8b98fbdc8f7ef 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Gallery.php +++ b/app/code/Magento/Catalog/Block/Product/View/Gallery.php @@ -207,8 +207,8 @@ public function isMainImage($image) */ public function getImageAttribute($imageId, $attributeName, $default = null) { - $attributes = - $this->getConfigView()->getMediaAttributes('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId); + $attributes = $this->getConfigView() + ->getMediaAttributes('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId); return $attributes[$attributeName] ?? $default; } diff --git a/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php new file mode 100644 index 0000000000000..0384c9cd9acce --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php @@ -0,0 +1,156 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Block\Product\View; + +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Catalog\Block\Product\Context; +use Magento\Framework\Stdlib\ArrayUtils; + +/** + * Gallery options block. + */ +class GalleryOptions extends AbstractView implements ArgumentInterface +{ + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var Gallery + */ + private $gallery; + + /** + * @param Context $context + * @param ArrayUtils $arrayUtils + * @param Json $jsonSerializer + * @param Gallery $gallery + * @param array $data + */ + public function __construct( + Context $context, + ArrayUtils $arrayUtils, + Json $jsonSerializer, + Gallery $gallery, + array $data = [] + ) { + $this->gallery = $gallery; + $this->jsonSerializer = $jsonSerializer; + parent::__construct($context, $arrayUtils, $data); + } + + /** + * Retrieve gallery options in JSON format + * + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ElseExpression) + */ + public function getOptionsJson() + { + $optionItems = null; + + //Special case for gallery/nav which can be the string "thumbs/false/dots" + if (is_bool($this->getVar("gallery/nav"))) { + $optionItems['nav'] = $this->getVar("gallery/nav") ? 'true' : 'false'; + } else { + $optionItems['nav'] = $this->escapeHtml($this->getVar("gallery/nav")); + } + + $optionItems['loop'] = $this->getVar("gallery/loop"); + $optionItems['keyboard'] = $this->getVar("gallery/keyboard"); + $optionItems['arrows'] = $this->getVar("gallery/arrows"); + $optionItems['allowfullscreen'] = $this->getVar("gallery/allowfullscreen"); + $optionItems['showCaption'] = $this->getVar("gallery/caption"); + $optionItems['width'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_medium', 'width') + ); + $optionItems['thumbwidth'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_small', 'width') + ); + + if ($this->gallery->getImageAttribute('product_page_image_small', 'height') || + $this->gallery->getImageAttribute('product_page_image_small', 'width')) { + $optionItems['thumbheight'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_small', 'height') ?: + $this->gallery->getImageAttribute('product_page_image_small', 'width') + ); + } + + if ($this->gallery->getImageAttribute('product_page_image_medium', 'height') || + $this->gallery->getImageAttribute('product_page_image_medium', 'width')) { + $optionItems['height'] = (int)$this->escapeHtml( + $this->gallery->getImageAttribute('product_page_image_medium', 'height') ?: + $this->gallery->getImageAttribute('product_page_image_medium', 'width') + ); + } + + if ($this->getVar("gallery/transition/duration")) { + $optionItems['transitionduration'] = + (int)$this->escapeHtml($this->getVar("gallery/transition/duration")); + } + + $optionItems['transition'] = $this->escapeHtml($this->getVar("gallery/transition/effect")); + $optionItems['navarrows'] = $this->getVar("gallery/navarrows"); + $optionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/navtype")); + $optionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/navdir")); + + if ($this->getVar("gallery/thumbmargin")) { + $optionItems['thumbmargin'] = (int)$this->escapeHtml($this->getVar("gallery/thumbmargin")); + } + + return $this->jsonSerializer->serialize($optionItems); + } + + /** + * Retrieve gallery fullscreen options in JSON format + * + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ElseExpression) + */ + public function getFSOptionsJson() + { + $fsOptionItems = null; + + //Special case for gallery/nav which can be the string "thumbs/false/dots" + if (is_bool($this->getVar("gallery/fullscreen/nav"))) { + $fsOptionItems['nav'] = $this->getVar("gallery/fullscreen/nav") ? 'true' : 'false'; + } else { + $fsOptionItems['nav'] = $this->escapeHtml($this->getVar("gallery/fullscreen/nav")); + } + + $fsOptionItems['loop'] = $this->getVar("gallery/fullscreen/loop"); + $fsOptionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navdir")); + $fsOptionItems['navarrows'] = $this->getVar("gallery/fullscreen/navarrows"); + $fsOptionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navtype")); + $fsOptionItems['arrows'] = $this->getVar("gallery/fullscreen/arrows"); + $fsOptionItems['showCaption'] = $this->getVar("gallery/fullscreen/caption"); + + if ($this->getVar("gallery/fullscreen/transition/duration")) { + $fsOptionItems['transitionduration'] = (int)$this->escapeHtml( + $this->getVar("gallery/fullscreen/transition/duration") + ); + } + + $fsOptionItems['transition'] = $this->escapeHtml($this->getVar("gallery/fullscreen/transition/effect")); + + if ($this->getVar("gallery/fullscreen/keyboard")) { + $fsOptionItems['keyboard'] = $this->getVar("gallery/fullscreen/keyboard"); + } + + if ($this->getVar("gallery/fullscreen/thumbmargin")) { + $fsOptionItems['thumbmargin'] = + (int)$this->escapeHtml($this->getVar("gallery/fullscreen/thumbmargin")); + } + + return $this->jsonSerializer->serialize($fsOptionItems); + } +} diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php index 170f1209ad9e6..9b8d0ad75a8c9 100644 --- a/app/code/Magento/Catalog/Helper/Image.php +++ b/app/code/Magento/Catalog/Helper/Image.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Helper; use Magento\Framework\App\Helper\AbstractHelper; +use Magento\Framework\View\Element\Block\ArgumentInterface; /** * Catalog image helper @@ -14,7 +15,7 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @since 100.0.2 */ -class Image extends AbstractHelper +class Image extends AbstractHelper implements ArgumentInterface { /** * Media config node @@ -764,7 +765,7 @@ protected function getImageFile() protected function parseSize($string) { $size = explode('x', strtolower($string)); - if (sizeof($size) == 2) { + if (count($size) == 2) { return ['width' => $size[0] > 0 ? $size[0] : null, 'height' => $size[1] > 0 ? $size[1] : null]; } return false; diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php index 0941aa2478935..9cb6cda4d0a09 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Product\Option; use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface as OptionRepository; @@ -58,11 +60,26 @@ public function execute($entity, $arguments = []) } } if ($options) { - foreach ($options as $option) { - $this->optionRepository->save($option); - } + $this->processOptionsSaving($options, (bool)$entity->dataHasChangedFor('sku'), (string)$entity->getSku()); } return $entity; } + + /** + * Save custom options + * + * @param array $options + * @param bool $hasChangedSku + * @param string $newSku + */ + private function processOptionsSaving(array $options, bool $hasChangedSku, string $newSku) + { + foreach ($options as $option) { + if ($hasChangedSku && $option->hasData('product_sku')) { + $option->setProductSku($newSku); + } + $this->optionRepository->save($option); + } + } } diff --git a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php index f6893a41113e6..dabfdb74f0118 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php +++ b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php @@ -17,6 +17,9 @@ * * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @deprecated + * @see \Magento\Catalog\Model\Product\Type\Price */ class FrontSpecialPrice extends Price { @@ -66,6 +69,8 @@ public function __construct( /** * @inheritdoc + * + * @deprecated */ protected function _applySpecialPrice($product, $finalPrice) { diff --git a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php index a597b8fddda9f..ed9f89efc6891 100644 --- a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php +++ b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php @@ -3,9 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Observer; use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** * Set value for Special Price start date @@ -13,21 +16,20 @@ class SetSpecialPriceStartDate implements ObserverInterface { /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var TimezoneInterface */ private $localeDate; /** - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @codeCoverageIgnore + * @param TimezoneInterface $localeDate */ - public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate) + public function __construct(TimezoneInterface $localeDate) { $this->localeDate = $localeDate; } /** - * Set the current date to Special Price From attribute if it empty + * Set the current date to Special Price From attribute if it's empty. * * @param \Magento\Framework\Event\Observer $observer * @return $this @@ -36,8 +38,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var $product \Magento\Catalog\Model\Product */ $product = $observer->getEvent()->getProduct(); - if ($product->getSpecialPrice() && !$product->getSpecialFromDate()) { - $product->setData('special_from_date', $this->localeDate->date()); + if ($product->getSpecialPrice() && ! $product->getSpecialFromDate()) { + $product->setData('special_from_date', $this->localeDate->date()->setTime(0, 0)); } return $this; diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index cbdabe6a2d218..ad32b8edbd243 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -289,4 +289,13 @@ <waitForPageLoad stepKey="waitForProductToLoad"/> <waitForElementVisible selector="{{AdminHeaderSection.pageTitle}}" stepKey="waitForProductTitle"/> </actionGroup> + + <actionGroup name="deleteProductsIfTheyExist"> + <conditionalClick selector="{{AdminProductGridSection.multicheckDropdown}}" dependentSelector="{{AdminProductGridSection.firstProductRow}}" visible="true" stepKey="openMulticheckDropdown"/> + <conditionalClick selector="{{AdminProductGridSection.multicheckOption('Select All')}}" dependentSelector="{{AdminProductGridSection.firstProductRow}}" visible="true" stepKey="selectAllProductInFilteredGrid"/> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Delete')}}" stepKey="clickDeleteAction"/> + <waitForElementVisible selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="waitForModalPopUp"/> + <click selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="confirmProductDelete"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml index 2d966dde64c4a..b914d5e20712d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml @@ -52,4 +52,51 @@ <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('0')}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> <fillField selector="{{AdminProductCustomizableOptionsSection.optionFileExtensions('0')}}" userInput="{{option.file_extension}}" stepKey="fillCompatibleExtensions"/> </actionGroup> + <actionGroup name="AddProductCustomOptionField"> + <arguments> + <argument name="option" defaultValue="ProductOptionField"/> + <argiment name="optionIndex" type="string"/> + </arguments> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" stepKey="waitForOption"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.lastOptionTitle}}" userInput="{{option.title}}" stepKey="fillTitle"/> + <click selector="{{AdminProductCustomizableOptionsSection.lastOptionTypeParent}}" stepKey="openTypeSelect"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionType('Field')}}" stepKey="selectTypeFile"/> + <waitForElementVisible selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" stepKey="waitForElements"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" userInput="{{option.price}}" stepKey="fillPrice"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType(optionIndex)}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku(optionIndex)}}" userInput="{{option.title}}" stepKey="fillSku"/> + </actionGroup> + <actionGroup name="importProductCustomizableOptions"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <click selector="{{AdminProductCustomizableOptionsSection.importOptions}}" stepKey="clickImportOptions"/> + <waitForElementVisible selector="{{AdminProductImportOptionsSection.selectProductTitle}}" stepKey="waitForTitleVisible"/> + <conditionalClick selector="{{AdminProductImportOptionsSection.resetFiltersButton}}" dependentSelector="{{AdminProductImportOptionsSection.resetFiltersButton}}" visible="true" stepKey="clickResetFilters"/> + <click selector="{{AdminProductImportOptionsSection.filterButton}}" stepKey="clickFilterButton"/> + <waitForElementVisible selector="{{AdminProductImportOptionsSection.nameField}}" stepKey="waitForNameField"/> + <fillField selector="{{AdminProductImportOptionsSection.nameField}}" userInput="{{productName}}" stepKey="fillProductName"/> + <click selector="{{AdminProductImportOptionsSection.applyFiltersButton}}" stepKey="clickApplyFilters"/> + <checkOption selector="{{AdminProductImportOptionsSection.firstRowItemCheckbox}}" stepKey="checkProductCheckbox"/> + <click selector="{{AdminProductImportOptionsSection.importButton}}" stepKey="clickImport"/> + </actionGroup> + <actionGroup name="resetImportOptionFilter"> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <click selector="{{AdminProductCustomizableOptionsSection.importOptions}}" stepKey="clickImportOptions"/> + <click selector="{{AdminProductImportOptionsSection.resetFiltersButton}}" stepKey="clickResetFilterButton"/> + </actionGroup> + <actionGroup name="checkCustomizableOptionImport"> + <arguments> + <argument name="option" defaultValue="ProductOptionField"/> + <argiment name="optionIndex" type="string"/> + </arguments> + <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionTitleInput(optionIndex)}}" stepKey="grabOptionTitle"/> + <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionPrice(optionIndex)}}" stepKey="grabOptionPrice"/> + <grabValueFrom selector="{{AdminProductCustomizableOptionsSection.optionSku(optionIndex)}}" stepKey="grabOptionSku"/> + <assertEquals expected="{{option.title}}" expectedType="string" actual="$grabOptionTitle" stepKey="assertOptionTitle"/> + <assertEquals expected="{{option.price}}" expectedType="string" actual="$grabOptionPrice" stepKey="assertOptionPrice"/> + <assertEquals expected="{{option.title}}" expectedType="string" actual="$grabOptionSku" stepKey="assertOptionSku"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml index 8a26b6babdbbc..d09880f14afbf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml @@ -13,4 +13,8 @@ <data key="one">1</data> <data key="two">2</data> </entity> + <entity name="prodNameWithSpecChars"> + <data key="trademark">"Pursuit Lumaflex™ Tone Band"</data> + <data key="skumark">"x™"</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 383797933074e..ba4a623e35def 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -35,6 +35,10 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiSimpleProductWithSpecCharInName" type="product" extends="ApiSimpleProduct"> + <data key="name">Pursuit Lumaflex&trade; Tone Band</data> + <data key="sku" unique="suffix">x&trade;</data> + </entity> <entity name="ApiSimpleProductWithCustomPrice" type="product" extends="ApiSimpleProduct"> <data key="price">100</data> </entity> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml index ea0bcafe56c48..71c8af318e9b4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml @@ -12,4 +12,7 @@ <data key="from">10</data> <data key="to">100</data> </entity> + <entity name="ProductPerPage"> + <data key="productCount">1</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index 14e714cb2b6b7..fba28b3feaff1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -16,5 +16,6 @@ <element name="categoryInTreeUnderRoot" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> <element name="lastCreatedCategory" type="block" selector=".x-tree-root-ct li li:last-child" /> <element name="treeContainer" type="block" selector=".tree-holder" /> + <element name="expandRootCategory" type="text" selector="img.x-tree-elbow-end-plus"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml index fc78c25ec49fa..352d219351fb8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml @@ -45,5 +45,15 @@ <element name="optionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{var}}][price_type]']" parameterized="true"/> <element name="optionSku" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][sku]']" parameterized="true"/> <element name="optionFileExtensions" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][file_extension]']" parameterized="true"/> + <element name="importOptions" type="button" selector="//button[@data-index='button_import']" timeout="30"/> </section> -</sections> \ No newline at end of file + <section name="AdminProductImportOptionsSection"> + <element name="selectProductTitle" type="text" selector="//h1[contains(text(), 'Select Product')]" timeout="30"/> + <element name="filterButton" type="button" selector="//button[@data-action='grid-filter-expand']" timeout="30"/> + <element name="nameField" type="input" selector="//input[@name='name']" timeout="30"/> + <element name="applyFiltersButton" type="button" selector="//button[@data-action='grid-filter-apply']" timeout="30"/> + <element name="resetFiltersButton" type="button" selector="//button[@data-action='grid-filter-reset']" timeout="30"/> + <element name="firstRowItemCheckbox" type="input" selector="//input[@data-action='select-row']" timeout="30"/> + <element name="importButton" type="button" selector="//button[contains(@class, 'action-primary')]/span[contains(text(), 'Import')]" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index 02bdbac313076..07dd26381fe08 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -17,6 +17,7 @@ <element name="productGridElement2" type="text" selector="#addselector" /> <element name="productGridRows" type="text" selector="table.data-grid tr.data-row"/> <element name="firstProductRow" type="text" selector="table.data-grid tr.data-row:first-of-type"/> + <element name="firstProductRowName" type="text" selector="table.data-grid tr.data-row:first-of-type > td:nth-of-type(4)"/> <element name="firstProductRowEditButton" type="button" selector="table.data-grid tr.data-row td .action-menu-item:first-of-type"/> <element name="productThumbnail" type="text" selector="table.data-grid tr:nth-child({{row}}) td.data-grid-thumbnail-cell > img" parameterized="true"/> <element name="productThumbnailBySrc" type="text" selector="img.admin__control-thumbnail[src*='{{pattern}}']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml index c6bad0efb3ca7..292b2d7008bc1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml @@ -12,5 +12,6 @@ <element name="subCategory" type="button" selector="//ul[contains(@class,'submenu')]//span[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="breadcrumbs" type="textarea" selector=".items"/> <element name="categoryBreadcrumbs" type="textarea" selector=".breadcrumbs li"/> + <element name="categoryBreadcrumbsByNumber" type="textarea" selector=".breadcrumbs li:nth-of-type({{number}})" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml index 3027416ee520b..bcfab6ccfdf1f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml @@ -34,7 +34,7 @@ <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> <actionGroup ref="LoginAsAdmin" stepKey="login"/> - <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$$createAttributeSet.attribute_set_id$$}}/" stepKey="onAttributeSetEdit"/> + <amOnPage url="{{AdminProductAttributeSetEditPage.url}}/$$createAttributeSet.attribute_set_id$$/" stepKey="onAttributeSetEdit"/> <actionGroup ref="AssignAttributeToGroup" stepKey="assignAttributeToGroup"> <argument name="group" value="Product Details"/> <argument name="attribute" value="$$createProductAttribute.attribute_code$$"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml new file mode 100644 index 0000000000000..f3ec225540c75 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminFilterByNameByStoreViewOnProductGridTest"> + <annotations> + <features value="Catalog"/> + <stories value="Filter products"/> + <title value="Product grid filtering by store view level attribute"/> + <description value="Verify that products grid can be filtered on all store view level by attribute"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98755"/> + <useCaseId value="MAGETWO-98335"/> + <group value="catalog"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToEditPage"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchToDefaultStoreView"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.productNameUseDefault}}" stepKey="uncheckUseDefault"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{SimpleProduct.name}}" stepKey="fillNewName"/> + <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <actionGroup ref="filterProductGridByName" stepKey="filterGridByName"> + <argument name="product" value="SimpleProduct"/> + </actionGroup> + <see selector="{{AdminProductGridSection.firstProductRow}}" userInput="{{SimpleProduct2.name}}" stepKey="seeProductNameInGrid"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml new file mode 100644 index 0000000000000..b24ed7f9c9a81 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminGridPageNumberAfterSaveAndCloseActionTest"> + <annotations> + <features value="Catalog"/> + <title value="Checking Catalog grid page number after Save and Close action"/> + <description value="Checking Catalog grid page number after Save and Close action"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96164"/> + <useCaseId value="MAGETWO-96127"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Clear product grid--> + <comment userInput="Clear product grid" stepKey="commentClearProductGrid"/> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <actionGroup ref="deleteProductsIfTheyExist" stepKey="deleteProductIfTheyExist"/> + <createData stepKey="category1" entity="SimpleSubCategory"/> + <createData stepKey="product1" entity="SimpleProduct"> + <requiredEntity createDataKey="category1"/> + </createData> + <createData stepKey="category2" entity="SimpleSubCategory"/> + <createData stepKey="product2" entity="SimpleProduct"> + <requiredEntity createDataKey="category2"/> + </createData> + </before> + <after> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <click selector="{{AdminDataGridPaginationSection.previousPage}}" stepKey="clickPrevPageOrderGrid"/> + <actionGroup ref="adminDataGridDeleteCustomPerPage" stepKey="deleteCustomAddedPerPage"> + <argument name="perPage" value="ProductPerPage.productCount"/> + </actionGroup> + <deleteData stepKey="deleteCategory1" createDataKey="category1"/> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteCategory2" createDataKey="category2"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="goToProductCatalog"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="adminDataGridSelectCustomPerPage" stepKey="select1OrderPerPage"> + <argument name="perPage" value="ProductPerPage.productCount"/> + </actionGroup> + <!--Go to the next page and edit the product--> + <comment userInput="Go to the next page and edit the product" stepKey="commentEdiProduct"/> + <click selector="{{AdminDataGridPaginationSection.nextPage}}" stepKey="clickNextPageOrderGrid"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct2"> + <argument name="product" value="$$product2$$"/> + </actionGroup> + <actionGroup ref="AdminFormSaveAndClose" stepKey="saveAndCloseProduct"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <seeInField selector="{{AdminDataGridPaginationSection.currentPage}}" userInput="2" stepKey="seeOnSecondPageOrderGrid"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml new file mode 100644 index 0000000000000..79ff7bcade77b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminImportCustomizableOptionToProductWithSKUTest"> + <annotations> + <features value="Catalog"/> + <title value="Import customizable options to a product with existing SKU"/> + <description value="Import customizable options to a product with existing SKU"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98211"/> + <useCaseId value="MAGETWO-70232"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create category--> + <comment userInput="Create category" stepKey="commentCreateCategory"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!-- Create two product --> + <comment userInput="Create two product" stepKey="commentCreateTwoProduct"/> + <createData entity="SimpleProduct2" stepKey="createFirstProduct"/> + <createData entity="ApiSimpleProduct" stepKey="createSecondProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <!--Delete second product with changed sku--> + <comment userInput="Delete second product with changed sku" stepKey="commentDeleteProduct"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteSecondProduct"> + <argument name="sku" value="$$createFirstProduct.sku$$-1"/> + </actionGroup> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="commentDeleteCreatedData"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createFirstProduct" stepKey="deleteFirstProduct"/> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!--Go to product page --> + <comment userInput="Go to product page" stepKey="commentGoToProductPage"/> + <amOnPage url="{{AdminProductEditPage.url($$createFirstProduct.id$$)}}" stepKey="goToProductEditPage"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad"/> + <actionGroup ref="AddProductCustomOptionField" stepKey="addCutomOption1"> + <argument name="option" value="ProductOptionField"/> + <argument name="optionIndex" value="0"/> + </actionGroup> + <actionGroup ref="AddProductCustomOptionField" stepKey="addCutomOption2"> + <argument name="option" value="ProductOptionField2"/> + <argument name="optionIndex" value="1"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <!--Change second product sku to first product sku--> + <comment userInput="Change second product sku to first product sku" stepKey="commentChangeSecondProduct"/> + <amOnPage url="{{AdminProductEditPage.url($$createSecondProduct.id$$)}}" stepKey="goToProductEditPage1"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad1"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="$$createFirstProduct.sku$$" stepKey="fillProductSku1"/> + <!--Import customizable options and check--> + <comment userInput="Import customizable options and check" stepKey="commentImportOptions"/> + <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" visible="false" stepKey="openCustomOptionSection"/> + <actionGroup ref="importProductCustomizableOptions" stepKey="importOptions"> + <argument name="productName" value="$$createFirstProduct.name$$"/> + </actionGroup> + <actionGroup ref="checkCustomizableOptionImport" stepKey="checkFirstOptionImport"> + <argument name="option" value="ProductOptionField"/> + <argument name="optionIndex" value="0"/> + </actionGroup> + <actionGroup ref="checkCustomizableOptionImport" stepKey="checkSecondOptionImport"> + <argument name="option" value="ProductOptionField2"/> + <argument name="optionIndex" value="1"/> + </actionGroup> + <!--Save product and check sku changed message--> + <comment userInput="Save product and check sku changed message" stepKey="commentSAveProductAndCheck"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> + <see userInput="SKU for product $$createSecondProduct.name$$ has been changed to $$createFirstProduct.sku$$-1." stepKey="seeSkuChangedMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml index 060720ab007eb..8316f54c15a52 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml @@ -65,6 +65,8 @@ </actionGroup> <deleteData createDataKey="category" stepKey="deletePreReqCategory"/> <deleteData createDataKey="product" stepKey="deleteFirstProduct"/> + <magentoCLI stepKey="reindex" command="indexer:reindex"/> + <magentoCLI stepKey="flushCache" command="cache:flush"/> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml index 3086f4398e08d..51af9d78dfd46 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml @@ -11,8 +11,8 @@ <test name="AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest"> <annotations> <features value="Catalog"/> - <title value="Check that 'trie price' block not available for simple product from options without 'trie price'"/> - <description value="Check that 'trie price' block not available for simple product from options without 'trie price'"/> + <title value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> + <description value="Check that 'tier price' block not available for simple product from options without 'tier price'"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-97050"/> <useCaseId value="MAGETWO-96842"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml index 52022f32fd8ec..d895993217e32 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml @@ -262,10 +262,10 @@ </actionGroup> <click stepKey="openAttributes" selector="{{AdminProductAttributesSection.sectionHeader}}"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" stepKey="waitforLabel"/> - <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_frontend}}" stepKey="seeDefaultIsCorrect"/> - <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option1_frontend}}"/> - <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option2_frontend}}"/> - <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_frontend}}"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_admin}}" stepKey="seeDefaultIsCorrect"/> + <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option1_admin}}"/> + <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option2_admin}}"/> + <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttribute.attribute_code)}}" userInput="{{dropdownProductAttribute.option3_admin}}"/> </test> <test name="CreateProductAttributeEntityDropdownWithSingleQuoteTest"> @@ -333,8 +333,8 @@ </actionGroup> <click stepKey="openAttributes" selector="{{AdminProductAttributesSection.sectionHeader}}"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" stepKey="waitforLabel"/> - <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_frontend}}" stepKey="seeDefaultIsCorrect"/> - <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_frontend}}"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_admin}}" stepKey="seeDefaultIsCorrect"/> + <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(dropdownProductAttributeWithQuote.attribute_code)}}" userInput="{{dropdownProductAttributeWithQuote.option1_admin}}"/> </test> <test name="CreateProductAttributeEntityMultiSelectTest"> @@ -417,9 +417,9 @@ </actionGroup> <click stepKey="openAttributes" selector="{{AdminProductAttributesSection.sectionHeader}}"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" stepKey="waitforLabel"/> - <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_frontend}}" stepKey="seeDefaultIsCorrect"/> - <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option1_frontend}}"/> - <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option2_frontend}}"/> - <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_frontend}}"/> + <seeOptionIsSelected selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_admin}}" stepKey="seeDefaultIsCorrect"/> + <see stepKey="seeOption1Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option1_admin}}"/> + <see stepKey="seeOption2Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option2_admin}}"/> + <see stepKey="seeOption3Available" selector="{{AdminProductAttributesSection.attributeDropdownByCode(multiselectProductAttribute.attribute_code)}}" userInput="{{multiselectProductAttribute.option3_admin}}"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php new file mode 100644 index 0000000000000..7ed8b13fce750 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php @@ -0,0 +1,223 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Block\Product\View; + +use Magento\Catalog\Block\Product\Context; +use Magento\Catalog\Block\Product\View\Gallery; +use Magento\Catalog\Block\Product\View\GalleryOptions; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Escaper; +use Magento\Framework\View\Config; +use Magento\Framework\Config\View; +use Magento\Framework\Serialize\Serializer\Json; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GalleryOptionsTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var GalleryOptions + */ + private $model; + + /** + * @var Gallery|\PHPUnit_Framework_MockObject_MockObject + */ + private $gallery; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var View|\PHPUnit_Framework_MockObject_MockObject + */ + private $configView; + + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $viewConfig; + + /** + * @var Escaper + */ + private $escaper; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->escaper = $objectManager->getObject(Escaper::class); + $this->configView = $this->createMock(View::class); + + $this->viewConfig = $this->createConfiguredMock( + Config::class, + [ + 'getViewConfig' => $this->configView + ] + ); + + $this->context = $this->createConfiguredMock( + Context::class, + [ + 'getEscaper' => $this->escaper, + 'getViewConfig' => $this->viewConfig + ] + ); + + $this->gallery = $this->createMock(Gallery::class); + + $this->jsonSerializer = $objectManager->getObject( + Json::class + ); + + $this->model = $objectManager->getObject(GalleryOptions::class, [ + 'context' => $this->context, + 'jsonSerializer' => $this->jsonSerializer, + 'gallery' => $this->gallery + ]); + } + + public function testGetOptionsJson() + { + $configMap = [ + ['Magento_Catalog', 'gallery/nav', 'thumbs'], + ['Magento_Catalog', 'gallery/loop', false], + ['Magento_Catalog', 'gallery/keyboard', true], + ['Magento_Catalog', 'gallery/arrows', true], + ['Magento_Catalog', 'gallery/caption', false], + ['Magento_Catalog', 'gallery/allowfullscreen', true], + ['Magento_Catalog', 'gallery/navdir', 'horizontal'], + ['Magento_Catalog', 'gallery/navarrows', true], + ['Magento_Catalog', 'gallery/navtype', 'slides'], + ['Magento_Catalog', 'gallery/thumbmargin', '5'], + ['Magento_Catalog', 'gallery/transition/effect', 'slide'], + ['Magento_Catalog', 'gallery/transition/duration', '500'], + ]; + + $imageAttributesMap = [ + ['product_page_image_medium','height',null, 100], + ['product_page_image_medium','width',null, 200], + ['product_page_image_small','height',null, 300], + ['product_page_image_small','width',null, 400] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + $this->gallery->expects($this->any()) + ->method('getImageAttribute') + ->will($this->returnValueMap($imageAttributesMap)); + + $json = $this->model->getOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + $this->assertSame('thumbs', $decodedJson['nav']); + $this->assertSame(false, $decodedJson['loop']); + $this->assertSame(true, $decodedJson['keyboard']); + $this->assertSame(true, $decodedJson['arrows']); + $this->assertSame(false, $decodedJson['showCaption']); + $this->assertSame(true, $decodedJson['allowfullscreen']); + $this->assertSame('horizontal', $decodedJson['navdir']); + $this->assertSame(true, $decodedJson['navarrows']); + $this->assertSame('slides', $decodedJson['navtype']); + $this->assertSame(5, $decodedJson['thumbmargin']); + $this->assertSame('slide', $decodedJson['transition']); + $this->assertSame(500, $decodedJson['transitionduration']); + $this->assertSame(100, $decodedJson['height']); + $this->assertSame(200, $decodedJson['width']); + $this->assertSame(300, $decodedJson['thumbheight']); + $this->assertSame(400, $decodedJson['thumbwidth']); + } + + public function testGetFSOptionsJson() + { + $configMap = [ + ['Magento_Catalog', 'gallery/fullscreen/nav', false], + ['Magento_Catalog', 'gallery/fullscreen/loop', true], + ['Magento_Catalog', 'gallery/fullscreen/keyboard', true], + ['Magento_Catalog', 'gallery/fullscreen/arrows', false], + ['Magento_Catalog', 'gallery/fullscreen/caption', true], + ['Magento_Catalog', 'gallery/fullscreen/navdir', 'vertical'], + ['Magento_Catalog', 'gallery/fullscreen/navarrows', false], + ['Magento_Catalog', 'gallery/fullscreen/navtype', 'thumbs'], + ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', '10'], + ['Magento_Catalog', 'gallery/fullscreen/transition/effect', 'dissolve'], + ['Magento_Catalog', 'gallery/fullscreen/transition/duration', '300'] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + + $json = $this->model->getFSOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + //Note, this tests the special case for nav variable set to false. It + //Should not be converted to boolean. + $this->assertSame('false', $decodedJson['nav']); + $this->assertSame(true, $decodedJson['loop']); + $this->assertSame(false, $decodedJson['arrows']); + $this->assertSame(true, $decodedJson['keyboard']); + $this->assertSame(true, $decodedJson['showCaption']); + $this->assertSame('vertical', $decodedJson['navdir']); + $this->assertSame(false, $decodedJson['navarrows']); + $this->assertSame(10, $decodedJson['thumbmargin']); + $this->assertSame('thumbs', $decodedJson['navtype']); + $this->assertSame('dissolve', $decodedJson['transition']); + $this->assertSame(300, $decodedJson['transitionduration']); + } + + public function testGetOptionsJsonOptionals() + { + $configMap = [ + ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false], + ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + + $json = $this->model->getOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + $this->assertArrayNotHasKey('thumbmargin', $decodedJson); + $this->assertArrayNotHasKey('transitionduration', $decodedJson); + } + + public function testGetFSOptionsJsonOptionals() + { + $configMap = [ + ['Magento_Catalog', 'gallery/fullscreen/keyboard', false], + ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false], + ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false] + ]; + + $this->configView->expects($this->any()) + ->method('getVarValue') + ->will($this->returnValueMap($configMap)); + + $json = $this->model->getFSOptionsJson(); + + $decodedJson = $this->jsonSerializer->unserialize($json); + + $this->assertArrayNotHasKey('thumbmargin', $decodedJson); + $this->assertArrayNotHasKey('keyboard', $decodedJson); + $this->assertArrayNotHasKey('transitionduration', $decodedJson); + } +} diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php old mode 100755 new mode 100644 index 99f7122efa0a8..8326c3b531892 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -676,7 +676,7 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC // TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done $attributeModel = $this->getAttributeModel($attribute); if ($attributeModel->usesSource()) { - $options = $attributeModel->getSource()->getAllOptions(); + $options = $attributeModel->getSource()->getAllOptions(true, true); $meta = $this->arrayManager->merge($configPath, $meta, [ 'options' => $this->convertOptionsValueToString($options), ]); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php index f4334bc25efd8..e5451c8e49847 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php @@ -5,10 +5,16 @@ */ namespace Magento\Catalog\Ui\DataProvider\Product; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Framework\Exception\LocalizedException; +use Magento\Eav\Model\Entity\Attribute\AttributeInterface; + /** * Collection which is used for rendering product list in the backend. * * Used for product grid and customizes behavior of the default Product collection for grid needs. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class ProductCollection extends \Magento\Catalog\Model\ResourceModel\Product\Collection { @@ -25,4 +31,63 @@ protected function _productLimitationJoinPrice() $this->_productLimitationFilters->setUsePriceIndex(false); return $this->_productLimitationPrice(true); } + + /** + * Add attribute filter to collection + * + * @param AttributeInterface|integer|string|array $attribute + * @param null|string|array $condition + * @param string $joinType + * @return $this + * @throws LocalizedException + */ + public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner') + { + $storeId = (int)$this->getStoreId(); + if ($attribute === 'is_saleable' + || is_array($attribute) + || $storeId !== $this->getDefaultStoreId() + ) { + return parent::addAttributeToFilter($attribute, $condition, $joinType); + } + + if ($attribute instanceof AttributeInterface) { + $attributeModel = $attribute; + } else { + $attributeModel = $this->getEntity()->getAttribute($attribute); + if ($attributeModel === false) { + throw new LocalizedException( + __('Invalid attribute identifier for filter (%1)', get_class($attribute)) + ); + } + } + + if ($attributeModel->isScopeGlobal() || $attributeModel->getBackend()->isStatic()) { + return parent::addAttributeToFilter($attribute, $condition, $joinType); + } + + $this->addAttributeToFilterAllStores($attributeModel, $condition); + + return $this; + } + + /** + * Add attribute to filter by all stores + * + * @param Attribute $attributeModel + * @param array $condition + * @return void + */ + private function addAttributeToFilterAllStores(Attribute $attributeModel, array $condition): void + { + $tableName = $this->getTable($attributeModel->getBackendTable()); + $entity = $this->getEntity(); + $fKey = 'e.' . $this->getEntityPkName($entity); + $pKey = $tableName . '.' . $this->getEntityPkName($entity); + $condition = "({$pKey} = {$fKey}) AND (" + . $this->_getConditionSql("{$tableName}.value", $condition) + . ')'; + $selectExistsInAllStores = $this->getConnection()->select()->from($tableName); + $this->getSelect()->exists($selectExistsInAllStores, $condition); + } } diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index 793a2291f599c..ee9c5b29da894 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -120,5 +120,4 @@ <plugin name="catalog_app_action_dispatch_controller_context_plugin" type="Magento\Catalog\Plugin\Framework\App\Action\ContextPlugin" /> </type> - <preference for="Magento\Catalog\Model\Product\Type\Price" type="Magento\Catalog\Model\Product\Type\FrontSpecialPrice" /> </config> diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml index 578281f44c4cf..d689daef4bcab 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml @@ -132,6 +132,7 @@ <settings> <addField>true</addField> <filter>text</filter> + <bodyTmpl>ui/grid/cells/html</bodyTmpl> <label translate="true">Name</label> </settings> </column> @@ -154,6 +155,7 @@ <column name="sku" sortOrder="60"> <settings> <filter>text</filter> + <bodyTmpl>ui/grid/cells/html</bodyTmpl> <label translate="true">SKU</label> </settings> </column> diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml index 8d3248896b434..41a2a6142d506 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml @@ -121,7 +121,12 @@ </arguments> </block> </container> - <block class="Magento\Catalog\Block\Product\View\Gallery" name="product.info.media.image" template="Magento_Catalog::product/view/gallery.phtml"/> + <block class="Magento\Catalog\Block\Product\View\Gallery" name="product.info.media.image" template="Magento_Catalog::product/view/gallery.phtml"> + <arguments> + <argument name="gallery_options" xsi:type="object">Magento\Catalog\Block\Product\View\GalleryOptions</argument> + <argument name="imageHelper" xsi:type="object">Magento\Catalog\Helper\Image</argument> + </arguments> + </block> <container name="skip_gallery_after.wrapper" htmlTag="div" htmlClass="action-skip-wrapper"> <block class="Magento\Framework\View\Element\Template" after="product.info.media.image" name="skip_gallery_after" template="Magento_Theme::html/skip.phtml"> <arguments> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml index 1bfa30478df8a..1f06b90758d0b 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml @@ -12,32 +12,32 @@ * @var $block \Magento\Catalog\Block\Product\View\Gallery */ ?> -<div class="gallery-placeholder _block-content-loading" data-gallery-role="gallery-placeholder"> - <div data-role="loader" class="loading-mask"> - <div class="loader"> - <img src="<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>" - alt="<?= /* @escapeNotVerified */ __('Loading...') ?>"> - </div> - </div> -</div> -<!--Fix for jumping content. Loader must be the same size as gallery.--> -<script> - var config = { - "width": <?= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_medium', 'width') ?>, - "thumbheight": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_small', 'height') - ?: $block->getImageAttribute('product_page_image_small', 'width'); ?>, - "navtype": "<?= /* @escapeNotVerified */ $block->getVar("gallery/navtype") ?>", - "height": <?= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_medium', 'height') ?> - }, - thumbBarHeight = 0, - loader = document.querySelectorAll('[data-gallery-role="gallery-placeholder"] [data-role="loader"]')[0]; - if (config.navtype === 'horizontal') { - thumbBarHeight = config.thumbheight; +<?php + $images = $block->getGalleryImages()->getItems(); + $mainImage = current(array_filter($images, function ($img) use ($block) { + return $block->isMainImage($img); + })); + + if (!empty($images) && empty($mainImage)) { + $mainImage = $block->getGalleryImages()->getFirstItem(); } - loader.style.paddingBottom = ( config.height / config.width * 100) + "%"; -</script> + $helper = $block->getData('imageHelper'); + $mainImageData = $mainImage ? + $mainImage->getData('medium_image_url') : + $helper->getDefaultPlaceholderUrl('image'); + +?> + +<div class="gallery-placeholder _block-content-loading" data-gallery-role="gallery-placeholder"> + <img + alt="main product photo" + class="gallery-placeholder__image" + src="<?= /* @noEscape */ $mainImageData ?>" + /> +</div> + <script type="text/x-magento-init"> { "[data-gallery-role=gallery-placeholder]": { @@ -45,44 +45,8 @@ "mixins":["magnifier/magnify"], "magnifierOpts": <?= /* @escapeNotVerified */ $block->getMagnifier() ?>, "data": <?= /* @escapeNotVerified */ $block->getGalleryImagesJson() ?>, - "options": { - "nav": "<?= /* @escapeNotVerified */ $block->getVar("gallery/nav") ?>", - "loop": <?= /* @escapeNotVerified */ $block->getVar("gallery/loop") ? 'true' : 'false' ?>, - "keyboard": <?= /* @escapeNotVerified */ $block->getVar("gallery/keyboard") ? 'true' : 'false' ?>, - "arrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/arrows") ? 'true' : 'false' ?>, - "allowfullscreen": <?= /* @escapeNotVerified */ $block->getVar("gallery/allowfullscreen") ? 'true' : 'false' ?>, - "showCaption": <?= /* @escapeNotVerified */ $block->getVar("gallery/caption") ? 'true' : 'false' ?>, - "width": "<?= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_medium', 'width') ?>", - "thumbwidth": "<?= /* @escapeNotVerified */ $block->getImageAttribute('product_page_image_small', 'width') ?>", - <?php if ($block->getImageAttribute('product_page_image_small', 'height') || $block->getImageAttribute('product_page_image_small', 'width')): ?> - "thumbheight": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_small', 'height') - ?: $block->getImageAttribute('product_page_image_small', 'width'); ?>, - <?php endif; ?> - <?php if ($block->getImageAttribute('product_page_image_medium', 'height') || $block->getImageAttribute('product_page_image_medium', 'width')): ?> - "height": <?php /* @escapeNotVerified */ echo $block->getImageAttribute('product_page_image_medium', 'height') - ?: $block->getImageAttribute('product_page_image_medium', 'width'); ?>, - <?php endif; ?> - <?php if ($block->getVar("gallery/transition/duration")): ?> - "transitionduration": <?= /* @escapeNotVerified */ $block->getVar("gallery/transition/duration") ?>, - <?php endif; ?> - "transition": "<?= /* @escapeNotVerified */ $block->getVar("gallery/transition/effect") ?>", - "navarrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/navarrows") ? 'true' : 'false' ?>, - "navtype": "<?= /* @escapeNotVerified */ $block->getVar("gallery/navtype") ?>", - "navdir": "<?= /* @escapeNotVerified */ $block->getVar("gallery/navdir") ?>" - }, - "fullscreen": { - "nav": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/nav") ?>", - "loop": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/loop") ? 'true' : 'false' ?>, - "navdir": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navdir") ?>", - "navarrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navarrows") ? 'true' : 'false' ?>, - "navtype": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/navtype") ?>", - "arrows": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/arrows") ? 'true' : 'false' ?>, - "showCaption": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/caption") ? 'true' : 'false' ?>, - <?php if ($block->getVar("gallery/fullscreen/transition/duration")): ?> - "transitionduration": <?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/transition/duration") ?>, - <?php endif; ?> - "transition": "<?= /* @escapeNotVerified */ $block->getVar("gallery/fullscreen/transition/effect") ?>" - }, + "options": <?= /* @noEscape */ $block->getGalleryOptions()->getOptionsJson() ?>, + "fullscreen": <?= /* @noEscape */ $block->getGalleryOptions()->getFSOptionsJson() ?>, "breakpoints": <?= /* @escapeNotVerified */ $block->getBreakpoints() ?> } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Search/Adapter/Mysql/Query/Builder/Match.php b/app/code/Magento/CatalogGraphQl/Model/Search/Adapter/Mysql/Query/Builder/Match.php new file mode 100644 index 0000000000000..4490cf031e5e1 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Search/Adapter/Mysql/Query/Builder/Match.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Search\Adapter\Mysql\Query\Builder; + +use Magento\Framework\DB\Helper\Mysql\Fulltext; +use Magento\Framework\Search\Adapter\Mysql\Field\ResolverInterface; +use Magento\Framework\Search\Adapter\Mysql\Query\Builder\Match as BuilderMatch; +use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; +use Magento\Framework\Search\Request\Query\BoolExpression; +use Magento\Search\Helper\Data; + +/** + * @inheritdoc + */ +class Match extends BuilderMatch +{ + /** + * @var Data + */ + private $searchHelper; + + /** + * @param ResolverInterface $resolver + * @param Fulltext $fulltextHelper + * @param Data $searchHelper + * @param string $fulltextSearchMode + * @param PreprocessorInterface[] $preprocessors + */ + public function __construct( + ResolverInterface $resolver, + Fulltext $fulltextHelper, + Data $searchHelper, + $fulltextSearchMode = Fulltext::FULLTEXT_MODE_BOOLEAN, + array $preprocessors = [] + ) { + parent::__construct($resolver, $fulltextHelper, $fulltextSearchMode, $preprocessors); + $this->searchHelper = $searchHelper; + } + + /** + * @inheritdoc + */ + protected function prepareQuery($queryValue, $conditionType) + { + $replaceSymbols = str_split(self::SPECIAL_CHARACTERS, 1); + $queryValue = str_replace($replaceSymbols, ' ', $queryValue); + foreach ($this->preprocessors as $preprocessor) { + $queryValue = $preprocessor->process($queryValue); + } + + $stringPrefix = ''; + if ($conditionType === BoolExpression::QUERY_CONDITION_MUST) { + $stringPrefix = '+'; + } elseif ($conditionType === BoolExpression::QUERY_CONDITION_NOT) { + $stringPrefix = '-'; + } + + $queryValues = explode(' ', $queryValue); + + foreach ($queryValues as $queryKey => $queryValue) { + if (empty($queryValue)) { + unset($queryValues[$queryKey]); + } else { + $stringSuffix = $this->searchHelper->getMinQueryLength() > strlen($queryValue) ? '' : '*'; + $queryValues[$queryKey] = $stringPrefix . $queryValue . $stringSuffix; + } + } + + $queryValue = implode(' ', $queryValues); + + return $queryValue; + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index b5622e948b7cd..a5bd42860ded0 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -78,4 +78,6 @@ </argument> </arguments> </virtualType> + <preference for="Magento\Framework\Search\Adapter\Mysql\Query\Builder\Match" + type="Magento\CatalogGraphQl\Model\Search\Adapter\Mysql\Query\Builder\Match" /> </config> diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php index 31fd5606a9849..6851b05aa56a6 100644 --- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php @@ -119,14 +119,12 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQ $result->setItemIsQtyDecimal($stockItem->getIsQtyDecimal()); if (!$stockItem->getIsQtyDecimal()) { $result->setHasQtyOptionUpdate(true); - $qty = (int) $qty; + $qty = (int) $qty ?: 1; /** * Adding stock data to quote item */ $result->setItemQty($qty); - $qty = $this->getNumber($qty); - $origQty = (int) $origQty; - $result->setOrigQty($origQty); + $result->setOrigQty((int)$this->getNumber($origQty) ?: 1); } if ($stockItem->getMinSaleQty() && $qty < $stockItem->getMinSaleQty()) { diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml new file mode 100644 index 0000000000000..b9ef37cb4effe --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminSetMinimalQueryLengthActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SetMinimalQueryLengthActionGroup"> + <arguments> + <argument name="minLength" type="string" defaultValue="1"/> + </arguments> + <amOnPage url="{{AdminCatalogSearchConfigurationPage.url}}" stepKey="navigateToConfigurationPage"/> + <waitForPageLoad stepKey="wait1"/> + <scrollTo selector="{{AdminCatalogSearchConfigurationSection.catalogSearchTab}}" stepKey="scrollToCatalogSearchTab"/> + <conditionalClick selector="{{AdminCatalogSearchConfigurationSection.catalogSearchTab}}" dependentSelector="{{AdminCatalogSearchConfigurationSection.minQueryLength}}" visible="false" stepKey="expandCatalogSearchTab"/> + <waitForElementVisible selector="{{AdminCatalogSearchConfigurationSection.minQueryLength}}" stepKey="waitTabToCollapse"/> + <see userInput="{{MinMaxQueryLength.Hint}}" selector="{{AdminCatalogSearchConfigurationSection.minQueryLengthHint}}" stepKey="seeHint1"/> + <see userInput="{{MinMaxQueryLength.Hint}}" selector="{{AdminCatalogSearchConfigurationSection.maxQueryLengthHint}}" stepKey="seeHint2"/> + <uncheckOption selector="{{AdminCatalogSearchConfigurationSection.minQueryLengthInherit}}" stepKey="uncheckSystemValue"/> + <fillField selector="{{AdminCatalogSearchConfigurationSection.minQueryLength}}" userInput="{{minLength}}" stepKey="setMinQueryLength"/> + <click selector="{{AdminCatalogSearchConfigurationSection.catalogSearchTab}}" stepKey="collapseTab"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForConfigSaved"/> + <see userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/CatalogSearchData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/CatalogSearchData.xml new file mode 100644 index 0000000000000..6868456079110 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/CatalogSearchData.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SetMinQueryLengthToDefault" type="catalog_search_config_def"> + <requiredEntity type="enable">DefaultMinQueryLength</requiredEntity> + </entity> + <entity name="UncheckMinQueryLengthAndSet" type="catalog_search_config_query_length"> + <requiredEntity type="number">SetMinQueryLengthToOne</requiredEntity> + </entity> + <entity name="DefaultMinQueryLength" type="enable"> + <data key="inherit">true</data> + </entity> + <entity name="DefaultMinQueryLengthDisable" type="enable"> + <data key="inherit">0</data> + </entity> + <entity name="SetMinQueryLengthToOne" type="number"> + <data key="value">1</data> + </entity> + +</entities> \ No newline at end of file diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/MinMaxQueryLengthHintsData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/MinMaxQueryLengthHintsData.xml new file mode 100644 index 0000000000000..6fb254afea347 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/MinMaxQueryLengthHintsData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="MinMaxQueryLength" type="constant"> + <data key="Hint">This value must be compatible with the corresponding setting in the configured search engine</data> + </entity> +</entities> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Metadata/catalog_search-meta.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Metadata/catalog_search-meta.xml new file mode 100644 index 0000000000000..7405377249aa4 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Metadata/catalog_search-meta.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CatalogSearchConfigDefault" dataType="catalog_search_config_def" type="create" auth="adminFormKey" url="/admin/system_config/save/section/catalog/" method="POST"> + <object key="groups" dataType="catalog_search_config_def"> + <object key="search" dataType="catalog_search_config_def"> + <object key="fields" dataType="catalog_search_config_def"> + <object key="min_query_length" dataType="enable"> + <field key="inherit">boolean</field> + </object> + </object> + </object> + </object> + </operation> + <operation name="CatalogSearchConfigQueryLength" dataType="catalog_search_config_query_length" type="create" auth="adminFormKey" url="/admin/system_config/save/section/catalog/" method="POST"> + <object key="groups" dataType="catalog_search_config_query_length"> + <object key="search" dataType="catalog_search_config_query_length"> + <object key="fields" dataType="catalog_search_config_query_length"> + <object key="min_query_length" dataType="number"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/CatalogSearchAdminConfigSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/CatalogSearchAdminConfigSection.xml new file mode 100644 index 0000000000000..605bcabb8a81d --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/CatalogSearchAdminConfigSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogSearchConfigurationSection"> + <element name="minQueryLength" type="input" selector="#catalog_search_min_query_length"/> + <element name="minQueryLengthInherit" type="checkbox" selector="#catalog_search_min_query_length_inherit"/> + <element name="minQueryLengthHint" type="text" selector="#row_catalog_search_min_query_length .value span"/> + <element name="maxQueryLengthHint" type="text" selector="#row_catalog_search_max_query_length .value span"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml new file mode 100644 index 0000000000000..2fea5c988bf9d --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/MinimalQueryLengthForCatalogSearchTest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MinimalQueryLengthForCatalogSearchTest"> + <annotations> + <features value="CatalogSearch"/> + <title value="Minimal query length for catalog search"/> + <description value="Minimal query length for catalog search"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-6325"/> + <useCaseId value="MAGETWO-58764"/> + <group value="CatalogSearch"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <createData entity="SetMinQueryLengthToDefault" stepKey="setMinimumQueryLengthToDefault"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="SetMinimalQueryLengthActionGroup" stepKey="setMinQueryLength"/> + <comment userInput="Go to Storefront and search for product" stepKey="searchProdUsingMinQueryLength"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="s" stepKey="fillAttribute"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{StorefrontCategoryMainSection.productName}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInCategoryPage"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml index b8f2863139e9b..c358062b88a41 100644 --- a/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogSearch/etc/adminhtml/system.xml @@ -19,13 +19,15 @@ <field id="engine" canRestore="1"> <backend_model>Magento\CatalogSearch\Model\Adminhtml\System\Config\Backend\Engine</backend_model> </field> - <field id="min_query_length" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="min_query_length" translate="label comment" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Minimal Query Length</label> <validate>validate-digits</validate> + <comment>This value must be compatible with the corresponding setting in the configured search engine. Be aware: a low query length limit may cause the performance impact.</comment> </field> - <field id="max_query_length" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="max_query_length" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum Query Length</label> <validate>validate-digits</validate> + <comment>This value must be compatible with the corresponding setting in the configured search engine.</comment> </field> <field id="max_count_cacheable_search_terms" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Number of top search results to cache</label> diff --git a/app/code/Magento/CatalogSearch/etc/config.xml b/app/code/Magento/CatalogSearch/etc/config.xml index 66b79226c9f34..7ea15c6caa590 100644 --- a/app/code/Magento/CatalogSearch/etc/config.xml +++ b/app/code/Magento/CatalogSearch/etc/config.xml @@ -13,7 +13,7 @@ </seo> <search> <engine>mysql</engine> - <min_query_length>1</min_query_length> + <min_query_length>3</min_query_length> <max_query_length>128</max_query_length> <max_count_cacheable_search_terms>100</max_count_cacheable_search_terms> <autocomplete_limit>8</autocomplete_limit> diff --git a/app/code/Magento/CatalogSearch/i18n/en_US.csv b/app/code/Magento/CatalogSearch/i18n/en_US.csv index ba97dc9de1d31..f25d1a589d455 100644 --- a/app/code/Magento/CatalogSearch/i18n/en_US.csv +++ b/app/code/Magento/CatalogSearch/i18n/en_US.csv @@ -38,3 +38,5 @@ name,name "Maximum Query Length","Maximum Query Length" "Rebuild Catalog product fulltext search index","Rebuild Catalog product fulltext search index" "Please enter a valid price range.","Please enter a valid price range." +"This value must be compatible with the corresponding setting in the configured search engine. Be aware: a low query length limit may cause the performance impact.","This value must be compatible with the corresponding setting in the configured search engine. Be aware: a low query length limit may cause the performance impact." +"This value must be compatible with the corresponding setting in the configured search engine.","This value must be compatible with the corresponding setting in the configured search engine." \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index 5b3679bed77e0..393e25e474f12 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -46,7 +46,9 @@ <selectOption selector="{{WidgetSection.WidgetTemplate}}" userInput="Category Link Block Template" stepKey="selectTemplate" /> <click selector="{{WidgetSection.BtnChooser}}" stepKey="clickSelectCategoryBtn" /> <waitForLoadingMaskToDisappear stepKey="wait3"/> - <click userInput="$$createPreReqCategory.name$$" stepKey="selectPreCreateCategory" /> + <click selector="{{AdminCategorySidebarTreeSection.expandRootCategory}}" stepKey="expandRootCategory" /> + <waitForElementVisible selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="expandWait" /> + <click selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="selectPreCreateCategory" /> <waitForElementNotVisible selector="{{WidgetSection.SelectCategoryTitle}}" stepKey="waitForSlideoutCloses1" /> <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidget" /> <waitForElementNotVisible selector="{{WidgetSection.InsertWidgetTitle}}" stepKey="waitForSlideOutCloses2" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 123d25f92b6b7..9ee9d27de477a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -50,6 +50,8 @@ <selectOption selector="{{WidgetSection.WidgetTemplate}}" userInput="Product Link Block Template" stepKey="selectTemplate" /> <click selector="{{WidgetSection.BtnChooser}}" stepKey="clickSelectPageBtn" /> <waitForLoadingMaskToDisappear stepKey="wait4"/> + <click selector="{{AdminCategorySidebarTreeSection.expandRootCategory}}" stepKey="expandRootCategory" /> + <waitForElementVisible selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="expandWait" /> <click selector="{{WidgetSection.PreCreateCategory('$$createPreReqCategory.name$$')}}" stepKey="selectPreCategory" /> <waitForLoadingMaskToDisappear stepKey="waitLoadingMask" /> <click selector="{{WidgetSection.PreCreateProduct('$$createPreReqProduct.name$$')}}" stepKey="selectPreProduct" /> diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php index 86ae1f96749df..c622a48b7f2c8 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php @@ -90,10 +90,10 @@ public function process($path, $value, $scope, $scopeCode) } try { - $config = $this->configFactory->create([ + $config = $this->configFactory->create(['data' => [ 'scope' => $scope, 'scope_code' => $scopeCode, - ]); + ]]); $config->setDataByPath($path, $value); $config->save(); } catch (\Exception $exception) { diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php index edb76c067bf35..35b2406b328cb 100644 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php +++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php @@ -103,7 +103,7 @@ public function testProcess($path, $value, $scope, $scopeCode) $config = $this->createMock(Config::class); $this->configFactory->expects($this->once()) ->method('create') - ->with(['scope' => $scope, 'scope_code' => $scopeCode]) + ->with(['data' => ['scope' => $scope, 'scope_code' => $scopeCode]]) ->willReturn($config); $config->expects($this->once()) ->method('setDataByPath') diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 46f10608bc95e..a849d964eaed5 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -1232,6 +1232,8 @@ public function isPossibleBuyFromList($product) /** * Returns array of sub-products for specified configurable product * + * $requiredAttributeIds - one dimensional array, if provided + * * Result array contains all children for specified configurable product * * @param \Magento\Catalog\Model\Product $product @@ -1245,9 +1247,12 @@ public function getUsedProducts($product, $requiredAttributeIds = null) __METHOD__, $product->getData($metadata->getLinkField()), $product->getStoreId(), - $this->getCustomerSession()->getCustomerGroupId(), - $requiredAttributeIds + $this->getCustomerSession()->getCustomerGroupId() ]; + if ($requiredAttributeIds !== null) { + sort($requiredAttributeIds); + $keyParts[] = implode('', $requiredAttributeIds); + } $cacheKey = $this->getUsedProductsCacheKey($keyParts); return $this->loadUsedProducts($product, $cacheKey); } diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..eec2194825166 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageContactUsFormActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="Thanks for contacting us with your comments and questions. We'll respond to you very soon." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontContactUsMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..df4964ea0423d --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillContactUsFormActionGroup"> + <arguments> + <argument name="customer" type="entity" /> + <argument name="contactUsData" type="entity" /> + </arguments> + <fillField selector="{{StorefrontContactUsFormSection.nameField}}" userInput="{{customer.firstname}}" stepKey="fillName"/> + <fillField selector="{{StorefrontContactUsFormSection.emailField}}" userInput="{{customer.email}}" stepKey="fillEmail"/> + <fillField selector="{{StorefrontContactUsFormSection.commentField}}" userInput="{{contactUsData.comment}}" stepKey="fillComment"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml new file mode 100644 index 0000000000000..d333d5d998960 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenContactUsPageActionGroup"> + <amOnPage url="{{StorefrontContactUsPage.url}}" stepKey="amOnContactUpPage"/> + <waitForPageLoad stepKey="waitForContactUpPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml new file mode 100644 index 0000000000000..f3fe34f20c319 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontSubmitContactUsFormActionGroup"> + <click selector="{{StorefrontContactUsFormSection.submitFormButton}}" stepKey="clickSubmitFormButton"/> + <waitForPageLoad stepKey="waitForCommentSubmitted" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml b/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml new file mode 100644 index 0000000000000..eadf760776c58 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DefaultContactUsData"> + <data key="comment" unique="suffix">Lorem ipsum dolor sit amet, ne enim aliquando eam, oblique deserunt no usu. Unique: </data> + </entity> +</entities> diff --git a/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml b/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml new file mode 100644 index 0000000000000..5e793b2338507 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontContactUsPage" url="/contact/" area="storefront" module="Magento_Contact"> + <section name="StorefrontContactUsFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml new file mode 100644 index 0000000000000..fdaddf33f5170 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsFormSection"> + <element name="nameField" type="input" selector="#contact-form input[name='name']" /> + <element name="emailField" type="input" selector="#contact-form input[name='email']" /> + <element name="phoneField" type="input" selector="#contact-form input[name='telephone']" /> + <element name="commentField" type="textarea" selector="#contact-form textarea[name='comment']" /> + <element name="submitFormButton" type="button" selector="#contact-form button[type='submit']" timeout="30" /> + </section> +</sections> diff --git a/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml new file mode 100644 index 0000000000000..0970f1f8f6b20 --- /dev/null +++ b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontContactUsMessagesSection"> + <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true" /> + </section> +</sections> diff --git a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less index 0aaec05aa2afe..d79806eecbe9b 100644 --- a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less +++ b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less @@ -21,6 +21,16 @@ } } +// +// Desktop +// _____________________________________________ + +.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { + .contact-index-index .column:not(.sidebar-additional) .form.contact { + min-width: 600px; + } +} + // Mobile .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .contact-index-index { diff --git a/app/code/Magento/Customer/Controller/Address/File/Upload.php b/app/code/Magento/Customer/Controller/Address/File/Upload.php new file mode 100644 index 0000000000000..adb4c7abd1729 --- /dev/null +++ b/app/code/Magento/Customer/Controller/Address/File/Upload.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Address\File; + +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Api\CustomAttributesDataInterface; +use Magento\Customer\Api\AddressMetadataInterface; +use Magento\Customer\Model\FileUploader; +use Magento\Customer\Model\FileUploaderFactory; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; +use Magento\Customer\Model\FileProcessorFactory; + +/** + * Class for upload files for customer custom address attributes + */ +class Upload extends Action implements HttpPostActionInterface +{ + /** + * @var FileUploaderFactory + */ + private $fileUploaderFactory; + + /** + * @var AddressMetadataInterface + */ + private $addressMetadataService; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var FileProcessorFactory + */ + private $fileProcessorFactory; + + /** + * @param Context $context + * @param FileUploaderFactory $fileUploaderFactory + * @param AddressMetadataInterface $addressMetadataService + * @param LoggerInterface $logger + * @param FileProcessorFactory $fileProcessorFactory + */ + public function __construct( + Context $context, + FileUploaderFactory $fileUploaderFactory, + AddressMetadataInterface $addressMetadataService, + LoggerInterface $logger, + FileProcessorFactory $fileProcessorFactory + ) { + $this->fileUploaderFactory = $fileUploaderFactory; + $this->addressMetadataService = $addressMetadataService; + $this->logger = $logger; + $this->fileProcessorFactory = $fileProcessorFactory; + parent::__construct($context); + } + + /** + * @inheritDoc + */ + public function execute() + { + try { + $requestedFiles = $this->getRequest()->getFiles('custom_attributes'); + if (empty($requestedFiles)) { + $result = $this->processError(__('No files for upload.')); + } else { + $attributeCode = key($requestedFiles); + $attributeMetadata = $this->addressMetadataService->getAttributeMetadata($attributeCode); + + /** @var FileUploader $fileUploader */ + $fileUploader = $this->fileUploaderFactory->create([ + 'attributeMetadata' => $attributeMetadata, + 'entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS, + 'scope' => CustomAttributesDataInterface::CUSTOM_ATTRIBUTES, + ]); + + $errors = $fileUploader->validate(); + if (true !== $errors) { + $errorMessage = implode('</br>', $errors); + $result = $this->processError(($errorMessage)); + } else { + $result = $fileUploader->upload(); + $this->moveTmpFileToSuitableFolder($result); + } + } + } catch (LocalizedException $e) { + $result = $this->processError($e->getMessage(), $e->getCode()); + } catch (\Exception $e) { + $this->logger->critical($e); + $result = $this->processError($e->getMessage(), $e->getCode()); + } + + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $resultJson->setData($result); + return $resultJson; + } + + /** + * Move file from temporary folder to the 'customer_address' media folder + * + * @param array $fileInfo + * @throws LocalizedException + */ + private function moveTmpFileToSuitableFolder(&$fileInfo) + { + $fileName = $fileInfo['file']; + $fileProcessor = $this->fileProcessorFactory + ->create(['entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS]); + + $newFilePath = $fileProcessor->moveTemporaryFile($fileName); + $fileInfo['file'] = $newFilePath; + $fileInfo['url'] = $fileProcessor->getViewUrl( + $newFilePath, + 'file' + ); + } + + /** + * Prepare result array for errors + * + * @param string $message + * @param int $code + * @return array + */ + private function processError($message, $code = 0) + { + $result = [ + 'error' => $message, + 'errorcode' => $code, + ]; + + return $result; + } +} diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index e9aa2839095d5..ea9b103f42273 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -122,7 +122,7 @@ public function __construct( } /** - * Init model + * Initialize address model * * @return void */ @@ -167,7 +167,14 @@ public function updateData(AddressInterface $address) } /** - * @inheritdoc + * Create address data object based on current address model. + * + * @param int|null $defaultBillingAddressId + * @param int|null $defaultShippingAddressId + * @return AddressInterface + * Use Api/Data/AddressInterface as a result of service operations. Don't rely on the model to provide + * the instance of Api/Data/AddressInterface + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getDataModel($defaultBillingAddressId = null, $defaultShippingAddressId = null) { @@ -257,7 +264,7 @@ public function getDefaultAttributeCodes() } /** - * Clone object handler + * Clone address * * @return void */ @@ -356,7 +363,11 @@ public function reindex() } /** - * @inheritdoc + * Get a list of custom attribute codes. + * + * By default, entity can be extended only using extension attributes functionality. + * + * @return string[] * @since 100.0.6 */ protected function getCustomAttributesCodes() diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index b00f393f53734..1287dbe5df708 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -1054,17 +1054,6 @@ public function resetErrors() return $this; } - /** - * Prepare customer for delete - * - * @return $this - */ - public function beforeDelete() - { - //TODO : Revisit and figure handling permissions in MAGETWO-11084 Implementation: Service Context Provider - return parent::beforeDelete(); - } - /** * Processing object after save data * @@ -1305,7 +1294,7 @@ public function getResetPasswordLinkExpirationPeriod() } /** - * Create address instance + * Create Address from Factory * * @return Address */ @@ -1315,7 +1304,7 @@ protected function _createAddressInstance() } /** - * Create address collection instance + * Create Address Collection from Factory * * @return \Magento\Customer\Model\ResourceModel\Address\Collection */ @@ -1325,7 +1314,7 @@ protected function _createAddressCollection() } /** - * Returns templates types + * Get Template Types * * @return array */ diff --git a/app/code/Magento/Customer/Model/FileProcessor.php b/app/code/Magento/Customer/Model/FileProcessor.php index 6a8472758c169..c16faea284296 100644 --- a/app/code/Magento/Customer/Model/FileProcessor.php +++ b/app/code/Magento/Customer/Model/FileProcessor.php @@ -3,8 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Model; +/** + * Processor class for work with uploaded files + */ class FileProcessor { /** @@ -232,7 +237,7 @@ public function moveTemporaryFile($fileName) ); } - $fileName = $dispersionPath . '/' . $fileName; + $fileName = $dispersionPath . '/' . $destinationFileName; return $fileName; } diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index b25838e245488..ddc0576cb0dae 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -171,7 +171,14 @@ public function __construct( } /** - * @inheritdoc + * Create or update a customer. + * + * @param \Magento\Customer\Api\Data\CustomerInterface $customer + * @param string $passwordHash + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws \Magento\Framework\Exception\InputException If bad input is provided + * @throws \Magento\Framework\Exception\State\InputMismatchException If the provided email is already used + * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -304,7 +311,13 @@ private function populateCustomerWithSecureData($customerModel, $passwordHash = } /** - * @inheritdoc + * Retrieve customer. + * + * @param string $email + * @param int|null $websiteId + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified email does not exist. + * @throws \Magento\Framework\Exception\LocalizedException */ public function get($email, $websiteId = null) { @@ -313,7 +326,12 @@ public function get($email, $websiteId = null) } /** - * @inheritdoc + * Get customer by Customer ID. + * + * @param int $customerId + * @return \Magento\Customer\Api\Data\CustomerInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified ID does not exist. + * @throws \Magento\Framework\Exception\LocalizedException */ public function getById($customerId) { @@ -322,7 +340,15 @@ public function getById($customerId) } /** - * @inheritdoc + * Retrieve customers which match a specified criteria. + * + * This call returns an array of objects, but detailed information about each object’s attributes might not be + * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine + * which call to use to get detailed information about all attributes for an object. + * + * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria + * @return \Magento\Customer\Api\Data\CustomerSearchResultsInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function getList(SearchCriteriaInterface $searchCriteria) { @@ -362,7 +388,11 @@ public function getList(SearchCriteriaInterface $searchCriteria) } /** - * @inheritdoc + * Delete customer. + * + * @param \Magento\Customer\Api\Data\CustomerInterface $customer + * @return bool true on success + * @throws \Magento\Framework\Exception\LocalizedException */ public function delete(CustomerInterface $customer) { @@ -370,7 +400,12 @@ public function delete(CustomerInterface $customer) } /** - * @inheritdoc + * Delete customer by Customer ID. + * + * @param int $customerId + * @return bool true on success + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException */ public function deleteById($customerId) { diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml new file mode 100644 index 0000000000000..d9da950fe7115 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerWelcomeMessageActionGroup"> + <arguments> + <argument name="customerFullName" type="string" /> + </arguments> + <see userInput="Welcome, {{customerFullName}}!" selector="{{StorefrontPanelHeaderSection.welcomeMessage}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml new file mode 100644 index 0000000000000..644254443d129 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertCustomerResetPasswordActionGroup"> + <arguments> + <argument name="url" type="string"/> + <argument name="message" type="string" defaultValue="" /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + + <waitForElementVisible selector="{{StorefrontCustomerLoginMessagesSection.messageByType(messageType)}}" stepKey="waitForMessage" /> + <see stepKey="seeMessage" userInput="{{message}}" selector="{{StorefrontCustomerLoginMessagesSection.messageByType(messageType)}}"/> + <seeInCurrentUrl stepKey="seeCorrectCurrentUrl" url="{{url}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml new file mode 100644 index 0000000000000..ab48184ed98a8 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageCustomerChangeAccountInfoActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="You saved the account information." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontCustomerAccountMainSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml new file mode 100644 index 0000000000000..65c9b025a9c2d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageCustomerCreateAccountActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="Thank you for registering with Main Website Store." /> + <argument name="messageType" type="string" defaultValue="success" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontCustomerAccountMainSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml new file mode 100644 index 0000000000000..6b88661985873 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertMessageCustomerLoginActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue="The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later." /> + <argument name="messageType" type="string" defaultValue="error" /> + </arguments> + <see userInput="{{message}}" selector="{{StorefrontCustomerLoginMessagesSection.messageByType(messageType)}}" stepKey="verifyMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml deleted file mode 100644 index b12858fc1037e..0000000000000 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="StorefrontClickSignInButtonActionGroup"> - <click stepKey="signIn" selector="{{StorefrontPanelHeaderSection.customerLoginLink}}" /> - <waitForPageLoad stepKey="waitForStorefrontSignInPageLoad"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml new file mode 100644 index 0000000000000..dfb9d1b2c259a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup"> + <click stepKey="clickCreateAccountButton" selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" /> + <waitForPageLoad stepKey="waitForCustomerSaved" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..9cd52b841fca4 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickSignOnCustomerLoginFormActionGroup"> + <click stepKey="clickSignInButton" selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}" /> + <waitForPageLoad stepKey="waitForCustomerSignIn" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml new file mode 100644 index 0000000000000..844d13aa1fe43 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerChangeEmailActionGroup"> + <arguments> + <argument name="email" type="string" /> + <argument name="password" type="string" /> + </arguments> + <conditionalClick selector="{{StorefrontCustomerSidebarSection.sidebarTab('Account Information')}}" dependentSelector="{{StorefrontCustomerSidebarSection.sidebarTab('Account Information')}}" visible="true" stepKey="openAccountInfoTab" /> + <waitForPageLoad stepKey="waitForAccountInfoTabOpened" /> + <checkOption selector="{{StorefrontCustomerAccountInformationSection.changeEmail}}" stepKey="clickChangeEmailCheckbox" /> + <fillField selector="{{StorefrontCustomerAccountInformationSection.email}}" userInput="{{email}}" stepKey="fillEmail" /> + <fillField selector="{{StorefrontCustomerAccountInformationSection.currentPassword}}" userInput="{{password}}" stepKey="fillCurrentPassword" /> + <click selector="{{StorefrontCustomerAccountInformationSection.saveButton}}" stepKey="saveChange" /> + <waitForPageLoad stepKey="waitForPageLoaded" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml new file mode 100644 index 0000000000000..a28593f1b77b7 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerResetPasswordActionGroup"> + <arguments> + <argument name="email" type="string" /> + </arguments> + + <amOnPage stepKey="amOnSignInPage" url="{{StorefrontCustomerSignInPage.url}}"/> + <click stepKey="clickForgotPasswordLink" selector="{{StorefrontCustomerSignInFormSection.forgotPasswordLink}}"/> + <see stepKey="seePageTitle" userInput="Forgot Your Password" selector="{{StorefrontForgotPasswordSection.pageTitle}}"/> + <!-- Enter email and submit the forgot password form --> + <fillField stepKey="fillEmailField" userInput="{{email}}" selector="{{StorefrontForgotPasswordSection.email}}"/> + <click stepKey="clickResetPassword" selector="{{StorefrontForgotPasswordSection.resetMyPasswordButton}}"/> + <waitForPageLoad stepKey="waitForPageLoaded" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml new file mode 100644 index 0000000000000..62d77d4548cce --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerAccountCreationFormActionGroup"> + <arguments> + <argument name="customer" type="entity" /> + </arguments> + + <fillField stepKey="fillFirstName" userInput="{{customer.firstname}}" selector="{{StorefrontCustomerCreateFormSection.firstnameField}}" /> + <fillField stepKey="fillLastName" userInput="{{customer.lastname}}" selector="{{StorefrontCustomerCreateFormSection.lastnameField}}" /> + <fillField stepKey="fillEmail" userInput="{{customer.email}}" selector="{{StorefrontCustomerCreateFormSection.emailField}}"/> + <fillField stepKey="fillPassword" userInput="{{customer.password}}" selector="{{StorefrontCustomerCreateFormSection.passwordField}}"/> + <fillField stepKey="fillConfirmPassword" userInput="{{customer.password}}" selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml new file mode 100644 index 0000000000000..22883ada7c2b1 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFillCustomerLoginFormActionGroup"> + <arguments> + <argument name="customer" type="entity" /> + </arguments> + + <fillField userInput="{{customer.email}}" selector="{{StorefrontCustomerSignInFormSection.emailField}}" stepKey="fillEmail"/> + <fillField userInput="{{customer.password}}" selector="{{StorefrontCustomerSignInFormSection.passwordField}}" stepKey="fillPassword"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml similarity index 50% rename from app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml index 23a067cd94eea..0ae470d0497ab 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml @@ -5,10 +5,11 @@ * See COPYING.txt for license details. */ --> + <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AssertStorefrontPasswordAutoCompleteOffActionGroup"> - <amOnPage stepKey="amOnSignInPage" url="{{StorefrontCustomerSignInPage.url}}"/> - <assertElementContainsAttribute selector="{{StorefrontCustomerSignInFormSection.passwordField}}" attribute="autocomplete" expectedValue="off" stepKey="assertSignInPasswordAutocompleteOff"/> + <actionGroup name="StorefrontOpenCustomerAccountCreatePageActionGroup"> + <amOnPage url="{{StorefrontCustomerCreatePage.url}}" stepKey="goToCustomerAccountCreatePage"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml new file mode 100644 index 0000000000000..c1ea2da8a9519 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenCustomerAccountInfoEditPageActionGroup"> + <amOnPage url="{{StorefrontCustomerEditPage.url}}" stepKey="goToCustomerEditPage"/> + <waitForPageLoad stepKey="waitForEditPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml new file mode 100644 index 0000000000000..0a5c72265528a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenCustomerLoginPageActionGroup"> + <amOnPage url="{{StorefrontCustomerSignInPage.url}}" stepKey="amOnSignInPage"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml index 3cbd70d342824..11a47459ab7b3 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml @@ -21,4 +21,11 @@ <entity name="GlobalCustomerAccountSharing" type="account_share_scope_value"> <data key="value">0</data> </entity> + + <entity name="CustomerAccountSharingSystemValue" type="customer_account_sharing_config_inherit"> + <requiredEntity type="account_share_scope_inherit">CustomerAccountSharingInherit</requiredEntity> + </entity> + <entity name="CustomerAccountSharingInherit" type="account_share_scope_inherit"> + <data key="inherit">true</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml index 41701bfac11ad..c3132b5b6a44f 100644 --- a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml @@ -18,4 +18,16 @@ </object> </object> </operation> + <operation name="CustomerAccountShareConfigInherit" dataType="customer_account_sharing_config_inherit" type="create" auth="adminFormKey" url="/admin/system_config/save/section/customer/" + method="POST"> + <object key="groups" dataType="customer_account_sharing_config_inherit"> + <object key="account_share" dataType="customer_account_sharing_config_inherit"> + <object key="fields" dataType="customer_account_sharing_config_inherit"> + <object key="scope" dataType="account_share_scope_inherit"> + <field key="inherit">boolean</field> + </object> + </object> + </object> + </object> + </operation> </operations> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml new file mode 100644 index 0000000000000..d4cf90dde08ff --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerEditPage" url="/customer/account/edit/" area="storefront" module="Magento_Customer"> + <section name="StorefrontCustomerAccountInformationSection"/> + </page> +</pages> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml index 9e104eb52cf90..a934d71397b8c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml @@ -8,5 +8,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerConfigSection"> <element name="customerDataLifetime" type="input" selector="#customer_online_customers_section_data_lifetime"/> + <element name="accountSharingOptionsTab" type="button" selector="#customer_account_share-head"/> + <element name="shareCustomerAccountInherit" type="checkbox" selector="#customer_account_share_scope_inherit"/> + <element name="shareCustomerAccount" type="select" selector="#customer_account_share_scope"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml index 25617ca05dd44..17a4a283c2648 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml @@ -18,5 +18,6 @@ <element name="clearAll" type="button" selector=".admin__data-grid-header .action-tertiary.action-clear" timeout="30"/> <element name="viewDropdown" type="button" selector=".admin__data-grid-action-bookmarks button.admin__action-dropdown"/> <element name="viewBookmark" type="button" selector="//div[contains(@class, 'admin__data-grid-action-bookmarks')]/ul/li/div/a[text() = '{{label}}']" parameterized="true" timeout="30"/> + <element name="countryOptions" type="button" selector=".admin__data-grid-filters select[name=billing_country_id] option"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml index b819a78002c62..8a633ec5bc271 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml @@ -11,13 +11,14 @@ <section name="StorefrontCustomerAccountInformationSection"> <element name="firstName" type="input" selector="#firstname"/> <element name="lastName" type="input" selector="#lastname"/> - <element name="changeEmail" type="checkbox" selector="#change_email"/> - <element name="changePassword" type="checkbox" selector="#change_password"/> + <element name="changeEmail" type="checkbox" selector=".form-edit-account input[name='change_email']"/> + <element name="changePassword" type="checkbox" selector=".form-edit-account input[name='change_password']"/> <element name="testAddedAttributeFiled" type="input" selector="//input[contains(@id,'{{var}}')]" parameterized="true"/> <element name="saveButton" type="button" selector="#form-validate .action.save.primary" timeout="30"/> <element name="currentPassword" type="input" selector="#current-password"/> <element name="newPassword" type="input" selector="#password"/> <element name="confirmNewPassword" type="input" selector="#password-confirmation"/> <element name="confirmNewPasswordError" type="text" selector="#password-confirmation-error"/> + <element name="email" type="input" selector=".form-edit-account input[name='email']" /> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml index 3a4329969ae2b..c00b54b3964da 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerAccountMainSection"> <element name="pageTitle" type="text" selector="#maincontent .column.main [data-ui-id='page-title-wrapper']" /> + <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true" /> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml index 078021db062cc..a9859cf58751b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontCustomerLoginMessagesSection"> <element name="successMessage" type="text" selector=".message-success"/> <element name="errorMessage" type="text" selector=".message-error"/> + <element name="messageByType" type="block" selector="#maincontent .message-{{messageType}}" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml index f52b379379ad1..7bc057b8be7b7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -12,7 +12,7 @@ <element name="emailField" type="input" selector="#email"/> <element name="passwordField" type="input" selector="#pass"/> <element name="signInAccountButton" type="button" selector="#send2" timeout="30"/> - <element name="forgotPasswordLink" type="link" selector=".action.remind" timeout="10"/> + <element name="forgotPasswordLink" type="button" selector=".action.remind" timeout="10"/> </section> <section name="StorefrontCustomerSignInPopupFormSection"> <element name="errorMessage" type="input" selector="[data-ui-id='checkout-cart-validationmessages-message-error']"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml index 4d7572aedc59b..dab298f6b416b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml @@ -9,7 +9,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontPanelHeaderSection"> + <!-- Element name="WelcomeMessage" Deprecated due to incorrect naming convention please use name="welcomeMessage" --> <element name="WelcomeMessage" type="text" selector=".greet.welcome span"/> + + <element name="welcomeMessage" type="text" selector="header>.panel .greet.welcome" /> <element name="createAnAccountLink" type="select" selector="//div[@class='panel wrapper']//li/a[contains(.,'Create an Account')]" timeout="30"/> <element name="notYouLink" type="button" selector=".greet.welcome span a"/> <element name="customerWelcome" type="text" selector=".panel.header .customer-welcome"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml new file mode 100644 index 0000000000000..f39394ef312e4 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AllowedCountriesRestrictionApplyOnBackendTest"> + <annotations> + <title value="Country filter on Customers page when allowed countries restriction for a default website is applied"/> + <description value="Country filter on Customers page when allowed countries restriction for a default website is applied"/> + <features value="Customer"/> + <severity value="MAJOR"/> + <testCaseId value="MC-6441"/> + <useCaseId value="MAGETWO-91523"/> + <group value="customer"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginActionGroup" stepKey="login"/> + <!--Create new website,store and store view--> + <comment userInput="Create new website,store and store view" stepKey="createWebsite"/> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="goToAdminSystemStorePage"/> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="adminCreateNewWebsite"> + <argument name="newWebsiteName" value="{{NewWebSiteData.name}}"/> + <argument name="websiteCode" value="{{NewWebSiteData.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="adminCreateNewStore"> + <argument name="website" value="{{NewWebSiteData.name}}"/> + <argument name="storeGroupName" value="{{NewStoreData.name}}"/> + <argument name="storeGroupCode" value="{{NewStoreData.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="adminCreateNewStoreView"> + <argument name="StoreGroup" value="NewStoreData"/> + </actionGroup> + <!--Set account sharing option - Default value is 'Per Website'--> + <comment userInput="Set account sharing option - Default value is 'Per Website'" stepKey="setAccountSharingOption"/> + <createData entity="CustomerAccountSharingDefault" stepKey="setToAccountSharingToDefault"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!--delete all created data and set main website country options to default--> + <comment userInput="Delete all created data and set main website country options to default" stepKey="resetConfigToDefault"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteTestWebsite"> + <argument name="websiteName" value="{{NewWebSiteData.name}}"/> + </actionGroup> + <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigGeneralPage2"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="adminSwitchWebsiteActionGroup"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <actionGroup ref="SetWebsiteCountryOptionsToDefaultActionGroup" stepKey="setCountryOptionsToDefault"/> + <createData entity="CustomerAccountSharingSystemValue" stepKey="setAccountSharingToSystemValue"/> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + <!--Check that all countries are allowed initially and get amount--> + <comment userInput="Check that all countries are allowed initially and get amount" stepKey="checkAllCountriesAreAllowed"/> + <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigGeneralPage"/> + <createData entity="DisableAdminAccountAllowCountry" stepKey="setDefaultValueForAllowCountries"/> + <executeJS function="return document.querySelectorAll('{{CountryOptionsSection.allowedCountries}} option').length" stepKey="countriesAmount"/> + <!-- Create customer for US --> + <comment userInput="Create customer for US" stepKey="createUSCustomer"/> + <createData entity="Simple_US_CA_Customer" stepKey="createCustomer"/> + <!-- Switch to first website, allow only Canada and set Canada as default country --> + <comment userInput="Switch to first website, allow only Canada and set Canada as default country" stepKey="setCanadaAsDefaultCountry"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="adminSwitchWebsiteActionGroup"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation2"/> + <waitForElementVisible selector="{{CountryOptionsSection.allowedCountries}}" stepKey="waitAllowedCountriesToBeVisible"/> + <uncheckOption selector="{{CountryOptionsSection.generalCountryDefaultInherit}}" stepKey="uncheckCheckbox1"/> + <selectOption selector="{{CountryOptionsSection.generalCountryDefault}}" userInput="Canada" stepKey="chooseCanada1"/> + <uncheckOption selector="{{CountryOptionsSection.generalCountryAllowInherit}}" stepKey="uncheckCheckbox2"/> + <selectOption selector="{{CountryOptionsSection.allowedCountries}}" userInput="Canada" stepKey="chooseCanada2"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig2"/> + <waitForPageLoad stepKey="waitForSavingSystemConfiguration2"/> + <!--Switch to second website and allow all countries except Canada--> + <comment userInput="Switch to second website and allow all countries except Canada" stepKey="switchToWebsiteAndAllowOnlyCanada"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="adminSwitchWebsiteActionGroup2"> + <argument name="website" value="NewWebSiteData"/> + </actionGroup> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation3"/> + <waitForElementVisible selector="{{CountryOptionsSection.allowedCountries}}" stepKey="waitAllowedCountriesToBeVisible2"/> + <uncheckOption selector="{{CountryOptionsSection.generalCountryAllowInherit}}" stepKey="uncheckCheckbox3"/> + <unselectOption selector="{{CountryOptionsSection.allowedCountries}}" userInput="Canada" stepKey="unselectCanada"/> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig3"/> + <waitForPageLoad stepKey="waitForSavingSystemConfiguration3"/> + <amOnPage url="{{AdminEditCustomerPage.url($$createCustomer.id$$)}}" stepKey="goToCustomerEditPage"/> + <waitForPageLoad stepKey="waitPageToLoad"/> + <!--Open created customer details page and change US address to Canada address--> + <comment userInput="Open created customer details page and change US address to Canada address" stepKey="changeCustomerAddressToCanada"/> + <actionGroup ref="OpenEditCustomerAddressFromAdminActionGroup" stepKey="editCustomerAddress"> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <selectOption selector="{{AdminEditCustomerAddressesSection.country}}" userInput="Canada" stepKey="selectCountry"/> + <selectOption selector="{{AdminEditCustomerAddressesSection.state}}" userInput="Quebec" stepKey="selectState"/> + <click selector="{{AdminEditCustomerAddressesSection.save}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + <click stepKey="saveCustomer" selector="{{AdminCustomerAccountInformationSection.saveCustomerAndContinueEdit}}"/> + <waitForPageLoad stepKey="waitForCustomersPage"/> + <!--Go to Customers grid and check that filter countries amount is the same as initial allowed countries amount--> + <comment userInput="Go to Customers grid and check that filter countries amount is the same as initial allowed countries amount" stepKey="compareCountriesAmount"/> + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomersGrid"/> + <waitForPageLoad stepKey="waitForCustomersGrid"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersSectionOnCustomersGrid"/> + <executeJS function="var len = document.querySelectorAll('{{AdminCustomerFiltersSection.countryOptions}}').length; return len-1;" stepKey="countriesAmount2"/> + <assertEquals expected='($countriesAmount)' expectedType="integer" actual="($countriesAmount2)" stepKey="assertCountryAmounts"/> + </test> +</tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml index 12e603bd3748c..8d4be5fda3c79 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml @@ -20,10 +20,11 @@ <group value="mtf_migrated"/> </annotations> <before> - <magentoCLI command="config:set customer/captcha/enable 0" stepKey="disableCaptcha"/> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> <createData stepKey="customer" entity="Simple_US_Customer"/> </before> <after> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> <deleteData stepKey="deleteCustomer" createDataKey="customer" /> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml new file mode 100644 index 0000000000000..3121bd0da9d2d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontResetCustomerPasswordFailedTest"> + <annotations> + <features value="Customer"/> + <title value="Customer tries to reset password several times"/> + <description value="Customer tries to reset password several times"/> + <severity value="CRITICAL" /> + <testCaseId value="MC-14374" /> + <group value="Customer"/> + <group value="security"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <createData stepKey="customer" entity="Simple_US_Customer"/> + </before> + <after> + <deleteData stepKey="deleteCustomer" createDataKey="customer" /> + </after> + + <actionGroup ref="StorefrontCustomerResetPasswordActionGroup" stepKey="resetPasswordFirstAttempt"> + <argument name="email" value="$$customer.email$$" /> + </actionGroup> + <actionGroup ref="AssertCustomerResetPasswordActionGroup" stepKey="seePageWithSuccessMessage"> + <argument name="url" value="{{StorefrontCustomerSignInPage.url}}"/> + <argument name="message" value="If there is an account associated with $$customer.email$$ you will receive an email with a link to reset your password."/> + </actionGroup> + <actionGroup ref="StorefrontCustomerResetPasswordActionGroup" stepKey="resetPasswordSecondAttempt"> + <argument name="email" value="$$customer.email$$" /> + </actionGroup> + <actionGroup ref="AssertCustomerResetPasswordActionGroup" stepKey="seePageWithErrorMessage"> + <argument name="url" value="{{StorefrontForgotPasswordPage.url}}"/> + <argument name="message" value="We received too many requests for password resets. Please wait and try again later or contact hello@example.com."/> + <argument name="messageType" value="error" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml index 6b479ad1cb290..f845d407d401a 100644 --- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml @@ -174,6 +174,7 @@ </column> <column name="billing_country_id" component="Magento_Ui/js/grid/columns/select" sortOrder="80"> <settings> + <options class="Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites"/> <filter>select</filter> <dataType>select</dataType> <label translate="true">Country</label> diff --git a/app/code/Magento/Developer/i18n/de_DE.csv b/app/code/Magento/Developer/i18n/de_DE.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/de_DE.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/es_ES.csv b/app/code/Magento/Developer/i18n/es_ES.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/es_ES.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/fr_FR.csv b/app/code/Magento/Developer/i18n/fr_FR.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/fr_FR.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/nl_NL.csv b/app/code/Magento/Developer/i18n/nl_NL.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/nl_NL.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/pt_BR.csv b/app/code/Magento/Developer/i18n/pt_BR.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/pt_BR.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Developer/i18n/zh_Hans_CN.csv b/app/code/Magento/Developer/i18n/zh_Hans_CN.csv deleted file mode 100644 index e9c858101b71c..0000000000000 --- a/app/code/Magento/Developer/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,5 +0,0 @@ -"Front-end development workflow","Front-end development workflow" -"Workflow type","Workflow type" -"Server side less compilation","Server side less compilation" -"Client side less compilation","Client side less compilation" -"Not available in production mode","Not available in production mode" diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php index 875d384a20596..56c84593256be 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php @@ -72,13 +72,15 @@ public function __construct( */ public function getFields(array $productIds, $storeId) { + $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); + $priceData = $this->dataProvider->getSearchableAttribute('price') ? $this->resourceIndex->getPriceIndexData($productIds, $storeId) : []; $fields = []; foreach ($productIds as $productId) { - $fields[$productId] = $this->getProductPriceData($productId, $storeId, $priceData); + $fields[$productId] = $this->getProductPriceData($productId, $websiteId, $priceData); } return $fields; diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index c04bb7f5775a0..9e27ca5d608f0 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -12,6 +12,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; use Magento\Framework\GraphQl\Exception\ExceptionFormatter; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\QueryProcessor; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface; @@ -47,12 +48,12 @@ class GraphQl implements FrontControllerInterface private $queryProcessor; /** - * @var \Magento\Framework\GraphQl\Exception\ExceptionFormatter + * @var ExceptionFormatter */ private $graphQlError; /** - * @var \Magento\Framework\GraphQl\Query\Resolver\ContextInterface + * @var ContextInterface */ private $resolverContext; @@ -71,8 +72,8 @@ class GraphQl implements FrontControllerInterface * @param SchemaGeneratorInterface $schemaGenerator * @param SerializerInterface $jsonSerializer * @param QueryProcessor $queryProcessor - * @param \Magento\Framework\GraphQl\Exception\ExceptionFormatter $graphQlError - * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $resolverContext + * @param ExceptionFormatter $graphQlError + * @param ContextInterface $resolverContext * @param HttpRequestProcessor $requestProcessor * @param QueryFields $queryFields */ @@ -107,12 +108,14 @@ public function dispatch(RequestInterface $request) : ResponseInterface $statusCode = 200; try { /** @var Http $request */ + $this->requestProcessor->validateRequest($request); $this->requestProcessor->processHeaders($request); - $data = $this->jsonSerializer->unserialize($request->getContent()); - $query = isset($data['query']) ? $data['query'] : ''; - $variables = isset($data['variables']) ? $data['variables'] : null; - // We have to extract queried field names to avoid instantiation of non necessary fields in webonyx schema + $data = $this->getDataFromRequest($request); + $query = $data['query'] ?? ''; + $variables = $data['variables'] ?? null; + + // We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema // Temporal coupling is required for performance optimization $this->queryFields->setQuery($query, $variables); $schema = $this->schemaGenerator->generate(); @@ -121,7 +124,7 @@ public function dispatch(RequestInterface $request) : ResponseInterface $schema, $query, $this->resolverContext, - isset($data['variables']) ? $data['variables'] : [] + $data['variables'] ?? [] ); } catch (\Exception $error) { $result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : []; @@ -134,4 +137,26 @@ public function dispatch(RequestInterface $request) : ResponseInterface )->setHttpResponseCode($statusCode); return $this->response; } + + /** + * Get data from request body or query string + * + * @param RequestInterface $request + * @return array + */ + private function getDataFromRequest(RequestInterface $request) : array + { + /** @var Http $request */ + if ($request->isPost()) { + $data = $this->jsonSerializer->unserialize($request->getContent()); + } elseif ($request->isGet()) { + $data = $request->getParams(); + $data['variables'] = isset($data['variables']) ? + $this->jsonSerializer->unserialize($data['variables']) : null; + } else { + return []; + } + + return $data; + } } diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php deleted file mode 100644 index 2270f2616e67b..0000000000000 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Controller\HttpHeaderProcessor; - -use Magento\Framework\Exception\LocalizedException; -use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; - -/** - * Processes the "Content-Type" header entry - */ -class ContentTypeProcessor implements HttpHeaderProcessorInterface -{ - /** - * Handle the mandatory application/json header - * - * {@inheritDoc} - * @throws LocalizedException - */ - public function processHeaderValue(string $headerValue) : void - { - if (!$headerValue || strpos($headerValue, 'application/json') === false) { - throw new LocalizedException( - new \Magento\Framework\Phrase('Request content type must be application/json') - ); - } - } -} diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php index be359eafdf246..246ad15379f85 100644 --- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php +++ b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php @@ -7,7 +7,7 @@ namespace Magento\GraphQl\Controller\HttpHeaderProcessor; -use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\App\HttpRequestInterface; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\GraphQl\Controller\HttpHeaderProcessorInterface; use Magento\Store\Model\StoreManagerInterface; @@ -35,8 +35,9 @@ public function __construct(StoreManagerInterface $storeManager) /** * Handle the value of the store and set the scope * - * {@inheritDoc} - * @throws NoSuchEntityException + * @param string $headerValue + * @return void + * @throws GraphQlInputException */ public function processHeaderValue(string $headerValue) : void { diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php b/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php index 5e42b81a61981..bb29f1fa68af9 100644 --- a/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php +++ b/app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php @@ -19,12 +19,19 @@ class HttpRequestProcessor */ private $headerProcessors = []; + /** + * @var HttpRequestValidatorInterface[] array + */ + private $requestValidators = []; + /** * @param HttpHeaderProcessorInterface[] $graphQlHeaders + * @param HttpRequestValidatorInterface[] $requestValidators */ - public function __construct(array $graphQlHeaders = []) + public function __construct(array $graphQlHeaders = [], array $requestValidators = []) { $this->headerProcessors = $graphQlHeaders; + $this->requestValidators = $requestValidators; } /** @@ -39,4 +46,17 @@ public function processHeaders(Http $request) : void $headerClass->processHeaderValue((string)$request->getHeader($headerName)); } } + + /** + * Validate HTTP request + * + * @param Http $request + * @return void + */ + public function validateRequest(Http $request) : void + { + foreach ($this->requestValidators as $requestValidator) { + $requestValidator->validate($request); + } + } } diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php new file mode 100644 index 0000000000000..555048aac6771 --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Controller\HttpRequestValidator; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\GraphQl\Controller\HttpRequestValidatorInterface; + +/** + * Processes the "Content-Type" header entry + */ +class ContentTypeValidator implements HttpRequestValidatorInterface +{ + /** + * Handle the mandatory application/json header + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request) : void + { + $headerName = 'Content-Type'; + $requiredHeaderValue = 'application/json'; + + $headerValue = (string)$request->getHeader($headerName); + if ($request->isPost() + && strpos($headerValue, $requiredHeaderValue) === false + ) { + throw new GraphQlInputException( + new \Magento\Framework\Phrase('Request content type must be application/json') + ); + } + } +} diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php new file mode 100644 index 0000000000000..300b3d4f44dca --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Controller\HttpRequestValidator; + +use Magento\Framework\App\HttpRequestInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\App\Request\Http; +use Magento\GraphQl\Controller\HttpRequestValidatorInterface; + +/** + * Validator to check HTTP verb for Graphql requests + */ +class HttpVerbValidator implements HttpRequestValidatorInterface +{ + /** + * Check if request is using correct verb for query or mutation + * + * @param HttpRequestInterface $request + * @return void + * @throws GraphQlInputException + */ + public function validate(HttpRequestInterface $request) : void + { + /** @var Http $request */ + if (false === $request->isPost()) { + $query = $request->getParam('query', ''); + // The easiest way to determine mutations without additional parsing + if (strpos(trim($query), 'mutation') === 0) { + throw new GraphQlInputException( + new \Magento\Framework\Phrase('Mutation requests allowed only for POST requests') + ); + } + } + } +} diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php new file mode 100644 index 0000000000000..c0873b0caff89 --- /dev/null +++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Controller; + +use Magento\Framework\App\HttpRequestInterface; + +/** + * Use this interface to implement a validator for a Graphql HTTP requests + */ +interface HttpRequestValidatorInterface +{ + /** + * Perform validation of request + * + * @param HttpRequestInterface $request + * @return void + */ + public function validate(HttpRequestInterface $request) : void; +} diff --git a/app/code/Magento/GraphQl/etc/graphql/di.xml b/app/code/Magento/GraphQl/etc/graphql/di.xml index f4e6ca59364b2..b4f0113f58776 100644 --- a/app/code/Magento/GraphQl/etc/graphql/di.xml +++ b/app/code/Magento/GraphQl/etc/graphql/di.xml @@ -28,9 +28,12 @@ <type name="Magento\GraphQl\Controller\HttpRequestProcessor"> <arguments> <argument name="graphQlHeaders" xsi:type="array"> - <item name="Content-Type" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\ContentTypeProcessor</item> <item name="Store" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\StoreProcessor</item> </argument> + <argument name="requestValidators" xsi:type="array"> + <item name="ContentTypeValidator" xsi:type="object">Magento\GraphQl\Controller\HttpRequestValidator\ContentTypeValidator</item> + <item name="VerbValidator" xsi:type="object">Magento\GraphQl\Controller\HttpRequestValidator\HttpVerbValidator</item> + </argument> </arguments> </type> </config> diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php index d115aea7f2ff9..2a4d6904b11b5 100644 --- a/app/code/Magento/ImportExport/Model/Import.php +++ b/app/code/Magento/ImportExport/Model/Import.php @@ -126,6 +126,11 @@ class Import extends \Magento\ImportExport\Model\AbstractModel */ protected $_importExportData = null; + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $_coreConfig; + /** * @var \Magento\ImportExport\Model\Import\ConfigInterface */ diff --git a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php index 1fc3257ff2c1e..10b29a50a4064 100644 --- a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php +++ b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php @@ -408,7 +408,9 @@ protected function _saveValidatedBunches() if ($source->valid()) { try { $rowData = $source->current(); - $skuSet[$rowData['sku']] = true; + if (array_key_exists('sku', $rowData)) { + $skuSet[$rowData['sku']] = true; + } } catch (\InvalidArgumentException $e) { $this->addRowError($e->getMessage(), $this->_processedRowsCount); $this->_processedRowsCount++; @@ -436,7 +438,7 @@ protected function _saveValidatedBunches() $source->next(); } } - $this->_processedEntitiesCount = count($skuSet); + $this->_processedEntitiesCount = (count($skuSet)) ? : $this->_processedRowsCount; return $this; } diff --git a/app/code/Magento/Msrp/i18n/de_DE.csv b/app/code/Magento/Msrp/i18n/de_DE.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/de_DE.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/es_ES.csv b/app/code/Magento/Msrp/i18n/es_ES.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/es_ES.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/fr_FR.csv b/app/code/Magento/Msrp/i18n/fr_FR.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/fr_FR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/nl_NL.csv b/app/code/Magento/Msrp/i18n/nl_NL.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/nl_NL.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/pt_BR.csv b/app/code/Magento/Msrp/i18n/pt_BR.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/pt_BR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv b/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv deleted file mode 100644 index be8e733a4bfd1..0000000000000 --- a/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront." -"Warning!","Warning!" diff --git a/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml b/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml index da553b7823da9..4354cfb7c1c3e 100644 --- a/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml +++ b/app/code/Magento/Multishipping/view/frontend/templates/checkout/billing.phtml @@ -55,6 +55,10 @@ <div class="box-content"> <address> <?= /* @noEscape */ $block->getCheckoutData()->getAddressHtml($block->getAddress()); ?> + <input type="hidden" + id="multishipping_billing_country_id" + value="<?= /* @noEscape */ $block->getAddress()->getCountryId(); ?>" + name="multishipping_billing_country_id"/> </address> </div> </div> @@ -80,35 +84,44 @@ $block->setMethodFormTemplate($code, $methodsForms[$code]); } ?> - <dt class="item-title"> - <?php if ($methodsCount > 1) : ?> - <input type="radio" - id="p_method_<?= $block->escapeHtml($code); ?>" - value="<?= $block->escapeHtml($code); ?>" - name="payment[method]" - title="<?= $block->escapeHtml($_method->getTitle()) ?>" - <?php if ($checked) : ?> - checked="checked" + <div data-bind="scope: 'payment_method_<?= $block->escapeHtml($code);?>'"> + <dt class="item-title"> + <?php if ($methodsCount > 1) : ?> + <input type="radio" + id="p_method_<?= $block->escapeHtml($code); ?>" + value="<?= $block->escapeHtml($code); ?>" + name="payment[method]" + title="<?= $block->escapeHtml($_method->getTitle()) ?>" + data-bind=" + value: getCode(), + checked: isChecked, + click: selectPaymentMethod, + visible: isRadioButtonVisible()" + <?php if ($checked) : ?> + checked="checked" + <?php endif; ?> + class="radio"/> + <?php else : ?> + <input type="radio" + id="p_method_<?= $block->escapeHtml($code); ?>" + value="<?= $block->escapeHtml($code); ?>" + name="payment[method]" + data-bind=" + value: getCode(), + afterRender: selectPaymentMethod" + checked="checked" + class="radio solo method" /> <?php endif; ?> - class="radio"/> - <?php else : ?> - <input type="radio" - id="p_method_<?= $block->escapeHtml($code); ?>" - value="<?= $block->escapeHtml($code); ?>" - name="payment[method]" - checked="checked" - class="radio solo method" /> - <?php endif; ?> - <label for="p_method_<?= $block->escapeHtml($code); ?>"> - <?= $block->escapeHtml($_method->getTitle()) ?> - </label> - </dt> - <?php if ($html = $block->getChildHtml('payment.method.' . $code)) : ?> - <dd class="item-content <?= $checked ? '' : 'no-display'; ?>" - data-bind="scope: 'payment_method_<?= $block->escapeHtml($code);?>'"> - <?= /* @noEscape */ $html; ?> - </dd> - <?php endif; ?> + <label for="p_method_<?= $block->escapeHtml($code); ?>"> + <?= $block->escapeHtml($_method->getTitle()) ?> + </label> + </dt> + <?php if ($html = $block->getChildHtml('payment.method.' . $code)) : ?> + <dd class="item-content <?= $checked ? '' : 'no-display'; ?>"> + <?= /* @noEscape */ $html; ?> + </dd> + <?php endif; ?> + </div> <?php endforeach; ?> </dl> <?= $block->getChildHtml('payment_methods_after') ?> diff --git a/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml b/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml new file mode 100644 index 0000000000000..32810ecef20da --- /dev/null +++ b/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="checkout_billing"> + <arguments> + <argument name="form_templates" xsi:type="array"> + <item name="checkmo" xsi:type="string">Magento_OfflinePayments::multishipping/checkmo_form.phtml</item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml b/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml new file mode 100644 index 0000000000000..b96918243a7a7 --- /dev/null +++ b/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<script> + require([ + 'uiLayout', + 'jquery' + ], function (layout, $) { + $(function () { + var paymentMethodData = { + method: 'checkmo' + }; + layout([ + { + component: 'Magento_Checkout/js/view/payment/default', + name: 'payment_method_checkmo', + method: paymentMethodData.method, + item: paymentMethodData + } + ]); + + $('body').trigger('contentUpdated'); + }) + }) +</script> diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 8068447e5ca99..c73c4e39170e6 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -91,10 +91,11 @@ sub vcl_recv { } } - # Remove Google gclid parameters to minimize the cache objects - set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA" - set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar" - set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz" + # Remove all marketing get parameters to minimize the cache objects + if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); + set req.url = regsub(req.url, "[?|&]+$", ""); + } # Static files caching if (req.url ~ "^/(pub/)?(media|static)/") { diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 6c8414a5cb641..ea586858eff75 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -92,10 +92,11 @@ sub vcl_recv { } } - # Remove Google gclid parameters to minimize the cache objects - set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA" - set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar" - set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz" + # Remove all marketing get parameters to minimize the cache objects + if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") { + set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", ""); + set req.url = regsub(req.url, "[?|&]+$", ""); + } # Static files caching if (req.url ~ "^/(pub/)?(media|static)/") { diff --git a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml index afa71fe591495..ea06c60c30e20 100644 --- a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml +++ b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml @@ -40,7 +40,7 @@ $params = $block->getParams(); $(parent).trigger('clearTimeout'); fullScreenLoader.stopLoader(); globalMessageList.addErrorMessage({ - message: $t(<?= /* @escapeNotVerified */ json_encode($params['error_msg'])?>) + message: $t(<?= /* @noEscape */ json_encode($params['error_msg'])?>) }); } ); diff --git a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php index f4b4c39ca4021..e2701bab1f062 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php +++ b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php @@ -90,6 +90,10 @@ public function execute() return $this->getErrorResponse(); } + if (!$this->transparent->isActive($quote->getStoreId())) { + return $this->getErrorResponse(); + } + $this->sessionTransparent->setQuoteId($quote->getId()); try { $token = $this->secureTokenService->requestToken($quote); diff --git a/app/code/Magento/Paypal/README.md b/app/code/Magento/Paypal/README.md index 8f4453ae0a058..0ed4f2e90291b 100644 --- a/app/code/Magento/Paypal/README.md +++ b/app/code/Magento/Paypal/README.md @@ -1,6 +1,6 @@ Module Magento\PayPal implements integration with the PayPal payment system. Namely, it enables the following payment methods: -*PayPal Express Checkout -*PayPal Payments Standard -*PayPal Payments Pro -*PayPal Credit -*PayFlow Payment Gateway +* PayPal Express Checkout +* PayPal Payments Standard +* PayPal Payments Pro +* PayPal Credit +* PayFlow Payment Gateway diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php index 60451a9827097..6752eab6a7783 100644 --- a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php @@ -3,16 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Paypal\Test\Unit\Controller\Transparent; use Magento\Framework\App\Action\Context; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\Session\Generic; use Magento\Framework\Session\SessionManager; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Paypal\Controller\Transparent\RequestSecureToken; use Magento\Paypal\Model\Payflow\Service\Request\SecureToken; use Magento\Paypal\Model\Payflow\Transparent; +use PHPUnit\Framework\MockObject\MockObject; /** * Class RequestSecureTokenTest @@ -22,39 +24,39 @@ class RequestSecureTokenTest extends \PHPUnit\Framework\TestCase { /** - * @var Transparent|\PHPUnit_Framework_MockObject_MockObject + * @var Transparent|MockObject */ - protected $transparentMock; + private $transparent; /** - * @var RequestSecureToken|\PHPUnit_Framework_MockObject_MockObject + * @var RequestSecureToken|MockObject */ - protected $controller; + private $controller; /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|MockObject */ - protected $contextMock; + private $context; /** - * @var JsonFactory|\PHPUnit_Framework_MockObject_MockObject + * @var JsonFactory|MockObject */ - protected $resultJsonFactoryMock; + private $resultJsonFactory; /** - * @var Generic|\PHPUnit_Framework_MockObject_MockObject + * @var Generic|MockObject */ - protected $sessionTransparentMock; + private $sessionTransparent; /** - * @var SecureToken|\PHPUnit_Framework_MockObject_MockObject + * @var SecureToken|MockObject */ - protected $secureTokenServiceMock; + private $secureTokenService; /** - * @var SessionManager|\PHPUnit_Framework_MockObject_MockObject + * @var SessionManager|MockObject */ - protected $sessionManagerMock; + private $sessionManager; /** * Set up @@ -64,45 +66,46 @@ class RequestSecureTokenTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->contextMock = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) + $this->context = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) ->disableOriginalConstructor() ->getMock(); - $this->resultJsonFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\JsonFactory::class) + $this->resultJsonFactory = $this->getMockBuilder(\Magento\Framework\Controller\Result\JsonFactory::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->sessionTransparentMock = $this->getMockBuilder(\Magento\Framework\Session\Generic::class) + $this->sessionTransparent = $this->getMockBuilder(\Magento\Framework\Session\Generic::class) ->setMethods(['setQuoteId']) ->disableOriginalConstructor() ->getMock(); - $this->secureTokenServiceMock = $this->getMockBuilder( + $this->secureTokenService = $this->getMockBuilder( \Magento\Paypal\Model\Payflow\Service\Request\SecureToken::class ) ->setMethods(['requestToken']) ->disableOriginalConstructor() ->getMock(); - $this->sessionManagerMock = $this->getMockBuilder(\Magento\Framework\Session\SessionManager::class) + $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManager::class) ->setMethods(['getQuote']) ->disableOriginalConstructor() ->getMock(); - $this->transparentMock = $this->getMockBuilder(\Magento\Paypal\Model\Payflow\Transparent::class) - ->setMethods(['getCode']) + $this->transparent = $this->getMockBuilder(\Magento\Paypal\Model\Payflow\Transparent::class) + ->setMethods(['getCode', 'isActive']) ->disableOriginalConstructor() ->getMock(); $this->controller = new \Magento\Paypal\Controller\Transparent\RequestSecureToken( - $this->contextMock, - $this->resultJsonFactoryMock, - $this->sessionTransparentMock, - $this->secureTokenServiceMock, - $this->sessionManagerMock, - $this->transparentMock + $this->context, + $this->resultJsonFactory, + $this->sessionTransparent, + $this->secureTokenService, + $this->sessionManager, + $this->transparent ); } public function testExecuteSuccess() { $quoteId = 99; + $storeId = 2; $tokenFields = ['fields-1', 'fields-2', 'fields-3']; $secureToken = 'token_hash'; $resultExpectation = [ @@ -116,6 +119,8 @@ public function testExecuteSuccess() $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) ->disableOriginalConstructor() ->getMock(); + $quoteMock->method('getStoreId') + ->willReturn($storeId); $tokenMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) ->disableOriginalConstructor() ->getMock(); @@ -123,21 +128,23 @@ public function testExecuteSuccess() ->disableOriginalConstructor() ->getMock(); - $this->sessionManagerMock->expects($this->atLeastOnce()) + $this->sessionManager->expects($this->atLeastOnce()) ->method('getQuote') ->willReturn($quoteMock); + $this->transparent->method('isActive') + ->with($storeId) + ->willReturn(true); $quoteMock->expects($this->once()) ->method('getId') ->willReturn($quoteId); - $this->sessionTransparentMock->expects($this->once()) + $this->sessionTransparent->expects($this->once()) ->method('setQuoteId') ->with($quoteId); - $this->secureTokenServiceMock->expects($this->once()) + $this->secureTokenService->expects($this->once()) ->method('requestToken') ->with($quoteMock) ->willReturn($tokenMock); - $this->transparentMock->expects($this->once()) - ->method('getCode') + $this->transparent->method('getCode') ->willReturn('transparent'); $tokenMock->expects($this->atLeastOnce()) ->method('getData') @@ -147,7 +154,7 @@ public function testExecuteSuccess() ['securetoken', null, $secureToken] ] ); - $this->resultJsonFactoryMock->expects($this->once()) + $this->resultJsonFactory->expects($this->once()) ->method('create') ->willReturn($jsonMock); $jsonMock->expects($this->once()) @@ -161,6 +168,7 @@ public function testExecuteSuccess() public function testExecuteTokenRequestException() { $quoteId = 99; + $storeId = 2; $resultExpectation = [ 'success' => false, 'error' => true, @@ -170,24 +178,29 @@ public function testExecuteTokenRequestException() $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) ->disableOriginalConstructor() ->getMock(); + $quoteMock->method('getStoreId') + ->willReturn($storeId); $jsonMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class) ->disableOriginalConstructor() ->getMock(); - $this->sessionManagerMock->expects($this->atLeastOnce()) + $this->sessionManager->expects($this->atLeastOnce()) ->method('getQuote') ->willReturn($quoteMock); $quoteMock->expects($this->once()) ->method('getId') ->willReturn($quoteId); - $this->sessionTransparentMock->expects($this->once()) + $this->transparent->method('isActive') + ->with($storeId) + ->willReturn(true); + $this->sessionTransparent->expects($this->once()) ->method('setQuoteId') ->with($quoteId); - $this->secureTokenServiceMock->expects($this->once()) + $this->secureTokenService->expects($this->once()) ->method('requestToken') ->with($quoteMock) ->willThrowException(new \Exception()); - $this->resultJsonFactoryMock->expects($this->once()) + $this->resultJsonFactory->expects($this->once()) ->method('create') ->willReturn($jsonMock); $jsonMock->expects($this->once()) @@ -211,10 +224,10 @@ public function testExecuteEmptyQuoteError() ->disableOriginalConstructor() ->getMock(); - $this->sessionManagerMock->expects($this->atLeastOnce()) + $this->sessionManager->expects($this->atLeastOnce()) ->method('getQuote') ->willReturn($quoteMock); - $this->resultJsonFactoryMock->expects($this->once()) + $this->resultJsonFactory->expects($this->once()) ->method('create') ->willReturn($jsonMock); $jsonMock->expects($this->once()) diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml index 425e4cffb666c..694e517816b22 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml @@ -6,7 +6,7 @@ */ --> <include xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_include.xsd"> - <group id="paypal_payflowpro_with_express_checkout" translate="label comment" extends="payment_all_paypal/paypal_payflowpro"> + <group id="paypal_payflowpro_with_express_checkout" translate="label" extends="payment_all_paypal/paypal_payflowpro"> <label>Payflow Pro</label> <attribute type="paypal_ec_separate">0</attribute> <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> @@ -30,7 +30,7 @@ <field id="enable_paypal_payflow"/> </requires> </field> - <field id="enable_express_checkout_bml_payflow" translate="label" type="select" sortOrder="21" showInWebsite="1" showInDefault="1"> + <field id="enable_express_checkout_bml_payflow" translate="label comment" type="select" sortOrder="21" showInWebsite="1" showInDefault="1"> <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. diff --git a/app/code/Magento/Paypal/i18n/en_US.csv b/app/code/Magento/Paypal/i18n/en_US.csv index ad07d642de127..e7264a6de807f 100644 --- a/app/code/Magento/Paypal/i18n/en_US.csv +++ b/app/code/Magento/Paypal/i18n/en_US.csv @@ -697,6 +697,11 @@ User,User The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. " +"PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. + You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. + <a href=""https://www.paypal.com/webapps/mpp/promotional-financing"" target=""_blank"">Learn More</a>","PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. + You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. + <a href=""https://www.paypal.com/webapps/mpp/promotional-financing"" target=""_blank"">Learn More</a>" "Customize Smart Buttons","Customize Smart Buttons" "Checkout Page","Checkout Page" "Label","Label" @@ -731,4 +736,4 @@ User,User "PayPal will automatically display each enabled funding option to eligible buyers. For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is offered and the currency offered by the merchant is USD.","PayPal will automatically display each enabled funding option to eligible buyers. For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is offered and the currency offered by the merchant is USD." "PayPal Credit","PayPal Credit" "PayPal Guest Checkout Credit Card Icons","PayPal Guest Checkout Credit Card Icons" -"Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV" \ No newline at end of file +"Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV" diff --git a/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html b/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html index f1b14831bab31..d6fb2f3e6fc75 100644 --- a/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html +++ b/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html @@ -58,6 +58,7 @@ </div> <!-- /ko --> </form> + <div class="checkout-agreements-block"> <!-- ko foreach: $parent.getRegion('before-place-order') --> <!-- ko template: getTemplate() --><!-- /ko --> diff --git a/app/code/Magento/PaypalCaptcha/LICENSE.txt b/app/code/Magento/PaypalCaptcha/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/PaypalCaptcha/LICENSE_AFL.txt b/app/code/Magento/PaypalCaptcha/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/PaypalCaptcha/Model/Checkout/ConfigProviderPayPal.php b/app/code/Magento/PaypalCaptcha/Model/Checkout/ConfigProviderPayPal.php new file mode 100644 index 0000000000000..289a1631ed1f6 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/Model/Checkout/ConfigProviderPayPal.php @@ -0,0 +1,135 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\PaypalCaptcha\Model\Checkout; + +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\CaptchaInterface; +use Magento\Checkout\Model\ConfigProviderInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Configuration provider for Captcha rendering. + */ +class ConfigProviderPayPal implements ConfigProviderInterface +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var Data + */ + private $captchaData; + + /** + * @var string + */ + private static $formId = 'co-payment-form'; + + /** + * @param StoreManagerInterface $storeManager + * @param Data $captchaData + */ + public function __construct( + StoreManagerInterface $storeManager, + Data $captchaData + ) { + $this->storeManager = $storeManager; + $this->captchaData = $captchaData; + } + + /** + * @inheritdoc + */ + public function getConfig(): array + { + $config['captchaPayments'][self::$formId] = [ + 'isCaseSensitive' => $this->isCaseSensitive(self::$formId), + 'imageHeight' => $this->getImageHeight(self::$formId), + 'imageSrc' => $this->getImageSrc(self::$formId), + 'refreshUrl' => $this->getRefreshUrl(), + 'isRequired' => $this->isRequired(self::$formId), + 'timestamp' => time() + ]; + + return $config; + } + + /** + * Returns is captcha case sensitive + * + * @param string $formId + * @return bool + */ + private function isCaseSensitive(string $formId): bool + { + return (bool)$this->getCaptchaModel($formId)->isCaseSensitive(); + } + + /** + * Returns captcha image height + * + * @param string $formId + * @return int + */ + private function getImageHeight(string $formId): int + { + return (int)$this->getCaptchaModel($formId)->getHeight(); + } + + /** + * Returns captcha image source path + * + * @param string $formId + * @return string + */ + private function getImageSrc(string $formId): string + { + if ($this->isRequired($formId)) { + $captcha = $this->getCaptchaModel($formId); + $captcha->generate(); + return $captcha->getImgSrc(); + } + + return ''; + } + + /** + * Returns URL to controller action which returns new captcha image + * + * @return string + */ + private function getRefreshUrl(): string + { + $store = $this->storeManager->getStore(); + return $store->getUrl('captcha/refresh', ['_secure' => $store->isCurrentlySecure()]); + } + + /** + * Whether captcha is required to be inserted to this form + * + * @param string $formId + * @return bool + */ + private function isRequired(string $formId): bool + { + return (bool)$this->getCaptchaModel($formId)->isRequired(); + } + + /** + * Return captcha model for specified form + * + * @param string $formId + * @return CaptchaInterface + */ + private function getCaptchaModel(string $formId): CaptchaInterface + { + return $this->captchaData->getCaptcha($formId); + } +} diff --git a/app/code/Magento/PaypalCaptcha/Observer/CaptchaRequestToken.php b/app/code/Magento/PaypalCaptcha/Observer/CaptchaRequestToken.php new file mode 100644 index 0000000000000..e7cb282b1799b --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/Observer/CaptchaRequestToken.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\PaypalCaptcha\Observer; + +use Magento\Captcha\Helper\Data; +use Magento\Framework\App\Action\Action; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\App\ActionFlag ; + +/** + * Validates Captcha for Request Token controller + */ +class CaptchaRequestToken implements ObserverInterface +{ + /** + * @var Data + */ + private $helper; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var ActionFlag + */ + private $actionFlag; + + /** + * @param Data $helper + * @param Json $jsonSerializer + * @param ActionFlag $actionFlag + */ + public function __construct(Data $helper, Json $jsonSerializer, ActionFlag $actionFlag) + { + $this->helper = $helper; + $this->jsonSerializer = $jsonSerializer; + $this->actionFlag = $actionFlag; + } + + /** + * @inheritdoc + */ + public function execute(Observer $observer) + { + $formId = 'co-payment-form'; + $captcha = $this->helper->getCaptcha($formId); + + if (!$captcha->isRequired()) { + return; + } + + /** @var Action $controller */ + $controller = $observer->getControllerAction(); + $word = $controller->getRequest()->getPost('captcha_string'); + if ($captcha->isCorrect($word)) { + return; + } + + $data = $this->jsonSerializer->serialize([ + 'success' => false, + 'error' => true, + 'error_messages' => __('Incorrect CAPTCHA.') + ]); + $this->actionFlag->set('', Action::FLAG_NO_DISPATCH, true); + $controller->getResponse()->representJson($data); + } +} diff --git a/app/code/Magento/PaypalCaptcha/README.md b/app/code/Magento/PaypalCaptcha/README.md new file mode 100644 index 0000000000000..71588599a5ecd --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/README.md @@ -0,0 +1 @@ +The PayPal Captcha module provides a possibility to enable Captcha validation on Payflow Pro payment form. \ No newline at end of file diff --git a/app/code/Magento/PaypalCaptcha/composer.json b/app/code/Magento/PaypalCaptcha/composer.json new file mode 100644 index 0000000000000..e71ef8c0ec7de --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/composer.json @@ -0,0 +1,30 @@ +{ + "name": "magento/module-paypal-captcha", + "description": "Provides CAPTCHA validation for PayPal Payflow Pro", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-captcha": "*", + "magento/module-checkout": "*", + "magento/module-store": "*" + }, + "suggest": { + "magento/module-paypal": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\PaypalCaptcha\\": "" + } + } +} diff --git a/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml b/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..12afd8ceda60e --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="customer"> + <group id="captcha" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1" showInStore="0"> + <field id="forms" translate="label comment" type="multiselect" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <comment>CAPTCHA for "Create user", "Forgot password", "Payflow Pro" forms is always enabled if chosen.</comment> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/PaypalCaptcha/etc/config.xml b/app/code/Magento/PaypalCaptcha/etc/config.xml new file mode 100644 index 0000000000000..133a78a42f7b4 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/etc/config.xml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <customer> + <captcha> + <shown_to_logged_in_user> + <co-payment-form>1</co-payment-form> + </shown_to_logged_in_user> + <always_for> + <co-payment-form>1</co-payment-form> + </always_for> + </captcha> + </customer> + <captcha translate="label"> + <frontend> + <areas> + <co-payment-form> + <label>Payflow Pro</label> + </co-payment-form> + </areas> + </frontend> + </captcha> + </default> +</config> diff --git a/app/code/Magento/PaypalCaptcha/etc/frontend/di.xml b/app/code/Magento/PaypalCaptcha/etc/frontend/di.xml new file mode 100644 index 0000000000000..c236d5ea04ca0 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/etc/frontend/di.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Checkout\Model\CompositeConfigProvider"> + <arguments> + <argument name="configProviders" xsi:type="array"> + <item name="paypal_captcha_config_provider" xsi:type="object">Magento\PaypalCaptcha\Model\Checkout\ConfigProviderPayPal</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/PaypalCaptcha/etc/frontend/events.xml b/app/code/Magento/PaypalCaptcha/etc/frontend/events.xml new file mode 100644 index 0000000000000..ae706c4485d61 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/etc/frontend/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="controller_action_predispatch_paypal_transparent_requestsecuretoken"> + <observer name="captcha_request_token" instance="Magento\PaypalCaptcha\Observer\CaptchaRequestToken"/> + </event> +</config> diff --git a/app/code/Magento/PaypalCaptcha/etc/module.xml b/app/code/Magento/PaypalCaptcha/etc/module.xml new file mode 100644 index 0000000000000..425c829a5d391 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/etc/module.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_PaypalCaptcha" > + <sequence> + <module name="Magento_Captcha"/> + <module name="Magento_Paypal"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/PaypalCaptcha/registration.php b/app/code/Magento/PaypalCaptcha/registration.php new file mode 100644 index 0000000000000..4dac0582a6d1b --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use \Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_PaypalCaptcha', __DIR__); diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/PaypalCaptcha/view/frontend/layout/checkout_index_index.xml new file mode 100644 index 0000000000000..9837068faab73 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/view/frontend/layout/checkout_index_index.xml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceBlock name="checkout.root"> + <arguments> + <argument name="jsLayout" xsi:type="array"> + <item name="components" xsi:type="array"> + <item name="checkout" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="steps" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="billing-step" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="payment" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="payments-list" xsi:type="array"> + <item name="children" xsi:type="array"> + <item name="paypal-captcha" xsi:type="array"> + <item name="component" xsi:type="string">uiComponent</item> + <item name="displayArea" xsi:type="string">paypal-captcha</item> + <item name="dataScope" xsi:type="string">paypal-captcha</item> + <item name="provider" xsi:type="string">checkoutProvider</item> + <item name="config" xsi:type="array"> + <item name="template" xsi:type="string">Magento_Checkout/payment/before-place-order</item> + </item> + <item name="children" xsi:type="array"> + <item name="captcha" xsi:type="array"> + <item name="component" xsi:type="string">Magento_PaypalCaptcha/js/view/checkout/paymentCaptcha</item> + <item name="displayArea" xsi:type="string">paypal-captcha</item> + <item name="formId" xsi:type="string">co-payment-form</item> + <item name="configSource" xsi:type="string">checkoutConfig</item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </item> + </argument> + </arguments> + </referenceBlock> + </body> +</page> diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/requirejs-config.js b/app/code/Magento/PaypalCaptcha/view/frontend/requirejs-config.js new file mode 100644 index 0000000000000..78e7add4ec690 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/view/frontend/requirejs-config.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_Checkout/js/view/payment/list': { + 'Magento_PaypalCaptcha/js/view/payment/list-mixin': true + } + } + } +}; diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/checkout/paymentCaptcha.js b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/checkout/paymentCaptcha.js new file mode 100644 index 0000000000000..f8f119e3b3396 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/checkout/paymentCaptcha.js @@ -0,0 +1,44 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Captcha/js/view/checkout/defaultCaptcha', + 'Magento_Captcha/js/model/captchaList', + 'Magento_Captcha/js/model/captcha' +], +function ($, defaultCaptcha, captchaList, Captcha) { + 'use strict'; + + return defaultCaptcha.extend({ + + /** @inheritdoc */ + initialize: function () { + var captchaConfigPayment, + currentCaptcha; + + this._super(); + + if (window[this.configSource] && window[this.configSource].captchaPayments) { + captchaConfigPayment = window[this.configSource].captchaPayments; + + $.each(captchaConfigPayment, function (formId, captchaData) { + var captcha; + + captchaData.formId = formId; + captcha = Captcha(captchaData); + captchaList.add(captcha); + }); + } + + currentCaptcha = captchaList.getCaptchaByFormId(this.formId); + + if (currentCaptcha != null) { + currentCaptcha.setIsVisible(true); + this.setCurrentCaptcha(currentCaptcha); + } + } + }); +}); diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/payment/list-mixin.js b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/payment/list-mixin.js new file mode 100644 index 0000000000000..60172f696e9ed --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/payment/list-mixin.js @@ -0,0 +1,54 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Captcha/js/model/captchaList' +], function ($, captchaList) { + 'use strict'; + + var mixin = { + + formId: 'co-payment-form', + + /** + * Sets custom template for Payflow Pro + * + * @param {Object} payment + * @returns {Object} + */ + createComponent: function (payment) { + + var component = this._super(payment); + + if (component.component === 'Magento_Paypal/js/view/payment/method-renderer/payflowpro-method') { + component.template = 'Magento_PaypalCaptcha/payment/payflowpro-form'; + $(window).off('clearTimeout') + .on('clearTimeout', this.clearTimeout.bind(this)); + } + + return component; + }, + + /** + * Overrides default window.clearTimeout() to catch errors from iframe and reload Captcha. + */ + clearTimeout: function () { + var captcha = captchaList.getCaptchaByFormId(this.formId); + + if (captcha !== null) { + captcha.refresh(); + } + clearTimeout(); + } + }; + + /** + * Overrides `Magento_Checkout/js/view/payment/list::createComponent` + */ + return function (target) { + return target.extend(mixin); + }; +}); diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/web/template/payment/payflowpro-form.html b/app/code/Magento/PaypalCaptcha/view/frontend/web/template/payment/payflowpro-form.html new file mode 100644 index 0000000000000..fec5cf96b0324 --- /dev/null +++ b/app/code/Magento/PaypalCaptcha/view/frontend/web/template/payment/payflowpro-form.html @@ -0,0 +1,90 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}"> + <div class="payment-method-title field choice"> + <input type="radio" + name="payment[method]" + class="radio" + data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()"/> + <label class="label" data-bind="attr: {'for': getCode()}"> + <span data-bind="text: getTitle()"></span> + </label> + </div> + + <div class="payment-method-content"> + <!-- ko foreach: getRegion('messages') --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!--/ko--> + <div class="payment-method-billing-address"> + <!-- ko foreach: $parent.getRegion(getBillingAddressFormName()) --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!--/ko--> + </div> + <iframe width="0" + height="0" + data-bind="src: getSource(), attr: {id: getCode() + '-transparent-iframe', 'data-container': getCode() + '-transparent-iframe'}" + allowtransparency="true" + frameborder="0" + name="iframeTransparent" + class="payment-method-iframe"> + </iframe> + <form class="form" id="co-transparent-form" action="#" method="post" data-bind="mageInit: { + 'transparent':{ + 'context': context(), + 'controller': getControllerName(), + 'gateway': getCode(), + 'orderSaveUrl':getPlaceOrderUrl(), + 'cgiUrl': getCgiUrl(), + 'dateDelim': getDateDelim(), + 'cardFieldsMap': getCardFieldsMap(), + 'nativeAction': getSaveOrderUrl() + }, 'validation':[]}"> + + <!-- ko template: 'Magento_Payment/payment/cc-form' --><!-- /ko --> + + <!-- ko if: (isVaultEnabled())--> + <div class="field-tooltip-content"> + <input type="checkbox" + name="vault[is_enabled]" + class="checkbox-inline" + data-bind="attr: {'id': getCode() + '_enable_vault'}, checked: vaultEnabler.isActivePaymentTokenEnabler"/> + <label class="label" data-bind="attr: {'for': getCode() + '_enable_vault'}"> + <span><!-- ko i18n: 'Save credit card information for future use.'--><!-- /ko --></span> + </label> + </div> + <!-- /ko --> + </form> + <fieldset class="fieldset payment items ccard"> + <!-- ko foreach: $parent.getRegion('paypal-captcha') --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!-- /ko --> + </fieldset> + + + <div class="checkout-agreements-block"> + <!-- ko foreach: $parent.getRegion('before-place-order') --> + <!-- ko template: getTemplate() --><!-- /ko --> + <!--/ko--> + </div> + <div class="actions-toolbar"> + <div class="primary"> + <button data-role="review-save" + type="submit" + data-bind=" + attr: {title: $t('Place Order')}, + enable: (getCode() == isChecked()), + click: placeOrder, + css: {disabled: !isPlaceOrderActionAllowed()} + " + class="action primary checkout" + disabled> + <span data-bind="i18n: 'Place Order'"></span> + </button> + </div> + </div> + </div> +</div> diff --git a/app/code/Magento/ProductVideo/i18n/de_DE.csv b/app/code/Magento/ProductVideo/i18n/de_DE.csv deleted file mode 100644 index ca24668bb8d16..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/de_DE.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/es_ES.csv b/app/code/Magento/ProductVideo/i18n/es_ES.csv deleted file mode 100644 index ca24668bb8d16..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/es_ES.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/fr_FR.csv b/app/code/Magento/ProductVideo/i18n/fr_FR.csv deleted file mode 100644 index ca24668bb8d16..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/fr_FR.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/nl_NL.csv b/app/code/Magento/ProductVideo/i18n/nl_NL.csv deleted file mode 100644 index 5ad8386573040..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/nl_NL.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/pt_BR.csv b/app/code/Magento/ProductVideo/i18n/pt_BR.csv deleted file mode 100644 index 5ad8386573040..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/pt_BR.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv deleted file mode 100644 index 5ad8386573040..0000000000000 --- a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,10 +0,0 @@ -"Add video","Add video" -"New Video","New Video" -"Product Video","Product Video" -"YouTube API key","YouTube API key" -"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved." -"Url","Url" -"Preview Image","Preview Image" -"Get Video Information","Get Video Information" -"Youtube or Vimeo supported","Youtube or Vimeo supported" -"Delete image in all store views","Delete image in all store views" \ No newline at end of file diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php index 145e9c01980a5..3506ffc8c8792 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -13,6 +13,7 @@ use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; use Magento\Quote\Model\Quote; +use Magento\Store\Model\StoreManagerInterface; /** * Get cart @@ -29,16 +30,24 @@ class GetCartForUser */ private $cartRepository; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId * @param CartRepositoryInterface $cartRepository + * @param StoreManagerInterface $storeManager */ public function __construct( MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId, - CartRepositoryInterface $cartRepository + CartRepositoryInterface $cartRepository, + StoreManagerInterface $storeManager ) { $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; $this->cartRepository = $cartRepository; + $this->storeManager = $storeManager; } /** @@ -75,6 +84,15 @@ public function execute(string $cartHash, ?int $customerId): Quote ); } + if ((int)$cart->getStoreId() !== (int)$this->storeManager->getStore()->getId()) { + throw new GraphQlNoSuchEntityException( + __( + 'Wrong store code specified for cart "%masked_cart_id"', + ['masked_cart_id' => $cartHash] + ) + ); + } + $cartCustomerId = (int)$cart->getCustomerId(); /* Guest cart, allow operations */ diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php index 76bdc74611131..13d6a1d3dce70 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php @@ -7,9 +7,12 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\Customer\Api\Data\AddressInterface as CustomerAddress; +use Magento\Customer\Helper\Address as AddressHelper; +use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Quote\Model\Quote\Address as QuoteAddress; use Magento\Quote\Model\Quote\AddressFactory as BaseQuoteAddressFactory; @@ -23,13 +26,29 @@ class QuoteAddressFactory */ private $quoteAddressFactory; + /** + * @var GetCustomerAddress + */ + private $getCustomerAddress; + + /** + * @var AddressHelper + */ + private $addressHelper; + /** * @param BaseQuoteAddressFactory $quoteAddressFactory + * @param GetCustomerAddress $getCustomerAddress + * @param AddressHelper $addressHelper */ public function __construct( - BaseQuoteAddressFactory $quoteAddressFactory + BaseQuoteAddressFactory $quoteAddressFactory, + GetCustomerAddress $getCustomerAddress, + AddressHelper $addressHelper ) { $this->quoteAddressFactory = $quoteAddressFactory; + $this->getCustomerAddress = $getCustomerAddress; + $this->addressHelper = $addressHelper; } /** @@ -37,25 +56,38 @@ public function __construct( * * @param array $addressInput * @return QuoteAddress + * @throws GraphQlInputException */ public function createBasedOnInputData(array $addressInput): QuoteAddress { $addressInput['country_id'] = $addressInput['country_code'] ?? ''; + $maxAllowedLineCount = $this->addressHelper->getStreetLines(); + if (is_array($addressInput['street']) && count($addressInput['street']) > $maxAllowedLineCount) { + throw new GraphQlInputException( + __('"Street Address" cannot contain more than %1 lines.', $maxAllowedLineCount) + ); + } + $quoteAddress = $this->quoteAddressFactory->create(); $quoteAddress->addData($addressInput); return $quoteAddress; } /** - * Create QuoteAddress based on CustomerAddress + * Create Quote Address based on Customer Address * - * @param CustomerAddress $customerAddress + * @param int $customerAddressId + * @param int $customerId * @return QuoteAddress * @throws GraphQlInputException + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException */ - public function createBasedOnCustomerAddress(CustomerAddress $customerAddress): QuoteAddress + public function createBasedOnCustomerAddress(int $customerAddressId, int $customerId): QuoteAddress { + $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, $customerId); + $quoteAddress = $this->quoteAddressFactory->create(); try { $quoteAddress->importCustomerAddressData($customerAddress); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php index 31524ea023222..c2bac13c07067 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php @@ -7,8 +7,9 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress; use Magento\CustomerGraphQl\Model\Customer\GetCustomer; +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; @@ -29,11 +30,6 @@ class SetBillingAddressOnCart */ private $getCustomer; - /** - * @var GetCustomerAddress - */ - private $getCustomerAddress; - /** * @var AssignBillingAddressToCart */ @@ -42,18 +38,15 @@ class SetBillingAddressOnCart /** * @param QuoteAddressFactory $quoteAddressFactory * @param GetCustomer $getCustomer - * @param GetCustomerAddress $getCustomerAddress * @param AssignBillingAddressToCart $assignBillingAddressToCart */ public function __construct( QuoteAddressFactory $quoteAddressFactory, GetCustomer $getCustomer, - GetCustomerAddress $getCustomerAddress, AssignBillingAddressToCart $assignBillingAddressToCart ) { $this->quoteAddressFactory = $quoteAddressFactory; $this->getCustomer = $getCustomer; - $this->getCustomerAddress = $getCustomerAddress; $this->assignBillingAddressToCart = $assignBillingAddressToCart; } @@ -65,6 +58,8 @@ public function __construct( * @param array $billingAddressInput * @return void * @throws GraphQlInputException + * @throws GraphQlAuthenticationException + * @throws GraphQlAuthorizationException * @throws GraphQlNoSuchEntityException */ public function execute(ContextInterface $context, CartInterface $cart, array $billingAddressInput): void @@ -97,8 +92,10 @@ public function execute(ContextInterface $context, CartInterface $cart, array $b $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } else { $customer = $this->getCustomer->execute($context); - $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$customer->getId()); - $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); + $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( + (int)$customerAddressId, + (int)$customer->getId() + ); } $this->assignBillingAddressToCart->execute($cart, $billingAddress, $useForShipping); diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php index bbca7721754cb..6b0e2a311bf44 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php @@ -7,7 +7,6 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress; use Magento\CustomerGraphQl\Model\Customer\GetCustomer; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; @@ -28,11 +27,6 @@ class SetShippingAddressesOnCart implements SetShippingAddressesOnCartInterface */ private $getCustomer; - /** - * @var GetCustomerAddress - */ - private $getCustomerAddress; - /** * @var AssignShippingAddressToCart */ @@ -41,18 +35,15 @@ class SetShippingAddressesOnCart implements SetShippingAddressesOnCartInterface /** * @param QuoteAddressFactory $quoteAddressFactory * @param GetCustomer $getCustomer - * @param GetCustomerAddress $getCustomerAddress * @param AssignShippingAddressToCart $assignShippingAddressToCart */ public function __construct( QuoteAddressFactory $quoteAddressFactory, GetCustomer $getCustomer, - GetCustomerAddress $getCustomerAddress, AssignShippingAddressToCart $assignShippingAddressToCart ) { $this->quoteAddressFactory = $quoteAddressFactory; $this->getCustomer = $getCustomer; - $this->getCustomerAddress = $getCustomerAddress; $this->assignShippingAddressToCart = $assignShippingAddressToCart; } @@ -86,8 +77,10 @@ public function execute(ContextInterface $context, CartInterface $cart, array $s $shippingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } else { $customer = $this->getCustomer->execute($context); - $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$customer->getId()); - $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress); + $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( + (int)$customerAddressId, + (int)$customer->getId() + ); } $this->assignShippingAddressToCart->execute($cart, $shippingAddress); diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php index ca69b763929be..8251089abcd60 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php @@ -11,12 +11,27 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Api\CouponManagementInterface; /** * @inheritdoc */ class AppliedCoupon implements ResolverInterface { + /** + * @var CouponManagementInterface + */ + private $couponManagement; + + /** + * @param CouponManagementInterface $couponManagement + */ + public function __construct( + CouponManagementInterface $couponManagement + ) { + $this->couponManagement = $couponManagement; + } + /** * @inheritdoc */ @@ -26,9 +41,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value throw new LocalizedException(__('"model" value should be specified')); } $cart = $value['model']; + $cartId = $cart->getId(); - $appliedCoupon = $cart->getCouponCode(); - + $appliedCoupon = $this->couponManagement->get($cartId); return $appliedCoupon ? ['code' => $appliedCoupon] : null; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php new file mode 100644 index 0000000000000..3bd46a664f2ab --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Api\CartManagementInterface; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; +use Magento\Sales\Api\OrderRepositoryInterface; + +/** + * @inheritdoc + */ +class PlaceOrder implements ResolverInterface +{ + /** + * @var CartManagementInterface + */ + private $cartManagement; + + /** + * @var GetCartForUser + */ + private $getCartForUser; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @param GetCartForUser $getCartForUser + * @param CartManagementInterface $cartManagement + * @param OrderRepositoryInterface $orderRepository + */ + public function __construct( + GetCartForUser $getCartForUser, + CartManagementInterface $cartManagement, + OrderRepositoryInterface $orderRepository + ) { + $this->getCartForUser = $getCartForUser; + $this->cartManagement = $cartManagement; + $this->orderRepository = $orderRepository; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['input']['cart_id']; + + $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId()); + + try { + $orderId = $this->cartManagement->placeOrder($cart->getId()); + $order = $this->orderRepository->get($orderId); + + return [ + 'order' => [ + 'order_id' => $order->getIncrementId(), + ], + ]; + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Unable to place order: %message', ['message' => $e->getMessage()]), $e); + } + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php index 5a708ceaedc28..f81ea3020d3d0 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php @@ -62,7 +62,11 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->couponManagement->remove($cartId); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + $message = $e->getMessage(); + if (preg_match('/The "\d+" Cart doesn\'t contain products/', $message)) { + $message = 'Cart does not contain products'; + } + throw new GraphQlNoSuchEntityException(__($message), $e); } catch (CouldNotDeleteException $e) { throw new LocalizedException(__($e->getMessage()), $e); } diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index 1bf4d581a5fe3..22ca9cfdfae9a 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -10,7 +10,8 @@ "magento/module-catalog": "*", "magento/module-store": "*", "magento/module-customer": "*", - "magento/module-customer-graph-ql": "*" + "magento/module-customer-graph-ql": "*", + "magento/module-sales": "*" }, "suggest": { "magento/module-graph-ql": "*" diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 79cea3855f6f3..9ec3492f64531 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -17,6 +17,7 @@ type Mutation { setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetBillingAddressOnCart") setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingMethodsOnCart") setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart") + placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") } input AddSimpleProductsToCartInput { @@ -114,6 +115,10 @@ input ShippingMethodInput { method_code: String! } +input PlaceOrderInput { + cart_id: String! +} + input SetPaymentMethodOnCartInput { cart_id: String! payment_method: PaymentMethodInput! @@ -144,6 +149,10 @@ type ApplyCouponToCartOutput { cart: Cart! } +type PlaceOrderOutput { + order: Order! +} + type Cart { items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") applied_coupon: AppliedCoupon @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupon") @@ -285,3 +294,7 @@ type CartItemSelectedOptionValuePrice { units: String! type: PriceTypeEnum! } + +type Order { + order_id: String +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php index 4bd2227d4bb1e..9a271f741edda 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php @@ -5,12 +5,17 @@ */ namespace Magento\Sales\Block\Adminhtml\Order\Create\Search; +use Magento\Sales\Block\Adminhtml\Order\Create\Search\Grid\DataProvider\ProductCollection + as ProductCollectionDataProvider; +use Magento\Framework\App\ObjectManager; + /** * Adminhtml sales order create search products block * * @api * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended { @@ -42,6 +47,11 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended */ protected $_productFactory; + /** + * @var ProductCollectionDataProvider $productCollectionProvider + */ + private $productCollectionProvider; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Helper\Data $backendHelper @@ -50,6 +60,7 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended * @param \Magento\Backend\Model\Session\Quote $sessionQuote * @param \Magento\Sales\Model\Config $salesConfig * @param array $data + * @param ProductCollectionDataProvider|null $productCollectionProvider */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -58,12 +69,15 @@ public function __construct( \Magento\Catalog\Model\Config $catalogConfig, \Magento\Backend\Model\Session\Quote $sessionQuote, \Magento\Sales\Model\Config $salesConfig, - array $data = [] + array $data = [], + ProductCollectionDataProvider $productCollectionProvider = null ) { $this->_productFactory = $productFactory; $this->_catalogConfig = $catalogConfig; $this->_sessionQuote = $sessionQuote; $this->_salesConfig = $salesConfig; + $this->productCollectionProvider = $productCollectionProvider + ?: ObjectManager::getInstance()->get(ProductCollectionDataProvider::class); parent::__construct($context, $backendHelper, $data); } @@ -140,20 +154,18 @@ protected function _addColumnFilterToCollection($column) */ protected function _prepareCollection() { + $attributes = $this->_catalogConfig->getProductAttributes(); + $store = $this->getStore(); + /* @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */ - $collection = $this->_productFactory->create()->getCollection(); - $collection->setStore( - $this->getStore() - )->addAttributeToSelect( + $collection = $this->productCollectionProvider->getCollectionForStore($store); + $collection->addAttributeToSelect( $attributes - )->addAttributeToSelect( - 'sku' - )->addStoreFilter()->addAttributeToFilter( + ); + $collection->addAttributeToFilter( 'type_id', $this->_salesConfig->getAvailableProductTypes() - )->addAttributeToSelect( - 'gift_message_available' ); $this->setCollection($collection); diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php new file mode 100644 index 0000000000000..733791a2f9549 --- /dev/null +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Block\Adminhtml\Order\Create\Search\Grid\DataProvider; + +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Store\Model\Store; + +/** + * Prepares product collection for the grid + */ +class ProductCollection +{ + /** + * @var ProductCollectionFactory + */ + private $collectionFactory; + + /** + * @param ProductCollectionFactory $collectionFactory + */ + public function __construct( + ProductCollectionFactory $collectionFactory + ) { + $this->collectionFactory = $collectionFactory; + } + + /** + * Provide products collection filtered with store + * + * @param Store $store + * @return Collection + */ + public function getCollectionForStore(Store $store):Collection + { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + + $collection->setStore($store); + $collection->addAttributeToSelect( + 'gift_message_available' + ); + $collection->addAttributeToSelect( + 'sku' + ); + $collection->addStoreFilter(); + + return $collection; + } +} diff --git a/app/code/Magento/Sales/Block/Adminhtml/Totals.php b/app/code/Magento/Sales/Block/Adminhtml/Totals.php index 83b155293c2b9..8172a3c0db4ad 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Totals.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Totals.php @@ -5,6 +5,11 @@ */ namespace Magento\Sales\Block\Adminhtml; +use Magento\Sales\Model\Order; + +/** + * Adminhtml sales totals block + */ class Totals extends \Magento\Sales\Block\Order\Totals { /** @@ -67,12 +72,16 @@ protected function _initTotals() if (!$this->getSource()->getIsVirtual() && ((double)$this->getSource()->getShippingAmount() || $this->getSource()->getShippingDescription()) ) { + $shippingLabel = __('Shipping & Handling'); + if ($this->isFreeShipping($this->getOrder()) && $this->getSource()->getDiscountDescription()) { + $shippingLabel .= sprintf(' (%s)', $this->getSource()->getDiscountDescription()); + } $this->_totals['shipping'] = new \Magento\Framework\DataObject( [ 'code' => 'shipping', 'value' => $this->getSource()->getShippingAmount(), 'base_value' => $this->getSource()->getBaseShippingAmount(), - 'label' => __('Shipping & Handling'), + 'label' => $shippingLabel, ] ); } @@ -109,4 +118,23 @@ protected function _initTotals() return $this; } + + /** + * Availability of free shipping in at least one order item + * + * @param Order $order + * @return bool + */ + private function isFreeShipping(Order $order): bool + { + $isFreeShipping = false; + foreach ($order->getItems() as $orderItem) { + if ($orderItem->getFreeShipping() == '1') { + $isFreeShipping = true; + break; + } + } + + return $isFreeShipping; + } } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php index 11908864236f6..48934e24a3795 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf\Items\Creditmemo; /** @@ -66,11 +68,18 @@ public function draw() $lines = []; // draw Product name - $lines[0] = [['text' => $this->string->split($item->getName(), 35, true, true), 'feed' => 35]]; + $lines[0] = [ + [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true), + 'feed' => 35 + ] + ]; // draw SKU $lines[0][] = [ - 'text' => $this->string->split($this->getSku($item), 17), + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17), 'feed' => 255, 'align' => 'right', ]; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index 8562328025540..23c2c00daadc3 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf\Items\Invoice; /** @@ -66,11 +68,18 @@ public function draw() $lines = []; // draw Product name - $lines[0] = [['text' => $this->string->split($item->getName(), 35, true, true), 'feed' => 35]]; + $lines[0] = [ + [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true), + 'feed' => 35 + ] + ]; // draw SKU $lines[0][] = [ - 'text' => $this->string->split($this->getSku($item), 17), + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17), 'feed' => 290, 'align' => 'right', ]; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php index 6007e1dcf2b47..a88b508ba0789 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf\Items\Shipment; /** @@ -65,14 +67,21 @@ public function draw() $lines = []; // draw Product name - $lines[0] = [['text' => $this->string->split($item->getName(), 60, true, true), 'feed' => 100]]; + $lines[0] = [ + [ + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($item->getName()), 60, true, true), + 'feed' => 100 + ] + ]; // draw QTY $lines[0][] = ['text' => $item->getQty() * 1, 'feed' => 35]; // draw SKU $lines[0][] = [ - 'text' => $this->string->split($this->getSku($item), 25), + // phpcs:ignore Magento2.Functions.DiscouragedFunction + 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 25), 'feed' => 565, 'align' => 'right', ]; diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml index 0061411d576e2..b90bac7e0881b 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml @@ -19,18 +19,15 @@ <see selector="{{AdminInvoiceOrderInformationSection.customerName}}" userInput="{{customer.firstname}}" stepKey="seeCustomerName"/> <see selector="{{AdminInvoiceOrderInformationSection.customerEmail}}" userInput="{{customer.email}}" stepKey="seeCustomerEmail"/> <see selector="{{AdminInvoiceOrderInformationSection.customerGroup}}" userInput="{{customerGroup.code}}" stepKey="seeCustomerGroup"/> - <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.street[0]}}" stepKey="seeBillingAddressStreet"/> <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.city}}" stepKey="seeBillingAddressCity"/> <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.country_id}}" stepKey="seeBillingAddressCountry"/> <see selector="{{AdminInvoiceAddressInformationSection.billingAddress}}" userInput="{{billingAddress.postcode}}" stepKey="seeBillingAddressPostcode"/> - <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.street[0]}}" stepKey="seeShippingAddressStreet"/> <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.city}}" stepKey="seeShippingAddressCity"/> <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.country_id}}" stepKey="seeShippingAddressCountry"/> <see selector="{{AdminInvoiceAddressInformationSection.shippingAddress}}" userInput="{{shippingAddress.postcode}}" stepKey="seeShippingAddressPostcode"/> </actionGroup> - <!--Check that product is in invoice items--> <actionGroup name="seeProductInInvoiceItems"> <arguments> @@ -38,7 +35,6 @@ </arguments> <see selector="{{AdminInvoiceItemsSection.skuColumn}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> </actionGroup> - <!--Admin Fast Create Invoice--> <actionGroup name="adminFastCreateInvoice"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> @@ -50,19 +46,22 @@ <click selector="{{AdminOrderInvoicesTabSection.viewInvoice}}" stepKey="openInvoicePage"/> <waitForPageLoad stepKey="waitForInvoicePageLoad"/> </actionGroup> - + <actionGroup name="clearInvoicesGridFilters"> + <amOnPage url="{{AdminInvoicesPage.url}}" stepKey="goToInvoices"/> + <waitForPageLoad stepKey="waitInvoicesGridToLoad"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearFilters" /> + <waitForPageLoad stepKey="waitInvoicesGrid"/> + </actionGroup> <actionGroup name="goToInvoiceIntoOrder"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceAction"/> <seeInCurrentUrl url="{{AdminInvoiceNewPage.url}}" stepKey="seeOrderInvoiceUrl"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seePageNameNewInvoicePage"/> </actionGroup> - <actionGroup name="StartCreateInvoiceFromOrderPage"> <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceAction"/> <seeInCurrentUrl url="{{AdminInvoiceNewPage.url}}" stepKey="seeNewInvoiceUrl"/> <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoicePageTitle"/> </actionGroup> - <actionGroup name="SubmitInvoice"> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> <waitForElementVisible selector="{{AdminMessagesSection.successMessage}}" stepKey="waitForMessageAppears"/> @@ -70,4 +69,15 @@ <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderId"/> <seeInCurrentUrl url="{{AdminOrderDetailsPage.url('$grabOrderId')}}" stepKey="seeViewOrderPageInvoice"/> </actionGroup> + <!--Filter invoices by order id --> + <actionGroup name="filterInvoiceGridByOrderId"> + <arguments> + <argument name="orderId" type="string"/> + </arguments> + <amOnPage url="{{AdminInvoicesPage.url}}" stepKey="goToInvoices"/> + <click selector="{{AdminInvoicesGridSection.filter}}" stepKey="clickFilter"/> + <fillField selector="{{AdminInvoicesFiltersSection.orderNum}}" userInput="{{orderId}}" stepKey="fillOrderIdForFilter"/> + <click selector="{{AdminInvoicesFiltersSection.applyFilters}}" stepKey="clickApplyFilters"/> + <waitForPageLoad stepKey="waitForFiltersApply"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml index 81b025c9554e2..ff9fce57c4524 100644 --- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml +++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml @@ -12,6 +12,7 @@ <element name="searchTextBox" type="text" selector="#search"/> <element name="searchTextBoxButton" type="button" selector="button[class='action search']"/> <element name="productLink" type="select" selector="a[class='product-item-link']"/> + <element name="productSpecialPrice" type="text" selector="//a[contains(text(), '{{productName}}')]/ancestor::div//span[contains(@data-price-type, 'finalPrice')]/span[contains(@class, 'price')]" parameterized="true"/> <element name="asLowAsLabel" type="text" selector=".minimal-price-link > span"/> <element name="textArea" type="text" selector="li[class='item']"/> <element name="regularPrice" type="text" selector="//span[@class='price-wrapper ']/span[@class='price']"/> diff --git a/app/code/Magento/Search/i18n/de_DE.csv b/app/code/Magento/Search/i18n/de_DE.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/de_DE.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/es_ES.csv b/app/code/Magento/Search/i18n/es_ES.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/es_ES.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/fr_FR.csv b/app/code/Magento/Search/i18n/fr_FR.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/fr_FR.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/nl_NL.csv b/app/code/Magento/Search/i18n/nl_NL.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/nl_NL.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Search/i18n/pt_BR.csv b/app/code/Magento/Search/i18n/pt_BR.csv deleted file mode 100644 index c10566a7c9800..0000000000000 --- a/app/code/Magento/Search/i18n/pt_BR.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Mecanismo de Busca" diff --git a/app/code/Magento/Search/i18n/zh_Hans_CN.csv b/app/code/Magento/Search/i18n/zh_Hans_CN.csv deleted file mode 100644 index 8b4b04aa3b9ec..0000000000000 --- a/app/code/Magento/Search/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1 +0,0 @@ -"Search Engine","Search Engine" diff --git a/app/code/Magento/Swatches/i18n/de_DE.csv b/app/code/Magento/Swatches/i18n/de_DE.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/de_DE.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/es_ES.csv b/app/code/Magento/Swatches/i18n/es_ES.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/es_ES.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/fr_FR.csv b/app/code/Magento/Swatches/i18n/fr_FR.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/fr_FR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/nl_NL.csv b/app/code/Magento/Swatches/i18n/nl_NL.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/nl_NL.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/pt_BR.csv b/app/code/Magento/Swatches/i18n/pt_BR.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/pt_BR.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Swatches/i18n/zh_Hans_CN.csv b/app/code/Magento/Swatches/i18n/zh_Hans_CN.csv deleted file mode 100644 index f0aa34bbef26e..0000000000000 --- a/app/code/Magento/Swatches/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,2 +0,0 @@ -"Swatch","Swatch" -"Swatches per Product","Swatches per Product" diff --git a/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php new file mode 100644 index 0000000000000..a81f29280af96 --- /dev/null +++ b/app/code/Magento/Theme/Controller/Result/JsFooterPlugin.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Theme\Controller\Result; + +/** + * Plugin for putting messages to cookies + */ +class JsFooterPlugin +{ + /** + * Put all javascript to footer before sending the response + * + * @param \Magento\Framework\App\Response\Http $subject + * @return void + */ + public function beforeSendResponse(\Magento\Framework\App\Response\Http $subject) + { + $content = $subject->getContent(); + $script = []; + if (strpos($content, '</body') !== false) { + $pattern = '#<script[^>]*+(?<!text/x-magento-template.)>.*?</script>#is'; + $content = preg_replace_callback( + $pattern, + function ($matchPart) use (&$script) { + $script[] = $matchPart[0]; + return ''; + }, + $content + ); + $subject->setContent( + str_replace('</body', implode("\n", $script) . "\n</body", $content) + ); + } + } +} diff --git a/app/code/Magento/Theme/etc/frontend/di.xml b/app/code/Magento/Theme/etc/frontend/di.xml index 7db2783cd8dfa..3837c6f717b54 100644 --- a/app/code/Magento/Theme/etc/frontend/di.xml +++ b/app/code/Magento/Theme/etc/frontend/di.xml @@ -26,4 +26,7 @@ <type name="Magento\Framework\Controller\ResultInterface"> <plugin name="result-messages" type="Magento\Theme\Controller\Result\MessagePlugin"/> </type> + <type name="Magento\Framework\App\Response\Http"> + <plugin name="result-js-footer" type="Magento\Theme\Controller\Result\JsFooterPlugin"/> + </type> </config> diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index 9304d25cc8a98..d16b1eaad7f37 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -115,7 +115,7 @@ public function __toString() $this->compiler->compile($templateRootElement, $this->component, $this->component); $this->appendLayoutConfiguration(); $result = $this->compiler->postprocessing($this->template->__toString()); - } catch (\Exception $e) { + } catch (\Throwable $e) { $this->logger->critical($e->getMessage()); $result = $e->getMessage(); } diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml index 9148c22976c19..fbb543a6cab92 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridPaginationActionGroup.xml @@ -23,8 +23,23 @@ </arguments> <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown"/> <click selector="{{AdminDataGridPaginationSection.perPageOption('Custom')}}" stepKey="selectCustomPerPage"/> + <waitForElementVisible selector="{{AdminDataGridPaginationSection.perPageInput}}" time="30" stepKey="waitForInputVisible"/> <fillField selector="{{AdminDataGridPaginationSection.perPageInput}}" userInput="{{perPage}}" stepKey="fillCustomPerPage"/> <click selector="{{AdminDataGridPaginationSection.perPageApplyInput}}" stepKey="applyCustomPerPage"/> <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <seeInField selector="{{AdminDataGridPaginationSection.perPageDropDownValue}}" userInput="{{perPage}}" stepKey="seePerPageValueInDropDown"/> + </actionGroup> + + <actionGroup name="adminDataGridDeleteCustomPerPage"> + <arguments> + <argument name="perPage"/> + </arguments> + <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown1"/> + <click selector="{{AdminDataGridPaginationSection.perPageEditCustomValue(perPage)}}" stepKey="clickToEditCustomPerPage"/> + <waitForElementVisible selector="{{AdminDataGridPaginationSection.perPageDeleteCustomValue(perPage)}}" time="30" stepKey="waitForDeleteButtonVisible"/> + <click selector="{{AdminDataGridPaginationSection.perPageDeleteCustomValue(perPage)}}" stepKey="clickToDeleteCustomPerPage"/> + <waitForLoadingMaskToDisappear stepKey="waitForGridLoad"/> + <click selector="{{AdminDataGridPaginationSection.perPageDropdown}}" stepKey="clickPerPageDropdown"/> + <dontSeeElement selector="{{AdminDataGridPaginationSection.perPageDropDownItem(perPage)}}" stepKey="dontSeeDropDownItem"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml index 0f54f51549e7a..133836761174d 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridPaginationSection.xml @@ -11,11 +11,15 @@ <section name="AdminDataGridPaginationSection"> <element name="perPageDropdown" type="select" selector=".admin__data-grid-pager-wrap .selectmenu"/> <element name="perPageOption" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//button[text()='{{var1}}']" parameterized="true"/> - <element name="perPageInput" type="input" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//div[@class='selectmenu-item-edit']//input"/> - <element name="perPageApplyInput" type="button" selector="//div[@class='admin__data-grid-pager-wrap']//div[@class='selectmenu-items _active']//li//div[@class='selectmenu-item-edit']//button"/> + <element name="perPageInput" type="input" selector="//div[contains(@class, 'admin__data-grid-pager-wrap')]//div[contains(@class, 'selectmenu-items _active')]//li[contains(@class, '_edit')]//div[contains(@class, 'selectmenu-item-edit')]//input"/> + <element name="perPageApplyInput" type="button" selector="//div[contains(@class, 'admin__data-grid-pager-wrap')]//div[contains(@class, 'selectmenu-items _active')]//li[@class='_edit']//div[contains(@class, 'selectmenu-item-edit')]//button"/> + <element name="perPageDropDownItem" type="button" selector="//*[contains(@class, 'selectmenu-items _active')]//button[contains(@class, 'selectmenu-item-action') and text()='{{dropDownItem}}']" timeout="30" parameterized="true"/> + <element name="perPageEditCustomValue" type="button" selector="//div[contains(@class, 'selectmenu-items _active')]//div[contains(@class, 'selectmenu-item')]//button[text()='{{perPageCustomValue}}']/following-sibling::button[contains(@class, 'action-edit')]" parameterized="true"/> + <element name="perPageDeleteCustomValue" type="button" selector="//div[contains(@class, 'selectmenu-items _active')]//div[contains(@class, 'selectmenu-item')]//button[text()='{{perPageCustomValue}}']/parent::div/preceding-sibling::div/button[contains(@class, 'action-delete')]" parameterized="true"/> <element name="nextPage" type="button" selector="div.admin__data-grid-pager > button.action-next" timeout="30"/> <element name="previousPage" type="button" selector="div.admin__data-grid-pager > button.action-previous" timeout="30"/> <element name="currentPage" type="input" selector="div.admin__data-grid-pager > input[data-ui-id='current-page-input']"/> <element name="totalPages" type="text" selector="div.admin__data-grid-pager > label"/> + <element name="perPageDropDownValue" type="input" selector=".selectmenu-value input" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Ui/i18n/de_DE.csv b/app/code/Magento/Ui/i18n/de_DE.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/de_DE.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/es_ES.csv b/app/code/Magento/Ui/i18n/es_ES.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/es_ES.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/fr_FR.csv b/app/code/Magento/Ui/i18n/fr_FR.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/fr_FR.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/nl_NL.csv b/app/code/Magento/Ui/i18n/nl_NL.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/nl_NL.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/pt_BR.csv b/app/code/Magento/Ui/i18n/pt_BR.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/pt_BR.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/i18n/zh_Hans_CN.csv b/app/code/Magento/Ui/i18n/zh_Hans_CN.csv deleted file mode 100644 index 2efac126b857c..0000000000000 --- a/app/code/Magento/Ui/i18n/zh_Hans_CN.csv +++ /dev/null @@ -1,4 +0,0 @@ -"Log JS Errors to Session Storage","Log JS Errors to Session Storage" -"If enabled, can be used by functional tests for extended reporting","If enabled, can be used by functional tests for extended reporting" -"Log JS Errors to Session Storage Key","Log JS Errors to Session Storage Key" -"Use this key to retrieve collected js errors","Use this key to retrieve collected js errors" diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js index 0ae09f14fa946..b490ac557e71b 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/image-uploader.js @@ -11,8 +11,7 @@ define([ 'Magento_Ui/js/modal/alert', 'Magento_Ui/js/lib/validation/validator', 'Magento_Ui/js/form/element/file-uploader', - 'mage/adminhtml/browser', - 'mage/adminhtml/tools' + 'mage/adminhtml/browser' ], function ($, _, utils, uiAlert, validator, Element, browser) { 'use strict'; diff --git a/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js b/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js index abd79e797e413..8e6f1496495c7 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/paging/paging.js @@ -19,7 +19,9 @@ define([ defaults: { template: 'ui/grid/paging/paging', totalTmpl: 'ui/grid/paging-total', + totalRecords: 0, pageSize: 20, + pages: 1, current: 1, selectProvider: 'ns = ${ $.ns }, index = ids', @@ -35,7 +37,8 @@ define([ imports: { pageSize: '${ $.sizesConfig.name }:value', totalSelected: '${ $.selectProvider }:totalSelected', - totalRecords: '${ $.provider }:data.totalRecords' + totalRecords: '${ $.provider }:data.totalRecords', + filters: '${ $.provider }:params.filters' }, exports: { @@ -43,6 +46,11 @@ define([ current: '${ $.provider }:params.paging.current' }, + statefull: { + pageSize: true, + current: true + }, + listens: { 'pages': 'onPagesChange', 'pageSize': 'onPageSizeChange', @@ -173,7 +181,9 @@ define([ * @returns {Paging} Chainable. */ goFirst: function () { - this.current = 1; + if (!_.isUndefined(this.filters)) { + this.current = 1; + } return this; }, @@ -219,13 +229,11 @@ define([ /** * Calculates new page cursor based on the * previous and current page size values. - * - * @returns {Number} Updated cursor value. */ updateCursor: function () { var cursor = this.current - 1, size = this.pageSize, - oldSize = this.previousSize, + oldSize = _.isUndefined(this.previousSize) ? this.pageSize : this.previousSize, delta = cursor * (oldSize - size) / size; delta = size > oldSize ? diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js index 038907c21224d..adeb510ab3e40 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/storage/local.js @@ -114,8 +114,8 @@ define([ * * @param {String} path - Path to the property. * - * @example Retrieveing data. - * localStoarge => + * @example Retrieving data. + * localStorage => * 'appData' => ' * "one": {"two": "three"} * ' @@ -139,7 +139,7 @@ define([ * * @example Setting data. * storage.set('one.two', 'four'); - * => localStoarge => + * => localStorage => * 'appData' => ' * "one": {"two": "four"} * ' @@ -159,7 +159,7 @@ define([ * * @example Removing data. * storage.remove('one.two', 'four'); - * => localStoarge => + * => localStorage => * 'appData' => ' * "one": {} * ' diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js index 2e0c53373f807..2cfd961619249 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js @@ -519,7 +519,7 @@ define([ }, /** - * Custom 'render' attrobute handler function. Wraps child elements + * Custom 'render' attribute handler function. Wraps child elements * of a node with knockout's 'ko template:' comment tag. * * @param {HTMLElement} node - Element to be processed. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js b/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js index fe83f600132ed..bf7ae0cdc3e98 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/logger/logger-utils.js @@ -42,14 +42,14 @@ define([], function () { * Method that creates object of messages * @param {String} requested - log message that showing that request for class is started * @param {String} loaded - log message that show when requested class is loaded - * @param {String} failded - log message that show when requested class is failed + * @param {String} failed - log message that show when requested class is failed * @returns {Object} */ - LogUtils.prototype.createMessages = function (requested, loaded, failded) { + LogUtils.prototype.createMessages = function (requested, loaded, failed) { return { requested: requested || '', loaded: loaded || '', - failed: failded || '' + failed: failed || '' }; }; @@ -57,14 +57,14 @@ define([], function () { * Method that creates object of log levels * @param {String} requested - log message that showing that request for class is started * @param {String} loaded - log message that show when requested class is loaded - * @param {String} failded - log message that show when requested class is failed + * @param {String} failed - log message that show when requested class is failed * @returns {Object} */ - LogUtils.prototype.createLevels = function (requested, loaded, failded) { + LogUtils.prototype.createLevels = function (requested, loaded, failed) { return { requested: requested || 'info', loaded: loaded || 'info', - failed: failded || 'warn' + failed: failed || 'warn' }; }; diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js index 8ebbf88775b86..407984c7881a2 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js @@ -64,7 +64,7 @@ define([ } /** - * Validates provied value by a specified set of rules. + * Validates provided value by a specified set of rules. * * @param {(String|Object)} rules - One or many validation rules. * @param {*} value - Value to be checked. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js index 03012918f4a0d..f8e752fb77af2 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/dom-observer.js @@ -141,7 +141,7 @@ define([ } /** - * Calls handlers assocoiated with an added node. + * Calls handlers associated with an added node. * Adds listeners for the node removal. * * @param {HTMLElement} node - Added node. @@ -163,7 +163,7 @@ define([ } /** - * Calls handlers assocoiated with a removed node. + * Calls handlers associated with a removed node. * * @param {HTMLElement} node - Removed node. */ diff --git a/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html b/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html new file mode 100644 index 0000000000000..226ad2915bb61 --- /dev/null +++ b/app/code/Magento/Ui/view/frontend/web/templates/form/element/uploader/uploader.html @@ -0,0 +1,37 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<div class="field-control" css="'_with-tooltip': $data.tooltip"> + <div class="file-uploader" data-role="drop-zone" css="_loading: isLoading"> + <div class="file-uploader-area"> + <input type="file" afterRender="onElementRender" attr="id: uid, name: inputName, multiple: isMultipleFiles" + disable="disabled"/> + <label class="file-uploader-button action-default" attr="for: uid" translate="'Upload'"/> + + <span class="file-uploader-spinner"/> + <render args="fallbackResetTpl" if="$data.showFallbackReset && $data.isDifferedFromDefault"/> + </div> + + <render args="tooltipTpl" if="$data.tooltip"/> + + <div class="field-note" if="$data.notice" attr="id: noticeId"> + <span html="notice"/> + </div> + + <each args="data: value, as: '$file'" render="$parent.getPreviewTmpl($file)"/> + + <div if="isMultipleFiles" class="file-uploader-summary"> + <label attr="for: uid" + class="file-uploader-placeholder" + css="'placeholder-' + placeholderType"> + <span class="file-uploader-placeholder-text" + translate="'Click here or drag and drop to add files.'"/> + </label> + </div> + </div> + <render args="$data.service.template" if="$data.hasService()"/> +</div> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml b/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml deleted file mode 100644 index 0738b17d6e0f0..0000000000000 --- a/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> - <operation name="CreateUrlRewrite" dataType="urlRewrite" type="create" auth="adminFormKey" url="/admin/url_rewrite/save/" method="POST" successRegex="/messages-message-success/"> - <field key="store_id">integer</field> - <field key="redirect_type">integer</field> - <field key="request_path">string</field> - <field key="target_path">string</field> - <field key="description">string</field> - </operation> -</operations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml index 7453b7b5a43f3..7e1b9acbc47ab 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml @@ -10,7 +10,7 @@ <test name="AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest"> <annotations> <stories value="Update URL rewrite"/> - <title value="Update Category URL Rewrites, no redirect type"/> + <title value="Update Category URL Rewrites, Temporary redirect type"/> <description value="Login as Admin and update category UrlRewrite and add Temporary redirect type"/> <testCaseId value="MC-5356"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/User/Test/Mftf/Data/UserData.xml b/app/code/Magento/User/Test/Mftf/Data/UserData.xml index 80c1cc3022964..d602f094ce4e5 100644 --- a/app/code/Magento/User/Test/Mftf/Data/UserData.xml +++ b/app/code/Magento/User/Test/Mftf/Data/UserData.xml @@ -8,6 +8,14 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="DefaultAdminUser" type="user"> + <data key="username">{{_ENV.MAGENTO_ADMIN_USERNAME}}</data> + <data key="password">{{_ENV.MAGENTO_ADMIN_PASSWORD}}</data> + </entity> + <entity name="AdminUserWrongCredentials"> + <data key="username" unique="suffix">username_</data> + <data key="password" unique="suffix">password_</data> + </entity> <entity name="admin" type="user"> <data key="email">admin@magento.com</data> <data key="password">admin123</data> diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php index cc2af4996d562..32bae10c801c8 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Options.php @@ -91,7 +91,7 @@ public function getMainFieldset() if ($this->_getData('main_fieldset') instanceof \Magento\Framework\Data\Form\Element\Fieldset) { return $this->_getData('main_fieldset'); } - $mainFieldsetHtmlId = 'options_fieldset' . md5($this->getWidgetType()); + $mainFieldsetHtmlId = 'options_fieldset' . hash('sha256', $this->getWidgetType()); $this->setMainFieldsetHtmlId($mainFieldsetHtmlId); $fieldset = $this->getForm()->addFieldset( $mainFieldsetHtmlId, @@ -141,7 +141,6 @@ protected function _addField($parameter) { $form = $this->getForm(); $fieldset = $this->getMainFieldset(); - //$form->getElement('options_fieldset'); // prepare element data with values (either from request of from default values) $fieldName = $parameter->getKey(); @@ -166,9 +165,13 @@ protected function _addField($parameter) if (is_array($data['value'])) { foreach ($data['value'] as &$value) { - $value = html_entity_decode($value); + if (is_string($value)) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $value = html_entity_decode($value); + } } } else { + // phpcs:ignore Magento2.Functions.DiscouragedFunction $data['value'] = html_entity_decode($data['value']); } diff --git a/app/code/Magento/Widget/Model/Widget.php b/app/code/Magento/Widget/Model/Widget.php index 52dc8e7837a3c..d07e84186b2c9 100644 --- a/app/code/Magento/Widget/Model/Widget.php +++ b/app/code/Magento/Widget/Model/Widget.php @@ -248,17 +248,13 @@ public function getWidgets($filters = []) $result = $widgets; // filter widgets by params - if (is_array($filters) && count($filters) > 0) { + if (is_array($filters) && !empty($filters)) { foreach ($widgets as $code => $widget) { - try { - foreach ($filters as $field => $value) { - if (!isset($widget[$field]) || (string)$widget[$field] != $value) { - throw new \Exception(); - } + foreach ($filters as $field => $value) { + if (!isset($widget[$field]) || (string)$widget[$field] != $value) { + unset($result[$code]); + break; } - } catch (\Exception $e) { - unset($result[$code]); - continue; } } } @@ -323,8 +319,6 @@ public function getWidgetDeclaration($type, $params = [], $asIs = true) $directive .= $this->getWidgetPageVarName($params); - $directive .= sprintf(' type_name="%s"', $widget['name']); - $directive .= '}}'; if ($asIs) { diff --git a/app/code/Magento/Widget/Model/Widget/Config.php b/app/code/Magento/Widget/Model/Widget/Config.php index 4f81ef33f47f7..00b055b35a69d 100644 --- a/app/code/Magento/Widget/Model/Widget/Config.php +++ b/app/code/Magento/Widget/Model/Widget/Config.php @@ -120,6 +120,7 @@ public function getWidgetPlaceholderImageUrls() /** * Return url to error image + * * @return string */ public function getErrorImageUrl() @@ -129,6 +130,7 @@ public function getErrorImageUrl() /** * Return url to wysiwyg plugin + * * @return string */ public function getWysiwygJsPluginSrc() @@ -157,7 +159,7 @@ public function getWidgetWindowUrl($config) } } - if (count($skipped) > 0) { + if (!empty($skipped)) { $params['skip_widgets'] = $this->encodeWidgetsToQuery($skipped); } return $this->_backendUrl->getUrl('adminhtml/widget/index', $params); @@ -189,6 +191,8 @@ public function decodeWidgetsFromQuery($queryParam) } /** + * Get available widgets. + * * @param \Magento\Framework\DataObject $config Editor element config * @return array */ @@ -202,7 +206,7 @@ public function getAvailableWidgets($config) if (is_array($skipped) && in_array($widget['type'], $skipped)) { continue; } - $result[] = $widget['name']->getText(); + $result[$widget['type']] = $widget['name']->getText(); } } diff --git a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php index 5c546d7e2435c..850a3fbe83211 100644 --- a/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php +++ b/app/code/Magento/Widget/Test/Unit/Model/WidgetTest.php @@ -224,7 +224,6 @@ public function testGetWidgetDeclaration() $this->assertContains('title="my "widget""', $result); $this->assertContains('conditions_encoded="encoded-conditions-string"', $result); $this->assertContains('page_var_name="pasdf"', $result); - $this->assertContains('type_name=""}}', $result); } /** @@ -275,7 +274,6 @@ public function testGetWidgetDeclarationWithZeroValueParam() ); $this->assertContains('{{widget type="Magento\CatalogWidget\Block\Product\ProductsList"', $result); $this->assertContains('page_var_name="pasdf"', $result); - $this->assertContains('type_name=""}}', $result); $this->assertContains('products_count=""', $result); } } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less index 3ce46a73a11c4..4d04b6e0b9653 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payment-options.less @@ -137,6 +137,7 @@ } } + .captcha, .number { .input-text { width: 225px; diff --git a/app/design/frontend/Magento/blank/web/css/source/_forms.less b/app/design/frontend/Magento/blank/web/css/source/_forms.less index c9f3c3d72ef4c..26f5ff89e99e3 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_forms.less +++ b/app/design/frontend/Magento/blank/web/css/source/_forms.less @@ -101,6 +101,18 @@ .lib-form-validation-note(); } + .product-options-wrapper { + .date { + &.required { + div[for*='options'] { + &.mage-error { + display: none !important; + } + } + } + } + } + .field .tooltip { .lib-tooltip(right); .tooltip-content { diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index e5915969c91b9..77fb53a2ab02a 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -34,12 +34,12 @@ .product { &-items { - font-size: 0; + .lib-inline-block-space-container(); &:extend(.abs-reset-list all); } &-item { - font-size: 1.4rem; + .lib-inline-block-space-item(); vertical-align: top; .products-grid & { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less index 3ea1f5b7f6842..ac5ab0d87bf62 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less @@ -7,6 +7,8 @@ // Variables // _____________________________________________ +@import 'fields/_file-uploader.less'; + @checkout-wrapper__margin: @indent__base; @checkout-wrapper__columns: 16; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less index 3b584bc26fe34..23bb15e6fb4fe 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less @@ -143,6 +143,7 @@ } } + .captcha, .number { .input-text { width: 225px; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less new file mode 100644 index 0000000000000..7b06186ef9ad3 --- /dev/null +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/fields/_file-uploader.less @@ -0,0 +1,450 @@ +// /** +// * Copyright © Magento, Inc. All rights reserved. +// * See COPYING.txt for license details. +// */ + +// +// Components -> Single File Uploader +// _____________________________________________ + +// +// Variables +// --------------------------------------------- + +@icon-delete__content: '\e604'; +@icon-file__content: '\e626'; + + +@file-uploader-preview__border-color: @color-lighter-grayish; +@file-uploader-preview__background-color: @color-white; +@file-uploader-preview-focus__color: @color-blue2; + +@file-uploader-document-icon__color: @color-gray80; +@file-uploader-document-icon__size: 7rem; +@file-uploader-document-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-video-icon__color: @color-gray80; +@file-uploader-video-icon__size: 4rem; +@file-uploader-video-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-placeholder-icon__color: @color-gray80; +@file-uploader-placeholder-icon__z-index: @data-grid-file-uploader-image__z-index + 1; + +@file-uploader-delete-icon__color: @color-brownie; +@file-uploader-delete-icon__hover__color: @color-brownie-vanilla; +@file-uploader-delete-icon__font-size: 1.6rem; + +@file-uploader-muted-text__color: @color-gray62; + +@file-uploader-preview__width: 150px; +@file-uploader-preview__height: @file-uploader-preview__width; +@file-uploader-preview__opacity: .7; + +@file-uploader-spinner-dimensions: 15px; + +@file-uploader-dragover__background: @color-gray83; +@file-uploader-dragover-focus__color: @color-blue2; + +// Grid uploader + +@data-grid-file-uploader-image__size: 5rem; +@data-grid-file-uploader-image__z-index: 1; + +@data-grid-file-uploader-menu-button__width: 2rem; + +@data-grid-file-uploader-upload-icon__color: @color-darkie-gray; +@data-grid-file-uploader-upload-icon__hover__color: @color-very-dark-gray; +@data-grid-file-uploader-upload-icon__line-height: 48px; + +@data-grid-file-uploader-wrapper__size: @data-grid-file-uploader-image__size + 2rem; + +// +// Single file uploader +// --------------------------------------------- + +.file-uploader-area { + position: relative; + + input[type='file'] { + cursor: pointer; + opacity: 0; + overflow: hidden; + position: absolute; + visibility: hidden; + width: 0; + + &:focus { + + .file-uploader-button { + box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; + } + } + + &:disabled { + + .file-uploader-button { + cursor: default; + opacity: .5; + pointer-events: none; + } + } + } +} + +.file-uploader-summary { + display: inline-block; + vertical-align: top; +} + +.file-uploader-button { + background: @color-gray-darken0; + border: 1px solid @color-gray_light; + box-sizing: border-box; + color: @color-black_dark; + cursor: pointer; + display: inline-block; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.4rem; + font-weight: 600; + line-height: 1.6rem; + margin: 0; + padding: 7px 15px; + vertical-align: middle; + + &._is-dragover { + background: @file-uploader-dragover__background; + border: 1px solid @file-uploader-preview-focus__color; + } +} + +.file-uploader-spinner { + background-image: url('@{baseDir}images/loader-1.gif'); + background-position: 50%; + background-repeat: no-repeat; + background-size: @file-uploader-spinner-dimensions; + display: none; + height: 30px; + margin-left: @indent__s; + vertical-align: top; + width: @file-uploader-spinner-dimensions; +} + +.file-uploader-preview { + .action-remove { + &:extend(.abs-action-reset all); + .lib-icon-font ( + @icon-delete__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-delete-icon__font-size, + @_icon-font-color: @file-uploader-delete-icon__color, + @_icon-font-color-hover: @file-uploader-delete-icon__hover__color, + @_icon-font-text-hide: true, + @_icon-font-display: block + ); + bottom: 4px; + cursor: pointer; + display: block; + height: 27px; + left: 6px; + padding: 2px; + position: absolute; + text-decoration: none; + width: 25px; + z-index: 2; + } + + &:hover { + .preview-image img, + .preview-link:before { + opacity: @file-uploader-preview__opacity; + } + } + + .preview-link { + display: block; + height: 100%; + } + + .preview-image img { + bottom: 0; + left: 0; + margin: auto; + max-height: 100%; + max-width: 100%; + position: absolute; + right: 0; + top: 0; + z-index: 1; + } + + .preview-video { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-video-icon__size, + @_icon-font-color: @file-uploader-video-icon__color, + @_icon-font-color-hover: @file-uploader-video-icon__color + ); + + &:before { + left: 0; + margin-top: -@file-uploader-video-icon__size / 2; + position: absolute; + right: 0; + top: 50%; + z-index: @file-uploader-video-icon__z-index; + } + } + + .preview-document { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: @file-uploader-document-icon__size, + @_icon-font-color: @file-uploader-document-icon__color, + @_icon-font-color-hover: @file-uploader-document-icon__color + ); + + &:before { + left: 0; + margin-top: -@file-uploader-document-icon__size / 2; + position: absolute; + right: 0; + top: 50%; + z-index: @file-uploader-document-icon__z-index; + } + } +} + +.file-uploader-preview, +.file-uploader-placeholder { + background: @file-uploader-preview__background-color; + border: 1px solid @file-uploader-preview__border-color; + box-sizing: border-box; + cursor: pointer; + display: block; + height: @file-uploader-preview__height; + line-height: 1; + margin: @indent__s @indent__m @indent__s 0; + overflow: hidden; + position: relative; + width: @file-uploader-preview__width; +} + +.file-uploader { + &._loading { + .file-uploader-spinner { + display: inline-block; + } + } + + .admin__field-note, + .admin__field-error { + margin-bottom: @indent__s; + } + + .file-uploader-filename { + .lib-text-overflow(); + max-width: @file-uploader-preview__width; + word-break: break-all; + + &:first-child { + margin-bottom: @indent__s; + } + } + + .file-uploader-meta { + color: @file-uploader-muted-text__color; + } + + .admin__field-fallback-reset { + margin-left: @indent__s; + } + + ._keyfocus & .action-remove { + &:focus { + box-shadow: 0 0 0 1px @file-uploader-preview-focus__color; + } + } +} + +// Placeholder for multiple uploader +.file-uploader-placeholder { + &.placeholder-document { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 5rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 20px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } + + &.placeholder-image { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 5rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 20px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } + + &.placeholder-video { + .lib-icon-font( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 3rem, + @_icon-font-color: @file-uploader-placeholder-icon__color, + @_icon-font-color-hover: @file-uploader-placeholder-icon__color + ); + + &:before { + left: 0; + position: absolute; + right: 0; + top: 30px; + z-index: @file-uploader-placeholder-icon__z-index; + } + } +} + +.file-uploader-placeholder-text { + bottom: 0; + color: @color-blue-dodger; + font-size: 1.1rem; + left: 0; + line-height: @line-height__base; + margin-bottom: 15%; + padding: 0 @indent__base; + position: absolute; + right: 0; + text-align: center; +} + +// +// Grid image uploader +// --------------------------------------------- + +.data-grid-file-uploader { + min-width: @data-grid-file-uploader-wrapper__size; + + &._loading { + .file-uploader-spinner { + display: block; + } + + .file-uploader-button { + &:before { + display: none; + } + } + } + + .file-uploader-image { + background: transparent; + bottom: 0; + left: 0; + margin: auto; + max-height: 100%; + max-width: 100%; + position: absolute; + right: 0; + top: 0; + z-index: @data-grid-file-uploader-image__z-index; + + + .file-uploader-area { + .file-uploader-button { + &:before { + display: none; + } + } + } + } + + .file-uploader-area { + z-index: @data-grid-file-uploader-image__z-index + 1; + } + + .file-uploader-spinner { + height: 100%; + margin: 0; + position: absolute; + top: 0; + width: 100%; + } + + .file-uploader-button { + display: block; + height: @data-grid-file-uploader-upload-icon__line-height; + text-align: center; + + .lib-icon-font ( + @icon-file__content, + @_icon-font: @icons__font-name, + @_icon-font-size: 1.3rem, + @_icon-font-line-height: @data-grid-file-uploader-upload-icon__line-height, + @_icon-font-color: @data-grid-file-uploader-upload-icon__color, + @_icon-font-color-hover: @data-grid-file-uploader-upload-icon__hover__color, + @_icon-font-text-hide: true, + @_icon-font-display: block + ); + } + + .action-select-wrap { + float: left; + + .action-select { + border: 1px solid @file-uploader-preview__border-color; + display: block; + height: @data-grid-file-uploader-image__size; + margin-left: -1px; + padding: 0; + width: @data-grid-file-uploader-menu-button__width; + + &:after { + border-color: @data-grid-file-uploader-upload-icon__color transparent transparent transparent; + left: 50%; + margin: 0 0 0 -5px; + } + + &:hover { + &:after { + border-color: @data-grid-file-uploader-upload-icon__hover__color transparent transparent transparent; + } + } + + > span { + display: none; + } + } + + .action-menu { + left: 4rem; + right: auto; + z-index: @data-grid-file-uploader-image__z-index + 1; + } + } +} + +.data-grid-file-uploader-inner { + border: 1px solid @file-uploader-preview__border-color; + float: left; + height: @data-grid-file-uploader-image__size; + position: relative; + width: @data-grid-file-uploader-image__size; +} diff --git a/app/design/frontend/Magento/luma/web/css/source/_forms.less b/app/design/frontend/Magento/luma/web/css/source/_forms.less index 98dd57dead74c..8533318a12d1b 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_forms.less +++ b/app/design/frontend/Magento/luma/web/css/source/_forms.less @@ -104,6 +104,10 @@ .select-styling(); } + select.admin__control-multiselect { + height: auto; + } + .field-error, div.mage-error[generated] { margin-top: 7px; @@ -113,6 +117,18 @@ .lib-form-validation-note(); } + .product-options-wrapper { + .date { + &.required { + div[for*='options'] { + &.mage-error { + display: none !important; + } + } + } + } + } + // TEMP .field .tooltip { diff --git a/composer.json b/composer.json index aa4976aa50a99..525f3a21d9577 100644 --- a/composer.json +++ b/composer.json @@ -193,6 +193,7 @@ "magento/module-page-cache": "*", "magento/module-payment": "*", "magento/module-paypal": "*", + "magento/module-paypal-captcha": "*", "magento/module-persistent": "*", "magento/module-product-alert": "*", "magento/module-product-video": "*", diff --git a/composer.lock b/composer.lock index e2d00ad07c79a..06ab71ee75970 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "825a03a040f3eff4ea6ea537b7530f15", + "content-hash": "c43d19692d25afef14dd42eb893eb4ca", "packages": [ { "name": "braintree/braintree_php", @@ -164,21 +164,21 @@ }, { "name": "colinmollenhour/php-redis-session-abstract", - "version": "v1.4.0", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/colinmollenhour/php-redis-session-abstract.git", - "reference": "4cb15d557f58f45ad257cbcce3c12511e6deb5bc" + "reference": "4949ca28b86037abb44984c77bab9d0a4e075643" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/4cb15d557f58f45ad257cbcce3c12511e6deb5bc", - "reference": "4cb15d557f58f45ad257cbcce3c12511e6deb5bc", + "url": "https://api.github.com/repos/colinmollenhour/php-redis-session-abstract/zipball/4949ca28b86037abb44984c77bab9d0a4e075643", + "reference": "4949ca28b86037abb44984c77bab9d0a4e075643", "shasum": "" }, "require": { "colinmollenhour/credis": "~1.6", - "php": "~5.5.0|~5.6.0|~7.0.0|~7.1.0|~7.2.0" + "php": "^5.5 || ^7.0" }, "type": "library", "autoload": { @@ -197,7 +197,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2018-03-29T15:54:15+00:00" + "time": "2019-03-18T14:43:14+00:00" }, { "name": "composer/ca-bundle", @@ -337,16 +337,16 @@ }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -395,28 +395,27 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.1", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", + "reference": "a1aa51cf3ab838b83b0867b14e56fc20fbd55b3d", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -456,7 +455,7 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2019-03-26T10:23:26+00:00" }, { "name": "composer/xdebug-handler", @@ -1106,21 +1105,21 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.8.1", + "version": "v1.9.1", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "57bb5ef079d3724148da3d5c99e30695ab17afda" + "reference": "87125d5b265f98c4d1b8d83a1f0726607c229421" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/57bb5ef079d3724148da3d5c99e30695ab17afda", - "reference": "57bb5ef079d3724148da3d5c99e30695ab17afda", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/87125d5b265f98c4d1b8d83a1f0726607c229421", + "reference": "87125d5b265f98c4d1b8d83a1f0726607c229421", "shasum": "" }, "require": { "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7" + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" }, "require-dev": { "phpunit/phpunit": "^3|^4|^5" @@ -1184,7 +1183,7 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2019-01-03T21:00:55+00:00" + "time": "2019-03-20T17:19:05+00:00" }, { "name": "pelago/emogrifier", @@ -1382,16 +1381,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.14", + "version": "2.0.15", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "8ebfcadbf30524aeb75b2c446bc2519d5b321478" + "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/8ebfcadbf30524aeb75b2c446bc2519d5b321478", - "reference": "8ebfcadbf30524aeb75b2c446bc2519d5b321478", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/11cf67cf78dc4acb18dc9149a57be4aee5036ce0", + "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0", "shasum": "" }, "require": { @@ -1470,7 +1469,7 @@ "x.509", "x509" ], - "time": "2019-01-27T19:37:29+00:00" + "time": "2019-03-10T16:53:45+00:00" }, { "name": "psr/container", @@ -2127,16 +2126,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -2148,7 +2147,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -2170,7 +2169,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -2181,20 +2180,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -2206,7 +2205,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -2240,7 +2239,7 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", @@ -4965,16 +4964,16 @@ }, { "name": "consolidation/annotated-command", - "version": "2.11.2", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572" + "reference": "512a2e54c98f3af377589de76c43b24652bcb789" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/004af26391cd7d1cd04b0ac736dc1324d1b4f572", - "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/512a2e54c98f3af377589de76c43b24652bcb789", + "reference": "512a2e54c98f3af377589de76c43b24652bcb789", "shasum": "" }, "require": { @@ -5057,7 +5056,7 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2019-02-02T02:29:53+00:00" + "time": "2019-03-08T16:55:03+00:00" }, { "name": "consolidation/config", @@ -5232,16 +5231,16 @@ }, { "name": "consolidation/output-formatters", - "version": "3.4.0", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19" + "reference": "0881112642ad9059071f13f397f571035b527cb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19", - "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/0881112642ad9059071f13f397f571035b527cb9", + "reference": "0881112642ad9059071f13f397f571035b527cb9", "shasum": "" }, "require": { @@ -5251,11 +5250,10 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", "phpunit/phpunit": "^5.7.27", - "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7", - "symfony/console": "3.2.3", "symfony/var-dumper": "^2.8|^3|^4", "victorjonsson/markdowndocs": "^1.3" }, @@ -5264,6 +5262,52 @@ }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, "branch-alias": { "dev-master": "3.x-dev" } @@ -5284,25 +5328,25 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-10-19T22:35:38+00:00" + "time": "2019-03-14T03:45:44+00:00" }, { "name": "consolidation/robo", - "version": "1.4.6", + "version": "1.4.9", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3" + "reference": "5c6b3840a45afda1cbffbb3bb1f94dd5f9f83345" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3", - "reference": "d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/5c6b3840a45afda1cbffbb3bb1f94dd5f9f83345", + "reference": "5c6b3840a45afda1cbffbb3bb1f94dd5f9f83345", "shasum": "" }, "require": { "consolidation/annotated-command": "^2.10.2", - "consolidation/config": "^1.0.10", + "consolidation/config": "^1.2", "consolidation/log": "~1", "consolidation/output-formatters": "^3.1.13", "consolidation/self-update": "^1", @@ -5392,7 +5436,7 @@ } ], "description": "Modern task runner", - "time": "2019-02-17T05:32:27+00:00" + "time": "2019-03-19T18:07:19+00:00" }, { "name": "consolidation/self-update", @@ -5505,16 +5549,16 @@ }, { "name": "doctrine/annotations", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", "shasum": "" }, "require": { @@ -5569,38 +5613,40 @@ "docblock", "parser" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2019-03-25T19:12:02+00:00" }, { "name": "doctrine/collections", - "version": "v1.5.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" + "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d2ae4ef05e25197343b6a39bae1d3c427a2f6956", + "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1.3" }, "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.2.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } }, "notification-url": "https://packagist.org/downloads/", @@ -5629,38 +5675,41 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", "keywords": [ "array", "collections", - "iterator" + "iterators", + "php" ], - "time": "2017-07-22T10:37:32+00:00" + "time": "2019-03-25T19:03:48+00:00" }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -5685,12 +5734,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "doctrine/lexer", @@ -6620,16 +6669,16 @@ }, { "name": "magento/magento-coding-standard", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/magento/magento-coding-standard.git", - "reference": "b79055fdd16e0657c95ee214ebb81466c5f08745" + "reference": "489029a285c637825294e272d31c3f4ac00a454e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/b79055fdd16e0657c95ee214ebb81466c5f08745", - "reference": "b79055fdd16e0657c95ee214ebb81466c5f08745", + "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/489029a285c637825294e272d31c3f4ac00a454e", + "reference": "489029a285c637825294e272d31c3f4ac00a454e", "shasum": "" }, "require": { @@ -6646,7 +6695,7 @@ "AFL-3.0" ], "description": "A set of Magento specific PHP CodeSniffer rules.", - "time": "2019-03-27T14:17:16+00:00" + "time": "2019-04-01T17:03:33+00:00" }, { "name": "magento/magento2-functional-testing-framework", @@ -9042,16 +9091,16 @@ }, { "name": "symfony/polyfill-php70", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224" + "reference": "bc4858fb611bda58719124ca079baff854149c89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/6b88000cdd431cd2e940caa2cb569201f3f84224", - "reference": "6b88000cdd431cd2e940caa2cb569201f3f84224", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", + "reference": "bc4858fb611bda58719124ca079baff854149c89", "shasum": "" }, "require": { @@ -9061,7 +9110,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -9097,20 +9146,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", "shasum": "" }, "require": { @@ -9119,7 +9168,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -9152,7 +9201,7 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/stopwatch", diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index add0510c6b40c..e18a8c8e97c79 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -51,7 +51,7 @@ public function __construct( * @return array|string|int|float|bool * @throws \Exception */ - public function postQuery(string $query, array $variables = [], string $operationName = '', array $headers = []) + public function post(string $query, array $variables = [], string $operationName = '', array $headers = []) { $url = $this->getEndpointUrl(); $headers = array_merge($headers, ['Accept: application/json', 'Content-Type: application/json']); @@ -63,19 +63,57 @@ public function postQuery(string $query, array $variables = [], string $operatio $postData = $this->json->jsonEncode($requestArray); $responseBody = $this->curlClient->post($url, $postData, $headers); - $responseBodyArray = $this->json->jsonDecode($responseBody); + return $this->processResponse($responseBody); + } + + /** + * Perform HTTP GET request for query + * + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return mixed + * @throws \Exception + */ + public function get(string $query, array $variables = [], string $operationName = '', array $headers = []) + { + $url = $this->getEndpointUrl(); + $requestArray = [ + 'query' => $query, + 'variables' => $variables ? $this->json->jsonEncode($variables) : null, + 'operationName' => $operationName ?? null + ]; + array_filter($requestArray); + + $responseBody = $this->curlClient->get($url, $requestArray, $headers); + return $this->processResponse($responseBody); + } + + /** + * Process response from GraphQl server + * + * @param string $response + * @return mixed + * @throws \Exception + */ + private function processResponse(string $response) + { + $responseArray = $this->json->jsonDecode($response); - if (!is_array($responseBodyArray)) { - throw new \Exception('Unknown GraphQL response body: ' . json_encode($responseBodyArray)); + if (!is_array($responseArray)) { + //phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception('Unknown GraphQL response body: ' . $response); } - $this->processErrors($responseBodyArray); + $this->processErrors($responseArray); - if (!isset($responseBodyArray['data'])) { - throw new \Exception('Unknown GraphQL response body: ' . json_encode($responseBodyArray)); - } else { - return $responseBodyArray['data']; + if (!isset($responseArray['data'])) { + //phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception('Unknown GraphQL response body: ' . $response); } + + return $responseArray['data']; } /** @@ -107,6 +145,7 @@ private function processErrors($responseBodyArray) $responseBodyArray ); } + //phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception('GraphQL responded with an unknown error: ' . json_encode($responseBodyArray)); } } diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php index 790581c476da1..8abd97b4b744d 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -6,6 +6,7 @@ namespace Magento\TestFramework\TestCase; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\App\Request\Http; /** * Test case for Web API functional tests for Graphql. @@ -27,7 +28,7 @@ abstract class GraphQlAbstract extends WebapiAbstract private $appCache; /** - * Perform GraphQL call to the system under test. + * Perform GraphQL query call via GET to the system under test. * * @see \Magento\TestFramework\TestCase\GraphQl\Client::call() * @param string $query @@ -43,7 +44,32 @@ public function graphQlQuery( string $operationName = '', array $headers = [] ) { - return $this->getGraphQlClient()->postQuery( + return $this->getGraphQlClient()->get( + $query, + $variables, + $operationName, + $this->composeHeaders($headers) + ); + } + + /** + * Perform GraphQL mutations call via POST to the system under test. + * + * @see \Magento\TestFramework\TestCase\GraphQl\Client::call() + * @param string $query + * @param array $variables + * @param string $operationName + * @param array $headers + * @return array|int|string|float|bool GraphQL call results + * @throws \Exception + */ + public function graphQlMutation( + string $query, + array $variables = [], + string $operationName = '', + array $headers = [] + ) { + return $this->getGraphQlClient()->post( $query, $variables, $operationName, diff --git a/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php b/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php index 6fd7551676660..4bf1335f20667 100644 --- a/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Analytics/Api/LinkProviderTest.php @@ -95,6 +95,6 @@ public function testGetAll() */ private function isTestBaseUrlSecure() { - return strpos('https://', TESTS_BASE_URL) !== false; + return strpos(TESTS_BASE_URL, 'https://') !== false; } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php index 17c2af8dc59d0..6616b40257f7d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php @@ -13,6 +13,9 @@ use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +/** + * Test for adding products to cart + */ class AddProductToCartTest extends GraphQlAbstract { /** @@ -54,7 +57,7 @@ public function testAddProductIfQuantityIsNotAvailable() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); + $this->graphQlMutation($query); self::fail('Should be "The requested qty is not available" error message.'); } @@ -74,7 +77,7 @@ public function testAddMoreProductsThatAllowed() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); + $this->graphQlMutation($query); self::fail('Should be "The most you may purchase is 5." error message.'); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php index 84c111bd25fd4..ab6a1d4b464dd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php @@ -14,6 +14,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Test change customer password + */ class ChangeCustomerPasswordTest extends GraphQlAbstract { /** @@ -50,7 +53,7 @@ public function testChangePassword() $query = $this->getChangePassQuery($oldCustomerPassword, $newCustomerPassword); $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); - $response = $this->graphQlQuery($query, [], '', $headerMap); + $response = $this->graphQlMutation($query, [], '', $headerMap); $this->assertEquals($customerEmail, $response['changeCustomerPassword']['email']); try { @@ -69,7 +72,7 @@ public function testChangePassword() public function testChangePasswordIfUserIsNotAuthorizedTest() { $query = $this->getChangePassQuery('currentpassword', 'newpassword'); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -87,7 +90,7 @@ public function testChangeWeakPassword() $this->expectException(\Exception::class); $this->expectExceptionMessageRegExp('/Minimum of different classes of characters in password is.*/'); - $this->graphQlQuery($query, [], '', $headerMap); + $this->graphQlMutation($query, [], '', $headerMap); } /** @@ -105,7 +108,7 @@ public function testChangePasswordIfPasswordIsInvalid() $query = $this->getChangePassQuery($incorrectPassword, $newCustomerPassword); $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); - $this->graphQlQuery($query, [], '', $headerMap); + $this->graphQlMutation($query, [], '', $headerMap); } private function getChangePassQuery($currentPassword, $newPassword) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php index 602d969924fbd..891c74ca3c1e2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; +/** + * Create customer address tests + */ class CreateCustomerAddressTest extends GraphQlAbstract { /** @@ -117,7 +120,7 @@ public function testCreateCustomerAddress() $userName = 'customer@example.com'; $password = 'password'; - $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $response = $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); $this->assertArrayHasKey('createCustomerAddress', $response); $this->assertArrayHasKey('customer_id', $response['createCustomerAddress']); $this->assertEquals($customerId, $response['createCustomerAddress']['customer_id']); @@ -158,7 +161,7 @@ public function testCreateCustomerAddressIfUserIsNotAuthorized() } } MUTATION; - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } /** @@ -195,7 +198,73 @@ public function testCreateCustomerAddressWithMissingAttribute() $userName = 'customer@example.com'; $password = 'password'; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer_without_addresses.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testCreateCustomerAddressWithRedundantStreetLine() + { + $newAddress = [ + 'region' => [ + 'region' => 'Arizona', + 'region_id' => 4, + 'region_code' => 'AZ' + ], + 'country_id' => 'US', + 'street' => ['Line 1 Street', 'Line 2', 'Line 3'], + 'company' => 'Company name', + 'telephone' => '123456789', + 'fax' => '123123123', + 'postcode' => '7777', + 'city' => 'City Name', + 'firstname' => 'Adam', + 'lastname' => 'Phillis', + 'middlename' => 'A', + 'prefix' => 'Mr.', + 'suffix' => 'Jr.', + 'vat_id' => '1', + 'default_shipping' => true, + 'default_billing' => false + ]; + + $mutation + = <<<MUTATION +mutation { + createCustomerAddress(input: { + region: { + region: "{$newAddress['region']['region']}" + region_id: {$newAddress['region']['region_id']} + region_code: "{$newAddress['region']['region_code']}" + } + country_id: {$newAddress['country_id']} + street: ["{$newAddress['street'][0]}","{$newAddress['street'][1]}","{$newAddress['street'][2]}"] + company: "{$newAddress['company']}" + telephone: "{$newAddress['telephone']}" + fax: "{$newAddress['fax']}" + postcode: "{$newAddress['postcode']}" + city: "{$newAddress['city']}" + firstname: "{$newAddress['firstname']}" + lastname: "{$newAddress['lastname']}" + middlename: "{$newAddress['middlename']}" + prefix: "{$newAddress['prefix']}" + suffix: "{$newAddress['suffix']}" + vat_id: "{$newAddress['vat_id']}" + default_shipping: true + default_billing: false + }) { + id + } +} +MUTATION; + + $userName = 'customer@example.com'; + $password = 'password'; + + self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index 7342800379d13..fc51f57a83a76 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -8,16 +8,19 @@ namespace Magento\GraphQl\Customer; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Customer\Model\CustomerRegistry; +use Magento\Framework\Registry; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Test for create customer functionallity + */ class CreateCustomerTest extends GraphQlAbstract { /** - * @var CustomerRegistry + * @var Registry */ - private $customerRegistry; + private $registry; /** * @var CustomerRepositoryInterface @@ -28,7 +31,7 @@ protected function setUp() { parent::setUp(); - $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); } @@ -40,7 +43,7 @@ public function testCreateCustomerAccountWithPassword() $newFirstname = 'Richard'; $newLastname = 'Rowe'; $currentPassword = 'test123#'; - $newEmail = 'customer_created' . rand(1, 2000000) . '@example.com'; + $newEmail = 'new_customer@example.com'; $query = <<<QUERY mutation { @@ -50,7 +53,7 @@ public function testCreateCustomerAccountWithPassword() lastname: "{$newLastname}" email: "{$newEmail}" password: "{$currentPassword}" - is_subscribed: true + is_subscribed: true } ) { customer { @@ -63,7 +66,7 @@ public function testCreateCustomerAccountWithPassword() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertEquals($newFirstname, $response['createCustomer']['customer']['firstname']); $this->assertEquals($newLastname, $response['createCustomer']['customer']['lastname']); @@ -78,7 +81,7 @@ public function testCreateCustomerAccountWithoutPassword() { $newFirstname = 'Richard'; $newLastname = 'Rowe'; - $newEmail = 'customer_created' . rand(1, 2000000) . '@example.com'; + $newEmail = 'new_customer@example.com'; $query = <<<QUERY mutation { @@ -87,7 +90,7 @@ public function testCreateCustomerAccountWithoutPassword() firstname: "{$newFirstname}" lastname: "{$newLastname}" email: "{$newEmail}" - is_subscribed: true + is_subscribed: true } ) { customer { @@ -100,7 +103,7 @@ public function testCreateCustomerAccountWithoutPassword() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertEquals($newFirstname, $response['createCustomer']['customer']['firstname']); $this->assertEquals($newLastname, $response['createCustomer']['customer']['lastname']); @@ -131,7 +134,7 @@ public function testCreateCustomerIfInputDataIsEmpty() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -151,7 +154,7 @@ public function testCreateCustomerIfEmailMissed() firstname: "{$newFirstname}" lastname: "{$newLastname}" password: "{$currentPassword}" - is_subscribed: true + is_subscribed: true } ) { customer { @@ -164,7 +167,7 @@ public function testCreateCustomerIfEmailMissed() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -186,7 +189,7 @@ public function testCreateCustomerIfEmailIsNotValid() lastname: "{$newLastname}" email: "{$newEmail}" password: "{$currentPassword}" - is_subscribed: true + is_subscribed: true } ) { customer { @@ -199,7 +202,7 @@ public function testCreateCustomerIfEmailIsNotValid() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -211,7 +214,7 @@ public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() $newFirstname = 'Richard'; $newLastname = 'Rowe'; $currentPassword = 'test123#'; - $newEmail = 'customer_created' . rand(1, 2000000) . '@example.com'; + $newEmail = 'new_customer@example.com'; $query = <<<QUERY mutation { @@ -222,7 +225,7 @@ public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() test123: "123test123" email: "{$newEmail}" password: "{$currentPassword}" - is_subscribed: true + is_subscribed: true } ) { customer { @@ -235,6 +238,23 @@ public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); + } + + public function tearDown() + { + $newEmail = 'new_customer@example.com'; + try { + $customer = $this->customerRepository->get($newEmail); + } catch (\Exception $exception) { + return; + } + + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + $this->customerRepository->delete($customer); + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + parent::tearDown(); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php index 1153b9662b41a..bdfd428a78c20 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; +/** + * Delete customer address tests + */ class DeleteCustomerAddressTest extends GraphQlAbstract { /** @@ -55,7 +58,7 @@ public function testDeleteCustomerAddress() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $response = $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); $this->assertArrayHasKey('deleteCustomerAddress', $response); $this->assertEquals(true, $response['deleteCustomerAddress']); } @@ -73,7 +76,7 @@ public function testDeleteCustomerAddressIfUserIsNotAuthorized() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } /** @@ -99,7 +102,7 @@ public function testDeleteDefaultShippingCustomerAddress() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** @@ -125,7 +128,7 @@ public function testDeleteDefaultBillingCustomerAddress() deleteCustomerAddress(id: {$addressId}) } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** @@ -144,7 +147,7 @@ public function testDeleteNonExistCustomerAddress() deleteCustomerAddress(id: 9999) } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php index ae28e23a28bf1..88eaeaa8f9dd5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/GenerateCustomerTokenTest.php @@ -38,7 +38,7 @@ public function testGenerateCustomerValidToken() } MUTATION; - $response = $this->graphQlQuery($mutation); + $response = $this->graphQlMutation($mutation); $this->assertArrayHasKey('generateCustomerToken', $response); $this->assertInternalType('array', $response['generateCustomerToken']); } @@ -66,6 +66,6 @@ public function testGenerateCustomerTokenWithInvalidCredentials() $this->expectException(\Exception::class); $this->expectExceptionMessage('GraphQL response contains errors: The account sign-in' . ' ' . 'was incorrect or your account is disabled temporarily. Please wait and try again later.'); - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php index 9bdbf3059eeaf..fc0c02bae5508 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php @@ -36,7 +36,7 @@ public function testRevokeCustomerTokenValidCredentials() $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; - $response = $this->graphQlQuery($query, [], '', $headerMap); + $response = $this->graphQlMutation($query, [], '', $headerMap); $this->assertTrue($response['revokeCustomerToken']['result']); } @@ -53,6 +53,6 @@ public function testRevokeCustomerTokenForGuestCustomer() } } QUERY; - $this->graphQlQuery($query, [], ''); + $this->graphQlMutation($query, [], ''); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php index 191ea1ae6b877..2b54c97cd1e97 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Tests for subscription status + */ class SubscriptionStatusTest extends GraphQlAbstract { /** @@ -88,7 +91,12 @@ public function testChangeSubscriptionStatusTest() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $response = $this->graphQlMutation( + $query, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); $this->assertTrue($response['updateCustomer']['customer']['is_subscribed']); } @@ -111,7 +119,7 @@ public function testChangeSubscriptionStatuIfUserIsNotAuthorizedTest() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php index 6a9708b4f86a2..e7a7eda2897b2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php @@ -14,6 +14,9 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; +/** + * Update customer address tests + */ class UpdateCustomerAddressTest extends GraphQlAbstract { /** @@ -128,7 +131,7 @@ public function testUpdateCustomerAddress() } MUTATION; - $response = $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $response = $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); $this->assertArrayHasKey('updateCustomerAddress', $response); $this->assertArrayHasKey('customer_id', $response['updateCustomerAddress']); $this->assertEquals($customerId, $response['updateCustomerAddress']['customer_id']); @@ -158,7 +161,7 @@ public function testUpdateCustomerAddressIfUserIsNotAuthorized() } } MUTATION; - $this->graphQlQuery($mutation); + $this->graphQlMutation($mutation); } /** @@ -187,7 +190,7 @@ public function testUpdateCustomerAddressWithMissingAttribute() } } MUTATION; - $this->graphQlQuery($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); + $this->graphQlMutation($mutation, [], '', $this->getCustomerAuthHeaders($userName, $password)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php index df45e1de771d9..08933f47191b9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php @@ -13,6 +13,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Tests for update customer + */ class UpdateCustomerTest extends GraphQlAbstract { /** @@ -87,7 +90,12 @@ public function testUpdateCustomer() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $response = $this->graphQlMutation( + $query, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); $this->assertEquals($newPrefix, $response['updateCustomer']['customer']['prefix']); $this->assertEquals($newFirstname, $response['updateCustomer']['customer']['firstname']); @@ -123,7 +131,7 @@ public function testUpdateCustomerIfInputDataIsEmpty() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -147,7 +155,7 @@ public function testUpdateCustomerIfUserIsNotAuthorized() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -176,7 +184,7 @@ public function testUpdateCustomerIfAccountIsLocked() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -203,7 +211,7 @@ public function testUpdateEmailIfPasswordIsMissed() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -232,7 +240,7 @@ public function testUpdateEmailIfPasswordIsInvalid() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** @@ -260,7 +268,7 @@ public function testUpdateEmailIfEmailAlreadyExists() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php index 352947714360a..e784061d5562f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Framework/QueryComplexityLimiterTest.php @@ -393,7 +393,8 @@ public function testQueryComplexityIsLimited() QUERY; self::expectExceptionMessageRegExp('/Max query complexity should be 300 but got 302/'); - $this->graphQlQuery($query); + //Use POST request because request uri is too large for some servers + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php index b3f16c8734203..f585469177bcd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php @@ -13,6 +13,9 @@ use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +/** + * Add configurable product to cart tests + */ class AddConfigurableProductToCartTest extends GraphQlAbstract { /** @@ -54,7 +57,7 @@ public function testAddConfigurableProductToCart() $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $cartItems = $response['addConfigurableProductsToCart']['cart']['items']; self::assertEquals($qty, $cartItems[0]['qty']); self::assertEquals($variantSku, $cartItems[0]['product']['sku']); @@ -74,7 +77,7 @@ public function testAddProductIfQuantityIsNotAvailable() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -90,7 +93,7 @@ public function testAddOutOfStockProduct() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php index d9ab8db62a195..65ed38ae90f5b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php @@ -13,6 +13,9 @@ use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +/** + * Add simple product to cart tests + */ class AddSimpleProductToCartTest extends GraphQlAbstract { /** @@ -52,7 +55,7 @@ public function testAddSimpleProductToCart() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']); self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][0]['qty']); @@ -72,7 +75,7 @@ public function testAddSimpleProductToCartWithNegativeQty() $maskedQuoteId = $this->getMaskedQuoteId(); $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -158,6 +161,6 @@ public function testAddProductWithWrongCartHash() } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php deleted file mode 100644 index 828784ca27885..0000000000000 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php +++ /dev/null @@ -1,225 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Quote; - -use Magento\Quote\Model\Quote; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\TestCase\GraphQlAbstract; - -/** - * Test for adding/removing shopping cart coupon codes - */ -class CouponTest extends GraphQlAbstract -{ - /** - * @var QuoteResource - */ - private $quoteResource; - - /** - * @var Quote - */ - private $quote; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; - - protected function setUp() - { - $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->create(QuoteResource::class); - $this->quote = $objectManager->create(Quote::class); - $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - */ - public function testApplyCouponToGuestCartWithItems() - { - $couponCode = '2?ds5!2d'; - - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); - $response = $this->graphQlQuery($query); - - self::assertArrayHasKey('applyCouponToCart', $response); - self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - */ - public function testApplyCouponTwice() - { - $couponCode = '2?ds5!2d'; - - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); - $response = $this->graphQlQuery($query); - - self::assertArrayHasKey("applyCouponToCart", $response); - self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); - - self::expectExceptionMessage('A coupon is already applied to the cart. Please remove it to apply another'); - $this->graphQlQuery($query); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - * @expectedException \Exception - * @expectedExceptionMessage Cart does not contain products. - */ - public function testApplyCouponToCartWithNoItems() - { - $couponCode = '2?ds5!2d'; - - $this->quoteResource->load($this->quote, 'test_order_1', 'reserved_order_id'); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); - - $this->graphQlQuery($query); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - * @magentoApiDataFixture Magento/Customer/_files/customer.php - */ - public function testGuestCustomerAttemptToChangeCustomerCart() - { - $couponCode = '2?ds5!2d'; - - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $this->quote->setCustomerId(1); - $this->quoteResource->save($this->quote); - $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); - - self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - */ - public function testRemoveCoupon() - { - $couponCode = '2?ds5!2d'; - - /* Apply coupon to the quote */ - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode); - $this->graphQlQuery($query); - - /* Remove coupon from quote */ - $query = $this->prepareRemoveCouponRequestQuery($maskedQuoteId); - $response = $this->graphQlQuery($query); - - self::assertArrayHasKey('removeCouponFromCart', $response); - self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php - * @magentoApiDataFixture Magento/Customer/_files/customer.php - */ - public function testRemoveCouponFromCustomerCartByGuest() - { - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); - $this->quoteResource->load( - $this->quote, - 'test_order_with_simple_product_without_address', - 'reserved_order_id' - ); - $this->quote->setCustomerId(1); - $this->quoteResource->save($this->quote); - $query = $this->prepareRemoveCouponRequestQuery($maskedQuoteId); - - self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); - $this->graphQlQuery($query); - } - - /** - * @param string $maskedQuoteId - * @param string $couponCode - * @return string - */ - private function prepareAddCouponRequestQuery(string $maskedQuoteId, string $couponCode): string - { - return <<<QUERY -mutation { - applyCouponToCart(input: {cart_id: "$maskedQuoteId", coupon_code: "$couponCode"}) { - cart { - applied_coupon { - code - } - } - } -} -QUERY; - } - - /** - * @param string $maskedQuoteId - * @return string - */ - private function prepareRemoveCouponRequestQuery(string $maskedQuoteId): string - { - return <<<QUERY -mutation { - removeCouponFromCart(input: {cart_id: "$maskedQuoteId"}) { - cart { - applied_coupon { - code - } - } - } -} - -QUERY; - } -} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php new file mode 100644 index 0000000000000..5a2221a184294 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/ApplyCouponToCartTest.php @@ -0,0 +1,283 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Framework\Exception\AuthenticationException; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test Apply Coupon to Cart functionality for customer + */ +class ApplyCouponToCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testApplyCouponToCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('applyCouponToCart', $response); + self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @expectedException \Exception + * @expectedExceptionMessage A coupon is already applied to the cart. Please remove it to apply another + */ + public function testApplyCouponTwice() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey("applyCouponToCart", $response); + self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @expectedException \Exception + * @expectedExceptionMessage Cart does not contain products. + */ + public function testApplyCouponToCartWithoutItems() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @expectedException \Exception + */ + public function testApplyCouponToGuestCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/Customer/_files/two_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @expectedException \Exception + */ + public function testApplyCouponToAnotherCustomerCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer_two@example.com')); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @expectedException \Exception + * @expectedExceptionMessage The coupon code isn't valid. Verify the code and try again. + */ + public function testApplyNonExistentCouponToCart() + { + $couponCode = 'non_existent_coupon_code'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @expectedException \Exception + */ + public function testApplyCouponToNonExistentCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessage('Could not find a cart with ID "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_coupon_expired.php + * @expectedException \Exception + * @expectedExceptionMessage The coupon code isn't valid. Verify the code and try again. + */ + public function testApplyExpiredCoupon() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/574'); + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * Products in cart don't fit to the coupon + * + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product.php + * @expectedException \Exception + * @expectedExceptionMessage The coupon code isn't valid. Verify the code and try again. + */ + public function testApplyCouponWhichIsNotApplicable() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @param string $input + * @param string $message + * @dataProvider dataProviderUpdateWithMissedRequiredParameters + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @expectedException \Exception + */ + public function testApplyCouponWithMissedRequiredParameters(string $input, string $message) + { + $query = <<<QUERY +mutation { + applyCouponToCart(input: {{$input}}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + + $this->expectExceptionMessage($message); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @return array + */ + public function dataProviderUpdateWithMissedRequiredParameters(): array + { + return [ + 'missed_cart_id' => [ + 'coupon_code: "test"', + 'Required parameter "cart_id" is missing' + ], + 'missed_coupon_code' => [ + 'cart_id: "test"', + 'Required parameter "coupon_code" is missing' + ], + ]; + } + + /** + * Retrieve customer authorization headers + * + * @param string $username + * @param string $password + * @return array + * @throws AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } + + /** + * @param string $maskedQuoteId + * @param string $couponCode + * @return string + */ + private function getQuery(string $maskedQuoteId, string $couponCode): string + { + return <<<QUERY +mutation { + applyCouponToCart(input: {cart_id: "$maskedQuoteId", coupon_code: "$couponCode"}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php index 0cb8a38b0cb5e..fc66e4647e576 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php @@ -8,6 +8,10 @@ namespace Magento\GraphQl\Quote\Customer; use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Quote\Api\GuestCartRepositoryInterface; @@ -27,11 +31,40 @@ class CreateEmptyCartTest extends GraphQlAbstract */ private $customerTokenService; + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var MaskedQuoteIdToQuoteIdInterface + */ + private $maskedQuoteIdToQuoteId; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var string + */ + private $maskedQuoteId; + protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->guestCartRepository = $objectManager->get(GuestCartRepositoryInterface::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->maskedQuoteIdToQuoteId = $objectManager->get(MaskedQuoteIdToQuoteIdInterface::class); + $this->quoteIdMaskFactory = $objectManager->get(QuoteIdMaskFactory::class); } /** @@ -39,23 +72,83 @@ protected function setUp() */ public function testCreateEmptyCart() { - $query = <<<QUERY + $query = $this->getQuery(); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMapWithCustomerToken()); + + self::assertArrayHasKey('createEmptyCart', $response); + self::assertNotEmpty($response['createEmptyCart']); + + $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); + $this->maskedQuoteId = $response['createEmptyCart']; + + self::assertNotNull($guestCart->getId()); + self::assertEquals(1, $guestCart->getCustomer()->getId()); + self::assertEquals('default', $guestCart->getStore()->getCode()); + } + + /** + * @magentoApiDataFixture Magento/Store/_files/second_store.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCreateEmptyCartWithNotDefaultStore() + { + $query = $this->getQuery(); + + $headerMap = $this->getHeaderMapWithCustomerToken(); + $headerMap['Store'] = 'fixture_second_store'; + $response = $this->graphQlMutation($query, [], '', $headerMap); + + self::assertArrayHasKey('createEmptyCart', $response); + self::assertNotEmpty($response['createEmptyCart']); + + /* guestCartRepository is used for registered customer to get the cart hash */ + $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); + $this->maskedQuoteId = $response['createEmptyCart']; + + self::assertNotNull($guestCart->getId()); + self::assertEquals(1, $guestCart->getCustomer()->getId()); + self::assertEquals('fixture_second_store', $guestCart->getStore()->getCode()); + } + + /** + * @return string + */ + private function getQuery(): string + { + return <<<QUERY mutation { createEmptyCart } QUERY; + } - $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMapWithCustomerToken( + string $username = 'customer@example.com', + string $password = 'password' + ): array { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } - $response = $this->graphQlQuery($query, [], '', $headerMap); - - self::assertArrayHasKey('createEmptyCart', $response); + public function tearDown() + { + if (null !== $this->maskedQuoteId) { + $quoteId = $this->maskedQuoteIdToQuoteId->execute($this->maskedQuoteId); - $maskedCartId = $response['createEmptyCart']; - $guestCart = $this->guestCartRepository->get($maskedCartId); + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $quoteId); + $this->quoteResource->delete($quote); - self::assertNotNull($guestCart->getId()); - self::assertEquals(1, $guestCart->getCustomer()->getId()); + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->setQuoteId($quoteId) + ->delete(); + } + parent::tearDown(); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php index ba640bc3402ba..673d496302662 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php @@ -39,7 +39,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -60,7 +60,7 @@ public function testGetAvailablePaymentMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -79,7 +79,7 @@ public function testGetAvailablePaymentMethodsFromGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -97,7 +97,7 @@ public function testGetAvailablePaymentMethodsFromAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php index 71cf0201951a3..2b647f61c4c63 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php @@ -41,7 +41,7 @@ protected function setUp() * Test case: get available shipping methods from current customer quote * * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -77,7 +77,7 @@ public function testGetAvailableShippingMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -98,7 +98,7 @@ public function testGetAvailableShippingMethodsFromGuestCart() * * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -118,7 +118,7 @@ public function testGetAvailableShippingMethodsFromAnotherCustomerCart() * Test case: get available shipping methods when all shipping methods are disabled * * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index eb62b8c92f310..19b72b9e3ca4c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -36,7 +36,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -55,7 +55,7 @@ public function testGetCart() self::assertNotEmpty($response['cart']['items'][0]['id']); self::assertEquals(2, $response['cart']['items'][0]['qty']); - self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']); + self::assertEquals('simple_product', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); self::assertEquals(2, $response['cart']['items'][1]['qty']); @@ -124,6 +124,58 @@ public function testGetInactiveCart() $this->graphQlQuery($query, [], '', $this->getHeaderMap()); } + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote_customer_not_default_store.php + */ + public function testGetCartWithNotDefaultStore() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1_not_default_store'); + $query = $this->getQuery($maskedQuoteId); + + $headerMap = $this->getHeaderMap(); + $headerMap['Store'] = 'fixture_second_store'; + + $response = $this->graphQlQuery($query, [], '', $headerMap); + + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('items', $response['cart']); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @magentoApiDataFixture Magento/Store/_files/second_store.php + * + * @expectedException \Exception + * @expectedExceptionMessage Wrong store code specified for cart + */ + public function testGetCartWithWrongStore() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $query = $this->getQuery($maskedQuoteId); + + $headerMap = $this->getHeaderMap(); + $headerMap['Store'] = 'fixture_second_store'; + + $this->graphQlQuery($query, [], '', $headerMap); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote_customer_not_default_store.php + * + * @expectedException \Exception + * @expectedExceptionMessage Store code not_existing_store does not exist + */ + public function testGetCartWithNotExistingStore() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1_not_default_store'); + $query = $this->getQuery($maskedQuoteId); + + $headerMap = $this->getHeaderMap(); + $headerMap['Store'] = 'not_existing_store'; + + $this->graphQlQuery($query, [], '', $headerMap); + } + /** * @param string $maskedQuoteId * @return string diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php index 16d085c1d09be..ba169d7a5bbc9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php @@ -39,7 +39,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -69,7 +69,7 @@ public function testGetSelectedShippingMethod() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -89,7 +89,7 @@ public function testGetSelectedShippingMethodFromGuestCart() /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -108,7 +108,7 @@ public function testGetSelectedShippingMethodFromAnotherCustomerCart() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php new file mode 100644 index 0000000000000..1ba94346073db --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedBillingAddressTest.php @@ -0,0 +1,219 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for get specified billing address + */ +class GetSpecifiedBillingAddressTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGeSpecifiedBillingAddress() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('billing_address', $response['cart']); + + $expectedBillingAddressData = [ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'company' => 'CompanyName', + 'street' => [ + 'Green str, 67' + ], + 'city' => 'CityM', + 'region' => [ + 'code' => 'AL', + 'label' => 'Alabama', + ], + 'postcode' => '75477', + 'country' => [ + 'code' => 'US', + 'label' => 'US', + ], + 'telephone' => '3468676', + 'address_type' => 'BILLING', + 'customer_notes' => null, + ]; + self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('billing_address', $response['cart']); + + $expectedBillingAddressData = [ + 'firstname' => null, + 'lastname' => null, + 'company' => null, + 'street' => [ + '' + ], + 'city' => null, + 'region' => [ + 'code' => null, + 'label' => null, + ], + 'postcode' => null, + 'country' => [ + 'code' => null, + 'label' => null, + ], + 'telephone' => null, + 'address_type' => 'BILLING', + 'customer_notes' => null, + ]; + self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testGeSpecifiedBillingAddressOfNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGeSpecifiedBillingAddressFromAnotherGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery($this->getQuery($maskedQuoteId), [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGeSpecifiedBillingAddressFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery( + $this->getQuery($maskedQuoteId), + [], + '', + $this->getHeaderMap('customer2@search.example.com') + ); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + billing_address { + firstname + lastname + company + street + city + region + { + code + label + } + postcode + country + { + code + label + } + telephone + address_type + customer_notes + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php new file mode 100644 index 0000000000000..b7d3b546ba194 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/PlaceOrderTest.php @@ -0,0 +1,296 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for placing an order for customer + */ +class PlaceOrderTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CollectionFactory + */ + private $orderCollectionFactory; + + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var Registry + */ + private $registry; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->orderCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrder() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('placeOrder', $response); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); + self::assertEquals($reservedOrderId, $response['placeOrder']['order']['order_id']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testPlaceOrderWithNoItemsInCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: A server error stopped your order from being placed. ' . + 'Please try to place your order again' + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testPlaceOrderWithNoShippingAddress() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: Some addresses can\'t be used due to the configurations for specific countries' + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testPlaceOrderWithNoShippingMethod() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage( + 'Unable to place order: The shipping method is missing. Select the shipping method and try again' + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderWithNoBillingAddress() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp( + '/Unable to place order: Please check the billing address information*/' + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testPlaceOrderWithNoPaymentMethod() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('Unable to place order: Enter a valid payment method and try again'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php + */ + public function testPlaceOrderWithOutOfStockProduct() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('Unable to place order: Some of the products are out of stock'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrderOfGuestCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php + */ + public function testPlaceOrderOfAnotherCustomerCart() + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessageRegExp('/The current user cannot perform operations on cart*/'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + placeOrder(input: {cart_id: "{$maskedQuoteId}"}) { + order { + order_id + } + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + * @throws \Magento\Framework\Exception\AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } + + /** + * @inheritdoc + */ + public function tearDown() + { + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', true); + + $orderCollection = $this->orderCollectionFactory->create(); + foreach ($orderCollection as $order) { + $this->orderRepository->delete($order); + } + $this->registry->unregister('isSecureArea'); + $this->registry->register('isSecureArea', false); + + parent::tearDown(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php new file mode 100644 index 0000000000000..ce1c85417b165 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveCouponFromCartTest.php @@ -0,0 +1,169 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Check removing of the coupon from customer cart + */ +class RemoveCouponFromCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testRemoveCouponFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @expectedException \Exception + * @expectedExceptionMessage Cart does not contain products + */ + public function testRemoveCouponFromEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveCouponFromCartIfCouponWasNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromGuestCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer3@search.example.com')); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + removeCouponFromCart(input: {cart_id: "$maskedQuoteId"}) { + cart { + applied_coupon { + code + } + } + } +} + +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php index e80a2127ad420..39803f8d58447 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php @@ -7,11 +7,9 @@ namespace Magento\GraphQl\Quote\Customer; -use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\GraphQl\Quote\GetQuoteItemIdByReservedQuoteIdAndSku; use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -26,47 +24,38 @@ class RemoveItemFromCartTest extends GraphQlAbstract private $customerTokenService; /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; + private $getMaskedQuoteIdByReservedOrderId; /** - * @var QuoteFactory + * @var GetQuoteItemIdByReservedQuoteIdAndSku */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; - - /** - * @var ProductRepositoryInterface - */ - private $productRepository; + private $getQuoteItemIdByReservedQuoteIdAndSku; protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); - $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->getQuoteItemIdByReservedQuoteIdAndSku = $objectManager->get( + GetQuoteItemIdByReservedQuoteIdAndSku::class + ); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testRemoveItemFromCart() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); - $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $itemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); - $query = $this->prepareMutationQuery($maskedQuoteId, $itemId); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $query = $this->getQuery($maskedQuoteId, $itemId); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('removeItemFromCart', $response); $this->assertArrayHasKey('cart', $response['removeItemFromCart']); @@ -80,106 +69,25 @@ public function testRemoveItemFromCart() */ public function testRemoveItemFromNonExistentCart() { - $query = $this->prepareMutationQuery('non_existent_masked_id', 1); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $query = $this->getQuery('non_existent_masked_id', 1); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testRemoveNonExistentItem() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id'); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); - $query = $this->prepareMutationQuery($maskedQuoteId, $notExistentItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - } - - /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php - */ - public function testRemoveItemIfItemIsNotBelongToCart() - { - $firstQuote = $this->quoteFactory->create(); - $this->quoteResource->load($firstQuote, 'test_order_1', 'reserved_order_id'); - $firstQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$firstQuote->getId()); - - $secondQuote = $this->quoteFactory->create(); - $this->quoteResource->load( - $secondQuote, - 'test_order_with_virtual_product_without_address', - 'reserved_order_id' - ); - $secondQuote->setCustomerId(1); - $this->quoteResource->save($secondQuote); - $secondQuoteItemId = (int)$secondQuote - ->getItemByProduct($this->productRepository->get('virtual-product')) - ->getId(); - - $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); - - $query = $this->prepareMutationQuery($firstQuoteMaskedId, $secondQuoteItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php - */ - public function testRemoveItemFromGuestCart() - { - $guestQuote = $this->quoteFactory->create(); - $this->quoteResource->load( - $guestQuote, - 'test_order_with_virtual_product_without_address', - 'reserved_order_id' - ); - $guestQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$guestQuote->getId()); - $guestQuoteItemId = (int)$guestQuote - ->getItemByProduct($this->productRepository->get('virtual-product')) - ->getId(); - - $this->expectExceptionMessage( - "The current user cannot perform operations on cart \"$guestQuoteMaskedId\"" - ); - - $query = $this->prepareMutationQuery($guestQuoteMaskedId, $guestQuoteItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php - */ - public function testRemoveItemFromAnotherCustomerCart() - { - $anotherCustomerQuote = $this->quoteFactory->create(); - $this->quoteResource->load( - $anotherCustomerQuote, - 'test_order_with_virtual_product_without_address', - 'reserved_order_id' - ); - $anotherCustomerQuote->setCustomerId(2); - $this->quoteResource->save($anotherCustomerQuote); - - $anotherCustomerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$anotherCustomerQuote->getId()); - $anotherCustomerQuoteItemId = (int)$anotherCustomerQuote - ->getItemByProduct($this->productRepository->get('virtual-product')) - ->getId(); - - $this->expectExceptionMessage( - "The current user cannot perform operations on cart \"$anotherCustomerQuoteMaskedId\"" - ); - - $query = $this->prepareMutationQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $query = $this->getQuery($maskedQuoteId, $notExistentItemId); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -206,7 +114,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -226,12 +134,77 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array ]; } + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php + */ + public function testRemoveItemIfItemIsNotBelongToCart() + { + $firstQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $secondQuoteItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute( + 'test_order_with_virtual_product', + 'virtual-product' + ); + + $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); + + $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemFromGuestCart() + { + $guestQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $guestQuoteItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$guestQuoteMaskedId\"" + ); + + $query = $this->getQuery($guestQuoteMaskedId, $guestQuoteItemId); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemFromAnotherCustomerCart() + { + $anotherCustomerQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $anotherCustomerQuoteItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute( + 'test_quote', + 'simple_product' + ); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$anotherCustomerQuoteMaskedId\"" + ); + + $query = $this->getQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + } + /** * @param string $maskedQuoteId * @param int $itemId * @return string */ - private function prepareMutationQuery(string $maskedQuoteId, int $itemId): string + private function getQuery(string $maskedQuoteId, int $itemId): string { return <<<QUERY mutation { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index 88e7b93dd1d08..553408880baa0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -57,7 +57,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -105,7 +105,7 @@ public function testSetNewBillingAddress() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -116,7 +116,7 @@ public function testSetNewBillingAddress() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -179,7 +179,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -194,7 +194,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -230,7 +230,7 @@ public function testSetBillingAddressFromAddressBook() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -241,7 +241,7 @@ public function testSetBillingAddressFromAddressBook() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -270,13 +270,13 @@ public function testSetNotExistedBillingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -318,14 +318,14 @@ public function testSetNewBillingAddressAndFromAddressBookAtSameTime() self::expectExceptionMessage( 'The billing address cannot contain "customer_address_id" and "address" at the same time.' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -355,7 +355,7 @@ public function testSetBillingAddressToGuestCart() "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -390,7 +390,7 @@ public function testSetBillingAddressToAnotherCustomerCart() "The current user cannot perform operations on cart \"{$maskedQuoteId}\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer@search.example.com')); } /** @@ -424,7 +424,7 @@ public function testSetBillingAddressIfCustomerIsNotOwnerOfAddress() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -454,12 +454,12 @@ public function testSetBillingAddressOnNonExistentCart() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -490,7 +490,50 @@ public function testSetBillingAddressWithoutRequiredParameters(string $input, st QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetNewBillingAddressWithRedundantStreetLine() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setBillingAddressOnCart( + input: { + cart_id: "$maskedQuoteId" + billing_address: { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2", "test street 3"] + city: "test city" + region: "test region" + postcode: "887766" + country_code: "US" + telephone: "88776655" + save_in_address_book: false + } + } + } + ) { + cart { + billing_address { + firstname + } + } + } +} +QUERY; + self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -568,16 +611,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string } /** - * @param string $reversedOrderId + * @param string $reservedOrderId * @param int $customerId * @return string */ private function assignQuoteToCustomer( - string $reversedOrderId = 'test_order_with_simple_product_without_address', + string $reservedOrderId = 'test_order_with_simple_product_without_address', int $customerId = 1 ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php index bd147cc4a197e..20462220ff6f7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php @@ -48,7 +48,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -72,7 +72,7 @@ public function testSetOfflineShippingMethod(string $carrierCode, string $method $carrierCode, $quoteAddressId ); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php index 98eded8300665..0f6f371177f92 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php @@ -41,7 +41,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -52,7 +52,7 @@ public function testSetPaymentOnCartWithSimpleProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -62,7 +62,7 @@ public function testSetPaymentOnCartWithSimpleProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -75,7 +75,7 @@ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -90,7 +90,7 @@ public function testSetPaymentOnCartWithVirtualProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -100,7 +100,7 @@ public function testSetPaymentOnCartWithVirtualProduct() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -114,7 +114,7 @@ public function testSetNonExistentPaymentMethod() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -129,13 +129,13 @@ public function testSetPaymentOnNonExistentCart() $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -149,13 +149,13 @@ public function testSetPaymentMethodToGuestCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -169,12 +169,12 @@ public function testSetPaymentMethodToAnotherCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -201,7 +201,7 @@ public function testSetPaymentMethodWithoutRequiredParameters(string $input, str } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -227,7 +227,7 @@ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -240,7 +240,7 @@ public function testReSetPayment() $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php index 5ff29d20b34d7..6b097e028ffe5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php @@ -57,7 +57,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -107,7 +107,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); $cartResponse = $response['setShippingAddressesOnCart']['cart']; @@ -160,13 +160,13 @@ public function testSetNewShippingAddressOnCartWithVirtualProduct() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -200,7 +200,7 @@ public function testSetShippingAddressFromAddressBook() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); $cartResponse = $response['setShippingAddressesOnCart']['cart']; @@ -211,7 +211,7 @@ public function testSetShippingAddressFromAddressBook() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -242,13 +242,13 @@ public function testSetNonExistentShippingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -291,7 +291,7 @@ public function testSetNewShippingAddressAndFromAddressBookAtSameTime() self::expectExceptionMessage( 'The shipping address cannot contain "customer_address_id" and "address" at the same time.' ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -328,7 +328,7 @@ public function testSetShippingAddressIfCustomerIsNotOwnerOfAddress() } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** @@ -366,12 +366,12 @@ public function testSetShippingAddressToAnotherCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com')); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -405,7 +405,7 @@ public function testSetNewShippingAddressWithMissedRequiredParameters(string $in } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -427,7 +427,7 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array /** * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -483,7 +483,53 @@ public function testSetMultipleNewShippingAddresses() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetNewShippingAddressOnCartWithRedundantStreetLine() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setShippingAddressesOnCart( + input: { + cart_id: "$maskedQuoteId" + shipping_addresses: [ + { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2", "test street 3"] + city: "test city" + region: "test region" + postcode: "887766" + country_code: "US" + telephone: "88776655" + save_in_address_book: false + } + } + ] + } + ) { + cart { + shipping_addresses { + firstname + } + } + } +} +QUERY; + + self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -541,16 +587,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string } /** - * @param string $reversedOrderId + * @param string $reservedOrderId * @param int $customerId * @return string */ private function assignQuoteToCustomer( - string $reversedOrderId = 'test_order_with_simple_product_without_address', + string $reservedOrderId = 'test_order_with_simple_product_without_address', int $customerId = 1 ): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); $quote->setCustomerId($customerId); $this->quoteResource->save($quote); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index b5634f51cb366..ecad31a8bd123 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -7,10 +7,9 @@ namespace Magento\GraphQl\Quote\Customer; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\GraphQl\Quote\GetQuoteShippingAddressIdByReservedQuoteId; use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\Quote\Model\QuoteFactory; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -20,24 +19,19 @@ class SetShippingMethodsOnCartTest extends GraphQlAbstract { /** - * @var CustomerTokenServiceInterface - */ - private $customerTokenService; - - /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; + private $getMaskedQuoteIdByReservedOrderId; /** - * @var QuoteFactory + * @var GetQuoteShippingAddressIdByReservedQuoteId */ - private $quoteFactory; + private $getQuoteShippingAddressIdByReservedQuoteId; /** - * @var QuoteIdToMaskedQuoteIdInterface + * @var CustomerTokenServiceInterface */ - private $quoteIdToMaskedId; + private $customerTokenService; /** * @inheritdoc @@ -45,157 +39,393 @@ class SetShippingMethodsOnCartTest extends GraphQlAbstract protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->getQuoteShippingAddressIdByReservedQuoteId = $objectManager->get( + GetQuoteShippingAddressIdByReservedQuoteId::class + ); $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); } - public function testShippingMethodWithVirtualProduct() + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testSetShippingMethodOnCartWithSimpleProduct() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); - } + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $carrierCode = 'flatrate'; + $methodCode = 'flatrate'; + $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); - public function testShippingMethodWithSimpleProduct() - { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); - } + $query = $this->getQuery( + $maskedQuoteId, + $methodCode, + $carrierCode, + $quoteAddressId + ); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); - public function testShippingMethodWithSimpleProductWithoutAddress() - { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertArrayHasKey('shipping_addresses', $response['setShippingMethodsOnCart']['cart']); + self::assertCount(1, $response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + + $shippingAddress = current($response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + self::assertArrayHasKey('selected_shipping_method', $shippingAddress); + + self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); + self::assertEquals($carrierCode, $shippingAddress['selected_shipping_method']['carrier_code']); + + self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); + self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); } - public function testSetShippingMethodWithMissedRequiredParameters() + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + */ + public function testReSetShippingMethod() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $carrierCode = 'freeshipping'; + $methodCode = 'freeshipping'; + $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); + + $query = $this->getQuery( + $maskedQuoteId, + $methodCode, + $carrierCode, + $quoteAddressId + ); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('setShippingMethodsOnCart', $response); + self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); + self::assertArrayHasKey('shipping_addresses', $response['setShippingMethodsOnCart']['cart']); + self::assertCount(1, $response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + + $shippingAddress = current($response['setShippingMethodsOnCart']['cart']['shipping_addresses']); + self::assertArrayHasKey('selected_shipping_method', $shippingAddress); + + self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); + self::assertEquals($carrierCode, $shippingAddress['selected_shipping_method']['carrier_code']); + + self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); + self::assertEquals($methodCode, $shippingAddress['selected_shipping_method']['method_code']); } - public function testSetNonExistentShippingMethod() + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @param string $input + * @param string $message + * @dataProvider dataProviderSetShippingMethodWithWrongParameters + * @throws \Exception + */ + public function testSetShippingMethodWithWrongParameters(string $input, string $message) { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); + $input = str_replace(['cart_id_value', 'cart_address_id_value'], [$maskedQuoteId, $quoteAddressId], $input); + + $query = <<<QUERY +mutation { + setShippingMethodsOnCart(input: { + {$input} + }) { + cart { + shipping_addresses { + selected_shipping_method { + carrier_code + } + } + } + } +} +QUERY; + $this->expectExceptionMessage($message); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } - public function testSetShippingMethodIfAddressIsNotBelongToCart() + /** + * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function dataProviderSetShippingMethodWithWrongParameters(): array { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + return [ + 'missed_cart_id' => [ + 'shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "flatrate" + method_code: "flatrate" + }]', + 'Required parameter "cart_id" is missing' + ], + 'missed_shipping_methods' => [ + 'cart_id: "cart_id_value"', + 'Required parameter "shipping_methods" is missing' + ], + 'shipping_methods_are_empty' => [ + 'cart_id: "cart_id_value" shipping_methods: []', + 'Required parameter "shipping_methods" is missing' + ], + 'missed_cart_address_id' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + carrier_code: "flatrate" + method_code: "flatrate" + }]', + 'Required parameter "cart_address_id" is missing.' + ], + 'non_existent_cart_address_id' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: -1 + carrier_code: "flatrate" + method_code: "flatrate" + }]', + 'Could not find a cart address with ID "-1"' + ], + 'missed_carrier_code' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: cart_address_id_value + method_code: "flatrate" + }]', + 'Field ShippingMethodInput.carrier_code of required type String! was not provided.' + ], + 'empty_carrier_code' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "" + method_code: "flatrate" + }]', + 'Required parameter "carrier_code" is missing.' + ], + 'non_existent_carrier_code' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "wrong-carrier-code" + method_code: "flatrate" + }]', + 'Carrier with such method not found: wrong-carrier-code, flatrate' + ], + 'missed_method_code' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "flatrate" + }]', + 'Required parameter "method_code" is missing.' + ], + 'empty_method_code' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "flatrate" + method_code: "" + }]', + 'Required parameter "method_code" is missing.' + ], + 'non_existent_method_code' => [ + 'cart_id: "cart_id_value", shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "flatrate" + method_code: "wrong-carrier-code" + }]', + 'Carrier with such method not found: flatrate, wrong-carrier-code' + ], + 'non_existent_shopping_cart' => [ + 'cart_id: "non_existent_masked_id", shipping_methods: [{ + cart_address_id: cart_address_id_value + carrier_code: "flatrate" + method_code: "flatrate" + }]', + 'Could not find a cart with ID "non_existent_masked_id"' + ], + ]; } - public function testSetShippingMethodToNonExistentCart() + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @expectedException \Exception + * @expectedExceptionMessage You cannot specify multiple shipping methods. + */ + public function testSetMultipleShippingMethods() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setShippingMethodsOnCart(input: { + cart_id: "{$maskedQuoteId}", + shipping_methods: [ + { + cart_address_id: {$quoteAddressId} + carrier_code: "flatrate" + method_code: "flatrate" + } + { + cart_address_id: {$quoteAddressId} + carrier_code: "flatrate" + method_code: "flatrate" + } + ] + }) { + cart { + shipping_addresses { + selected_shipping_method { + carrier_code + } + } + } + } +} +QUERY; + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @expectedException \Exception + */ public function testSetShippingMethodToGuestCart() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); - } + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $carrierCode = 'flatrate'; + $methodCode = 'flatrate'; + $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); + $query = $this->getQuery( + $maskedQuoteId, + $methodCode, + $carrierCode, + $quoteAddressId + ); - public function testSetShippingMethodToAnotherCustomerCart() - { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } - public function testSetShippingMethodToNonExistentCartAddress() + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/three_customers.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * + * @expectedException \Exception + */ + public function testSetShippingMethodToAnotherCustomerCart() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); - } + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $carrierCode = 'flatrate'; + $methodCode = 'flatrate'; + $quoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('test_quote'); + $query = $this->getQuery( + $maskedQuoteId, + $methodCode, + $carrierCode, + $quoteAddressId + ); - public function testSetShippingMethodToGuestCartAddress() - { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } - public function testSetShippingMethodToAnotherCustomerCartAddress() + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/quote_with_address.php + */ + public function testSetShippingMethodIfCustomerIsNotOwnerOfAddress() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); - } + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $carrierCode = 'flatrate'; + $methodCode = 'flatrate'; + $anotherQuoteAddressId = $this->getQuoteShippingAddressIdByReservedQuoteId->execute('guest_quote_with_address'); + $query = $this->getQuery( + $maskedQuoteId, + $methodCode, + $carrierCode, + $anotherQuoteAddressId + ); - public function testSetMultipleShippingMethods() - { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423'); + $this->expectExceptionMessage( + "Cart does not contain address with ID \"{$anotherQuoteAddressId}\"" + ); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @param string $maskedQuoteId * @param string $shippingMethodCode * @param string $shippingCarrierCode - * @param string $shippingAddressId + * @param int $shippingAddressId * @return string - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function prepareMutationQuery( + private function getQuery( string $maskedQuoteId, string $shippingMethodCode, string $shippingCarrierCode, - string $shippingAddressId - ) : string { + int $shippingAddressId + ): string { return <<<QUERY mutation { setShippingMethodsOnCart(input: { cart_id: "$maskedQuoteId", - shipping_addresses: [{ + shipping_methods: [{ cart_address_id: $shippingAddressId - shipping_method: { - method_code: "$shippingMethodCode" - carrier_code: "$shippingCarrierCode" - } + carrier_code: "$shippingCarrierCode" + method_code: "$shippingMethodCode" }] - }) { - + }) { cart { - cart_id, shipping_addresses { selected_shipping_method { carrier_code method_code - label - amount } } } } } - QUERY; } - /** - * @param string $reversedOrderId - * @return string - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - */ - private function getMaskedQuoteIdByReservedOrderId(string $reversedOrderId): string - { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); - - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - - /** - * @param string $reversedOrderId - * @param int $customerId - * @return string - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - */ - private function assignQuoteToCustomer( - string $reversedOrderId, - int $customerId - ): string { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); - $quote->setCustomerId($customerId); - $this->quoteResource->save($quote); - return $this->quoteIdToMaskedId->execute((int)$quote->getId()); - } - /** * @param string $username * @param string $password * @return array - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php index 74e7aa8b5d0a4..35e2d62214fb2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php @@ -67,7 +67,7 @@ public function testUpdateCartItemQty() $qty = 2; $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -91,7 +91,7 @@ public function testRemoveCartItemIfQuantityIsZero() $qty = 0; $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -108,7 +108,7 @@ public function testRemoveCartItemIfQuantityIsZero() public function testUpdateItemInNonExistentCart() { $query = $this->getQuery('non_existent_masked_id', 1, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -124,7 +124,7 @@ public function testUpdateNonExistentItem() $this->expectExceptionMessage("Could not find cart item with id: {$notExistentItemId}."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -152,7 +152,7 @@ public function testUpdateItemIfItemIsNotBelongToCart() $this->expectExceptionMessage("Could not find cart item with id: {$secondQuoteItemId}."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -177,7 +177,7 @@ public function testUpdateItemInGuestCart() ); $query = $this->getQuery($guestQuoteMaskedId, $guestQuoteItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -206,7 +206,7 @@ public function testUpdateItemInAnotherCustomerCart() ); $query = $this->getQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId, 2); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -235,7 +235,7 @@ public function testUpdateWithMissedCartItemId() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** @@ -266,7 +266,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php index eab362c3a0a6f..9bb9bef9bdb09 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php @@ -50,14 +50,14 @@ public function __construct( /** * Get masked quote id by reserved order id * - * @param string $reversedOrderId + * @param string $reservedOrderId * @return string * @throws NoSuchEntityException */ - public function execute(string $reversedOrderId): string + public function execute(string $reservedOrderId): string { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); return $this->quoteIdToMaskedId->execute((int)$quote->getId()); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteItemIdByReservedQuoteIdAndSku.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteItemIdByReservedQuoteIdAndSku.php new file mode 100644 index 0000000000000..6f027babc0e27 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteItemIdByReservedQuoteIdAndSku.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\Quote\Model\QuoteFactory; + +/** + * Get quote item id by reserved order id and product sku + */ +class GetQuoteItemIdByReservedQuoteIdAndSku +{ + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @param QuoteFactory $quoteFactory + * @param QuoteResource $quoteResource + * @param ProductRepositoryInterface $productRepository + */ + public function __construct( + QuoteFactory $quoteFactory, + QuoteResource $quoteResource, + ProductRepositoryInterface $productRepository + ) { + $this->quoteFactory = $quoteFactory; + $this->quoteResource = $quoteResource; + $this->productRepository = $productRepository; + } + + /** + * Get quote item id by reserved order id and product sku + * + * @param string $reservedOrderId + * @param string $sku + * @return int + * @throws NoSuchEntityException + */ + public function execute(string $reservedOrderId, string $sku): int + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); + $product = $this->productRepository->get($sku); + + return (int)$quote->getItemByProduct($product)->getId(); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php index fa42ad4d71fb2..a56949b6f563a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetQuoteShippingAddressIdByReservedQuoteId.php @@ -40,13 +40,13 @@ public function __construct( /** * Get quote shipping address id by reserved order id * - * @param string $reversedOrderId + * @param string $reservedOrderId * @return int */ - public function execute(string $reversedOrderId): int + public function execute(string $reservedOrderId): int { $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id'); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); return (int)$quote->getShippingAddress()->getId(); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php new file mode 100644 index 0000000000000..affe36ea8617d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/ApplyCouponToCartTest.php @@ -0,0 +1,232 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test Apply Coupon to Cart functionality for guest + */ +class ApplyCouponToCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + */ + public function testApplyCouponToCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('applyCouponToCart', $response); + self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @expectedException \Exception + * @expectedExceptionMessage A coupon is already applied to the cart. Please remove it to apply another + */ + public function testApplyCouponTwice() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey("applyCouponToCart", $response); + self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']); + + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @expectedException \Exception + * @expectedExceptionMessage Cart does not contain products. + */ + public function testApplyCouponToCartWithoutItems() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query); + } + + /** + * _security + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @expectedException \Exception + */ + public function testApplyCouponToCustomerCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @expectedException \Exception + * @expectedExceptionMessage The coupon code isn't valid. Verify the code and try again. + */ + public function testApplyNonExistentCouponToCart() + { + $couponCode = 'non_existent_coupon_code'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @expectedException \Exception + */ + public function testApplyCouponToNonExistentCart() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId, $couponCode); + + self::expectExceptionMessage('Could not find a cart with ID "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_coupon_expired.php + * @expectedException \Exception + * @expectedExceptionMessage The coupon code isn't valid. Verify the code and try again. + */ + public function testApplyExpiredCoupon() + { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/574'); + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query); + } + + /** + * Products in cart don't fit to the coupon + * + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product.php + * @expectedException \Exception + * @expectedExceptionMessage The coupon code isn't valid. Verify the code and try again. + */ + public function testApplyCouponWhichIsNotApplicable() + { + $couponCode = '2?ds5!2d'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $couponCode); + + $this->graphQlMutation($query); + } + + /** + * @param string $input + * @param string $message + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @dataProvider dataProviderUpdateWithMissedRequiredParameters + * @expectedException \Exception + */ + public function testApplyCouponWithMissedRequiredParameters(string $input, string $message) + { + $query = <<<QUERY +mutation { + applyCouponToCart(input: {{$input}}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + + $this->expectExceptionMessage($message); + $this->graphQlMutation($query); + } + + /** + * @return array + */ + public function dataProviderUpdateWithMissedRequiredParameters(): array + { + return [ + 'missed_cart_id' => [ + 'coupon_code: "test"', + 'Required parameter "cart_id" is missing' + ], + 'missed_coupon_code' => [ + 'cart_id: "test"', + 'Required parameter "coupon_code" is missing' + ], + ]; + } + + /** + * @param string $maskedQuoteId + * @param string $couponCode + * @return string + */ + private function getQuery(string $maskedQuoteId, string $couponCode): string + { + return <<<QUERY +mutation { + applyCouponToCart(input: {cart_id: "$maskedQuoteId", coupon_code: "$couponCode"}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php index 4fd398439913e..adb2879e186b1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php @@ -7,6 +7,10 @@ namespace Magento\GraphQl\Quote\Guest; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Quote\Api\GuestCartRepositoryInterface; @@ -21,27 +25,102 @@ class CreateEmptyCartTest extends GraphQlAbstract */ private $guestCartRepository; + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var MaskedQuoteIdToQuoteIdInterface + */ + private $maskedQuoteIdToQuoteId; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var string + */ + private $maskedQuoteId; + protected function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->guestCartRepository = $objectManager->get(GuestCartRepositoryInterface::class); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->maskedQuoteIdToQuoteId = $objectManager->get(MaskedQuoteIdToQuoteIdInterface::class); + $this->quoteIdMaskFactory = $objectManager->get(QuoteIdMaskFactory::class); } public function testCreateEmptyCart() { - $query = <<<QUERY + $query = $this->getQuery(); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('createEmptyCart', $response); + self::assertNotEmpty($response['createEmptyCart']); + + $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); + $this->maskedQuoteId = $response['createEmptyCart']; + + self::assertNotNull($guestCart->getId()); + self::assertNull($guestCart->getCustomer()->getId()); + self::assertEquals('default', $guestCart->getStore()->getCode()); + } + + /** + * @magentoApiDataFixture Magento/Store/_files/second_store.php + */ + public function testCreateEmptyCartWithNotDefaultStore() + { + $query = $this->getQuery(); + $headerMap = ['Store' => 'fixture_second_store']; + $response = $this->graphQlMutation($query, [], '', $headerMap); + + self::assertArrayHasKey('createEmptyCart', $response); + self::assertNotEmpty($response['createEmptyCart']); + + $guestCart = $this->guestCartRepository->get($response['createEmptyCart']); + $this->maskedQuoteId = $response['createEmptyCart']; + + self::assertNotNull($guestCart->getId()); + self::assertNull($guestCart->getCustomer()->getId()); + self::assertSame('fixture_second_store', $guestCart->getStore()->getCode()); + } + + /** + * @return string + */ + private function getQuery(): string + { + return <<<QUERY mutation { createEmptyCart } QUERY; - $response = $this->graphQlQuery($query); + } - self::assertArrayHasKey('createEmptyCart', $response); + public function tearDown() + { + if (null !== $this->maskedQuoteId) { + $quoteId = $this->maskedQuoteIdToQuoteId->execute($this->maskedQuoteId); - $maskedCartId = $response['createEmptyCart']; - $guestCart = $this->guestCartRepository->get($maskedCartId); + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $quoteId); + $this->quoteResource->delete($quote); - self::assertNotNull($guestCart->getId()); - self::assertNull($guestCart->getCustomer()->getId()); + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->setQuoteId($quoteId) + ->delete(); + } + parent::tearDown(); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php index 8271a76d88f12..af1f72fe71620 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php @@ -31,7 +31,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -52,7 +52,7 @@ public function testGetAvailablePaymentMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -69,7 +69,7 @@ public function testGetAvailablePaymentMethodsFromCustomerCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php index 38fda50ba5836..a8113657eff6e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php @@ -33,7 +33,7 @@ protected function setUp() /** * Test case: get available shipping methods from current customer quote * - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -69,7 +69,7 @@ public function testGetAvailableShippingMethods() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -87,7 +87,7 @@ public function testGetAvailableShippingMethodsFromCustomerCart() /** * Test case: get available shipping methods when all shipping methods are disabled * - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index 6b1f540e4d47a..832e15058a4ee 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -28,7 +28,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -47,7 +47,7 @@ public function testGetCart() self::assertNotEmpty($response['cart']['items'][0]['id']); self::assertEquals(2, $response['cart']['items'][0]['qty']); - self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']); + self::assertEquals('simple_product', $response['cart']['items'][0]['product']['sku']); self::assertNotEmpty($response['cart']['items'][1]['id']); self::assertEquals(2, $response['cart']['items'][1]['qty']); @@ -97,6 +97,54 @@ public function testGetInactiveCart() $this->graphQlQuery($query); } + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote_guest_not_default_store.php + */ + public function testGetCartWithNotDefaultStore() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1_not_default_store_guest'); + $query = $this->getQuery($maskedQuoteId); + + $headerMap = ['Store' => 'fixture_second_store']; + $response = $this->graphQlQuery($query, [], '', $headerMap); + + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('items', $response['cart']); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @magentoApiDataFixture Magento/Store/_files/second_store.php + * + * @expectedException \Exception + * @expectedExceptionMessage Wrong store code specified for cart + */ + public function testGetCartWithWrongStore() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $query = $this->getQuery($maskedQuoteId); + + $headerMap = ['Store' => 'fixture_second_store']; + $this->graphQlQuery($query, [], '', $headerMap); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote_guest_not_default_store.php + * + * @expectedException \Exception + * @expectedExceptionMessage Store code not_existing_store does not exist + */ + public function testGetCartWithNotExistingStore() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1_not_default_store_guest'); + + $headerMap['Store'] = 'not_existing_store'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlQuery($query, [], '', $headerMap); + } + /** * @param string $maskedQuoteId * @return string diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php index c31b48ccc1087..bfdecca782319 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php @@ -31,7 +31,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -61,7 +61,7 @@ public function testGetSelectedShippingMethod() /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -79,7 +79,7 @@ public function testGetSelectedShippingMethodFromCustomerCart() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php new file mode 100644 index 0000000000000..d592443aed499 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedBillingAddressTest.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for get specified billing address + */ +class GetSpecifiedBillingAddressTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGeSpecifiedBillingAddress() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('billing_address', $response['cart']); + + $expectedBillingAddressData = [ + 'firstname' => 'John', + 'lastname' => 'Smith', + 'company' => 'CompanyName', + 'street' => [ + 'Green str, 67' + ], + 'city' => 'CityM', + 'region' => [ + 'code' => 'AL', + 'label' => 'Alabama', + ], + 'postcode' => '75477', + 'country' => [ + 'code' => 'US', + 'label' => 'US', + ], + 'telephone' => '3468676', + 'address_type' => 'BILLING', + ]; + self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testGeSpecifiedBillingAddressIfBillingAddressIsNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('billing_address', $response['cart']); + + $expectedBillingAddressData = [ + 'firstname' => null, + 'lastname' => null, + 'company' => null, + 'street' => [ + '' + ], + 'city' => null, + 'region' => [ + 'code' => null, + 'label' => null, + ], + 'postcode' => null, + 'country' => [ + 'code' => null, + 'label' => null, + ], + 'telephone' => null, + 'address_type' => 'BILLING', + ]; + self::assertEquals($expectedBillingAddressData, $response['cart']['billing_address']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testGetBillingAddressOfNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + */ + public function testGetBillingAddressFromAnotherCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->expectExceptionMessage( + "The current user cannot perform operations on cart \"$maskedQuoteId\"" + ); + $this->graphQlQuery($query); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "$maskedQuoteId") { + billing_address { + firstname + lastname + company + street + city + region + { + code + label + } + postcode + country + { + code + label + } + telephone + address_type + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php new file mode 100644 index 0000000000000..5adb6ce65db6f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveCouponFromCartTest.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Check removing of the coupon from guest cart + */ +class RemoveCouponFromCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/SalesRule/_files/coupon_code_with_wildcard.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id" + */ + public function testRemoveCouponFromNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @expectedException \Exception + * @expectedExceptionMessage Cart does not contain products + */ + public function testRemoveCouponFromEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveCouponFromCartIfCouponWasNotSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('removeCouponFromCart', $response); + self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent_generalusers.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/apply_coupon.php + */ + public function testRemoveCouponFromCustomerCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + self::expectExceptionMessage('The current user cannot perform operations on cart "' . $maskedQuoteId . '"'); + $this->graphQlMutation($query); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + removeCouponFromCart(input: {cart_id: "{$maskedQuoteId}"}) { + cart { + applied_coupon { + code + } + } + } +} +QUERY; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php index a306b29e51197..27f3f6367f662 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php @@ -7,10 +7,8 @@ namespace Magento\GraphQl\Quote\Guest; -use Magento\Quote\Model\QuoteFactory; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; -use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\GraphQl\Quote\GetQuoteItemIdByReservedQuoteIdAndSku; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -20,46 +18,36 @@ class RemoveItemFromCartTest extends GraphQlAbstract { /** - * @var QuoteResource + * @var GetMaskedQuoteIdByReservedOrderId */ - private $quoteResource; + private $getMaskedQuoteIdByReservedOrderId; /** - * @var QuoteFactory + * @var GetQuoteItemIdByReservedQuoteIdAndSku */ - private $quoteFactory; - - /** - * @var QuoteIdToMaskedQuoteIdInterface - */ - private $quoteIdToMaskedId; - - /** - * @var ProductRepositoryInterface - */ - private $productRepository; + private $getQuoteItemIdByReservedQuoteIdAndSku; protected function setUp() { $objectManager = Bootstrap::getObjectManager(); - $this->quoteResource = $objectManager->get(QuoteResource::class); - $this->quoteFactory = $objectManager->get(QuoteFactory::class); - $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class); - $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->getQuoteItemIdByReservedQuoteIdAndSku = $objectManager->get( + GetQuoteItemIdByReservedQuoteIdAndSku::class + ); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testRemoveItemFromCart() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); - $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId(); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $itemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); - $query = $this->prepareMutationQuery($maskedQuoteId, $itemId); - $response = $this->graphQlQuery($query); + $query = $this->getQuery($maskedQuoteId, $itemId); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('removeItemFromCart', $response); $this->assertArrayHasKey('cart', $response['removeItemFromCart']); @@ -72,70 +60,24 @@ public function testRemoveItemFromCart() */ public function testRemoveItemFromNonExistentCart() { - $query = $this->prepareMutationQuery('non_existent_masked_id', 1); - $this->graphQlQuery($query); + $query = $this->getQuery('non_existent_masked_id', 1); + $this->graphQlMutation($query); } /** - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ public function testRemoveNonExistentItem() { - $quote = $this->quoteFactory->create(); - $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); - $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId()); + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); - $query = $this->prepareMutationQuery($maskedQuoteId, $notExistentItemId); - $this->graphQlQuery($query); - } - - /** - * Test mutation is only able to remove quote item belonging to the requested cart - * - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php - */ - public function testRemoveItemIfItemIsNotBelongToCart() - { - $firstQuote = $this->quoteFactory->create(); - $this->quoteResource->load($firstQuote, 'test_order_with_simple_product_without_address', 'reserved_order_id'); - $firstQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$firstQuote->getId()); - - $secondQuote = $this->quoteFactory->create(); - $this->quoteResource->load( - $secondQuote, - 'test_order_with_virtual_product_without_address', - 'reserved_order_id' - ); - $secondQuoteItemId = (int)$secondQuote - ->getItemByProduct($this->productRepository->get('virtual-product')) - ->getId(); - - $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); - - $query = $this->prepareMutationQuery($firstQuoteMaskedId, $secondQuoteItemId); - $this->graphQlQuery($query); - } - - /** - * Test mutation is only able to remove quote item belonging to the requested cart - * - * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php - */ - public function testRemoveItemFromCustomerCart() - { - $customerQuote = $this->quoteFactory->create(); - $this->quoteResource->load($customerQuote, 'test_order_1', 'reserved_order_id'); - $customerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$customerQuote->getId()); - $customerQuoteItemId = (int)$customerQuote->getItemByProduct($this->productRepository->get('simple'))->getId(); - - $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\""); - - $query = $this->prepareMutationQuery($customerQuoteMaskedId, $customerQuoteItemId); - $this->graphQlQuery($query); + $query = $this->getQuery($maskedQuoteId, $notExistentItemId); + $this->graphQlMutation($query); } /** @@ -161,7 +103,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -181,12 +123,51 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array ]; } + /** + * _security + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php + */ + public function testRemoveItemIfItemIsNotBelongToCart() + { + $firstQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $secondQuoteItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute( + 'test_order_with_virtual_product_without_address', + 'virtual-product' + ); + + $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); + + $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); + $this->graphQlMutation($query); + } + + /** + * _security + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemFromCustomerCart() + { + $customerQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $customerQuoteItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); + + $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\""); + + $query = $this->getQuery($customerQuoteMaskedId, $customerQuoteItemId); + $this->graphQlMutation($query); + } + /** * @param string $maskedQuoteId * @param int $itemId * @return string */ - private function prepareMutationQuery(string $maskedQuoteId, int $itemId): string + private function getQuery(string $maskedQuoteId, int $itemId): string { return <<<QUERY mutation { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index ae0a1a0e822ac..5b0e4cfdb6c52 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -28,7 +28,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -76,7 +76,7 @@ public function testSetNewBillingAddress() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -86,7 +86,7 @@ public function testSetNewBillingAddress() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -149,7 +149,7 @@ public function testSetNewBillingAddressWithUseForShippingParameter() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; @@ -203,12 +203,12 @@ public function testSetBillingAddressToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * _security - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -237,7 +237,7 @@ public function testSetBillingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -276,11 +276,11 @@ public function testSetBillingAddressOnNonExistentCart() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -310,7 +310,50 @@ public function testSetBillingAddressWithoutRequiredParameters(string $input, st } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetNewBillingAddressRedundantStreetLine() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setBillingAddressOnCart( + input: { + cart_id: "$maskedQuoteId" + billing_address: { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2", "test street 3"] + city: "test city" + region: "test region" + postcode: "887766" + country_code: "US" + telephone: "88776655" + save_in_address_book: false + } + } + } + ) { + cart { + billing_address { + firstname + } + } + } +} +QUERY; + + self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php index 8ed9ef84d6fbd..2c1333aa77326 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php @@ -40,7 +40,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -64,7 +64,7 @@ public function testSetOfflineShippingMethod(string $carrierCode, string $method $carrierCode, $quoteAddressId ); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php index c9078fd84f6bc..7f9aaf4296c30 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php @@ -33,7 +33,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -44,7 +44,7 @@ public function testSetPaymentOnCartWithSimpleProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -53,7 +53,7 @@ public function testSetPaymentOnCartWithSimpleProduct() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -66,7 +66,7 @@ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -80,7 +80,7 @@ public function testSetPaymentOnCartWithVirtualProduct() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); @@ -89,7 +89,7 @@ public function testSetPaymentOnCartWithVirtualProduct() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -103,7 +103,7 @@ public function testSetNonExistentPaymentMethod() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -116,13 +116,13 @@ public function testSetPaymentOnNonExistentCart() $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -137,11 +137,11 @@ public function testSetPaymentMethodToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -169,7 +169,7 @@ public function testSetPaymentMethodWithoutRequiredParameters(string $input, str } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -194,7 +194,7 @@ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -207,7 +207,7 @@ public function testReSetPayment() $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE; $query = $this->getQuery($maskedQuoteId, $methodCode); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setPaymentMethodOnCart', $response); self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php index e21d9ed64d491..888b0e87734b6 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php @@ -28,7 +28,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ @@ -78,7 +78,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() } } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']); $cartResponse = $response['setShippingAddressesOnCart']['cart']; @@ -130,12 +130,12 @@ public function testSetNewShippingAddressOnCartWithVirtualProduct() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * _security - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -166,13 +166,13 @@ public function testSetShippingAddressFromAddressBook() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -205,11 +205,11 @@ public function testSetShippingAddressToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -243,7 +243,51 @@ public function testSetNewShippingAddressWithMissedRequiredParameters(string $in } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetNewShippingAddressOnCartWithRedundantStreetLine() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<<QUERY +mutation { + setShippingAddressesOnCart( + input: { + cart_id: "$maskedQuoteId" + shipping_addresses: [ + { + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2", "test street 3"] + city: "test city" + region: "test region" + postcode: "887766" + country_code: "US" + telephone: "88776655" + save_in_address_book: false + } + } + ] + } + ) { + cart { + shipping_addresses { + firstname + } + } + } +} +QUERY; + self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); + $this->graphQlMutation($query); } /** @@ -264,7 +308,7 @@ public function dataProviderUpdateWithMissedRequiredParameters(): array } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @@ -320,7 +364,7 @@ public function testSetMultipleNewShippingAddresses() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index 6289d88de6ee4..2f3c109b99523 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -40,7 +40,7 @@ protected function setUp() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -58,7 +58,7 @@ public function testSetShippingMethodOnCartWithSimpleProduct() $carrierCode, $quoteAddressId ); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -76,9 +76,14 @@ public function testSetShippingMethodOnCartWithSimpleProduct() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * Shipping address for quote will be created automatically BUT with NULL values (considered that address + * is not set) + * + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php * * @expectedException \Exception * @expectedExceptionMessage The shipping address is missing. Set the address and try again. @@ -96,12 +101,12 @@ public function testSetShippingMethodOnCartWithSimpleProductAndWithoutAddress() $carrierCode, $quoteAddressId ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -120,7 +125,7 @@ public function testReSetShippingMethod() $carrierCode, $quoteAddressId ); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertArrayHasKey('setShippingMethodsOnCart', $response); self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']); @@ -138,7 +143,7 @@ public function testReSetShippingMethod() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -170,7 +175,7 @@ public function testSetShippingMethodWithWrongParameters(string $input, string $ } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -270,7 +275,7 @@ public function dataProviderSetShippingMethodWithWrongParameters(): array /** * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -309,13 +314,13 @@ public function testSetMultipleShippingMethods() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -338,13 +343,12 @@ public function testSetShippingMethodToCustomerCart() $this->expectExceptionMessage( "The current user cannot perform operations on cart \"$maskedQuoteId\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** * _security - * - * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php @@ -366,7 +370,7 @@ public function testSetShippingMethodIfGuestIsNotOwnerOfAddress() $this->expectExceptionMessage( "Cart does not contain address with ID \"{$anotherQuoteAddressId}\"" ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php index fca7a4287620b..1b8cf2e1c57f7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php @@ -60,7 +60,7 @@ public function testUpdateCartItemQty() $qty = 2; $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -84,7 +84,7 @@ public function testRemoveCartItemIfQuantityIsZero() $qty = 0; $query = $this->getQuery($maskedQuoteId, $itemId, $qty); - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('updateCartItems', $response); $this->assertArrayHasKey('cart', $response['updateCartItems']); @@ -100,7 +100,7 @@ public function testRemoveCartItemIfQuantityIsZero() public function testUpdateItemInNonExistentCart() { $query = $this->getQuery('non_existent_masked_id', 1, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -116,7 +116,7 @@ public function testUpdateNonExistentItem() $this->expectExceptionMessage("Could not find cart item with id: {$notExistentItemId}."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -142,7 +142,7 @@ public function testUpdateItemIfItemIsNotBelongToCart() $this->expectExceptionMessage("Could not find cart item with id: {$secondQuoteItemId}."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -158,7 +158,7 @@ public function testUpdateItemFromCustomerCart() $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\""); $query = $this->getQuery($customerQuoteMaskedId, $customerQuoteItemId, 2); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -186,7 +186,7 @@ public function testUpdateWithMissedCartItemId() } } QUERY; - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -217,7 +217,7 @@ public function testUpdateWithMissedItemRequiredParameters(string $input, string } QUERY; $this->expectExceptionMessage($message); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php index 05e3e608c5e52..ae6faae7650b9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php @@ -12,6 +12,9 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +/** + * Tests for send email to friend + */ class SendFriendTest extends GraphQlAbstract { @@ -66,7 +69,7 @@ public function testSendFriend() } QUERY; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); self::assertEquals('Name', $response['sendEmailToFriend']['sender']['name']); self::assertEquals('e@mail.com', $response['sendEmailToFriend']['sender']['email']); self::assertEquals('Lorem Ipsum', $response['sendEmailToFriend']['sender']['message']); @@ -117,7 +120,7 @@ public function testSendWithoutExistProduct() $this->expectExceptionMessage( 'The product that was requested doesn\'t exist. Verify the product and try again.' ); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -181,7 +184,7 @@ public function testMaxSendEmailToFriend() QUERY; $this->expectException(\Exception::class); $this->expectExceptionMessage("No more than {$sendFriend->getMaxRecipients()} emails can be sent at a time."); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -214,7 +217,7 @@ public function testErrors(string $input, string $errorMessage) QUERY; $this->expectException(\Exception::class); $this->expectExceptionMessage($errorMessage); - $this->graphQlQuery($query); + $this->graphQlMutation($query); } /** @@ -269,8 +272,9 @@ public function testLimitMessagesPerHour() "You can't send messages more than {$sendFriend->getMaxSendsToFriend()} times an hour." ); - for ($i = 0; $i <= $sendFriend->getMaxSendsToFriend() + 1; $i++) { - $this->graphQlQuery($query); + $maxSendToFriends = $sendFriend->getMaxSendsToFriend(); + for ($i = 0; $i <= $maxSendToFriends + 1; $i++) { + $this->graphQlMutation($query); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php index b6e1a61f0357c..c85f63c083700 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlMutationTest.php @@ -28,10 +28,31 @@ public function testMutation() } MUTATION; - $response = $this->graphQlQuery($query); + $response = $this->graphQlMutation($query); $this->assertArrayHasKey('testItem', $response); $testItem = $response['testItem']; $this->assertArrayHasKey('integer_list', $testItem); $this->assertEquals([4, 5, 6], $testItem['integer_list']); } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Mutation requests allowed only for POST requests + */ + public function testMutationIsNotAllowedViaGetRequest() + { + $id = 3; + + $query = <<<MUTATION +mutation { + testItem(id: {$id}) { + item_id, + name, + integer_list + } +} +MUTATION; + + $this->graphQlQuery($query, [], '', []); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index d59e255daa109..2db06e383758f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -58,4 +58,46 @@ public function testQueryTestModuleExtensionAttribute() $this->assertArrayHasKey('integer_list', $testItem); $this->assertEquals([3, 4, 5], $testItem['integer_list']); } + + public function testQueryViaGetRequestReturnsResults() + { + $id = 1; + + $query = <<<QUERY +{ + testItem(id: {$id}) + { + item_id + name + } +} +QUERY; + + $response = $this->graphQlQuery($query, [], '', []); + + $this->assertArrayHasKey('testItem', $response); + } + + public function testQueryViaGetRequestWithVariablesReturnsResults() + { + $id = 1; + + $query = <<<QUERY +query getTestItem(\$id: Int!) +{ + testItem(id: \$id) + { + item_id + name + } +} +QUERY; + $variables = [ + "id" => $id + ]; + + $response = $this->graphQlQuery($query, $variables, '', []); + + $this->assertArrayHasKey('testItem', $response); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php index 463f2c4af101f..6861bb8dbb240 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php @@ -142,6 +142,6 @@ private function sendRequestWithToken(string $query): array $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; - return $this->graphQlQuery($query, [], '', $headerMap); + return $this->graphQlMutation($query, [], '', $headerMap); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php index 89fbbb9c49ed3..45c82906d255d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php @@ -14,6 +14,9 @@ use Magento\Vault\Model\ResourceModel\PaymentToken as TokenResource; use Magento\Vault\Model\ResourceModel\PaymentToken\CollectionFactory; +/** + * Tests for customer payment tokens + */ class CustomerPaymentTokensTest extends GraphQlAbstract { /** @@ -139,7 +142,12 @@ public function testDeletePaymentToken() } } QUERY; - $response = $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $response = $this->graphQlMutation( + $query, + [], + '', + $this->getCustomerAuthHeaders($currentEmail, $currentPassword) + ); $this->assertTrue($response['deletePaymentToken']['result']); $this->assertEquals(1, count($response['deletePaymentToken']['customerPaymentTokens']['items'])); @@ -168,7 +176,7 @@ public function testDeletePaymentTokenIfUserIsNotAuthorized() } } QUERY; - $this->graphQlQuery($query, [], ''); + $this->graphQlMutation($query, [], ''); } /** @@ -190,7 +198,7 @@ public function testDeletePaymentTokenInvalidPublicHash() } } QUERY; - $this->graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php index 1aee493d8e0cb..1fb8fc43b0db6 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CouponManagementTest.php @@ -9,6 +9,9 @@ use Magento\TestFramework\TestCase\WebapiAbstract; +/** + * Coupon management service tests + */ class CouponManagementTest extends WebapiAbstract { const SERVICE_VERSION = 'V1'; @@ -93,7 +96,7 @@ public function testSetCouponThrowsExceptionIfCouponDoesNotExist() $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, ], 'soap' => [ @@ -129,7 +132,7 @@ public function testSetCouponSuccess() $couponCode = $salesRule->getPrimaryCoupon()->getCode(); $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, ], 'soap' => [ @@ -232,7 +235,7 @@ public function testSetMyCouponThrowsExceptionIfCouponDoesNotExist() $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, 'token' => $token, ], @@ -280,7 +283,7 @@ public function testSetMyCouponSuccess() $serviceInfo = [ 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . $couponCode, + 'resourcePath' => self::RESOURCE_PATH . 'mine/coupons/' . urlencode($couponCode), 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, 'token' => $token, ], diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml index 12b9808adb9e0..0c0e06d63b6c9 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaEditCustomerTest.xml @@ -14,6 +14,7 @@ <data name="attempts" xsi:type="number">3</data> <data name="captcha" xsi:type="string">111</data> <data name="configData" xsi:type="string">captcha_storefront_user_edit_failures_number, customer_max_login_failures_number</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Security\Test\Constraint\AssertCustomerIsLocked" /> <constraint name="Magento\Customer\Test\Constraint\AssertCustomerIsLockedOnBackend" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml index 186439bb9f157..9242bfbef2374 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnAdminLoginTest.xml @@ -12,6 +12,7 @@ <data name="customAdmin/data/captcha" xsi:type="string">111</data> <data name="pageTitle" xsi:type="string">Dashboard</data> <data name="configData" xsi:type="string">captcha_backend_login</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Backend\Test\Constraint\AssertBackendPageIsAvailable" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml index a88cd98e3c31b..1a25afeabc5de 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnContactUsTest.xml @@ -12,6 +12,7 @@ <data name="comment/data/captcha" xsi:type="string">111</data> <data name="comment/data/customer/dataset" xsi:type="string">default</data> <data name="configData" xsi:type="string">captcha_storefront_contact_us</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Contact\Test\Constraint\AssertContactUsSuccessMessage" /> </variation> </testCase> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml index 8e4327db5eddc..8068b2cbc050e 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontLoginTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Captcha\Test\TestCase\CaptchaOnStoreFrontLoginTest" summary="Check CAPTCHA on StoreFront Login Page" ticketId="MAGETWO-43639"> <variation name="CaptchaOnStoreFrontLoginTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">default</data> <data name="captcha" xsi:type="string">111</data> <data name="configData" xsi:type="string">captcha_storefront_login</data> diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml index 8e83c189efc2f..b0ce6dfa561ae 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/TestCase/CaptchaOnStoreFrontRegisterTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Captcha\Test\TestCase\CaptchaOnStoreFrontRegisterTest" summary="Check CAPTCHA on StoreFront Register Page" ticketId="MAGETWO-43602"> <variation name="CaptchaOnStoreFrontRegisterTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">register_customer</data> <data name="customer/data/captcha" xsi:type="string">111</data> <data name="configData" xsi:type="string">captcha_storefront_register</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php index dcc8cce970098..4e8e0f97d70d5 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php @@ -128,7 +128,8 @@ class CustomOptions extends Form * * @var string */ - private $validationErrorMessage = '//div[@class="mage-error"][contains(text(), "required field")]'; + private $validationErrorMessage = '//div[@class="mage-error"][contains(text(), "required field")' . + 'and not(contains(@style,\'display\'))]'; /** * Get product options @@ -148,6 +149,7 @@ public function getOptions(FixtureInterface $product) foreach ($dataOptions as $option) { $title = $option['title']; if (!isset($listCustomOptions[$title])) { + // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception("Can't find option: \"{$title}\""); } diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml index 9a6a66091d427..dc91608f361c2 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/SearchEntityResultsTest.xml @@ -57,11 +57,9 @@ <variation name="SearchEntityResultsTestVariation12" summary="Search for simple product name using 2 symbols query length" ticketId="MAGETWO-36542"> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::name</data> <data name="queryLength" xsi:type="string">2</data> - <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResultMessage" /> - <constraint name="Magento\CatalogSearch\Test\Constraint\AssertCatalogSearchNoResult" /> + <constraint name="Magento\CatalogSearch\Test\Constraint\AssertProductCanBeOpenedFromSearchResult" /> </variation> <variation name="SearchEntityResultsTestVariation13" summary="Search for simple product name using 3 symbols query length" ticketId="MAGETWO-36542"> - <data name="issue" xsi:type="string">MAGETWO-65509: [FT] Magento\CatalogSearch\Test\TestCase\SearchEntityResultsTest fails on Jenkins</data> <data name="tag" xsi:type="string">stable:no</data> <data name="catalogSearch/data/query_text/value" xsi:type="string">catalogProductSimple::default::name</data> <data name="queryLength" xsi:type="string">3</data> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml index 0b5b8a059cbbd..b4fbe7bb92929 100644 --- a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/ResetCustomerPasswordFailedTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Security\Test\TestCase\ResetCustomerPasswordFailedTest" summary="Reset customer password."> <variation name="ResetPasswordTestVariation"> - <data name="tag" xsi:type="string">severity:S1</data> + <data name="tag" xsi:type="string">severity:S1,mftf_migrated:yes</data> <data name="customer/dataset" xsi:type="string">customer_US</data> <data name="attempts" xsi:type="string">2</data> <data name="configData" xsi:type="string">captcha_storefront_disable</data> diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/assign_items_per_address.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/assign_items_per_address.php new file mode 100644 index 0000000000000..91cea7dc96602 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/assign_items_per_address.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CartRepositoryInterface; + +$store = $storeManager->getStore(); +$quote->setReservedOrderId('multishipping_quote_id_braintree') + ->setStoreId($store->getId()) + ->setCustomerEmail('customer001@test.com'); + +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->get(CartRepositoryInterface::class); +$quote->collectTotals(); +$quoteRepository->save($quote); + +$items = $quote->getAllItems(); +$addressList = $quote->getAllShippingAddresses(); + +foreach ($addressList as $key => $address) { + $item = $items[$key]; + // set correct quantity per shipping address + $item->setQty(1); + $address->setTotalQty(1); + $address->addItem($item); +} + +// assign virtual product to the billing address +$billingAddress = $quote->getBillingAddress(); +$virtualItem = $items[sizeof($items) - 1]; +$billingAddress->setTotalQty(1); +$billingAddress->addItem($virtualItem); + +// need to recollect totals +$quote->setTotalsCollectedFlag(false); +$quote->collectTotals(); +$quoteRepository->save($quote); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree.php new file mode 100644 index 0000000000000..3e1db90f1f2c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote\Payment; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Braintree\Model\Ui\ConfigProvider; + +/** + * @var Magento\Quote\Model\Quote $quote + */ + +if (empty($quote)) { + throw new \Exception('$quote should be defined in the parent fixture'); +} + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var PaymentInterface $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod(ConfigProvider::CODE); +$quote->setPayment($payment); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree_paypal.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree_paypal.php new file mode 100644 index 0000000000000..e4bba222078b0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/payment_braintree_paypal.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote\Payment; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider; + +/** + * @var Magento\Quote\Model\Quote $quote + */ + +if (empty($quote)) { + throw new \Exception('$quote should be defined in the parent fixture'); +} + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var PaymentInterface $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod(ConfigProvider::PAYPAL_CODE); +$quote->setPayment($payment); diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree.php new file mode 100644 index 0000000000000..1c56e611dd6db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); + +/** @var Quote $quote */ +$quote = $objectManager->create(Quote::class); + +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/shipping_address_list.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/billing_address.php'; +require __DIR__ . '/payment_braintree.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/items.php'; +require __DIR__ . '/assign_items_per_address.php'; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php new file mode 100644 index 0000000000000..4bd8e926abb76 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); + +/** @var Quote $quote */ +$quote = $objectManager->create(Quote::class); + +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/shipping_address_list.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/billing_address.php'; +require __DIR__ . '/payment_braintree_paypal.php'; +require __DIR__ . '/../../../Magento/Multishipping/Fixtures/items.php'; +require __DIR__ . '/assign_items_per_address.php'; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Model/MultishippingTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Model/MultishippingTest.php new file mode 100644 index 0000000000000..91bc0388d8551 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Model/MultishippingTest.php @@ -0,0 +1,254 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Model; + +use Braintree\Result\Successful; +use Braintree\Transaction; +use Magento\Braintree\Gateway\Command\GetPaymentNonceCommand; +use Magento\Braintree\Model\Adapter\BraintreeAdapter; +use Magento\Braintree\Model\Adapter\BraintreeAdapterFactory; +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Multishipping\Model\Checkout\Type\Multishipping; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\Email\Sender\OrderSender; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use \PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Payment\Gateway\Command\ResultInterface as CommandResultInterface; + +/** + * Tests Magento\Multishipping\Model\Checkout\Type\Multishipping with Braintree and BraintreePayPal payments. + * + * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MultishippingTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var BraintreeAdapter|MockObject + */ + private $adapter; + + /** + * @var Multishipping + */ + private $model; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + + $orderSender = $this->getMockBuilder(OrderSender::class) + ->disableOriginalConstructor() + ->getMock(); + + $adapterFactory = $this->getMockBuilder(BraintreeAdapterFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapter = $this->getMockBuilder(BraintreeAdapter::class) + ->disableOriginalConstructor() + ->getMock(); + $adapterFactory->method('create') + ->willReturn($this->adapter); + + $this->objectManager->addSharedInstance($adapterFactory, BraintreeAdapterFactory::class); + $this->objectManager->addSharedInstance($this->getPaymentNonceMock(), GetPaymentNonceCommand::class); + + $this->model = $this->objectManager->create( + Multishipping::class, + ['orderSender' => $orderSender] + ); + } + + /** + * Checks a case when multiple orders are created successfully using Braintree payment method. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Braintree/Fixtures/quote_with_split_items_braintree.php + * @magentoConfigFixture current_store payment/braintree/active 1 + * @return void + */ + public function testCreateOrdersWithBraintree() + { + $this->adapter->method('sale') + ->willReturn( + $this->getTransactionStub() + ); + $this->createOrders(); + } + + /** + * Checks a case when multiple orders are created successfully using Braintree PayPal payment method. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Braintree/Fixtures/quote_with_split_items_braintree_paypal.php + * @magentoConfigFixture current_store payment/braintree_paypal/active 1 + * @return void + */ + public function testCreateOrdersWithBraintreePaypal() + { + $this->adapter->method('sale') + ->willReturn( + $this->getTransactionPaypalStub() + ); + $this->createOrders(); + } + + /** + * Creates orders for multishipping checkout flow. + * + * @return void + */ + private function createOrders() + { + $expectedPlacedOrdersNumber = 3; + $quote = $this->getQuote('multishipping_quote_id_braintree'); + + /** @var CheckoutSession $session */ + $session = $this->objectManager->get(CheckoutSession::class); + $session->replaceQuote($quote); + + $this->model->createOrders(); + + $orderList = $this->getOrderList((int)$quote->getId()); + self::assertCount( + $expectedPlacedOrdersNumber, + $orderList, + 'Total successfully placed orders number mismatch' + ); + } + + /** + * Creates stub for Braintree capture Transaction. + * + * @return Successful + */ + private function getTransactionStub(): Successful + { + $transaction = $this->getMockBuilder(Transaction::class) + ->disableOriginalConstructor() + ->getMock(); + $transaction->status = 'submitted_for_settlement'; + $transaction->creditCard = [ + 'last4' => '1111', + 'cardType' => 'Visa', + 'expirationMonth' => '12', + 'expirationYear' => '2021' + ]; + + $creditCardDetails = new \stdClass(); + $creditCardDetails->token = '4fdg'; + $creditCardDetails->expirationMonth = '12'; + $creditCardDetails->expirationYear = '2021'; + $creditCardDetails->cardType = 'Visa'; + $creditCardDetails->last4 = '1111'; + $creditCardDetails->expirationDate = '12/2021'; + $transaction->creditCardDetails = $creditCardDetails; + + $response = new Successful(); + $response->success = true; + $response->transaction = $transaction; + + return $response; + } + + /** + * Creates stub for BraintreePaypal capture Transaction. + * + * @return Successful + */ + private function getTransactionPaypalStub(): Successful + { + $transaction = $this->getMockBuilder(Transaction::class) + ->disableOriginalConstructor() + ->getMock(); + $transaction->status = 'submitted_for_settlement'; + $transaction->paypal = [ + 'token' => 'fchxqx', + 'payerEmail' => 'payer@example.com', + 'paymentId' => 'PAY-33ac47a28e7f54791f6cda45', + ]; + $paypalDetails = new \stdClass(); + $paypalDetails->token = 'fchxqx'; + $paypalDetails->payerEmail = 'payer@example.com'; + $paypalDetails->paymentId = '33ac47a28e7f54791f6cda45'; + $transaction->paypalDetails = $paypalDetails; + + $response = new Successful(); + $response->success = true; + $response->transaction = $transaction; + + return $response; + } + + /** + * Retrieves quote by reserved order id. + * + * @param string $reservedOrderId + * @return Quote + */ + private function getQuote(string $reservedOrderId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria)->getItems(); + + return array_pop($items); + } + + /** + * Get list of orders by quote id. + * + * @param int $quoteId + * @return array + */ + private function getOrderList(int $quoteId): array + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('quote_id', $quoteId) + ->create(); + + /** @var OrderRepositoryInterface $orderRepository */ + $orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + return $orderRepository->getList($searchCriteria)->getItems(); + } + + /** + * Returns GetPaymentNonceCommand command mock. + * + * @return MockObject + */ + private function getPaymentNonceMock(): MockObject + { + $commandResult = $this->createMock(CommandResultInterface::class); + $commandResult->method('get') + ->willReturn(['paymentMethodNonce' => 'testNonce']); + $paymentNonce = $this->createMock(GetPaymentNonceCommand::class); + $paymentNonce->method('execute') + ->willReturn($commandResult); + + return $paymentNonce; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_customer_not_default_store.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_customer_not_default_store.php new file mode 100644 index 0000000000000..6448a570424e2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_customer_not_default_store.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +include 'testsuite/Magento/Store/_files/second_store.php'; +include 'testsuite/Magento/Customer/_files/customer.php'; + +$quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); +$quote->setStoreId($store->getId()) + ->setIsActive(true) + ->setIsMultiShipping(false) + ->setReservedOrderId('test_order_1_not_default_store') + ->setCustomerId($customer->getId()) + ->save(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_customer_not_default_store_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_customer_not_default_store_rollback.php new file mode 100644 index 0000000000000..e3e1513cb6144 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_customer_not_default_store_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +$quote->load('test_order_1_not_default_store', 'reserved_order_id')->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_guest_not_default_store.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_guest_not_default_store.php new file mode 100644 index 0000000000000..bbd3d5efbe8c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_guest_not_default_store.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +include 'testsuite/Magento/Store/_files/second_store.php'; + +$quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); +$quote->setStoreId($store->getId()) + ->setIsActive(true) + ->setIsMultiShipping(false) + ->setReservedOrderId('test_order_1_not_default_store_guest') + ->save(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_guest_not_default_store_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_guest_not_default_store_rollback.php new file mode 100644 index 0000000000000..f511133280e7f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_guest_not_default_store_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +$quote->load('test_order_1_not_default_store_guest', 'reserved_order_id')->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php index 507f6b755bcda..e66227a60e8f0 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/discount_10percent_generalusers.php @@ -21,7 +21,7 @@ ], 'customer_group_ids' => [1], 'coupon_type' => \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC, - 'coupon_code' => uniqid(), + 'coupon_code' => '2?ds5!2d', 'simple_action' => \Magento\SalesRule\Model\Rule::BY_PERCENT_ACTION, 'discount_amount' => 10, 'discount_step' => 1 diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ConverterStub.php b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ConverterStub.php similarity index 63% rename from dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ConverterStub.php rename to dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ConverterStub.php index 223ef35c0dcd3..7493d31f02b31 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ConverterStub.php +++ b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ConverterStub.php @@ -3,14 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Paypal\Model\Config\Structure\Reader; +declare(strict_types=1); + +namespace Magento\Config\Model\Config\Structure\Reader; + +use Magento\Config\Model\Config\Structure\Converter; /** - * Class ConverterStub + * Class ConverterStub used for ReaderTest. */ -class ConverterStub extends \Magento\Config\Model\Config\Structure\Converter +class ConverterStub extends Converter { /** + * Convert dom document wrapper. + * * @param \DOMDocument $document * @return array|null */ @@ -20,7 +26,7 @@ public function getArrayData(\DOMDocument $document) } /** - * Convert dom document + * Convert dom document. * * @param \DOMNode $source * @return array diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ReaderStub.php b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ReaderStub.php similarity index 53% rename from dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ReaderStub.php rename to dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ReaderStub.php index ed1366ad737f9..866ff91678ec4 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ReaderStub.php +++ b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ReaderStub.php @@ -3,14 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Paypal\Model\Config\Structure\Reader; +declare(strict_types=1); + +namespace Magento\Config\Model\Config\Structure\Reader; + +use Magento\Config\Model\Config\Structure\Reader; /** - * Class ReaderStub + * Class ReaderStub used for testing protected Reader::_readFiles() method. */ -class ReaderStub extends \Magento\Config\Model\Config\Structure\Reader +class ReaderStub extends Reader { /** + * Wrapper for protected Reader::_readFiles() method. + * * @param array $fileList * @return array * @throws \Magento\Framework\Exception\LocalizedException diff --git a/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ReaderTest.php b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ReaderTest.php new file mode 100644 index 0000000000000..eef8e68458d91 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/ReaderTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Config\Model\Config\Structure\Reader; + +use Magento\Config\Model\Config\SchemaLocator; +use Magento\Framework\App\Utility\Files; +use Magento\Framework\Config\Dom; +use Magento\Framework\Config\FileResolverInterface; +use Magento\Framework\Config\ValidationStateInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\TemplateEngine\Xhtml\CompilerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Class ReaderTest check Magento\Config\Model\Config\Structure\Reader::_readFiles() method. + */ +class ReaderTest extends \PHPUnit\Framework\TestCase +{ + /** + * Test config location. + * + * @string + */ + const CONFIG = '/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/_files/'; + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Files + */ + private $fileUtility; + + /** + * @var ValidationStateInterface + */ + private $validationStateMock; + + /** + * @var \Magento\Framework\Config\SchemaLocatorInterface + */ + private $schemaLocatorMock; + + /** + * @var FileResolverInterface + */ + private $fileResolverMock; + + /** + * @var ReaderStub + */ + private $reader; + + /** + * @var ConverterStub + */ + private $converter; + + /** + * @var CompilerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $compiler; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->fileUtility = Files::init(); + + $this->validationStateMock = $this->getMockBuilder(ValidationStateInterface::class) + ->setMethods(['isValidationRequired']) + ->getMockForAbstractClass(); + $this->schemaLocatorMock = $this->getMockBuilder(SchemaLocator::class) + ->disableOriginalConstructor() + ->setMethods(['getPerFileSchema']) + ->getMock(); + $this->fileResolverMock = $this->getMockBuilder(FileResolverInterface::class) + ->getMockForAbstractClass(); + + $this->validationStateMock->expects($this->atLeastOnce()) + ->method('isValidationRequired') + ->willReturn(false); + $this->schemaLocatorMock->expects($this->atLeastOnce()) + ->method('getPerFileSchema') + ->willReturn(false); + + $this->converter = $this->objectManager->create(ConverterStub::class); + + //Isolate test from actual configuration, and leave only sample data. + $this->compiler = $this->getMockBuilder(CompilerInterface::class) + ->disableOriginalConstructor() + ->setMethods(['compile']) + ->getMockForAbstractClass(); + + $this->reader = $this->objectManager->create( + ReaderStub::class, + [ + 'fileResolver' => $this->fileResolverMock, + 'converter' => $this->converter, + 'schemaLocator' => $this->schemaLocatorMock, + 'validationState' => $this->validationStateMock, + 'fileName' => 'no_existing_file.xml', + 'compiler' => $this->compiler, + 'domDocumentClass' => Dom::class + ] + ); + } + + /** + * The test checks the file structure after processing the nodes responsible for inserting content. + * + * @return void + */ + public function testXmlConvertedConfigurationAndCompereStructure() + { + $actual = $this->reader->readFiles(['actual' => $this->getContent()]); + + $document = new \DOMDocument(); + $document->loadXML($this->getContent()); + + $expected = $this->converter->getArrayData($document); + + $this->assertEquals($expected, $actual); + } + + /** + * Get config sample data for test. + * + * @return string + */ + protected function getContent() + { + $files = $this->fileUtility->getFiles([BP . static::CONFIG], 'config.xml'); + + return file_get_contents(reset($files)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/actual/config.xml b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/_files/config.xml similarity index 100% rename from dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/actual/config.xml rename to dev/tests/integration/testsuite/Magento/Config/Model/Config/Structure/Reader/_files/config.xml diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 7f8996daa6e97..10a6b9d8caae4 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -8,6 +8,7 @@ namespace Magento\Framework\GraphQl\Config; use Magento\Framework\App\Cache; +use Magento\Framework\App\Request\Http; use Magento\Framework\GraphQl\Config; use Magento\Framework\GraphQl\Schema\SchemaGenerator; use Magento\Framework\ObjectManagerInterface; @@ -175,8 +176,9 @@ enumValues(includeDeprecated: true) { 'operationName' => 'IntrospectionQuery' ]; /** @var Http $request */ - $request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class); + $request = $this->objectManager->get(Http::class); $request->setPathInfo('/graphql'); + $request->setMethod('POST'); $request->setContent(json_encode($postData)); $headers = $this->objectManager->create(\Zend\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php new file mode 100644 index 0000000000000..f465f482275c1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +$product = $productRepository->get('simple_product'); +$extensionAttributes = $product->getExtensionAttributes(); +$stockItem = $extensionAttributes->getStockItem(); +$stockItem->setIsInStock(false); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product.php new file mode 100644 index 0000000000000..732c18d4d7340 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Api\DataObjectHelper; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductInterfaceFactory $productFactory */ +$productFactory = $objectManager->get(ProductInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + +$product = $productFactory->create(); +$productData = [ + ProductInterface::TYPE_ID => Type::TYPE_SIMPLE, + ProductInterface::ATTRIBUTE_SET_ID => 4, + ProductInterface::SKU => 'simple_product', + ProductInterface::NAME => 'Simple Product', + ProductInterface::PRICE => 10, + ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH, + ProductInterface::STATUS => Status::STATUS_ENABLED, +]; +$dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); +/** Out of interface */ +$product + ->setWebsiteIds([1]) + ->setStockData([ + 'qty' => 85.5, + 'is_in_stock' => true, + 'manage_stock' => true, + 'is_qty_decimal' => true + ]); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product_rollback.php new file mode 100644 index 0000000000000..9a54f663c9c13 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Catalog/_files/simple_product_rollback.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$currentArea = $registry->registry('isSecureArea'); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + $productRepository->deleteById('simple_product'); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', $currentArea); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php index 384892d6fd5d2..d0d746812ec44 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php @@ -38,6 +38,9 @@ class GraphQlControllerTest extends \Magento\TestFramework\Indexer\TestCase /** @var MetadataPool */ private $metadataPool; + /** @var Http */ + private $request; + public static function setUpBeforeClass() { $db = Bootstrap::getInstance()->getBootstrap() @@ -57,6 +60,7 @@ protected function setUp() : void $this->graphql = $this->objectManager->get(\Magento\GraphQl\Controller\GraphQl::class); $this->jsonSerializer = $this->objectManager->get(SerializerInterface::class); $this->metadataPool = $this->objectManager->get(MetadataPool::class); + $this->request = $this->objectManager->get(Http::class); } /** @@ -86,27 +90,120 @@ public function testDispatch() : void } QUERY; $postData = [ - 'query' => $query, - 'variables' => null, + 'query' => $query, + 'variables' => null, 'operationName' => null ]; - /** @var Http $request */ - $request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class); - $request->setPathInfo('/graphql'); - $request->setContent(json_encode($postData)); + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('POST'); + $this->request->setContent(json_encode($postData)); $headers = $this->objectManager->create(\Zend\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); - $request->setHeaders($headers); - $response = $this->graphql->dispatch($request); + $this->request->setHeaders($headers); + $response = $this->graphql->dispatch($this->request); $output = $this->jsonSerializer->unserialize($response->getContent()); $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $this->assertArrayNotHasKey('errors', $output, 'Response has errors'); - $this->assertTrue(!empty($output['data']['products']['items']), 'Products array has items'); - $this->assertTrue(!empty($output['data']['products']['items'][0]), 'Products array has items'); - $this->assertEquals($output['data']['products']['items'][0]['id'], $product->getData($linkField)); - $this->assertEquals($output['data']['products']['items'][0]['sku'], $product->getSku()); - $this->assertEquals($output['data']['products']['items'][0]['name'], $product->getName()); + $this->assertNotEmpty($output['data']['products']['items'], 'Products array has items'); + $this->assertNotEmpty($output['data']['products']['items'][0], 'Products array has items'); + $this->assertEquals($product->getData($linkField), $output['data']['products']['items'][0]['id']); + $this->assertEquals($product->getSku(), $output['data']['products']['items'][0]['sku']); + $this->assertEquals($product->getName(), $output['data']['products']['items'][0]['name']); + } + + /** + * Test request is dispatched and response generated when using GET request with query string + * + * @return void + */ + public function testDispatchWithGet() : void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + /** @var ProductInterface $product */ + $product = $productRepository->get('simple1'); + + $query + = <<<QUERY + { + products(filter: {sku: {eq: "simple1"}}) + { + items { + id + name + sku + } + } + } +QUERY; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setQueryValue('query', $query); + $response = $this->graphql->dispatch($this->request); + $output = $this->jsonSerializer->unserialize($response->getContent()); + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + $this->assertArrayNotHasKey('errors', $output, 'Response has errors'); + $this->assertNotEmpty($output['data']['products']['items'], 'Products array has items'); + $this->assertNotEmpty($output['data']['products']['items'][0], 'Products array has items'); + $this->assertEquals($product->getData($linkField), $output['data']['products']['items'][0]['id']); + $this->assertEquals($product->getSku(), $output['data']['products']['items'][0]['sku']); + $this->assertEquals($product->getName(), $output['data']['products']['items'][0]['name']); + } + + /** Test request is dispatched and response generated when using GET request with parameterized query string + * + * @return void + */ + public function testDispatchGetWithParameterizedVariables() : void + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + /** @var ProductInterface $product */ + $product = $productRepository->get('simple1'); + $query = <<<QUERY +query GetProducts(\$filterInput:ProductFilterInput){ + products( + filter:\$filterInput + ){ + items{ + id + name + sku + } + } +} +QUERY; + + $variables = [ + 'filterInput' => [ + 'sku' => ['eq' => 'simple1'] + ] + ]; + $queryParams = [ + 'query' => $query, + 'variables' => json_encode($variables), + 'operationName' => 'GetProducts' + ]; + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('GET'); + $this->request->setParams($queryParams); + $response = $this->graphql->dispatch($this->request); + $output = $this->jsonSerializer->unserialize($response->getContent()); + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + $this->assertArrayNotHasKey('errors', $output, 'Response has errors'); + $this->assertNotEmpty($output['data']['products']['items'], 'Products array has items'); + $this->assertNotEmpty($output['data']['products']['items'][0], 'Products array has items'); + $this->assertEquals($product->getData($linkField), $output['data']['products']['items'][0]['id']); + $this->assertEquals($product->getSku(), $output['data']['products']['items'][0]['sku']); + $this->assertEquals($product->getName(), $output['data']['products']['items'][0]['name']); } /** @@ -136,25 +233,25 @@ public function testError() : void QUERY; $postData = [ - 'query' => $query, - 'variables' => null, + 'query' => $query, + 'variables' => null, 'operationName' => null ]; - /** @var Http $request */ - $request = $this->objectManager->get(\Magento\Framework\App\Request\Http::class); - $request->setPathInfo('/graphql'); - $request->setContent(json_encode($postData)); + + $this->request->setPathInfo('/graphql'); + $this->request->setMethod('POST'); + $this->request->setContent(json_encode($postData)); $headers = $this->objectManager->create(\Zend\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); - $request->setHeaders($headers); - $response = $this->graphql->dispatch($request); + $this->request->setHeaders($headers); + $response = $this->graphql->dispatch($this->request); $outputResponse = $this->jsonSerializer->unserialize($response->getContent()); if (isset($outputResponse['errors'][0])) { if (is_array($outputResponse['errors'][0])) { foreach ($outputResponse['errors'] as $error) { $this->assertEquals( - $error['category'], - \Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY + \Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY, + $error['category'] ); if (isset($error['message'])) { $this->assertEquals($error['message'], 'Invalid entity_type specified: invalid'); @@ -168,12 +265,4 @@ public function testError() : void } } } - - /** - * teardown - */ - public function tearDown() - { - parent::tearDown(); - } } diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php index d23381e33d436..f62b463d94003 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php @@ -20,7 +20,7 @@ /** @var CartRepositoryInterface $cartRepository */ $cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); -$product = $productRepository->get('simple'); +$product = $productRepository->get('simple_product'); $quote = $quoteFactory->create(); $quoteResource->load($quote, 'test_quote', 'reserved_order_id'); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon.php new file mode 100644 index 0000000000000..c70efa9a12a5d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CouponManagementInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponManagementInterface $couponManagement */ +$couponManagement = Bootstrap::getObjectManager()->get(CouponManagementInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$couponManagement->set($quote->getId(), '2?ds5!2d'); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon_rollback.php new file mode 100644 index 0000000000000..5431c25b7df53 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/apply_coupon_rollback.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Api\CouponManagementInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponManagementInterface $couponManagement */ +$couponManagement = Bootstrap::getObjectManager()->get(CouponManagementInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$couponManagement->remove($quote->getId()); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_coupon_expired.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_coupon_expired.php new file mode 100644 index 0000000000000..5316b184ecd15 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_coupon_expired.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SalesRule\Model\CouponFactory; +use Magento\SalesRule\Model\Spi\CouponResourceInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponResourceInterface $couponResource */ +$couponResource = Bootstrap::getObjectManager()->get(CouponResourceInterface::class); +/** @var CouponFactory $couponFactory */ +$couponFactory = Bootstrap::getObjectManager()->get(CouponFactory::class); + +$coupon = $couponFactory->create(); +$coupon->loadByCode('2?ds5!2d'); +$yesterday = new \DateTime(); +$yesterday->add(\DateInterval::createFromDateString('-1 day')); +$coupon->setExpirationDate($yesterday->format('Y-m-d')); +$couponResource->save($coupon); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_coupon_expired_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_coupon_expired_rollback.php new file mode 100644 index 0000000000000..32c3d78bafd09 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_coupon_expired_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SalesRule\Model\CouponFactory; +use Magento\SalesRule\Model\Spi\CouponResourceInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponResourceInterface $couponResource */ +$couponResource = Bootstrap::getObjectManager()->get(CouponResourceInterface::class); +/** @var CouponFactory $couponFactory */ +$couponFactory = Bootstrap::getObjectManager()->get(CouponFactory::class); + +$coupon = $couponFactory->create(); +$coupon->loadByCode('2?ds5!2d'); + +if ($coupon->getId()) { + $coupon->setExpirationDate(null); + $couponResource->save($coupon); +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product.php new file mode 100644 index 0000000000000..e58c6b21d8d23 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SalesRule\Api\Data\ConditionInterface; +use Magento\SalesRule\Api\Data\ConditionInterfaceFactory; +use Magento\SalesRule\Api\RuleRepositoryInterface; +use Magento\SalesRule\Model\CouponFactory; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponFactory $couponFactory */ +$couponFactory = Bootstrap::getObjectManager()->get(CouponFactory::class); +/** @var ConditionInterfaceFactory $conditionFactory */ +$conditionFactory = Bootstrap::getObjectManager()->get(ConditionInterfaceFactory::class); +/** @var RuleRepositoryInterface $ruleRepository */ +$ruleRepository = Bootstrap::getObjectManager()->get(RuleRepositoryInterface::class); + +$couponCode = '2?ds5!2d'; +$sku = 'simple_product'; + +$coupon = $couponFactory->create(); +$coupon->loadByCode($couponCode); +$ruleId = $coupon->getRuleId(); +$salesRule = $ruleRepository->getById($ruleId); + +/** @var ConditionInterface $conditionProductSku */ +$conditionProductSku = $conditionFactory->create(); +$conditionProductSku->setConditionType(\Magento\SalesRule\Model\Rule\Condition\Product::class); +$conditionProductSku->setAttributeName('sku'); +$conditionProductSku->setValue('1'); +$conditionProductSku->setOperator('!='); +$conditionProductSku->setValue($sku); + +/** @var ConditionInterface $conditionProductFound */ +$conditionProductFound = $conditionFactory->create(); +$conditionProductFound->setConditionType(\Magento\SalesRule\Model\Rule\Condition\Product\Found::class); +$conditionProductFound->setValue('1'); +$conditionProductFound->setAggregatorType('all'); +$conditionProductFound->setConditions([$conditionProductSku]); + +/** @var ConditionInterface $conditionCombine */ +$conditionCombine = $conditionFactory->create(); +$conditionCombine->setConditionType(\Magento\SalesRule\Model\Rule\Condition\Combine::class); +$conditionCombine->setValue('1'); +$conditionCombine->setAggregatorType('all'); +$conditionCombine->setConditions([$conditionProductFound]); + +$salesRule->setCondition($conditionCombine); +$ruleRepository->save($salesRule); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product_rollback.php new file mode 100644 index 0000000000000..86ab253f1d3c0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/restrict_coupon_usage_for_simple_product_rollback.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SalesRule\Api\Data\ConditionInterface; +use Magento\SalesRule\Api\Data\ConditionInterfaceFactory; +use Magento\SalesRule\Api\RuleRepositoryInterface; +use Magento\SalesRule\Model\CouponFactory; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var CouponFactory $couponFactory */ +$couponFactory = Bootstrap::getObjectManager()->get(CouponFactory::class); +/** @var ConditionInterfaceFactory $conditionFactory */ +$conditionFactory = Bootstrap::getObjectManager()->get(ConditionInterfaceFactory::class); +/** @var RuleRepositoryInterface $ruleRepository */ +$ruleRepository = Bootstrap::getObjectManager()->get(RuleRepositoryInterface::class); + +$couponCode = '2?ds5!2d'; +$sku = 'simple_product'; + +$coupon = $couponFactory->create(); +$coupon->loadByCode($couponCode); + +if ($coupon->getId()) { + $ruleId = $coupon->getRuleId(); + $salesRule = $ruleRepository->getById($ruleId); + + /** @var ConditionInterface $conditionCombine */ + $conditionCombine = $conditionFactory->create(); + $conditionCombine->setConditions([]); + + $salesRule->setCondition($conditionCombine); + $ruleRepository->save($salesRule); +} diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_billing_address.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_billing_address.php new file mode 100644 index 0000000000000..2e15ef218d0c5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_billing_address.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\DataObjectHelper; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\AddressInterfaceFactory; +use Magento\Quote\Api\BillingAddressManagementInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; + +use Magento\TestFramework\Helper\Bootstrap; + +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var AddressInterfaceFactory $quoteAddressFactory */ +$quoteAddressFactory = Bootstrap::getObjectManager()->get(AddressInterfaceFactory::class); +/** @var DataObjectHelper $dataObjectHelper */ +$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class); +/** @var BillingAddressManagementInterface $billingAddressManagement */ +$billingAddressManagement = Bootstrap::getObjectManager()->get(BillingAddressManagementInterface::class); + +$quoteAddressData = [ + AddressInterface::KEY_TELEPHONE => 3468676, + AddressInterface::KEY_POSTCODE => 75477, + AddressInterface::KEY_COUNTRY_ID => 'US', + AddressInterface::KEY_CITY => 'CityM', + AddressInterface::KEY_COMPANY => 'CompanyName', + AddressInterface::KEY_STREET => 'Green str, 67', + AddressInterface::KEY_LASTNAME => 'Smith', + AddressInterface::KEY_FIRSTNAME => 'John', + AddressInterface::KEY_REGION_ID => 1, +]; +$quoteAddress = $quoteAddressFactory->create(); +$dataObjectHelper->populateWithArray($quoteAddress, $quoteAddressData, AddressInterfaceFactory::class); + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$billingAddressManagement->assign($quote->getId(), $quoteAddress); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ReaderTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ReaderTest.php deleted file mode 100644 index 6b966a045c982..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/ReaderTest.php +++ /dev/null @@ -1,135 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Paypal\Model\Config\Structure\Reader; - -use Magento\Framework\ObjectManagerInterface; - -/** - * Class ReaderTest - */ -class ReaderTest extends \PHPUnit\Framework\TestCase -{ - const EXPECTED = '/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected'; - - const ACTUAL = '/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/actual'; - - /** - * @var ObjectManagerInterface - */ - protected $objectManager; - - /** - * @var \Magento\Framework\App\Utility\Files - */ - protected $fileUtility; - - /** - * @var \Magento\Framework\Config\ValidationStateInterface - */ - protected $validationStateMock; - - /** - * @var \Magento\Framework\Config\SchemaLocatorInterface - */ - protected $schemaLocatorMock; - - /** - * @var \Magento\Framework\Config\FileResolverInterface - */ - protected $fileResolverMock; - - /** - * @var \Magento\Paypal\Model\Config\Structure\Reader\ReaderStub - */ - protected $reader; - - /** - * @var \Magento\Paypal\Model\Config\Structure\Reader\ConverterStub - */ - protected $converter; - - /** - * Set up - * - * @return void - */ - protected function setUp() - { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->fileUtility = \Magento\Framework\App\Utility\Files::init(); - - $this->validationStateMock = $this->getMockBuilder(\Magento\Framework\Config\ValidationStateInterface::class) - ->setMethods(['isValidationRequired']) - ->getMockForAbstractClass(); - $this->schemaLocatorMock = $this->getMockBuilder(\Magento\Config\Model\Config\SchemaLocator::class) - ->disableOriginalConstructor() - ->setMethods(['getPerFileSchema']) - ->getMock(); - $this->fileResolverMock = $this->getMockBuilder(\Magento\Framework\Config\FileResolverInterface::class) - ->getMockForAbstractClass(); - - $this->validationStateMock->expects($this->atLeastOnce()) - ->method('isValidationRequired') - ->willReturn(false); - $this->schemaLocatorMock->expects($this->atLeastOnce()) - ->method('getPerFileSchema') - ->willReturn(false); - - /** @var \Magento\Paypal\Model\Config\Structure\Reader\ConverterStub $converter */ - $this->converter = $this->objectManager->create( - \Magento\Paypal\Model\Config\Structure\Reader\ConverterStub::class - ); - - $this->reader = $this->objectManager->create( - \Magento\Paypal\Model\Config\Structure\Reader\ReaderStub::class, - [ - 'fileResolver' => $this->fileResolverMock, - 'converter' => $this->converter, - 'schemaLocator' => $this->schemaLocatorMock, - 'validationState' => $this->validationStateMock, - 'fileName' => 'no_existing_file.xml', - 'domDocumentClass' => \Magento\Framework\Config\Dom::class - ] - ); - } - - /** - * The test checks the file structure after processing the nodes responsible for inserting content - * - * @return void - */ - public function testXmlConvertedConfigurationAndCompereStructure() - { - $actual = $this->reader->readFiles(['actual' => $this->getActualContent()]); - - $document = new \DOMDocument(); - $document->loadXML($this->getExpectedContent()); - - $expected = $this->converter->getArrayData($document); - - $this->assertEquals($expected, $actual); - } - - /** - * @return string - */ - protected function getActualContent() - { - $files = $this->fileUtility->getFiles([BP . static::ACTUAL], 'config.xml'); - - return file_get_contents(reset($files)); - } - - /** - * @return string - */ - protected function getExpectedContent() - { - $files = $this->fileUtility->getFiles([BP . static::EXPECTED], 'config.xml'); - - return file_get_contents(reset($files)); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml deleted file mode 100644 index 222b9974177de..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml +++ /dev/null @@ -1,2626 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> - <system> - <section id="payment"> - <group id="account" translate="label" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Merchant Location</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="merchant_country" type="select" translate="label comment" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Merchant Country</label> - <comment>If not specified, Default Country from General Config will be used</comment> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Country</frontend_model> - <source_model>Magento\Paypal\Model\System\Config\Source\MerchantCountry</source_model> - <backend_model>Magento\Paypal\Model\System\Config\Backend\MerchantCountry</backend_model> - <config_path>paypal/general/merchant_country</config_path> - </field> - </group> - <group id="recommended_solutions" translate="label" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Recommended Solutions:</label> - <fieldset_css>paypal-top-section paypal-recommended-header</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - </group> - <group id="other_paypal_payment_solutions" translate="label" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Other PayPal Payment Solutions:</label> - <fieldset_css>paypal-top-section paypal-other-header</fieldset_css> - <frontend_model>\Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - </group> - <group id="other_payment_methods" translate="label" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Other Payment Methods:</label> - <fieldset_css>paypal-top-section payments-other-header</fieldset_css> - <frontend_model>\Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> - </group> - </section> - <section id="payment_all_paypal" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="paypal_payflowpro" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="10"> - <label>Payflow Pro</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <fieldset_css>paypal-other-section</fieldset_css> - <comment><![CDATA[Connect your merchant account with a fully customizable gateway that lets customers pay without leaving your site. (<u>Includes Express Checkout</u>)]]></comment> - <attribute type="activity_path">payment/payflowpro/active</attribute> - <attribute type="paypal_ec_separate">1</attribute> - <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payflow-pro.html</comment> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> - </group> - <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Required PayPal Settings</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <group id="paypal_payflow_api_settings" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Payflow Pro</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="partner" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Partner</label> - <config_path>payment/payflowpro/partner</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="user" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>User</label> - <config_path>payment/payflowpro/user</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="vendor" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Vendor</label> - <config_path>payment/payflowpro/vendor</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="pwd" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Password</label> - <config_path>payment/payflowpro/pwd</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="sandbox_flag" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Test Mode</label> - <config_path>payment/payflowpro/sandbox_flag</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="use_proxy" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Use Proxy</label> - <config_path>payment/payflowpro/use_proxy</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_host" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Proxy Host</label> - <config_path>payment/payflowpro/proxy_host</config_path> - <depends> - <field id="use_proxy">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_port" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1"> - <label>Proxy Port</label> - <config_path>payment/payflowpro/proxy_port</config_path> - <depends> - <field id="use_proxy">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - </group> - <field id="enable_paypal_payflow" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Enable this Solution</label> - <config_path>payment/payflowpro/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - <requires> - <group id="paypal_payflow_api_settings"/> - </requires> - </field> - <field id="payflowpro_cc_vault_active" translate="label" type="select" sortOrder="22" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Vault Enabled</label> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <config_path>payment/payflowpro_cc_vault/active</config_path> - <attribute type="shared">1</attribute> - <requires> - <group id="paypal_payflow_api_settings"/> - </requires> - </field> - </group> - <group id="settings_paypal_payflow" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Basic Settings - PayPal Payflow Pro</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Title</label> - <comment>It is recommended to set this value to "Debit or Credit Card" per store views.</comment> - <config_path>payment/payflowpro/title</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="payflowpro_cc_vault_title" translate="label" type="text" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> - <label>Vault Title</label> - <config_path>payment/payflowpro_cc_vault/title</config_path> - </field> - <field id="sort_order" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Sort Order</label> - <config_path>payment/payflowpro/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - </field> - <field id="payment_action" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Payment Action</label> - <config_path>payment/payflowpro/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="heading_cc" translate="label" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Credit Card Settings</label> - <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> - <attribute type="shared">1</attribute> - </field> - <field id="cctypes" translate="label comment" type="multiselect" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Allowed Credit Card Types</label> - <comment> - <![CDATA[Supporting of American Express cards require additional agreement. Learn more at <a href="http://www.paypal.com/amexupdate">http://www.paypal.com/amexupdate</a>.]]> - </comment> - <config_path>payment/payflowpro/cctypes</config_path> - <source_model>Magento\Paypal\Model\Config::getPayflowproCcTypesAsOptionArray</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="settings_paypal_payflow_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="allowspecific" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>Payment Applicable From</label> - <config_path>payment/payflowpro/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Countries Payment Applicable From</label> - <config_path>payment/payflowpro/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <depends> - <field id="allowspecific">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="debug" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Debug Mode</label> - <config_path>payment/payflowpro/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="verify_peer" translate="label" type="select" sortOrder="35" showInDefault="1" showInWebsite="1"> - <label>Enable SSL verification</label> - <config_path>payment/payflowpro/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="useccv" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Require CVV Entry</label> - <config_path>payment/payflowpro/useccv</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="paypal_payflow_avs_check" translate="label" showInDefault="1" showInWebsite="1" sortOrder="80"> - <label>CVV and AVS Settings</label> - <field id="heading_avs_settings" translate="label" sortOrder="0" showInDefault="1" showInWebsite="1"> - <label>Reject Transaction if:</label> - <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> - <attribute type="shared">1</attribute> - </field> - <field id="avs_street" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1"> - <label>AVS Street Does Not Match</label> - <config_path>payment/payflowpro/avs_street</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="avs_zip" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1"> - <label>AVS Zip Does Not Match</label> - <config_path>payment/payflowpro/avs_zip</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="avs_international" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1"> - <label>Card Issuer Is Outside The United States</label> - <config_path>payment/payflowpro/avs_international</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="avs_security_code" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="1"> - <label>Card Security Code Does Not Match</label> - <config_path>payment/payflowpro/avs_security_code</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">0</attribute> - </field> - </group> - <group id="paypal_payflow_settlement_report" translate="label" showInDefault="1" showInWebsite="1" sortOrder="90"> - <label>Settlement Report Settings</label> - <field id="heading_sftp" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_sftp"/> - <field id="settlement_reports_ftp_login" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_login"/> - <field id="settlement_reports_ftp_password" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_password"/> - <field id="settlement_reports_ftp_sandbox" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_sandbox"/> - <field id="settlement_reports_ftp_ip" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_ip"/> - <field id="settlement_reports_ftp_path" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_path"/> - <field id="heading_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_schedule"/> - <field id="settlement_reports_active" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_active"/> - <field id="settlement_reports_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_schedule"/> - <field id="settlement_reports_time" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_time"/> - </group> - </group> - </group> - </group> - <group id="payflow_link" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Payflow Link</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <fieldset_css>paypal-other-section</fieldset_css> - <comment><![CDATA[Connect your merchant account with a PCI-compliant gateway that lets customers pay without leaving your site. (<u>Includes Express Checkout</u>)]]></comment> - <attribute type="activity_path">payment/payflow_link/active</attribute> - <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payflow-link.html</comment> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> - </group> - <group id="payflow_link_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Required PayPal Settings</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <group id="payflow_link_payflow_link" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Payflow Link and Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="business_account" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/business_account" translate="label" sortOrder="5"> - <frontend_class>not-required</frontend_class> - <label>Email Associated with PayPal Merchant Account (Optional)</label> - <attribute type="shared">1</attribute> - </field> - <field id="partner" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>Partner</label> - <config_path>payment/payflow_link/partner</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="vendor" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Vendor</label> - <config_path>payment/payflow_link/vendor</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="user" translate="label comment" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>User</label> - <comment>If you do not have multiple users set up on your account, please re-enter your Vendor/Merchant Login here.</comment> - <config_path>payment/payflow_link/user</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="pwd" translate="label" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Password</label> - <config_path>payment/payflow_link/pwd</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="sandbox_flag" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Test Mode</label> - <config_path>payment/payflow_link/sandbox_flag</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="use_proxy" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Use Proxy</label> - <config_path>payment/payflow_link/use_proxy</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_host" translate="label" type="text" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Proxy Host</label> - <config_path>payment/payflow_link/proxy_host</config_path> - <depends> - <field id="use_proxy">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_port" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Proxy Port</label> - <config_path>payment/payflow_link/proxy_port</config_path> - <depends> - <field id="use_proxy">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="payflowlink_info" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink\Info</frontend_model> - <attribute type="shared">1</attribute> - </field> - </group> - - - - <field id="enable_payflow_link" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Enable Payflow Link</label> - <config_path>payment/payflow_link/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - <requires> - <group id="payflow_link_payflow_link"/> - </requires> - </field> - <field id="enable_express_checkout_basic" translate="label" type="select" sortOrder="40"> - <label>Enable Express Checkout</label> - <config_path>payment/payflow_express/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Express</frontend_model> - </field> - <field id="enable_express_checkout" extends="payment_all_paypal/payflow_link/payflow_link_required/enable_express_checkout_basic" showInDefault="1" showInWebsite="1"> - <requires> - <field id="enable_payflow_link"/> - </requires> - </field> - <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41" showInDefault="1" showInWebsite="1"> - <comment><![CDATA[Payflow Link lets you give customers access to financing through PayPal Credit® - at no additional cost to you. - You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> - </comment> - <config_path>payment/payflow_express_bml/active</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> - <requires> - <field id="enable_express_checkout"/> - </requires> - </field> - <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> - <config_path>payment/payflow_express_bml/sort_order</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> - <depends> - <field id="enable_express_checkout_bml">1</field> - </depends> - </field> - - - - <group id="payflow_link_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="60"> - <label>Advertise PayPal Credit</label> - <comment> - <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> - <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing - from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. - Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. - The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> - </comment> - <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> - <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> - <group id="payflow_link_settings_bml_homepage" translate="label" showInWebsite="1" sortOrder="20" showInDefault="1" showInStore="1"> - <label>Home Page</label> - <field id="bml_homepage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_display"/> - <field id="payflow_link_bml_homepage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_position"/> - <field id="payflow_link_bml_homepage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/homepage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPH</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="payflow_link_bml_homepage_position">0</field> - </depends> - </field> - <field id="payflow_link_bml_homepage_size2" extends="payment_all_paypal/payflow_link/payflow_link_required/payflow_link_advertise_bml/payflow_link_settings_bml_homepage/payflow_link_bml_homepage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPS</source_model> - <depends> - <field id="payflow_link_bml_homepage_position">1</field> - </depends> - </field> - </group> - <group id="payflow_link_settings_bml_categorypage" translate="label" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Catalog Category Page</label> - <field id="bml_categorypage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_display"/> - <field id="payflow_link_bml_categorypage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_position" /> - <field id="payflow_link_bml_categorypage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="40"> - <label>Size</label> - <config_path>payment/paypal_express_bml/categorypage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="payflow_link_bml_categorypage_position">0</field> - </depends> - </field> - <field id="payflow_link_bml_categorypage_size2" extends="payment_all_paypal/payflow_link/payflow_link_required/payflow_link_advertise_bml/payflow_link_settings_bml_categorypage/payflow_link_bml_categorypage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPS</source_model> - <depends> - <field id="payflow_link_bml_categorypage_position">1</field> - </depends> - </field> - </group> - <group id="payflow_link_settings_bml_productpage" translate="label" showInDefault="1" showInStore="1" showInWebsite="1" sortOrder="50"> - <label>Catalog Product Page</label> - <field id="bml_productpage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_display" /> - <field id="payflow_link_bml_productpage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_position" /> - <field id="payflow_link_bml_productpage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/productpage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="payflow_link_bml_productpage_position">0</field> - </depends> - </field> - <field id="payflow_link_bml_productpage_size2" extends="payment_all_paypal/payflow_link/payflow_link_required/payflow_link_advertise_bml/payflow_link_settings_bml_productpage/payflow_link_bml_productpage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPN</source_model> - <depends> - <field id="payflow_link_bml_productpage_position">1</field> - </depends> - </field> - </group> - <group id="payflow_link_settings_bml_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> - <label>Checkout Cart Page</label> - <field id="bml_checkout_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_display" /> - <field id="payflow_link_bml_checkout_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_position" /> - <field id="payflow_link_bml_checkout_size1" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Size</label> - <config_path>payment/paypal_express_bml/checkout_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="payflow_link_bml_checkout_position">0</field> - </depends> - </field> - <field id="payflow_link_bml_checkout_size2" extends="payment_all_paypal/payflow_link/payflow_link_required/payflow_link_advertise_bml/payflow_link_settings_bml_checkout/payflow_link_bml_checkout_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="payflow_link_bml_checkout_position">1</field> - </depends> - </field> - </group> - </group> - </group> - <group id="settings_payflow_link" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Basic Settings - PayPal Payflow Link</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Title</label> - <comment>It is recommended to set this value to "Debit or Credit Card" per store views.</comment> - <config_path>payment/payflow_link/title</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="sort_order" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Sort Order</label> - <config_path>payment/payflow_link/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - </field> - <field id="payment_action" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Payment Action</label> - <config_path>payment/payflow_link/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="settings_payflow_link_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="allowspecific" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>Payment Applicable From</label> - <config_path>payment/payflow_link/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Countries Payment Applicable From</label> - <config_path>payment/payflow_link/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <depends> - <field id="allowspecific">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="debug" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Debug Mode</label> - <config_path>payment/payflow_link/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="verify_peer" translate="label" type="select" sortOrder="35" showInDefault="1" showInWebsite="1"> - <label>Enable SSL verification</label> - <config_path>payment/payflow_link/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="csc_editable" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>CVV Entry is Editable</label> - <config_path>payment/payflow_link/csc_editable</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="csc_required" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Require CVV Entry</label> - <config_path>payment/payflow_link/csc_required</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <depends> - <field id="csc_editable">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="email_confirmation" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Send Email Confirmation</label> - <config_path>payment/payflow_link/email_confirmation</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="url_method" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>URL method for Cancel URL and Return URL</label> - <config_path>payment/payflow_link/url_method</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\UrlMethod</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="payflow_link_settlement_report" translate="label" showInDefault="1" showInWebsite="1" sortOrder="80"> - <label>Settlement Report Settings</label> - <field id="heading_sftp" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_sftp"/> - <field id="settlement_reports_ftp_login" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_login"/> - <field id="settlement_reports_ftp_password" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_password"/> - <field id="settlement_reports_ftp_sandbox" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_sandbox"/> - <field id="settlement_reports_ftp_ip" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_ip"/> - <field id="settlement_reports_ftp_path" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_path"/> - <field id="heading_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_schedule"/> - <field id="settlement_reports_active" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_active"/> - <field id="settlement_reports_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_schedule"/> - <field id="settlement_reports_time" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_time"/> - </group> - <group id="payflow_link_frontend" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> - <label>Frontend Experience Settings</label> - <field id="logo" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/logo"/> - <field id="paypal_pages" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_pages"/> - <field id="page_style" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/page_style"/> - <field id="paypal_hdrimg" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrimg"/> - <field id="paypal_hdrbackcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbackcolor"/> - <field id="paypal_hdrbordercolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbordercolor"/> - <field id="paypal_payflowcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_payflowcolor"/> - </group> - </group> - </group> - <group id="settings_payflow_link_express_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Basic Settings - PayPal Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/title" /> - <field id="sort_order" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/sort_order" /> - <field id="payment_action" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/payment_action" /> - <field id="visible_on_product" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/visible_on_product" /> - <group id="settings_payflow_link_express_checkout_advanced" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/paypal_payflow_express_checkout_advanced"/> - </group> - </group> - <group id="express_checkout" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> - <label>Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <fieldset_css>paypal-other-section</fieldset_css> - <comment>Add PayPal as an additional payment method to your checkout page.</comment> - <attribute type="activity_path">payment/paypal_express/active</attribute> - <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-express-checkout.html</comment> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> - </group> - <group id="express_checkout_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Required PayPal Settings</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <group id="express_checkout_required_express_checkout" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="business_account" translate="label comment tooltip" showInDefault="1" showInWebsite="1" sortOrder="5"> - <label>Email Associated with PayPal Merchant Account (Optional)</label> - <frontend_class>not-required</frontend_class> - <comment> - <![CDATA[<a href="http://www.magentocommerce.com/paypal">Start accepting payments via PayPal!</a>]]> - </comment> - <tooltip>Don't have a PayPal account? Simply enter your email address.</tooltip> - <config_path>paypal/general/business_account</config_path> - <validate>validate-email</validate> - <attribute type="shared">1</attribute> - </field> - <field id="api_authentication" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>API Authentication Methods</label> - <config_path>paypal/wpp/api_authentication</config_path> - <source_model>Magento\Paypal\Model\Config::getApiAuthenticationMethods</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="api_username" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>API Username</label> - <config_path>paypal/wpp/api_username</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="api_password" translate="label" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>API Password</label> - <config_path>paypal/wpp/api_password</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="api_signature" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>API Signature</label> - <config_path>paypal/wpp/api_signature</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - <depends> - <field id="api_authentication">0</field> - </depends> - </field> - <field id="api_cert" translate="label" type="file" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>API Certificate</label> - <config_path>paypal/wpp/api_cert</config_path> - <backend_model>Magento\Paypal\Model\System\Config\Backend\Cert</backend_model> - <attribute type="shared">1</attribute> - <depends> - <field id="api_authentication">1</field> - </depends> - </field> - <field id="api_wizard" translate="button_label attribute sandbox_button_label" sortOrder="70" showInDefault="1" showInWebsite="1"> - - <attribute type="button_label">Get Credentials from PayPal</attribute> - <attribute type="button_url"> - <![CDATA[https://www.paypal.com/webapps/merchantboarding/webflow/externalpartnerflow]]> - </attribute> - - <attribute type="sandbox_button_label">Sandbox Credentials</attribute> - <attribute type="sandbox_button_url"> - <![CDATA[https://www.sandbox.paypal.com/webapps/merchantboarding/webflow/externalpartnerflow]]> - </attribute> - - <!-- partnerId --> - <attribute type="partner_id">NB9WWHYEMVUMS</attribute> - <!-- partnerLogoUrl --> - <attribute type="partner_logo_url">Magento_Backend/web/images/logo-magento.png</attribute> - <!-- receiveCredentials --> - <attribute type="receive_credentials">FALSE</attribute> - <!-- showPermissions --> - <attribute type="show_permissions">FALSE</attribute> - <!-- displayMode --> - <attribute type="display_mode">embedded</attribute> - <!-- productIntentID --> - <attribute type="product_intent_id">pp_express</attribute> - - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\ApiWizard</frontend_model> - <attribute type="shared">1</attribute> - </field> - <field id="sandbox_flag" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Sandbox Mode</label> - <config_path>paypal/wpp/sandbox_flag</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="use_proxy" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1"> - <label>API Uses Proxy</label> - <config_path>paypal/wpp/use_proxy</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_host" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1"> - <label>Proxy Host</label> - <config_path>paypal/wpp/proxy_host</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="use_proxy">1</field> - </depends> - </field> - <field id="proxy_port" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> - <label>Proxy Port</label> - <config_path>paypal/wpp/proxy_port</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="use_proxy">1</field> - </depends> - </field> - </group> - <field id="enable_express_checkout" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Enable this Solution</label> - <config_path>payment/paypal_express/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - <requires> - <group id="express_checkout_required_express_checkout"/> - </requires> - </field> - <field id="enable_in_context_checkout" translate="label comment" type="select" sortOrder="21" showInDefault="1" showInWebsite="1"> - <label>Enable In-Context Checkout Experience</label> - <comment> - <![CDATA[See PayPal Feature Support details and list of supported regions - <a href="https://developer.paypal.com/docs/classic/express-checkout/in-context/" target="_blank">here</a>.]]> - </comment> - <config_path>payment/paypal_express/in_context</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\InContextApi</frontend_model> - <requires> - <field id="enable_express_checkout"/> - </requires> - </field> - <field id="merchant_id" translate="label" type="text" sortOrder="22" showInDefault="1" showInWebsite="1"> - <label>Merchant Account ID</label> - <tooltip>You can look up your merchant ID by logging into https://www.paypal.com/. Click the profile icon on the top right side of the page and then select Profile and settings in the Business Profile menu. (If you do not see the profile icon at the top of the page, click Profile, which appears in the top menu when the My Account tab is selected.) Click My business info on the left, and the Merchant account ID is displayed in the list of profile items on the right.</tooltip> - <config_path>payment/paypal_express/merchant_id</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\MerchantId</frontend_model> - <depends> - <field id="enable_in_context_checkout">1</field> - </depends> - <validate>required-entry</validate> - </field> - <field id="enable_express_checkout_bml" translate="label comment" type="select" sortOrder="23" showInDefault="0" showInWebsite="0"> - <label>Enable PayPal Credit</label> - <comment><![CDATA[PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. - You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> - </comment> - <config_path>payment/paypal_express_bml/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\BmlApi</frontend_model> - <requires> - <field id="enable_express_checkout"/> - </requires> - </field> - <field id="express_checkout_bml_sort_order" translate="label" type="text" sortOrder="25" showInDefault="0" showInWebsite="0" showInStore="0"> - <label>Sort Order PayPal Credit</label> - <config_path>payment/paypal_express_bml/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlApiSortOrder</frontend_model> - <depends> - <field id="enable_express_checkout_bml">1</field> - </depends> - </field> - <group id="advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Advertise PayPal Credit</label> - <comment> - <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> - <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing - from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. - Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. - The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> - </comment> - <field id="bml_publisher_id" translate="label comment tooltip" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Publisher ID</label> - <comment><![CDATA[Required to display a banner]]></comment> - <config_path>payment/paypal_express_bml/publisher_id</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="bml_wizard" translate="button_label" sortOrder="15" showInDefault="1" showInWebsite="1"> - <button_label>Get Publisher ID from PayPal</button_label> - <button_url><![CDATA[https://financing.paypal.com/ppfinportal/cart/index?dcp=4eff8563b9cc505e0b9afaff3256705081553c79]]></button_url> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\BmlApiWizard</frontend_model> - </field> - <group id="settings_bml_homepage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Home Page</label> - <field id="bml_homepage_display" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Display</label> - <config_path>payment/paypal_express_bml/homepage_display</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_homepage_position" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="20"> - <label>Position</label> - <config_path>payment/paypal_express_bml/homepage_position</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsHP</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_homepage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/homepage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPH</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="bml_homepage_position">0</field> - </depends> - </field> - <field id="bml_homepage_size2" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPS</source_model> - <depends> - <field id="bml_homepage_position">1</field> - </depends> - </field> - </group> - <group id="settings_bml_categorypage" translate="label" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Catalog Category Page</label> - <field id="bml_categorypage_display" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Display</label> - <config_path>payment/paypal_express_bml/categorypage_display</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_categorypage_position" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="20"> - <label>Position</label> - <config_path>payment/paypal_express_bml/categorypage_position</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCCP</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_categorypage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/categorypage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPC</source_model> - <attribute type="shared">1</attribute> - <depends><field id="bml_categorypage_position">0</field></depends> - </field> - <field id="bml_categorypage_size2" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPS</source_model> - <depends><field id="bml_categorypage_position">1</field></depends> - </field> - </group> - <group id="settings_bml_productpage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>Catalog Product Page</label> - <field id="bml_productpage_display" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Display</label> - <config_path>payment/paypal_express_bml/productpage_display</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_productpage_position" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="20"> - <label>Position</label> - <config_path>payment/paypal_express_bml/productpage_position</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCPP</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_productpage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/productpage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPC</source_model> - <attribute type="shared">1</attribute> - <depends><field id="bml_productpage_position">0</field></depends> - </field> - <field id="bml_productpage_size2" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPN</source_model> - <depends><field id="bml_productpage_position">1</field></depends> - </field> - </group> - <group id="settings_bml_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> - <label>Checkout Cart Page</label> - <field id="bml_checkout_display" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Display</label> - <config_path>payment/paypal_express_bml/checkout_display</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_checkout_position" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="20"> - <label>Position</label> - <config_path>payment/paypal_express_bml/checkout_position</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlPosition::getBmlPositionsCheckout</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="bml_checkout_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/checkout_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC</source_model> - <attribute type="shared">1</attribute> - <depends><field id="bml_checkout_position">0</field></depends> - </field> - <field id="bml_checkout_size2" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN</source_model> - <attribute type="shared">1</attribute> - <depends><field id="bml_checkout_position">1</field></depends> - </field> - </group> - </group> - </group> - <group id="settings_ec" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Basic Settings - PayPal Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Title</label> - <comment>It is recommended to set this value to "PayPal" per store views.</comment> - <config_path>payment/paypal_express/title</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="sort_order" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Sort Order</label> - <config_path>payment/paypal_express/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - </field> - <field id="payment_action" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Payment Action</label> - <config_path>payment/paypal_express/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions\Express</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="visible_on_product" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Display on Product Details Page</label> - <config_path>payment/paypal_express/visible_on_product</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="authorization_honor_period" translate="label comment" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Authorization Honor Period (days)</label> - <comment>Specifies what the Authorization Honor Period is on the merchant’s PayPal account. It must mirror the setting in PayPal.</comment> - <config_path>payment/paypal_express/authorization_honor_period</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="payment_action">Order</field> - </depends> - </field> - <field id="order_valid_period" translate="label comment" type="text" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Order Valid Period (days)</label> - <comment>Specifies what the Order Valid Period is on the merchant’s PayPal account. It must mirror the setting in PayPal.</comment> - <config_path>payment/paypal_express/order_valid_period</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="payment_action">Order</field> - </depends> - </field> - <field id="child_authorization_number" translate="label comment" type="text" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Number of Child Authorizations</label> - <comment>The default number of child authorizations in your PayPal account is 1. To do multiple authorizations please contact PayPal to request an increase.</comment> - <config_path>payment/paypal_express/child_authorization_number</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="payment_action">Order</field> - </depends> - </field> - <group id="settings_ec_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="80"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="visible_on_cart" translate="label comment" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Display on Shopping Cart</label> - <config_path>payment/paypal_express/visible_on_cart</config_path> - <comment>Also affects mini-shopping cart.</comment> - <source_model>Magento\Paypal\Model\System\Config\Source\Yesnoshortcut</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="allowspecific" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>Payment Applicable From</label> - <config_path>payment/paypal_express/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Countries Payment Applicable From</label> - <config_path>payment/paypal_express/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="allowspecific">1</field> - </depends> - </field> - <field id="debug" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Debug Mode</label> - <config_path>payment/paypal_express/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="verify_peer" translate="label" type="select" sortOrder="35" showInDefault="1" showInWebsite="1"> - <label>Enable SSL verification</label> - <config_path>payment/paypal_express/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="line_items_enabled" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Transfer Cart Line Items</label> - <config_path>payment/paypal_express/line_items_enabled</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="transfer_shipping_options" translate="label tooltip comment" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Transfer Shipping Options</label> - <config_path>payment/paypal_express/transfer_shipping_options</config_path> - <tooltip>If this option is enabled, customer can change shipping address and shipping method on PayPal website. In live mode works via HTTPS protocol only.</tooltip> - <comment>Notice that PayPal can handle up to 10 shipping options. That is why Magento will transfer only first 10 cheapest shipping options if there are more than 10 available.</comment> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="line_items_enabled">1</field> - </depends> - </field> - <field id="button_flavor" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Shortcut Buttons Flavor</label> - <config_path>paypal/wpp/button_flavor</config_path> - <source_model>Magento\Paypal\Model\Config::getExpressCheckoutButtonFlavors</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="solution_type" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Enable PayPal Guest Checkout</label> - <comment>Ability for buyer to purchase without PayPal account.</comment> - <config_path>payment/paypal_express/solution_type</config_path> - <source_model>Magento\Paypal\Model\Config::getExpressCheckoutSolutionTypes</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="require_billing_address" translate="label comment" type="select" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Require Customer's Billing Address</label> - <comment>This feature needs be enabled first for the merchant account through PayPal technical support.</comment> - <config_path>payment/paypal_express/require_billing_address</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\RequireBillingAddress</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="allow_ba_signup" translate="label comment tooltip" type="select" sortOrder="90" showInDefault="1" showInWebsite="1"> - <label>Billing Agreement Signup</label> - <comment>Whether to create a billing agreement, if there are no active billing agreements available.</comment> - <tooltip> - <![CDATA[Merchants need to apply to PayPal for enabling billing agreements feature. Do not enable this option until PayPal confirms that billing agreements are enabled for your merchant account.]]> - </tooltip> - <config_path>payment/paypal_express/allow_ba_signup</config_path> - <source_model>Magento\Paypal\Model\Config::getExpressCheckoutBASignupOptions</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="skip_order_review_step" translate="label" type="select" sortOrder="95" showInDefault="1" showInWebsite="1"> - <label>Skip Order Review Step</label> - <config_path>payment/paypal_express/skip_order_review_step</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="express_checkout_billing_agreement" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> - <label>PayPal Billing Agreement Settings</label> - <field id="active" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>Enabled</label> - <comment> - <![CDATA[Will appear as a payment option only for customers who have at least one active billing agreement.]]> - </comment> - <config_path>payment/paypal_billing_agreement/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="title" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Title</label> - <config_path>payment/paypal_billing_agreement/title</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="sort_order" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Sort Order</label> - <config_path>payment/paypal_billing_agreement/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - </field> - <field id="payment_action" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Payment Action</label> - <config_path>payment/paypal_billing_agreement/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="allowspecific" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Payment Applicable From</label> - <config_path>payment/paypal_billing_agreement/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Countries Payment Applicable From</label> - <config_path>payment/paypal_billing_agreement/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="allowspecific">1</field> - </depends> - </field> - <field id="debug" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Debug Mode</label> - <config_path>payment/paypal_billing_agreement/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="verify_peer" translate="label" type="select" sortOrder="75" showInDefault="1" showInWebsite="1"> - <label>Enable SSL verification</label> - <config_path>payment/paypal_billing_agreement/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="line_items_enabled" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Transfer Cart Line Items</label> - <config_path>payment/paypal_billing_agreement/line_items_enabled</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="allow_billing_agreement_wizard" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1"> - <label>Allow in Billing Agreement Wizard</label> - <config_path>payment/paypal_billing_agreement/allow_billing_agreement_wizard</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - </group> - <group id="express_checkout_settlement_report" translate="label" showInDefault="1" showInWebsite="1" sortOrder="110"> - <label>Settlement Report Settings</label> - <field id="heading_sftp" translate="label" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>SFTP Credentials</label> - <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_ftp_login" translate="label" type="obscure" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Login</label> - <config_path>paypal/fetch_reports/ftp_login</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_ftp_password" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Password</label> - <config_path>paypal/fetch_reports/ftp_password</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_ftp_sandbox" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Sandbox Mode</label> - <config_path>paypal/fetch_reports/ftp_sandbox</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_ftp_ip" translate="label comment tooltip" type="text" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Custom Endpoint Hostname or IP-Address</label> - <comment>By default it is "reports.paypal.com".</comment> - <tooltip>Use colon to specify port. For example: "test.example.com:5224".</tooltip> - <config_path>paypal/fetch_reports/ftp_ip</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="settlement_reports_ftp_sandbox">0</field> - </depends> - </field> - <field id="settlement_reports_ftp_path" translate="label comeent" type="text" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Custom Path</label> - <comment>By default it is "/ppreports/outgoing".</comment> - <config_path>paypal/fetch_reports/ftp_path</config_path> - <attribute type="shared">1</attribute> - <depends> - <field id="settlement_reports_ftp_sandbox">0</field> - </depends> - </field> - <field id="heading_schedule" translate="label" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Scheduled Fetching</label> - <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_active" translate="label" type="select" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Enable Automatic Fetching</label> - <config_path>paypal/fetch_reports/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_schedule" translate="label comment" type="select" sortOrder="90" showInDefault="1"> - <label>Schedule</label> - <comment>PayPal retains reports for 45 days.</comment> - <config_path>paypal/fetch_reports/schedule</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\FetchingSchedule</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="settlement_reports_time" translate="label" type="time" sortOrder="100" showInDefault="1"> - <label>Time of Day</label> - <config_path>paypal/fetch_reports/time</config_path> - <attribute type="shared">1</attribute> - </field> - </group> - <group id="express_checkout_frontend" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="120"> - <label>Frontend Experience Settings</label> - <field id="logo" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>PayPal Product Logo</label> - <comment>Displays on catalog pages and homepage.</comment> - <config_path>paypal/style/logo</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\Logo</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="paypal_pages" translate="label" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>PayPal Merchant Pages Style</label> - <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> - <attribute type="shared">1</attribute> - </field> - <field id="page_style" translate="label tooltip" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Page Style</label> - <config_path>paypal/style/page_style</config_path> - <tooltip> - <![CDATA[Allowable values: "paypal", "primary" (default), your_custom_value (a custom payment page style from your merchant account profile).]]> - </tooltip> - <attribute type="shared">1</attribute> - </field> - <field id="paypal_hdrimg" translate="label tooltip" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Header Image URL</label> - <config_path>paypal/style/paypal_hdrimg</config_path> - <tooltip> - <![CDATA[The image at the top left of the checkout page. Max size is 750x90-pixel. <strong style="color:red">https</strong> is highly encouraged.]]> - </tooltip> - <attribute type="shared">1</attribute> - </field> - <field id="paypal_hdrbackcolor" translate="label tooltip" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Header Background Color</label> - <config_path>paypal/style/paypal_hdrbackcolor</config_path> - <tooltip> - <![CDATA[The background color for the header of the checkout page. Case-insensitive six-character HTML hexadecimal color code in ASCII.]]> - </tooltip> - <attribute type="shared">1</attribute> - </field> - <field id="paypal_hdrbordercolor" translate="label tooltip" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Header Border Color</label> - <config_path>paypal/style/paypal_hdrbordercolor</config_path> - <tooltip>2-pixel perimeter around the header space.</tooltip> - <attribute type="shared">1</attribute> - </field> - <field id="paypal_payflowcolor" translate="label tooltip" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Page Background Color</label> - <config_path>paypal/style/paypal_payflowcolor</config_path> - <tooltip> - <![CDATA[The background color for the checkout page around the header and payment form.]]> - </tooltip> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_display" translate="label" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Customize Smart Buttons</label> - <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> - <attribute type="shared">1</attribute> - </field> - <group id="checkout_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> - <label>Checkout Page</label> - <field id="checkout_page_button_customize" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="10"> - <label>Customize Button</label> - <config_path>paypal/style/checkout_page_button_customize</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_label" translate="label comment" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Label</label> - <comment><![CDATA[The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR.]]></comment> - <config_path>paypal/style/checkout_page_button_label</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\ButtonStylesLabel</frontend_model> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getLabel</source_model> - <depends> - <field id="checkout_page_button_customize">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_mx_installment_period" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Mexico Installment Period</label> - <config_path>paypal/style/checkout_page_button_mx_installment_period</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getMxInstallmentPeriod</source_model> - <depends> - <field id="checkout_page_button_customize">1</field> - <field id="checkout_page_button_label">installment</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_br_installment_period" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Brazil Installment Period</label> - <config_path>paypal/style/checkout_page_button_br_installment_period</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getBrInstallmentPeriod</source_model> - <depends> - <field id="checkout_page_button_customize">1</field> - <field id="checkout_page_button_label">installment</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_layout" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Layout</label> - <config_path>paypal/style/checkout_page_button_layout</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getLayout</source_model> - <depends> - <field id="checkout_page_button_customize">1</field> - <field id="checkout_page_button_label" negative="1">credit</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_size" translate="label tooltip" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>Size</label> - <config_path>paypal/style/checkout_page_button_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getSize</source_model> - <tooltip>Select Responsive to ensure the PayPal button renders correctly on mobile devices.</tooltip> - <depends> - <field id="checkout_page_button_customize">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_shape" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> - <label>Shape</label> - <config_path>paypal/style/checkout_page_button_shape</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getShape</source_model> - <depends> - <field id="checkout_page_button_customize">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="checkout_page_button_color" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> - <label>Color</label> - <config_path>paypal/style/checkout_page_button_color</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getColor</source_model> - <depends> - <field id="checkout_page_button_customize">1</field> - <field id="checkout_page_button_label" negative="1">credit</field> - </depends> - <attribute type="shared">1</attribute> - </field> - </group> - <group id="product_page_button" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> - <label>Product Pages</label> - <field id="product_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> - <config_path>paypal/style/product_page_button_customize</config_path> - </field> - <field id="product_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> - <config_path>paypal/style/product_page_button_label</config_path> - <depends> - <field id="product_page_button_customize">1</field> - </depends> - </field> - <field id="product_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> - <config_path>paypal/style/product_page_button_mx_installment_period</config_path> - <depends> - <field id="product_page_button_customize">1</field> - <field id="product_page_button_label">installment</field> - </depends> - </field> - <field id="product_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> - <config_path>paypal/style/product_page_button_br_installment_period</config_path> - <depends> - <field id="product_page_button_customize">1</field> - <field id="product_page_button_label">installment</field> - </depends> - </field> - <field id="product_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> - <config_path>paypal/style/product_page_button_layout</config_path> - <depends> - <field id="product_page_button_customize">1</field> - <field id="product_page_button_label" negative="1">credit</field> - </depends> - </field> - <field id="product_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> - <config_path>paypal/style/product_page_button_size</config_path> - <depends> - <field id="product_page_button_customize">1</field> - </depends> - </field> - <field id="product_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> - <config_path>paypal/style/product_page_button_shape</config_path> - <depends> - <field id="product_page_button_customize">1</field> - </depends> - </field> - <field id="product_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> - <config_path>paypal/style/product_page_button_color</config_path> - <depends> - <field id="product_page_button_customize">1</field> - <field id="product_page_button_label" negative="1">credit</field> - </depends> - </field> - </group> - <group id="cart_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="110"> - <label>Cart Page</label> - <field id="cart_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> - <config_path>paypal/style/cart_page_button_customize</config_path> - </field> - <field id="cart_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> - <config_path>paypal/style/cart_page_button_label</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - </depends> - </field> - <field id="cart_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> - <config_path>paypal/style/cart_page_button_mx_installment_period</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - <field id="cart_page_button_label">installment</field> - </depends> - </field> - <field id="cart_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> - <config_path>paypal/style/cart_page_button_br_installment_period</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - <field id="cart_page_button_label">installment</field> - </depends> - </field> - <field id="cart_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> - <config_path>paypal/style/cart_page_button_layout</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - <field id="cart_page_button_label" negative="1">credit</field> - </depends> - </field> - <field id="cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> - <config_path>paypal/style/cart_page_button_size</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - </depends> - </field> - <field id="cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> - <config_path>paypal/style/cart_page_button_shape</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - </depends> - </field> - <field id="cart_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> - <config_path>paypal/style/cart_page_button_color</config_path> - <depends> - <field id="cart_page_button_customize">1</field> - <field id="cart_page_button_label" negative="1">credit</field> - </depends> - </field> - </group> - <group id="mini_cart_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="120"> - <label>Mini Cart</label> - <field id="mini_cart_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> - <config_path>paypal/style/mini_cart_page_button_customize</config_path> - </field> - <field id="mini_cart_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> - <config_path>paypal/style/mini_cart_page_button_label</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - </depends> - </field> - <field id="mini_cart_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> - <config_path>paypal/style/mini_cart_page_button_mx_installment_period</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - <field id="mini_cart_page_button_label">installment</field> - </depends> - </field> - <field id="mini_cart_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> - <config_path>paypal/style/mini_cart_page_button_br_installment_period</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - <field id="mini_cart_page_button_label">installment</field> - </depends> - </field> - <field id="mini_cart_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> - <config_path>paypal/style/mini_cart_page_button_layout</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - <field id="mini_cart_page_button_label" negative="1">credit</field> - </depends> - </field> - <field id="mini_cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> - <config_path>paypal/style/mini_cart_page_button_size</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - </depends> - </field> - <field id="mini_cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> - <config_path>paypal/style/mini_cart_page_button_shape</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - </depends> - </field> - <field id="mini_cart_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> - <config_path>paypal/style/mini_cart_page_button_color</config_path> - <depends> - <field id="mini_cart_page_button_customize">1</field> - <field id="mini_cart_page_button_label" negative="1">credit</field> - </depends> - </field> - </group> - <group id="features" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="130"> - <label>Features</label> - <field id="disable_funding_options" translate="label comment" type="multiselect" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Disable Funding Options</label> - <comment> - <![CDATA[PayPal will automatically display each enabled funding option to eligible buyers. - For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is - offered and the currency offered by the merchant is USD.]]> - </comment> - <config_path>paypal/style/disable_funding_options</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\MultiSelect\DisabledFundingOptions</frontend_model> - <source_model>Magento\Paypal\Model\System\Config\Source\DisableFundingOptions</source_model> - <attribute type="shared">1</attribute> - <can_be_empty>1</can_be_empty> - </field> - </group> - </group> - </group> - </group> - </group> - <group id="payments_pro_hosted_solution" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Website Payments Pro Hosted Solution</label> - <fieldset_css>paypal-other-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <attribute type="activity_path">payment/hosted_pro/active</attribute> - <comment><![CDATA[Accept payments with a PCI compliant checkout that keeps customers on your site. (<u>Includes Express Checkout</u>)]]></comment> - <attribute type="paypal_ec_separate">1</attribute> - <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> - </group> - <group id="pphs_required_settings" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Required PayPal Settings</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <group id="pphs_required_settings_pphs" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Payments Pro Hosted Solution</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="business_account" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/business_account"/> - <field id="api_authentication" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/api_authentication"/> - <field id="api_username" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/api_username" /> - <field id="api_password" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/api_password" /> - <field id="api_signature" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/api_signature" /> - <field id="api_cert" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/api_cert" /> - <field id="api_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/api_wizard" /> - <field id="sandbox_flag" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/sandbox_flag" /> - <field id="use_proxy" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/use_proxy" /> - <field id="proxy_host" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/proxy_host" /> - <field id="proxy_port" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/proxy_port" /> - </group> - <field id="pphs_enable" type="select" translate="label" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Enable this Solution</label> - <config_path>payment/hosted_pro/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - <requires> - <group id="pphs_required_settings_pphs"/> - </requires> - <frontend_class>paypal-enabler paypal-ec-separate</frontend_class> - </field> - - <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21" showInDefault="1" showInWebsite="1"> - <comment><![CDATA[Payments Pro Hosted Solution lets you give customers access to financing through PayPal Credit® - at no additional cost to you. - You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> - </comment> - <requires> - <field id="pphs_enable"/> - </requires> - </field> - <group id="pphs_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="22"> - <label>Advertise PayPal Credit</label> - <comment> - <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> - <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing - from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. - Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. - The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> - </comment> - <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> - <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> - <group id="pphs_settings_bml_homepage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Home Page</label> - <field id="bml_homepage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_display"/> - <field id="pphs_bml_homepage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_position"/> - <field id="pphs_bml_homepage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/homepage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPH</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="pphs_bml_homepage_position">0</field> - </depends> - </field> - <field id="pphs_bml_homepage_size2" extends="payment_all_paypal/payments_pro_hosted_solution/pphs_required_settings/pphs_advertise_bml/pphs_settings_bml_homepage/pphs_bml_homepage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPS</source_model> - <depends> - <field id="pphs_bml_homepage_position">1</field> - </depends> - </field> - </group> - <group id="pphs_settings_bml_categorypage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Catalog Category Page</label> - <field id="bml_categorypage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_display"/> - <field id="pphs_bml_categorypage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_position" /> - <field id="pphs_bml_categorypage_size1" translate="label" showInDefault="1" showInWebsite="1" sortOrder="30" type="select"> - <label>Size</label> - <config_path>payment/paypal_express_bml/categorypage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="pphs_bml_categorypage_position">0</field> - </depends> - </field> - <field id="pphs_bml_categorypage_size2" extends="payment_all_paypal/payments_pro_hosted_solution/pphs_required_settings/pphs_advertise_bml/pphs_settings_bml_categorypage/pphs_bml_categorypage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPS</source_model> - <depends> - <field id="pphs_bml_categorypage_position">1</field> - </depends> - </field> - </group> - <group id="pphs_settings_bml_productpage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>Catalog Product Page</label> - <field id="bml_productpage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_display" /> - <field id="pphs_bml_productpage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_position" /> - <field id="pphs_bml_productpage_size1" translate="label" type="select" showInWebsite="1" showInDefault="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/productpage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="pphs_bml_productpage_position">0</field> - </depends> - </field> - <field id="pphs_bml_productpage_size2" extends="payment_all_paypal/payments_pro_hosted_solution/pphs_required_settings/pphs_advertise_bml/pphs_settings_bml_productpage/pphs_bml_productpage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPN</source_model> - <depends> - <field id="pphs_bml_productpage_position">1</field> - </depends> - </field> - </group> - <group id="pphs_settings_bml_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> - <label>Checkout Cart Page</label> - <field id="bml_checkout_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_display" /> - <field id="pphs_bml_checkout_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_position" /> - <field id="pphs_bml_checkout_size1" translate="label" type="select" showInWebsite="1" showInDefault="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/checkout_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="pphs_bml_checkout_position">0</field> - </depends> - </field> - <field id="pphs_bml_checkout_size2" extends="payment_all_paypal/payments_pro_hosted_solution/pphs_required_settings/pphs_advertise_bml/pphs_settings_bml_checkout/pphs_bml_checkout_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="pphs_bml_checkout_position">1</field> - </depends> - </field> - </group> - </group> - </group> - <group id="pphs_settings" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Basic Settings - PayPal Payments Pro Hosted Solution</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" type="text" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="10" translate="label comment"> - <label>Title</label> - <comment>It is recommended to set this value to "PayPal" per store views.</comment> - <config_path>payment/hosted_pro/title</config_path> - </field> - <field id="sort_order" type="text" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20" translate="label"> - <label>Sort Order</label> - <config_path>payment/hosted_pro/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - </field> - <field id="payment_action" type="select" showInDefault="1" showInWebsite="1" sortOrder="30" translate="label"> - <label>Payment Action</label> - <config_path>payment/hosted_pro/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions</source_model> - </field> - <field id="display_ec" type="select" showInDefault="1" showInWebsite="1" sortOrder="40" translate="label"> - <label>Display Express Checkout in the Payment Information step</label> - <config_path>payment/hosted_pro/display_ec</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> - <group id="pphs_settings_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="allowspecific" type="select" showInDefault="1" showInWebsite="1" sortOrder="10" translate="label"> - <label>Payment Applicable From</label> - <config_path>payment/hosted_pro/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - </field> - <field id="specificcountry" type="multiselect" showInDefault="1" showInWebsite="1" sortOrder="20" translate="label"> - <label>Countries Payment Applicable From</label> - <config_path>payment/hosted_pro/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <depends> - <field id="allowspecific">1</field> - </depends> - </field> - <field id="debug" type="select" showInDefault="1" showInWebsite="1" sortOrder="30" translate="label"> - <label>Debug Mode</label> - <config_path>payment/hosted_pro/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> - <field id="verify_peer" type="select" showInDefault="1" showInWebsite="1" sortOrder="35" translate="label"> - <label>Enable SSL verification</label> - <config_path>payment/hosted_pro/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> - <group id="pphs_settlement_report" showInDefault="1" showInWebsite="1" sortOrder="50" translate="label"> - <label>Settlement Report Settings</label> - <field id="heading_sftp" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_sftp"/> - <field id="settlement_reports_ftp_login" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_login" /> - <field id="settlement_reports_ftp_password" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_password" /> - <field id="settlement_reports_ftp_sandbox" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_sandbox" /> - <field id="settlement_reports_ftp_ip" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_ip" /> - <field id="settlement_reports_ftp_path" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_path" /> - <field id="heading_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_schedule" /> - <field id="settlement_reports_active" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_active" /> - <field id="settlement_reports_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_schedule" /> - <field id="settlement_reports_time" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_time" /> - </group> - </group> - </group> - </group> - <group id="payments_pro_hosted_solution_without_bml" extends="payments_pro_hosted_solution"> - <group id="pphs_required_settings"> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <group id="pphs_advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - </group> - </section> - <section id="payment_us" extends="payment" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="paypal_group_all_in_one" translate="label comment" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="1"> - <label><![CDATA[PayPal All-in-One Payment Solutions <i>Accept and process credit cards and PayPal payments.</i>]]></label> - <fieldset_css>complex paypal-other-section paypal-all-in-one-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <comment>Choose a secure bundled payment solution for your business.</comment> - <attribute type="displayIn">other_paypal_payment_solutions</attribute> - <group id="payflow_advanced" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Payments Advanced</label> - <fieldset_css>paypal-other-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <comment><![CDATA[Accept payments with a PCI-compliant checkout that keeps customers on your site. (<u>Includes Express Checkout</u>)]]></comment> - <attribute type="activity_path">payment/payflow_advanced/active</attribute> - <group id="configuration_details" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="4"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-advanced.html</comment> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Hint</frontend_model> - </group> - <group id="required_settings" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Required PayPal Settings</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <group id="payments_advanced" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Payments Advanced and Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="business_account" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/business_account"> - <label>Email Associated with PayPal Merchant Account (Optional)</label> - </field> - <field id="partner" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Partner</label> - <config_path>payment/payflow_advanced/partner</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="vendor" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Vendor</label> - <config_path>payment/payflow_advanced/vendor</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="user" translate="label comment tooltip" type="obscure" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>User</label> - <comment>PayPal recommends that you set up an additional User on your account at manager.paypal.com</comment> - <tooltip>PayPal recommends you set up an additional User on your account at manager.paypal.com, instead of entering your admin username and password here. This will enhance your security and prevent service interruptions if you later change your password. If you do not want to set up an additional User, you can re-enter your Merchant Login here.</tooltip> - <config_path>payment/payflow_advanced/user</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="pwd" translate="label" type="obscure" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Password</label> - <config_path>payment/payflow_advanced/pwd</config_path> - <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> - <attribute type="shared">1</attribute> - </field> - <field id="sandbox_flag" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Test Mode</label> - <config_path>payment/payflow_advanced/sandbox_flag</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="use_proxy" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>Use Proxy</label> - <config_path>payment/payflow_advanced/use_proxy</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_host" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1"> - <label>Proxy Host</label> - <config_path>payment/payflow_advanced/proxy_host</config_path> - <depends> - <field id="use_proxy">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="proxy_port" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1"> - <label>Proxy Port</label> - <config_path>payment/payflow_advanced/proxy_port</config_path> - <depends> - <field id="use_proxy">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="payflow_advanced_info" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink\Advanced</frontend_model> - <attribute type="shared">1</attribute> - </field> - </group> - <field id="enable_payflow_advanced" translate="label comment" type="select" sortOrder="41" showInDefault="1" showInWebsite="1"> - <label>Enable this Solution</label> - <config_path>payment/payflow_advanced/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - <requires> - <group id="payments_advanced"/> - </requires> - </field> - <field id="enable_express_checkout" extends="payment_all_paypal/payflow_link/payflow_link_required/enable_express_checkout_basic" showInDefault="1" showInWebsite="1"> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Hidden</frontend_model> - <requires> - <field id="enable_payflow_advanced"/> - </requires> - </field> - <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" showInDefault="1" showInWebsite="1"> - <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. - You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> - </comment> - <config_path>payment/payflow_express_bml/active</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> - <requires> - <field id="enable_payflow_advanced"/> - </requires> - </field> - <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> - <config_path>payment/payflow_express_bml/sort_order</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> - <depends> - <field id="enable_express_checkout_bml">1</field> - </depends> - </field> - <group id="advanced_advertise_bml" showInDefault="1" showInWebsite="1" sortOrder="60" translate="label comment"> - <label>Advertise PayPal Credit</label> - <comment> - <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> - <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing - from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. - Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. - The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> - </comment> - <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> - <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> - <group id="advanced_settings_bml_homepage" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20" translate="label"> - <label>Home Page</label> - <field id="bml_homepage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_display"/> - <field id="advanced_bml_homepage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_position"/> - <field id="advanced_bml_homepage_size1" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Size</label> - <config_path>payment/paypal_express_bml/homepage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPH</source_model> - <depends> - <field id="advanced_bml_homepage_position">0</field> - </depends> - </field> - <field id="advanced_bml_homepage_size2" extends="payment_us/paypal_group_all_in_one/payflow_advanced/required_settings/advanced_advertise_bml/advanced_settings_bml_homepage/advanced_bml_homepage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPS</source_model> - <depends> - <field id="advanced_bml_homepage_position">1</field> - </depends> - </field> - </group> - <group id="advanced_settings_bml_categorypage" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30" translate="label"> - <label>Catalog Category Page</label> - <field id="bml_categorypage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_display"/> - <field id="advanced_bml_categorypage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_position"/> - <field id="advanced_bml_categorypage_size1" type="select" showInDefault="1" showInWebsite="1" sortOrder="30" translate="label"> - <label>Size</label> - <config_path>payment/paypal_express_bml/categorypage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="advanced_bml_categorypage_position">0</field> - </depends> - </field> - <field id="advanced_bml_categorypage_size2" extends="payment_us/paypal_group_all_in_one/payflow_advanced/required_settings/advanced_advertise_bml/advanced_settings_bml_categorypage/advanced_bml_categorypage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPS</source_model> - <depends> - <field id="advanced_bml_categorypage_position">1</field> - </depends> - </field> - </group> - <group id="advanced_settings_bml_productpage" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40" translate="label"> - <label>Catalog Product Page</label> - <field id="bml_productpage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_display" /> - <field id="advanced_bml_productpage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_position" /> - <field id="advanced_bml_productpage_size1" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" translate="label"> - <label>Size</label> - <config_path>payment/paypal_express_bml/productpage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="advanced_bml_productpage_position">0</field> - </depends> - </field> - <field id="advanced_bml_productpage_size2" extends="payment_us/paypal_group_all_in_one/payflow_advanced/required_settings/advanced_advertise_bml/advanced_settings_bml_productpage/advanced_bml_productpage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPN</source_model> - <depends> - <field id="advanced_bml_productpage_position">1</field> - </depends> - </field> - - </group> - <group id="advanced_settings_bml_checkout" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50" translate="label"> - <label>Checkout Cart Page</label> - <field id="bml_checkout_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_display"/> - <field id="advanced_bml_checkout_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_position"/> - <field id="advanced_bml_checkout_size1" type="select" showInDefault="1" showInWebsite="1" sortOrder="30" translate="label"> - <label>Size</label> - <config_path>payment/paypal_express_bml/checkout_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="advanced_bml_checkout_position">0</field> - </depends> - </field> - <field id="advanced_bml_checkout_size2" extends="payment_us/paypal_group_all_in_one/payflow_advanced/required_settings/advanced_advertise_bml/advanced_settings_bml_checkout/advanced_bml_checkout_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="advanced_bml_checkout_position">1</field> - </depends> - </field> - </group> - </group> - </group> - <group id="settings_payments_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Basic Settings - PayPal Payments Advanced</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" translate="label comment" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Title</label> - <comment>It is recommended to set this value to "Debit or Credit Card" per store views.</comment> - <config_path>payment/payflow_advanced/title</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="sort_order" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Sort Order</label> - <config_path>payment/payflow_advanced/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - </field> - <field id="payment_action" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Payment Action</label> - <config_path>payment/payflow_advanced/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="settings_payments_advanced_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="allowspecific" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Payment Applicable From</label> - <config_path>payment/payflow_advanced/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="25" showInDefault="1" showInWebsite="1"> - <label>Countries Payment Applicable From</label> - <config_path>payment/payflow_advanced/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <depends> - <field id="allowspecific">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="debug" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Debug Mode</label> - <config_path>payment/payflow_advanced/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="verify_peer" translate="label" type="select" sortOrder="35" showInDefault="1" showInWebsite="1"> - <label>Enable SSL verification</label> - <config_path>payment/payflow_advanced/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="csc_editable" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>CVV Entry is Editable</label> - <config_path>payment/payflow_advanced/csc_editable</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="csc_required" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1"> - <label>Require CVV Entry</label> - <config_path>payment/payflow_advanced/csc_required</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <depends> - <field id="csc_editable">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="email_confirmation" translate="label" type="select" sortOrder="60" showInDefault="1" showInWebsite="1"> - <label>Send Email Confirmation</label> - <config_path>payment/payflow_advanced/email_confirmation</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="url_method" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1"> - <label>URL method for Cancel URL and Return URL</label> - <config_path>payment/payflow_advanced/url_method</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\UrlMethod</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="settlement_report" translate="label" showInDefault="1" showInWebsite="1" sortOrder="80"> - <label>Settlement Report Settings</label> - <field id="heading_sftp" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_sftp"/> - <field id="settlement_reports_ftp_login" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_login"/> - <field id="settlement_reports_ftp_password" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_password"/> - <field id="settlement_reports_ftp_sandbox" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_sandbox"/> - <field id="settlement_reports_ftp_ip" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_ip"/> - <field id="settlement_reports_ftp_path" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_ftp_path"/> - <field id="heading_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/heading_schedule"/> - <field id="settlement_reports_active" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_active"/> - <field id="settlement_reports_schedule" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_schedule"/> - <field id="settlement_reports_time" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_settlement_report/settlement_reports_time"/> - </group> - <group id="frontend" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> - <label>Frontend Experience Settings</label> - <field id="logo" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/logo"/> - <field id="paypal_pages" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_pages"/> - <field id="page_style" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/page_style"/> - <field id="paypal_hdrimg" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrimg"/> - <field id="paypal_hdrbackcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbackcolor"/> - <field id="paypal_hdrbordercolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbordercolor"/> - <field id="paypal_payflowcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_payflowcolor"/> - </group> - </group> - </group> - <group id="settings_express_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Basic Settings - PayPal Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/title" /> - <field id="sort_order" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/sort_order" /> - <field id="payment_action" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/payment_action" /> - <field id="visible_on_product" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/visible_on_product" /> - <group id="settings_express_checkout_advanced" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_express_checkout/paypal_payflow_express_checkout_advanced"/> - </group> - </group> - <group id="wpp_usuk" translate="label" sortOrder="40" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout"> - <label>Payments Pro</label> - <attribute type="activity_path">payment/paypal_payment_pro/active</attribute> - <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> - </group> - <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <field id="enable_paypal_payflow"> - <attribute type="shared">0</attribute> - <config_path>payment/paypal_payment_pro/active</config_path> - </field> - <group id="paypal_payflow_api_settings" translate="label"> - <label>Payments Pro and Express Checkout</label> - </group> - </group> - <group id="settings_paypal_payflow" translate="label"> - <label>Basic Settings - PayPal Payments Pro</label> - </group> - </group> - <group id="wps_express" extends="payment_all_paypal/express_checkout"> - <label>Payments Standard</label> - <comment>Accept credit card and PayPal payments securely.</comment> - <attribute type="activity_path">payment/wps_express/active</attribute> - <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> - </group> - <group id="express_checkout_required"> - <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> - <field id="merchant_id" showInDefault="0" showInWebsite="0"/> - <group id="express_checkout_required_express_checkout"> - <label>Payments Standard</label> - </group> - <field id="enable_express_checkout"> - <config_path>payment/wps_express/active</config_path> - </field> - <field id="enable_express_checkout_bml"> - <config_path>payment/wps_express_bml/active</config_path> - </field> - </group> - <group id="settings_ec"> - <label>Basic Settings - PayPal Website Payments Standard</label> - </group> - </group> - </group> - <group id="paypal_payment_gateways" translate="label" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="1"> - <label><![CDATA[PayPal Payment Gateways <i>Process payments using your own internet merchant account.</i>]]></label> - <fieldset_css>complex paypal-other-section paypal-gateways-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <attribute type="displayIn">other_paypal_payment_solutions</attribute> - <group id="paypal_payflowpro_with_express_checkout" translate="label comment" extends="payment_all_paypal/paypal_payflowpro"> - <label>Payflow Pro</label> - <attribute type="paypal_ec_separate">0</attribute> - <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <label>Required PayPal Settings</label> - <field id="enable_paypal_payflow"> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - </field> - <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> - <field id="merchant_id" showInDefault="0" showInWebsite="0"/> - <group id="paypal_payflow_api_settings" translate="label"> - <label>Payflow Pro and Express Checkout</label> - <field id="business_account" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/business_account" translate="label" sortOrder="10"> - <frontend_class>not-required</frontend_class> - <label>Email Associated with PayPal Merchant Account (Optional)</label> - <attribute type="shared">1</attribute> - </field> - </group> - <field id="enable_express_checkout" extends="payment_all_paypal/payflow_link/payflow_link_required/enable_express_checkout_basic" showInDefault="1" showInWebsite="1"> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Hidden</frontend_model> - <requires> - <field id="enable_paypal_payflow"/> - </requires> - </field> - <field id="enable_express_checkout_bml_payflow" translate="label" type="select" sortOrder="21" showInWebsite="1" showInDefault="1"> - <label>Enable PayPal Credit</label> - <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. - You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. - <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> - </comment> - <config_path>payment/payflow_express_bml/active</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Bml</frontend_model> - <requires> - <field id="enable_paypal_payflow"/> - </requires> - </field> - <field id="express_checkout_bml_sort_order" sortOrder="30" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> - <config_path>payment/payflow_express_bml/sort_order</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> - <depends> - <field id="enable_express_checkout_bml_payflow">1</field> - </depends> - </field> - <group id="paypal_payflow_advertise_bml" translate="label comment" showInDefault="1" showInWebsite="1" sortOrder="40"> - <label>Advertise PayPal Credit</label> - <comment> - <![CDATA[<a href="https://financing.paypal.com/ppfinportal/content/whyUseFinancing" target="_blank">Why Advertise Financing?</a><br/> - <strong>Give your sales a boost when you advertise financing.</strong><br/>PayPal helps turn browsers into buyers with financing - from PayPal Credit®. Your customers have more time to pay, while you get paid up front – at no additional cost to you. - Use PayPal’s free banner ads that let you advertise PayPal Credit® financing as a payment option when your customers check out with PayPal. - The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% - or more. <a href="https://financing.paypal.com/ppfinportal/content/forrester" target="_blank">See Details</a>.]]> - </comment> - <field id="bml_publisher_id" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_publisher_id" /> - <field id="bml_wizard" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/bml_wizard" /> - <group id="paypal_payflow_settings_bml_homepage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> - <label>Home Page</label> - <field id="bml_homepage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_display"/> - <field id="paypal_payflow_bml_homepage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_homepage/bml_homepage_position"/> - <field id="paypal_payflow_bml_homepage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/homepage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPH</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="paypal_payflow_bml_homepage_position">0</field> - </depends> - </field> - <field id="paypal_payflow_bml_homepage_size2" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_advertise_bml/paypal_payflow_settings_bml_homepage/paypal_payflow_bml_homepage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeHPS</source_model> - <depends> - <field id="paypal_payflow_bml_homepage_position">1</field> - </depends> - </field> - </group> - <group id="paypal_payflow_settings_bml_categorypage" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Catalog Category Page</label> - <field id="bml_categorypage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_display"/> - <field id="paypal_payflow_bml_categorypage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_categorypage/bml_categorypage_position" /> - <field id="paypal_payflow_bml_categorypage_size1" translate="label" sortOrder="30" showInWebsite="1" showInDefault="1" type="select"> - <label>Size</label> - <config_path>payment/paypal_express_bml/categorypage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="paypal_payflow_bml_categorypage_position">0</field> - </depends> - </field> - <field id="paypal_payflow_bml_categorypage_size2" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_advertise_bml/paypal_payflow_settings_bml_categorypage/paypal_payflow_bml_categorypage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCCPS</source_model> - <depends> - <field id="paypal_payflow_bml_categorypage_position">1</field> - </depends> - </field> - </group> - <group id="paypal_payflow_settings_bml_productpage" translate="label" showInDefault="1" showInStore="1" showInWebsite="1" sortOrder="40"> - <label>Catalog Product Page</label> - <field id="bml_productpage_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_display" /> - <field id="paypal_payflow_bml_productpage_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_productpage/bml_productpage_position" /> - <field id="paypal_payflow_bml_productpage_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/productpage_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="paypal_payflow_bml_productpage_position">0</field> - </depends> - </field> - <field id="paypal_payflow_bml_productpage_size2" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_advertise_bml/paypal_payflow_settings_bml_productpage/paypal_payflow_bml_productpage_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCPPN</source_model> - <depends> - <field id="paypal_payflow_bml_productpage_position">1</field> - </depends> - </field> - </group> - <group id="paypal_payflow_settings_bml_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> - <label>Checkout Cart Page</label> - <field id="bml_checkout_display" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_display" /> - <field id="paypal_payflow_bml_checkout_position" translate="label" extends="payment_all_paypal/express_checkout/express_checkout_required/advertise_bml/settings_bml_checkout/bml_checkout_position" /> - <field id="paypal_payflow_bml_checkout_size1" translate="label" type="select" showInDefault="1" showInWebsite="1" sortOrder="30"> - <label>Size</label> - <config_path>payment/paypal_express_bml/checkout_size</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutC</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="paypal_payflow_bml_checkout_position">0</field> - </depends> - </field> - <field id="paypal_payflow_bml_checkout_size2" extends="payment_us/paypal_payment_gateways/paypal_payflowpro_with_express_checkout/paypal_payflow_required/paypal_payflow_advertise_bml/paypal_payflow_settings_bml_checkout/paypal_payflow_bml_checkout_size1"> - <source_model>Magento\Paypal\Model\System\Config\Source\BmlSize::getBmlSizeCheckoutN</source_model> - <attribute type="shared">1</attribute> - <depends> - <field id="paypal_payflow_bml_checkout_position">1</field> - </depends> - </field> - </group> - </group> - </group> - <group id="settings_paypal_payflow" translate="label"> - <group id="settings_paypal_payflow_advanced" translate="label"> - <group id="paypal_payflow_frontend" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> - <label>Frontend Experience Settings</label> - <field id="logo" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/logo"/> - <field id="paypal_pages" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_pages"/> - <field id="page_style" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/page_style"/> - <field id="paypal_hdrimg" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrimg"/> - <field id="paypal_hdrbackcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbackcolor"/> - <field id="paypal_hdrbordercolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbordercolor"/> - <field id="paypal_payflowcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_payflowcolor"/> - </group> - </group> - </group> - <group id="paypal_payflow_express_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Basic Settings - PayPal Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Title</label> - <config_path>payment/payflow_express/title</config_path> - <attribute type="shared">1</attribute> - </field> - <field id="sort_order" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Sort Order</label> - <config_path>payment/payflow_express/sort_order</config_path> - <frontend_class>validate-number</frontend_class> - <attribute type="shared">1</attribute> - </field> - <field id="payment_action" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Payment Action</label> - <config_path>payment/payflow_express/payment_action</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\PaymentActions</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="visible_on_product" translate="label" type="select" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Display on Product Details Page</label> - <config_path>payment/payflow_express/visible_on_product</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <group id="paypal_payflow_express_checkout_advanced" translate="label" showInDefault="1" showInWebsite="1" sortOrder="60"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="visible_on_cart" translate="label comment" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Display on Shopping Cart</label> - <comment>Also affects mini-shopping cart.</comment> - <config_path>payment/payflow_express/visible_on_cart</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\Yesnoshortcut</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="allowspecific" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1"> - <label>Payment Applicable From</label> - <config_path>payment/payflow_express/allowspecific</config_path> - <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="specificcountry" translate="label" type="multiselect" sortOrder="20" showInDefault="1" showInWebsite="1"> - <label>Countries Payment Applicable From</label> - <config_path>payment/payflow_express/specificcountry</config_path> - <source_model>Magento\Paypal\Model\System\Config\Source\BuyerCountry</source_model> - <depends> - <field id="allowspecific">1</field> - </depends> - <attribute type="shared">1</attribute> - </field> - <field id="debug" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> - <label>Debug Mode</label> - <config_path>payment/payflow_express/debug</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="verify_peer" translate="label" type="select" sortOrder="35" showInDefault="1" showInWebsite="1"> - <label>Enable SSL verification</label> - <config_path>payment/payflow_express/verify_peer</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="line_items_enabled" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1"> - <label>Transfer Cart Line Items</label> - <config_path>payment/payflow_express/line_items_enabled</config_path> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <attribute type="shared">1</attribute> - </field> - <field id="skip_order_review_step" sortOrder="50" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/skip_order_review_step"/> - </group> - </group> - </group> - <group id="payflow_link_us" extends="payment_all_paypal/payflow_link"/> - </group> - <group id="paypal_alternative_payment_methods" sortOrder="5" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="express_checkout_us" translate="label comment" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>PayPal Express Checkout</label> - <fieldset_css>complex paypal-express-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <comment>Add another payment method to your existing solution or as a stand-alone option.</comment> - <help_url>https://merchant.paypal.com/cgi-bin/marketingweb?cmd=_render-content</help_url> - <attribute type="shared">0</attribute> - <attribute type="activity_path">payment/paypal_express/active</attribute> - <attribute type="activity_path">payment/payflow_express/active</attribute> - <attribute type="displayIn">recommended_solutions</attribute> - </group> - </group> - </section> - <section id="payment_gb" extends="payment" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="paypal_group_all_in_one" translate="label comment" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="1"> - <label><![CDATA[PayPal All-in-One Payment Solutions  <i>Accept and process credit cards and PayPal payments.</i>]]></label> - <fieldset_css>complex paypal-other-section paypal-all-in-one-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <comment>Choose a secure bundled payment solution for your business.</comment> - <help_url>https://www.paypal-marketing.com/emarketing/partner/na/merchantlineup/home.page#mainTab=checkoutlineup&subTab=newlineup</help_url> - <attribute type="displayIn">other_paypal_payment_solutions</attribute> - <group id="wpp_usuk" translate="comment" sortOrder="20"> - <fieldset_css>pp-general-uk</fieldset_css> - <demo_link>http://www.youtube.com/watch?v=LBe-TW87eGI&list=PLF18B1094ABCD7CE8&index=1&feature=plpp_video</demo_link> - <comment>Accept payments with a completely customizable checkout page.</comment> - <group id="wpp_required_settings"> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <group id="wpp_advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - </group> - <group id="payments_pro_hosted_solution_with_express_checkout" translate="label comment" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="30"> - <label>Website Payments Pro Hosted Solution</label> - <attribute type="paypal_ec_separate">0</attribute> - <group id="pphs_required_settings"> - <group id="pphs_required_settings_pphs" translate="label"> - <label>Website Payments Pro Hosted Solution and Express Checkout</label> - </group> - <field id="pphs_enable"> - <requires> - <group id="pphs_required_settings_pphs"/> - </requires> - <frontend_class>paypal-enabler</frontend_class> - </field> - </group> - <group id="pphs_settings" translate="label"> - <label>Basic Settings - PayPal Website Payments Pro Hosted Solution</label> - <group id="pphs_settings_advanced"> - <group id="pphs_billing_agreement" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> - <label>PayPal Billing Agreement Settings</label> - <field id="active" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/active" /> - <field id="title" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/title" /> - <field id="sort_order" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/sort_order" /> - <field id="payment_action" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/payment_action" /> - <field id="allowspecific" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/allowspecific" /> - <field id="specificcountry" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/specificcountry" /> - <field id="debug" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/debug" /> - <field id="verify_peer" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/verify_peer" /> - <field id="line_items_enabled" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/line_items_enabled" /> - <field id="allow_billing_agreement_wizard" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_billing_agreement/allow_billing_agreement_wizard" /> - </group> - <group id="pphs_frontend" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> - <label>Frontend Experience Settings</label> - <field id="logo" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/logo" /> - <field id="paypal_pages" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_pages" /> - <field id="page_style" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/page_style" /> - <field id="paypal_hdrimg" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrimg" /> - <field id="paypal_hdrbackcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbackcolor" /> - <field id="paypal_hdrbordercolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_hdrbordercolor" /> - <field id="paypal_payflowcolor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/paypal_payflowcolor" /> - </group> - </group> - </group> - <group id="pphs_settings_express_checkout" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> - <label>Basic Settings - PayPal Express Checkout</label> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <field id="title" extends="payment_all_paypal/express_checkout/settings_ec/title" /> - <field id="sort_order" extends="payment_all_paypal/express_checkout/settings_ec/sort_order" /> - <field id="payment_action" extends="payment_all_paypal/express_checkout/settings_ec/payment_action" /> - <field id="visible_on_product" extends="payment_all_paypal/express_checkout/settings_ec/visible_on_product" /> - <field id="authorization_honor_period" extends="payment_all_paypal/express_checkout/settings_ec/authorization_honor_period" /> - <field id="order_valid_period" extends="payment_all_paypal/express_checkout/settings_ec/order_valid_period" /> - <field id="child_authorization_number" extends="payment_all_paypal/express_checkout/settings_ec/child_authorization_number" /> - <group id="pphs_settings_express_checkout_advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="80"> - <label>Advanced Settings</label> - <fieldset_css>config-advanced</fieldset_css> - <field id="visible_on_cart" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/visible_on_cart" /> - <field id="allowspecific" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/allowspecific" /> - <field id="specificcountry" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/specificcountry" /> - <field id="debug" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/debug" /> - <field id="verify_peer" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/verify_peer" /> - <field id="line_items_enabled" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/line_items_enabled" /> - <field id="transfer_shipping_options" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/transfer_shipping_options" /> - <field id="button_flavor" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/button_flavor" /> - <field id="solution_type" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/solution_type" /> - <field id="require_billing_address" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/require_billing_address" /> - <field id="allow_ba_signup" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/allow_ba_signup" /> - <field id="skip_order_review_step" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/skip_order_review_step" /> - </group> - </group> - </group> - <group id="wps_express" extends="payment_all_paypal/express_checkout" sortOrder="50"> - <label>Website Payments Standard</label> - <comment>Accept credit card and PayPal payments securely.</comment> - <attribute type="activity_path">payment/wps_express/active</attribute> - <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> - </group> - <group id="express_checkout_required"> - <group id="express_checkout_required_express_checkout"> - <label>Website Payments Standard</label> - </group> - <field id="enable_express_checkout"> - <config_path>payment/wps_express/active</config_path> - </field> - <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> - <field id="merchant_id" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - <group id="settings_ec"> - <label>Basic Settings - PayPal Website Payments Standard</label> - </group> - </group> - </group> - <group id="paypal_alternative_payment_methods" sortOrder="5" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="express_checkout_gb" translate="label comment" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>PayPal Express Checkout</label> - <fieldset_css>complex paypal-express-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <comment>Add another payment method to your existing solution or as a stand-alone option.</comment> - <help_url>https://merchant.paypal.com/cgi-bin/marketingweb?cmd=_render-content</help_url> - <attribute type="displayIn">recommended_solutions</attribute> - <group id="express_checkout_required"> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - </group> - </group> - </section> - <section id="payment_de" extends="payment" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="paypal_payment_solutions" showInDefault="0" showInWebsite="0" showInStore="0" sortOrder="5"> - <group id="express_checkout_de" translate="label comment" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>PayPal Express Checkout</label> - <fieldset_css>complex paypal-express-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <comment>Add another payment method to your existing solution or as a stand-alone option.</comment> - <help_url>https://www.paypal-marketing.com/emarketing/partner/na/merchantlineup/home.page#mainTab=checkoutlineup&subTab=newlineup</help_url> - <attribute type="displayIn">recommended_solutions</attribute> - <group id="express_checkout_required"> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - <group id="settings_ec"> - <group id="settings_ec_advanced"> - <field id="solution_type" showInDefault="0" showInWebsite="0"/> - </group> - </group> - </group> - </group> - </section> - <section id="payment_other" extends="payment" showInDefault="0" showInWebsite="0" showInStore="0"> - <group id="express_checkout_other" translate="label comment" sortOrder="5" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>PayPal Express Checkout</label> - <fieldset_css>complex paypal-express-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <comment>Add another payment method to your existing solution or as a stand-alone option.</comment> - <help_url>https://www.paypal-marketing.com/emarketing/partner/na/merchantlineup/home.page#mainTab=checkoutlineup&subTab=newlineup</help_url> - <attribute type="displayIn">recommended_solutions</attribute> - <group id="express_checkout_required"> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - </group> - <group id="paypal_group_all_in_one" translate="label comment" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="1"> - <label><![CDATA[PayPal All-in-One Payment Solutions  <i>Accept and process credit cards and PayPal payments.</i>]]></label> - <fieldset_css>complex paypal-other-section paypal-all-in-one-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <comment>Choose a secure bundled payment solution for your business.</comment> - <help_url>https://www.paypal-marketing.com/emarketing/partner/na/merchantlineup/home.page#mainTab=checkoutlineup&subTab=newlineup</help_url> - <attribute type="displayIn">other_paypal_payment_solutions</attribute> - <group id="wps_other" extends="payment_all_paypal/express_checkout" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Website Payments Standard</label> - <fieldset_css>complex</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Payment</frontend_model> - <comment>Accept credit card and PayPal payments securely.</comment> - <attribute type="activity_path">payment/wps_express/active</attribute> - <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-standard.html</comment> - </group> - <group id="express_checkout_required"> - <group id="express_checkout_required_express_checkout"> - <label>Website Payments Standard</label> - </group> - <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> - <field id="merchant_id" showInDefault="0" showInWebsite="0"/> - <field id="enable_express_checkout"> - <config_path>payment/wps_express/active</config_path> - </field> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <group id="advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - <group id="settings_ec"> - <label>Basic Settings - PayPal Website Payments Standard</label> - </group> - </group> - </group> - <group id="paypal_payment_gateways" translate="label comment" sortOrder="8" showInDefault="0" showInWebsite="0" showInStore="0"> - <label><![CDATA[PayPal Payment Gateways <i>Process payments using your own internet merchant account.</i>]]></label> - <fieldset_css>complex paypal-other-section paypal-gateways-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <comment>Process payments using your own internet merchant account.</comment> - <help_url>https://merchant.paypal.com/cgi-bin/marketingweb?cmd=_render-content</help_url> - <attribute type="displayIn">other_paypal_payment_solutions</attribute> - </group> - </section> - <section id="payment_ca" extends="payment_other"> - <group id="express_checkout_other"> - <attribute type="activity_path">payment/paypal_express/active</attribute> - <attribute type="activity_path">payment/payflow_express/active</attribute> - </group> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="20"/> - </group> - <group id="paypal_payment_gateways" showInDefault="1" showInWebsite="1" showInStore="1"> - <fieldset_css>complex paypal-other-section paypal-gateways-section</fieldset_css> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> - <label><![CDATA[PayPal Payment Gateways <i>Process payments using your own internet merchant account.</i>]]></label> - <group id="wpp_ca" translate="label" extends="payment_all_paypal/paypal_payflowpro" sortOrder="30"> - <label>Website Payments Pro</label> - <attribute type="activity_path">payment/paypal_payment_pro/active</attribute> - <group id="configuration_details"> - <comment>http://docs.magento.com/m2/ce/user_guide/payment/paypal-payments-pro.html</comment> - </group> - <group id="paypal_payflow_required" translate="label" showInDefault="1" showInWebsite="1" sortOrder="10"> - <group id="paypal_payflow_api_settings"> - <label>Payments Pro</label> - </group> - <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> - <field id="merchant_id" showInDefault="0" showInWebsite="0"/> - <field id="enable_paypal_payflow"> - <frontend_class>paypal-enabler paypal-ec-pe</frontend_class> - <attribute type="shared">0</attribute> - <config_path>payment/paypal_payment_pro/active</config_path> - <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> - </field> - </group> - <group id="settings_paypal_payflow"> - <label>Basic Settings - PayPal Payments Pro</label> - </group> - </group> - <group id="paypal_payflowpro_ca" extends="payment_all_paypal/paypal_payflowpro" sortOrder="40"/> - <group id="payflow_link_ca" extends="payment_all_paypal/payflow_link" sortOrder="50"> - <group id="payflow_link_required"> - <field id="enable_express_checkout_bml" showInDefault="0" showInWebsite="0"/> - <field id="express_checkout_bml_sort_order" showInDefault="0" showInWebsite="0"/> - <group id="payflow_link_advertise_bml" showInDefault="0" showInWebsite="0"/> - </group> - </group> - </group> - </section> - <section id="payment_au" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="12"/> - <group id="payments_pro_hosted_solution_au" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="10"/> - </group> - <group id="paypal_payment_gateways" showInDefault="1" showInWebsite="1" showInStore="1"> - <group id="paypal_payflowpro_au" extends="payment_all_paypal/paypal_payflowpro" sortOrder="20"/> - </group> - </section> - <section id="payment_jp" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="12"/> - <group id="payments_pro_hosted_solution_jp" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="10"> - <label>Website Payments Plus</label> - </group> - </group> - </section> - <section id="payment_fr" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="12"/> - <group id="payments_pro_hosted_solution_fr" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="10"> - <label>Integral Evolution</label> - </group> - </group> - </section> - <section id="payment_it" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="12"/> - <group id="payments_pro_hosted_solution_it" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="10"> - <label>Pro</label> - </group> - </group> - </section> - <section id="payment_es" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="12"/> - <group id="payments_pro_hosted_solution_es" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="10"> - <label>Pasarela integral</label> - </group> - </group> - </section> - <section id="payment_hk" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other" sortOrder="12"/> - <group id="payments_pro_hosted_solution_hk" extends="payment_all_paypal/payments_pro_hosted_solution_without_bml" sortOrder="10"/> - </group> - </section> - <section id="payment_nz" extends="payment_other"> - <group id="express_checkout_other"/> - <group id="paypal_group_all_in_one"> - <group id="wps_other"/> - </group> - <group id="paypal_payment_gateways" showInDefault="1" showInWebsite="1" showInStore="1"> - <group id="paypal_payflowpro_nz" extends="payment_all_paypal/paypal_payflowpro"/> - </group> - </section> - </system> -</config> diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/TotalsTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/TotalsTest.php new file mode 100644 index 0000000000000..1125fc1730718 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/TotalsTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Block\Adminhtml; + +use Magento\Framework\View\LayoutInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\OrderFactory; + +/** + * Test class for \Magento\Sales\Block\Adminhtml\Totals + */ +class TotalsTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** @var LayoutInterface */ + private $layout; + + /** @var Totals */ + private $block; + + /** @var OrderFactory */ + private $orderFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->layout = $this->_objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(Totals::class, 'totals_block'); + $this->orderFactory = $this->_objectManager->get(OrderFactory::class); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order_with_free_shipping_by_coupon.php + */ + public function testShowShippingCoupon() + { + /** @var Order $order */ + $order = $this->orderFactory->create(); + $order->loadByIncrementId('100000001'); + + $this->block->setOrder($order); + $this->block->toHtml(); + + $shippingTotal = $this->block->getTotal('shipping'); + $this->assertNotFalse($shippingTotal, 'Shipping method is absent on the total\'s block.'); + $this->assertContains( + '1234567890', + $shippingTotal->getLabel(), + 'Coupon code is absent in the shipping method label name.' + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php index b8f2ca38e2489..1d03170d5b1e2 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php @@ -5,11 +5,16 @@ */ declare(strict_types=1); +use Magento\Catalog\Api\ProductRepositoryInterface; + require __DIR__ . '/address_list.php'; \Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea(\Magento\Framework\App\Area::AREA_FRONTEND); $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); + /** @var \Magento\Catalog\Model\Product $product */ $product = $objectManager->create(\Magento\Catalog\Model\Product::class); $product->setTypeId('simple') @@ -29,8 +34,6 @@ 'is_in_stock' => 1, ] )->save(); - -$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $product = $productRepository->get('simple-product-guest-quote'); $addressData = reset($addresses); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php index 02c42153b72c3..e8992aec3c924 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php @@ -13,13 +13,19 @@ /** @var $quote \Magento\Quote\Model\Quote */ $quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = $objectManager->create(\Magento\Quote\Model\QuoteIdMask::class); + $quote->load('guest_quote', 'reserved_order_id'); -if ($quote->getId()) { + +$quoteId = $quote->getId(); +if (null !== $quoteId) { $quote->delete(); + $quoteIdMask->delete($quoteId); } /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); try { $product = $productRepository->get('simple-product-guest-quote', false, null, true); @@ -28,5 +34,10 @@ //Product already removed } +/** @var \Magento\CatalogInventory\Model\StockRegistryStorage $stockRegistryStorage */ +$stockRegistryStorage = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\CatalogInventory\Model\StockRegistryStorage::class); +$stockRegistryStorage->clean(); + $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon.php new file mode 100644 index 0000000000000..57ccffadaa4d0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Item as OrderItem; + +require __DIR__ . '/../../../Magento/Sales/_files/order.php'; +/** @var \Magento\Catalog\Model\Product $product */ + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple') + ->setName($product->getName()) + ->setFreeShipping('1'); + +/** @var Order $order */ +$order->setShippingDescription('Flat Rate - Fixed') + ->setShippingAmount(0) + ->setCouponCode('1234567890') + ->setDiscountDescription('1234567890') + ->addItem($orderItem); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon_rollback.php new file mode 100644 index 0000000000000..1fb4b4636ab29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_free_shipping_by_coupon_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require 'default_rollback.php'; diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php index 49acd039a0960..3f477f7ce5033 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php @@ -301,8 +301,8 @@ public function validateDescriptionFormatStructure( $this->validateShortDescriptionFormat( $phpcsFile, (int) $shortPtr, - $commentStartPtr, - $commentEndPtr, + (int)$commentStartPtr, + (int)$commentEndPtr, $emptyTypeTokens ); } diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php index 01834ff81e81b..c37f0b500fe39 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php @@ -97,8 +97,8 @@ public function process(File $phpcsFile, $stackPtr) { $tokens = $phpcsFile->getTokens(); $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr - 1, 0); - $this->validateAnnotationBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr); - $commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, 0); + $this->validateAnnotationBlockExists($phpcsFile, (int)$previousCommentClosePtr, (int)$stackPtr); + $commentStartPtr = (int)$phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, 0); $commentCloserPtr = $tokens[$commentStartPtr]['comment_closer']; $emptyTypeTokens = [ T_DOC_COMMENT_WHITESPACE, @@ -111,7 +111,7 @@ public function process(File $phpcsFile, $stackPtr) } else { $this->annotationFormatValidator->validateDescriptionFormatStructure( $phpcsFile, - $commentStartPtr, + (int)$commentStartPtr, (int) $shortPtr, $previousCommentClosePtr, $emptyTypeTokens diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php deleted file mode 100644 index 72e4fff7e5162..0000000000000 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php -/** - * Rule for searching DB dependency - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\TestFramework\Dependency; - -/** - * Rule for testing integrity within declarative schema. - * - * @package Magento\TestFramework\Dependency - */ -class DeclarativeSchemaRule implements RuleInterface -{ - /** - * Map of tables and modules - * - * @var array - */ - protected $_moduleTableMap; - - /** - * Constructor - * - * @param array $tables - */ - public function __construct(array $tables) - { - $this->_moduleTableMap = $tables; - } - - /** - * Gets external dependencies information for current module by analyzing db_schema.xml files contents. - * - * @param string $currentModule - * @param string $fileType - * @param string $file - * @param string $contents - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function getDependencyInfo($currentModule, $fileType, $file, &$contents) - { - if ('db_schema' != $fileType || !preg_match('#.*/db_schema\.xml$#', $file)) { - return []; - } - - $dependenciesInfo = []; - $unKnowTables = []; - - $dom = new \DOMDocument(); - $dom->loadXML($contents); - $tables = $dom->getElementsByTagName('table'); - $constraints = $dom->getElementsByTagName('constraint'); - - $tableNames = []; - $foreignKeyTables = []; - $foreignKeyReferenceTables = []; - - /** @var \DOMElement $table */ - foreach ($tables as $table) { - $tableNames[] = $table->getAttribute('name'); - } - - /** @var \DOMElement $constraint */ - foreach ($constraints as $constraint) { - $xsiType = $constraint->getAttribute('xsi:type'); - if (strtolower($xsiType) == 'foreign' && $constraint->getAttribute('disabled') !== '1') { - $foreignKeyTables[] = $constraint->getAttribute('table'); - $foreignKeyReferenceTables[] = $constraint->getAttribute('referenceTable'); - } - } - - $tableNames = array_unique(array_merge($tableNames, $foreignKeyReferenceTables, $foreignKeyTables)); - - /** @var string $table */ - foreach ($tableNames as $table) { - if (!isset($this->_moduleTableMap[$table])) { - $unKnowTables[$file][$table] = $table; - continue; - } - if (strtolower($currentModule) !== strtolower($this->_moduleTableMap[$table])) { - $dependenciesInfo[] = [ - 'module' => $this->_moduleTableMap[$table], - 'type' => RuleInterface::TYPE_HARD, - 'source' => $table, - ]; - } - } - - foreach ($unKnowTables as $tables) { - foreach ($tables as $table) { - $dependenciesInfo[] = ['module' => 'Unknown', 'source' => $table]; - } - } - return $dependenciesInfo; - } -} diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php index cdaa49e8d37fb..b3ee0a2880308 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php @@ -7,11 +7,13 @@ */ namespace Magento\TestFramework\Dependency; -use DOMDocument; -use DOMXPath; +use Magento\Framework\App\Utility\Classes; use Magento\Framework\App\Utility\Files; use Magento\TestFramework\Dependency\VirtualType\VirtualTypeMapper; +/** + * Class provide dependency rule for di.xml config files. + */ class DiRule implements RuleInterface { /** @@ -33,6 +35,8 @@ public function __construct(VirtualTypeMapper $mapper) } /** + * Get class name pattern. + * * @return string * @throws \Exception */ @@ -73,6 +77,7 @@ private function getPattern() */ public function getDependencyInfo($currentModule, $fileType, $file, &$contents) { + //phpcs:ignore Magento2.Functions.DiscouragedFunction if (pathinfo($file, PATHINFO_BASENAME) !== 'di.xml') { return []; } @@ -99,12 +104,14 @@ public function getDependencyInfo($currentModule, $fileType, $file, &$contents) } /** + * Fetch all possible dependencies. + * * @param string $contents * @return array */ private function fetchPossibleDependencies($contents) { - $doc = new DOMDocument(); + $doc = new \DOMDocument(); $doc->loadXML($contents); return [ RuleInterface::TYPE_SOFT => $this->getSoftDependencies($doc), @@ -113,16 +120,22 @@ private function fetchPossibleDependencies($contents) } /** - * @param DOMDocument $doc + * Collect soft dependencies. + * + * @param \DOMDocument $doc * @return array */ - private function getSoftDependencies(DOMDocument $doc) + private function getSoftDependencies(\DOMDocument $doc) { $result = []; foreach (self::$tagNameMap as $tagName => $attributeNames) { $nodes = $doc->getElementsByTagName($tagName); /** @var \DOMElement $node */ foreach ($nodes as $node) { + if ($tagName === 'virtualType' && !$node->getAttribute('type')) { + $result[] = Classes::resolveVirtualType($node->getAttribute('name')); + continue; + } foreach ($attributeNames as $attributeName) { $result[] = $node->getAttribute($attributeName); } @@ -133,13 +146,15 @@ private function getSoftDependencies(DOMDocument $doc) } /** - * @param DOMDocument $doc + * Collect hard dependencies. + * + * @param \DOMDocument $doc * @return array */ - private function getHardDependencies(DOMDocument $doc) + private function getHardDependencies(\DOMDocument $doc) { $result = []; - $xpath = new DOMXPath($doc); + $xpath = new \DOMXPath($doc); $textNodes = $xpath->query('//*[@xsi:type="object"]'); /** @var \DOMElement $node */ foreach ($textNodes as $node) { diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php deleted file mode 100644 index 269eff8087a91..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php +++ /dev/null @@ -1,98 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\TestFramework\Dependency; - -/** - * Test for declarative schema integrity rule. - * - * @package Magento\TestFramework\Dependency - */ -class DeclarativeSchemaRuleTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var DeclarativeSchemaRule - */ - protected $model; - - protected function setUp() - { - $this->model = new DeclarativeSchemaRule(['some_table' => 'SomeModule']); - } - - /** - * @param string $module - * @param string $file - * @param string $contents - * @param array $expected - * @dataProvider getDependencyInfoDataProvider - */ - public function testGetDependencyInfo($module, $file, $contents, array $expected) - { - $actualDependencies = $this->model->getDependencyInfo($module, 'db_schema', $file, $contents); - $this->assertEquals( - $expected, - $actualDependencies - ); - } - - public function getDependencyInfoDataProvider() - { - return [ - ['any', 'non-db-schema-file.php', 'any', []], - [ - 'any', - '/app/Magento/Module/etc/db_schema.xml', - '<table name="unknown_table"></table>', - [['module' => 'Unknown', 'source' => 'unknown_table']] - ], - [ - 'SomeModule', - '/app/some/path/etc/db_schema.xml', - '<table name="some_table"></table>', - [] - ], - [ - 'any', - '/app/some/path/etc/db_schema.xml', - '<table name="some_table"></table>', - [ - [ - 'module' => 'SomeModule', - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, - 'source' => 'some_table', - ] - ] - ], - [ - 'any', - '/app/some/path/etc/db_schema.xml', - '<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> - <table name="some_table"> - <constraint xsi:type="foreign" - name="FK_NAME" - table="some_table" - column="some_col" - referenceTable="ref_table" - referenceColumn="ref_col" - onDelete="CASCADE"/> - </table> - </schema>', - [ - [ - 'module' => 'SomeModule', - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, - 'source' => 'some_table', - ], - [ - 'module' => 'Unknown', - 'source' => 'ref_table', - ] - ] - ] - ]; - } -} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php new file mode 100644 index 0000000000000..87cc5afd5ecb3 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php @@ -0,0 +1,179 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Test\Integrity; + +use Magento\Test\Integrity\Dependency\DeclarativeSchemaDependencyProvider; +use Magento\Framework\App\Utility\Files; +use Magento\Framework\Component\ComponentRegistrar; + +/** + * Class DeclarativeDependencyTest + */ +class DeclarativeDependencyTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var DeclarativeSchemaDependencyProvider + */ + private $dependencyProvider; + + /** + * @var array + */ + private $blacklistedDependencies = []; + + /** + * Sets up data + * + * @throws \Exception + */ + protected function setUp() + { + $root = BP; + $rootJson = $this->readJsonFile($root . '/composer.json', true); + if (preg_match('/magento\/project-*/', $rootJson['name']) == 1) { + // The Dependency test is skipped for vendor/magento build + self::markTestSkipped( + 'MAGETWO-43654: The build is running from vendor/magento. DependencyTest is skipped.' + ); + } + $this->dependencyProvider = new DeclarativeSchemaDependencyProvider(); + } + + /** + * @throws \Exception + */ + public function testUndeclaredDependencies() + { + /** TODO: Remove this temporary solution after MC-15534 is closed */ + $filePattern = __DIR__ . '/_files/dependency_test/blacklisted_dependencies_*.php'; + $blacklistedDependencies = []; + foreach (glob($filePattern) as $fileName) { + $blacklistedDependencies = array_merge($blacklistedDependencies, require $fileName); + } + $this->blacklistedDependencies = $blacklistedDependencies; + + $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); + $invoker( + /** + * Check undeclared modules dependencies for specified file + * + * @param string $fileType + * @param string $file + */ + function ($file) { + $componentRegistrar = new ComponentRegistrar(); + $foundModuleName = ''; + foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) { + if (strpos($file, $moduleDir . '/') !== false) { + $foundModuleName = str_replace('_', '\\', $moduleName); + break; + } + } + if (empty($foundModuleName)) { + return; + } + + $undeclaredDependency = $this->dependencyProvider->getUndeclaredModuleDependencies($foundModuleName); + + $result = []; + foreach ($undeclaredDependency as $name => $modules) { + $modules = array_unique($modules); + if ($this->filterBlacklistedDependencies($foundModuleName, $modules)) { + $result[] = $this->getErrorMessage($name) . "\n" . implode("\t\n", $modules) . "\n"; + } + } + if (!empty($result)) { + $this->fail( + 'Module ' . $moduleName . ' has undeclared dependencies: ' . "\n" . implode("\t\n", $result) + ); + } + }, + $this->prepareFiles(Files::init()->getDbSchemaFiles()) + ); + } + + /** + * Filter blacklisted dependencies. + * + * @todo Remove this temporary solution after MC-15534 is closed + * + * @param string $moduleName + * @param array $dependencies + * @return array + */ + private function filterBlacklistedDependencies(string $moduleName, array $dependencies): array + { + if (!empty($this->blacklistedDependencies[$moduleName])) { + $dependencies = array_diff($dependencies, $this->blacklistedDependencies[$moduleName]); + } + + return $dependencies; + } + + /** + * Convert file list to data provider structure. + * + * @param string[] $files + * @return array + */ + private function prepareFiles(array $files): array + { + $result = []; + foreach ($files as $relativePath => $file) { + $absolutePath = reset($file); + $result[$relativePath] = [$absolutePath]; + } + return $result; + } + + /** + * Retrieve error message for dependency. + * + * @param string $id + * @return string + */ + private function getErrorMessage(string $id): string + { + $decodedId = $this->dependencyProvider->decodeDependencyId($id); + $entityType = $decodedId['entityType']; + if ($entityType === DeclarativeSchemaDependencyProvider::SCHEMA_ENTITY_TABLE) { + $message = sprintf( + 'Table %s has undeclared dependency on one of the following modules:', + $decodedId['tableName'] + ); + } else { + $message = sprintf( + '%s %s from %s table has undeclared dependency on one of the following modules:', + ucfirst($entityType), + $decodedId['entityName'], + $decodedId['tableName'] + ); + } + + return $message; + } + + /** + * Read data from json file. + * + * @param string $file + * @return mixed + * @throws \Exception + */ + private function readJsonFile(string $file, bool $asArray = false) + { + $decodedJson = json_decode(file_get_contents($file), $asArray); + if (null == $decodedJson) { + //phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception("Invalid Json: $file"); + } + + return $decodedJson; + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php new file mode 100644 index 0000000000000..965bc6184144b --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php @@ -0,0 +1,758 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Test\Integrity\Dependency; + +use Magento\Framework\App\Utility\Files; +use Magento\Framework\Component\ComponentRegistrar; +use Magento\Framework\Setup\Declaration\Schema\Config\Converter; + +/** + * Provide information on the dependency between the modules according to the declarative schema. + * + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + */ +class DeclarativeSchemaDependencyProvider +{ + /** + * Types of dependency between modules. + */ + const TYPE_HARD = 'hard'; + + /** + * The identifier of dependency for mapping. + */ + const MAP_TYPE_DECLARED = 'declared'; + + /** + * The identifier of dependency for mapping. + */ + const MAP_TYPE_FOUND = 'found'; + + /** + * Declarative name for table entity of the declarative schema. + */ + const SCHEMA_ENTITY_TABLE = 'table'; + + /** + * Declarative name for column entity of the declarative schema. + */ + const SCHEMA_ENTITY_COLUMN = 'column'; + + /** + * Declarative name for constraint entity of the declarative schema. + */ + const SCHEMA_ENTITY_CONSTRAINT = 'constraint'; + + /** + * Declarative name for index entity of the declarative schema. + */ + const SCHEMA_ENTITY_INDEX = 'index'; + + /** + * @var array + */ + private $mapDependencies = []; + + /** + * @var array + */ + private $dbSchemaDeclaration = []; + + /** + * @var array + */ + private $packageModuleMapping = []; + + /** + * @var array + */ + private $moduleSchemaFileMapping = []; + + /** + * Provide declared dependencies between modules based on the declarative schema configuration. + * + * @param string $moduleName + * @return array + * @throws \Exception + */ + public function getDeclaredExistingModuleDependencies(string $moduleName): array + { + $this->initDeclaredDependencies(); + $dependencies = $this->getDependenciesFromFiles($this->getSchemaFileNameByModuleName($moduleName)); + $dependencies = $this->filterSelfDependency($moduleName, $dependencies); + $declared = $this->getDeclaredDependencies($moduleName, self::TYPE_HARD, self::MAP_TYPE_DECLARED); + + $existingDeclared = []; + foreach ($dependencies as $dependency) { + $checkResult = array_intersect($declared, $dependency); + if ($checkResult) { + $existingDeclared = array_merge($existingDeclared, array_values($checkResult)); + } + } + + return array_unique($existingDeclared); + } + + /** + * Provide undeclared dependencies between modules based on the declarative schema configuration. + * + * [ + * $dependencyId => [$module1, $module2, $module3 ...], + * ... + * ] + * + * @param string $moduleName + * @return array + * @throws \Exception + */ + public function getUndeclaredModuleDependencies(string $moduleName): array + { + $this->initDeclaredDependencies(); + $dependencies = $this->getDependenciesFromFiles($this->getSchemaFileNameByModuleName($moduleName)); + $dependencies = $this->filterSelfDependency($moduleName, $dependencies); + return $this->collectDependencies($moduleName, $dependencies); + } + + /** + * Provide schema file name by module name. + * + * @param string $module + * @return string + * @throws \Exception + */ + private function getSchemaFileNameByModuleName(string $module): string + { + if (empty($this->moduleSchemaFileMapping)) { + $componentRegistrar = new ComponentRegistrar(); + foreach (array_values(Files::init()->getDbSchemaFiles()) as $filePath) { + $filePath = reset($filePath); + foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) { + if (strpos($filePath, $moduleDir . '/') !== false) { + $foundModuleName = str_replace('_', '\\', $moduleName); + $this->moduleSchemaFileMapping[$foundModuleName] = $filePath; + break; + } + } + } + } + + return $this->moduleSchemaFileMapping[$module] ?? ''; + } + + /** + * Initialise map of dependencies. + * + * @throws \Exception + */ + private function initDeclaredDependencies() + { + if (empty($this->mapDependencies)) { + $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); + foreach ($jsonFiles as $file) { + $json = new \Magento\Framework\Config\Composer\Package($this->readJsonFile($file)); + $moduleName = $this->convertModuleName($json->get('name')); + $require = array_keys((array)$json->get('require')); + $this->presetDependencies($moduleName, $require, self::TYPE_HARD); + } + } + } + + /** + * Read data from json file. + * + * @param string $file + * @return mixed + * @throws \Exception + */ + private function readJsonFile(string $file, bool $asArray = false) + { + $decodedJson = json_decode(file_get_contents($file), $asArray); + if (null == $decodedJson) { + //phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception("Invalid Json: $file"); + } + + return $decodedJson; + } + + /** + * Remove self dependencies. + * + * @param string $moduleName + * @param array $dependencies + * @return array + */ + private function filterSelfDependency(string $moduleName, array $dependencies):array + { + foreach ($dependencies as $id => $modules) { + $decodedId = $this->decodeDependencyId($id); + $entityType = $decodedId['entityType']; + if ($entityType === self::SCHEMA_ENTITY_TABLE || $entityType === "column") { + if (array_search($moduleName, $modules) !== false) { + unset($dependencies[$id]); + } + } else { + $dependencies[$id] = $this->filterComplexDependency($moduleName, $modules); + } + } + + return array_filter($dependencies); + } + + /** + * Remove already declared dependencies. + * + * @param string $moduleName + * @param array $modules + * @return array + */ + private function filterComplexDependency(string $moduleName, array $modules): array + { + $resultDependencies = []; + if (!is_array(reset($modules))) { + if (array_search($moduleName, $modules) === false) { + $resultDependencies = $modules; + } + } else { + foreach ($modules as $dependencySet) { + if (array_search($moduleName, $dependencySet) === false) { + $resultDependencies = array_merge( + $resultDependencies, + $dependencySet + ); + } + } + } + + return array_values(array_unique($resultDependencies)); + } + + /** + * Retrieve declarative schema declaration. + * + * @return array + * @throws \Exception + */ + private function getDeclarativeSchema(): array + { + if ($this->dbSchemaDeclaration) { + return $this->dbSchemaDeclaration; + } + + $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; + $declaration = []; + foreach (Files::init()->getDbSchemaFiles() as $filePath) { + $filePath = reset($filePath); + preg_match('#app/code/(\w+/\w+)#', $filePath, $result); + $moduleName = str_replace('/', '\\', $result[1]); + $moduleDeclaration = $this->getDbSchemaDeclaration($filePath); + + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (!isset($tableDeclaration['modules'])) { + $tableDeclaration['modules'] = []; + } + array_push($tableDeclaration['modules'], $moduleName); + $moduleDeclaration = array_replace_recursive( + $moduleDeclaration, + [self::SCHEMA_ENTITY_TABLE => + [ + $tableName => $tableDeclaration, + ] + ] + ); + foreach ($entityTypes as $entityType) { + if (!isset($tableDeclaration[$entityType])) { + continue; + } + $moduleDeclaration = array_replace_recursive( + $moduleDeclaration, + [self::SCHEMA_ENTITY_TABLE => + [ + $tableName => + $this->addModuleAssigment($tableDeclaration, $entityType, $moduleName) + ] + ] + ); + } + } + $declaration = array_merge_recursive($declaration, $moduleDeclaration); + } + $this->dbSchemaDeclaration = $declaration; + + return $this->dbSchemaDeclaration; + } + + /** + * Get declared dependencies. + * + * @param string $tableName + * @param string $entityType + * @param null|string $entityName + * @return array + * @throws \Exception + */ + private function resolveEntityDependencies(string $tableName, string $entityType, ?string $entityName = null): array + { + switch ($entityType) { + case self::SCHEMA_ENTITY_COLUMN: + case self::SCHEMA_ENTITY_CONSTRAINT: + case self::SCHEMA_ENTITY_INDEX: + return $this->getDeclarativeSchema() + [self::SCHEMA_ENTITY_TABLE][$tableName][$entityType][$entityName]['modules']; + case self::SCHEMA_ENTITY_TABLE: + return $this->getDeclarativeSchema()[self::SCHEMA_ENTITY_TABLE][$tableName]['modules']; + default: + return []; + } + } + + /** + * @param string $filePath + * @return array + */ + private function getDbSchemaDeclaration(string $filePath): array + { + $dom = new \DOMDocument(); + $dom->loadXML(file_get_contents($filePath)); + return (new Converter())->convert($dom); + } + + /** + * Add dependency on the current module. + * + * @param array $tableDeclaration + * @param string $entityType + * @param string $moduleName + * @return array + */ + private function addModuleAssigment( + array $tableDeclaration, + string $entityType, + string $moduleName + ): array { + $declarationWithAssigment = []; + foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { + if (!isset($entityDeclaration['modules'])) { + $entityDeclaration['modules'] = []; + } + if (!$this->isEntityDisabled($entityDeclaration)) { + array_push($entityDeclaration['modules'], $moduleName); + } + + $declarationWithAssigment[$entityType][$entityName] = $entityDeclaration; + } + + return $declarationWithAssigment; + } + + /** + * Retrieve dependencies from files. + * + * @param string $file + * @return string[] + * @throws \Exception + */ + private function getDependenciesFromFiles($file) + { + if (!$file) { + return []; + } + + $moduleDbSchema = $this->getDbSchemaDeclaration($file); + $dependencies = array_merge_recursive( + $this->getDisabledDependencies($moduleDbSchema), + $this->getConstraintDependencies($moduleDbSchema), + $this->getIndexDependencies($moduleDbSchema) + ); + return $dependencies; + } + + /** + * Retrieve dependencies for disabled entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getDisabledDependencies(array $moduleDeclaration): array + { + $disabledDependencies = []; + $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + foreach ($entityTypes as $entityType) { + if (!isset($tableDeclaration[$entityType])) { + continue; + } + foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { + if ($this->isEntityDisabled($entityDeclaration)) { + $dependencyIdentifier = $this->getDependencyId($tableName, $entityType, $entityName); + $disabledDependencies[$dependencyIdentifier] = + $this->resolveEntityDependencies($tableName, $entityType, $entityName); + } + } + } + if ($this->isEntityDisabled($tableDeclaration)) { + $disabledDependencies[$this->getDependencyId($tableName)] = + $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_TABLE); + } + } + + return $disabledDependencies; + } + + /** + * Retrieve dependencies for foreign entities. + * + * @param array $constraintDeclaration + * @return array + * @throws \Exception + */ + private function getFKDependencies(array $constraintDeclaration): array + { + $referenceDependencyIdentifier = + $this->getDependencyId( + $constraintDeclaration['referenceTable'], + self::SCHEMA_ENTITY_CONSTRAINT, + $constraintDeclaration['referenceId'] + ); + $dependencyIdentifier = + $this->getDependencyId( + $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], + self::SCHEMA_ENTITY_CONSTRAINT, + $constraintDeclaration['referenceId'] + ); + + $constraintDependencies = []; + $constraintDependencies[$referenceDependencyIdentifier] = + $this->resolveEntityDependencies( + $constraintDeclaration['referenceTable'], + self::SCHEMA_ENTITY_COLUMN, + $constraintDeclaration['referenceColumn'] + ); + $constraintDependencies[$dependencyIdentifier] = + $this->resolveEntityDependencies( + $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], + self::SCHEMA_ENTITY_COLUMN, + $constraintDeclaration[self::SCHEMA_ENTITY_COLUMN] + ); + + return $constraintDependencies; + } + + /** + * Retrieve dependencies for constraint entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getConstraintDependencies(array $moduleDeclaration): array + { + $constraintDependencies = []; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (empty($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT])) { + continue; + } + foreach ($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT] as $constraintName => $constraintDeclaration) { + if ($this->isEntityDisabled($constraintDeclaration)) { + continue; + } + $dependencyIdentifier = + $this->getDependencyId($tableName, self::SCHEMA_ENTITY_CONSTRAINT, $constraintName); + switch ($constraintDeclaration['type']) { + case 'foreign': + $constraintDependencies = array_merge( + $constraintDependencies, + $this->getFKDependencies($constraintDeclaration) + ); + break; + case 'primary': + case 'unique': + $constraintDependencies[$dependencyIdentifier] = $this->getComplexDependency( + $tableName, + $constraintDeclaration + ); + } + } + } + return $constraintDependencies; + } + + /** + * Calculate complex dependency. + * + * @param string $tableName + * @param array $entityDeclaration + * @return array + * @throws \Exception + */ + private function getComplexDependency(string $tableName, array $entityDeclaration): array + { + $complexDependency = []; + if (empty($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { + return $complexDependency; + } + + if (!is_array($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { + $entityDeclaration[self::SCHEMA_ENTITY_COLUMN] = [$entityDeclaration[self::SCHEMA_ENTITY_COLUMN]]; + } + + foreach (array_keys($entityDeclaration[self::SCHEMA_ENTITY_COLUMN]) as $columnName) { + $complexDependency[] = + $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_COLUMN, $columnName); + } + + return array_values($complexDependency); + } + + /** + * Retrieve dependencies for index entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getIndexDependencies(array $moduleDeclaration): array + { + $indexDependencies = []; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (empty($tableDeclaration[self::SCHEMA_ENTITY_INDEX])) { + continue; + } + foreach ($tableDeclaration[self::SCHEMA_ENTITY_INDEX] as $indexName => $indexDeclaration) { + if ($this->isEntityDisabled($indexDeclaration)) { + continue; + } + $dependencyIdentifier = + $this->getDependencyId($tableName, self::SCHEMA_ENTITY_INDEX, $indexName); + $indexDependencies[$dependencyIdentifier] = + $this->getComplexDependency($tableName, $indexDeclaration); + } + } + + return $indexDependencies; + } + + /** + * Check status of the entity declaration. + * + * @param array $entityDeclaration + * @return bool + */ + private function isEntityDisabled(array $entityDeclaration): bool + { + return isset($entityDeclaration['disabled']) && $entityDeclaration['disabled'] == true; + } + + /** + * Retrieve dependency id. + * + * @param string $tableName + * @param string $entityType + * @param null|string $entityName + * @return string + */ + private function getDependencyId( + string $tableName, + string $entityType = self::SCHEMA_ENTITY_TABLE, + ?string $entityName = null + ) { + return implode('___', [$tableName, $entityType, $entityName ?: $tableName]); + } + + /** + * Retrieve dependency parameters from dependency id. + * + * @param string $id + * @return array + */ + public static function decodeDependencyId(string $id): array + { + $decodedValues = explode('___', $id); + $result = [ + 'tableName' => $decodedValues[0], + 'entityType' => $decodedValues[1], + 'entityName' => $decodedValues[2], + ]; + return $result; + } + + /** + * Collect module dependencies. + * + * @param string $currentModuleName + * @param array $dependencies + * @return array + */ + private function collectDependencies($currentModuleName, $dependencies = []) + { + if (empty($dependencies)) { + return []; + } + foreach ($dependencies as $dependencyName => $dependency) { + $this->collectDependency($dependencyName, $dependency, $currentModuleName); + } + + return $this->getDeclaredDependencies($currentModuleName, self::TYPE_HARD, self::MAP_TYPE_FOUND); + } + + /** + * Collect a module dependency. + * + * @param string $dependencyName + * @param array $dependency + * @param string $currentModule + */ + private function collectDependency( + string $dependencyName, + array $dependency, + string $currentModule + ) { + $declared = $this->getDeclaredDependencies($currentModule, self::TYPE_HARD, self::MAP_TYPE_DECLARED); + $checkResult = array_intersect($declared, $dependency); + + if (empty($checkResult)) { + $this->addDependencies( + $currentModule, + self::TYPE_HARD, + self::MAP_TYPE_FOUND, + [ + $dependencyName => $dependency, + ] + ); + } + } + + /** + * Add dependencies to dependency list. + * + * @param string $moduleName + * @param array $packageNames + * @param string $type + * + * @return void + * @throws \Exception + */ + private function presetDependencies( + string $moduleName, + array $packageNames, + string $type + ): void { + $packageNames = array_filter($packageNames, function ($packageName) { + return $this->getModuleName($packageName) || + 0 === strpos($packageName, 'magento/') && 'magento/magento-composer-installer' != $packageName; + }); + + foreach ($packageNames as $packageName) { + $this->addDependencies( + $moduleName, + $type, + self::MAP_TYPE_DECLARED, + [$this->convertModuleName($packageName)] + ); + } + } + + /** + * Returns package name on module name mapping. + * + * @return array + * @throws \Exception + */ + private function getPackageModuleMapping(): array + { + if (!$this->packageModuleMapping) { + $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); + + $packageModuleMapping = []; + foreach ($jsonFiles as $file) { + $moduleXml = simplexml_load_file(dirname($file) . '/etc/module.xml'); + $moduleName = str_replace('_', '\\', (string)$moduleXml->module->attributes()->name); + $composerJson = $this->readJsonFile($file); + $packageName = $composerJson->name; + $packageModuleMapping[$packageName] = $moduleName; + } + + $this->packageModuleMapping = $packageModuleMapping; + } + + return $this->packageModuleMapping; + } + + /** + * Retrieve Magento style module name. + * + * @param string $packageName + * @return null|string + * @throws \Exception + */ + private function getModuleName(string $packageName): ?string + { + return $this->getPackageModuleMapping()[$packageName] ?? null; + } + + /** + * Retrieve array of dependency items. + * + * @param $module + * @param $type + * @param $mapType + * @return array + */ + private function getDeclaredDependencies(string $module, string $type, string $mapType) + { + return $this->mapDependencies[$module][$type][$mapType] ?? []; + } + + /** + * Add dependency map items. + * + * @param $module + * @param $type + * @param $mapType + * @param $dependencies + */ + protected function addDependencies(string $module, string $type, string $mapType, array $dependencies) + { + $this->mapDependencies[$module][$type][$mapType] = array_merge_recursive( + $this->getDeclaredDependencies($module, $type, $mapType), + $dependencies + ); + } + + /** + * Converts a composer json component name into the Magento Module form. + * + * @param string $jsonName The name of a composer json component or dependency e.g. 'magento/module-theme' + * @return string The corresponding Magento Module e.g. 'Magento\Theme' + * @throws \Exception + */ + private function convertModuleName(string $jsonName): string + { + $moduleName = $this->getModuleName($jsonName); + if ($moduleName) { + return $moduleName; + } + + if (strpos($jsonName, 'magento/magento') !== false + || strpos($jsonName, 'magento/framework') !== false + ) { + $moduleName = str_replace('/', "\t", $jsonName); + $moduleName = str_replace('framework-', "Framework\t", $moduleName); + $moduleName = str_replace('-', ' ', $moduleName); + $moduleName = ucwords($moduleName); + $moduleName = str_replace("\t", '\\', $moduleName); + $moduleName = str_replace(' ', '', $moduleName); + } else { + $moduleName = $jsonName; + } + + return $moduleName; + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index e2e0357a38f77..9c03802602938 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -10,8 +10,8 @@ use Magento\Framework\App\Utility\Files; use Magento\Framework\Component\ComponentRegistrar; +use Magento\Test\Integrity\Dependency\DeclarativeSchemaDependencyProvider; use Magento\TestFramework\Dependency\DbRule; -use Magento\TestFramework\Dependency\DeclarativeSchemaRule; use Magento\TestFramework\Dependency\DiRule; use Magento\TestFramework\Dependency\LayoutRule; use Magento\TestFramework\Dependency\PhpRule; @@ -26,19 +26,28 @@ class DependencyTest extends \PHPUnit\Framework\TestCase { /** - * Types of dependencies between modules + * Soft dependency between modules */ const TYPE_SOFT = 'soft'; + /** + * Hard dependency between modules + */ const TYPE_HARD = 'hard'; /** - * Types of dependencies map arrays + * The identifier of dependency for mapping. */ const MAP_TYPE_DECLARED = 'declared'; + /** + * The identifier of dependency for mapping. + */ const MAP_TYPE_FOUND = 'found'; + /** + * The identifier of dependency for mapping. + */ const MAP_TYPE_REDUNDANT = 'redundant'; /** @@ -57,17 +66,6 @@ class DependencyTest extends \PHPUnit\Framework\TestCase */ protected static $_listConfigXml = []; - /** - * List of db_schema.xml files by modules - * - * Format: array( - * '{Module_Name}' => '{Filename}' - * ) - * - * @var array - */ - protected static $_listDbSchemaXml = []; - /** * List of routes.xml files by modules * @@ -186,7 +184,6 @@ public static function setUpBeforeClass() self::$_namespaces = implode('|', Files::init()->getNamespaces()); self::_prepareListConfigXml(); - self::_prepareListDbSchemaXml(); self::_prepareListRoutesXml(); self::_prepareListAnalyticsXml(); @@ -224,6 +221,7 @@ protected static function _initThemes() $defaultThemes = []; foreach (self::$_listConfigXml as $file) { $config = simplexml_load_file($file); + //phpcs:ignore Generic.PHP.NoSilencedErrors $nodes = @($config->xpath("/config/*/design/theme/full_name") ?: []); foreach ($nodes as $node) { $defaultThemes[] = (string)$node; @@ -237,13 +235,13 @@ protected static function _initThemes() */ protected static function _initRules() { - $replaceFilePattern = str_replace('\\', '/', realpath(__DIR__)) . '/_files/dependency_test/*.php'; + $replaceFilePattern = str_replace('\\', '/', realpath(__DIR__)) . '/_files/dependency_test/tables_*.php'; $dbRuleTables = []; foreach (glob($replaceFilePattern) as $fileName) { + //phpcs:ignore Generic.PHP.NoSilencedErrors $dbRuleTables = array_merge($dbRuleTables, @include $fileName); } self::$_rulesInstances = [ - new DeclarativeSchemaRule($dbRuleTables), new PhpRule(self::$_mapRouters, self::$_mapLayoutBlocks), new DbRule($dbRuleTables), new LayoutRule( @@ -275,7 +273,6 @@ protected function _getCleanedFileContents($fileType, $file) break; case 'layout': case 'config': - case 'db_schema': //Removing xml comments $contents = preg_replace('~\<!\-\-/.*?\-\-\>~s', '', $contents); break; @@ -299,6 +296,9 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { return $contents; } + /** + * @inheritdoc + */ public function testUndeclared() { $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); @@ -341,12 +341,12 @@ function ($fileType, $file) { $result = []; foreach ($undeclaredDependency as $type => $modules) { $modules = array_unique($modules); - if (!count($modules)) { + if (empty($modules)) { continue; } $result[] = sprintf("%s [%s]", $type, implode(', ', $modules)); } - if (count($result)) { + if (!empty($result)) { $this->fail('Module ' . $module . ' has undeclared dependencies: ' . implode(', ', $result)); } }, @@ -392,7 +392,7 @@ protected function getDependenciesFromFiles($module, $fileType, $file, $contents */ protected function _collectDependencies($currentModuleName, $dependencies = []) { - if (!count($dependencies)) { + if (empty($dependencies)) { return []; } $undeclared = []; @@ -430,18 +430,35 @@ private function collectDependency($dependency, $currentModule, &$undeclared) /** * Collect redundant dependencies + * * @SuppressWarnings(PHPMD.NPathComplexity) * @test * @depends testUndeclared + * @throws \Exception */ public function collectRedundant() { + $schemaDependencyProvider = new DeclarativeSchemaDependencyProvider(); + + /** TODO: Remove this temporary solution after MC-5806 is closed */ + $filePattern = __DIR__ . '/_files/dependency_test/undetected_dependencies_*.php'; + $undetectedDependencies = []; + foreach (glob($filePattern) as $fileName) { + $undetectedDependencies = array_merge($undetectedDependencies, require $fileName); + } + foreach (array_keys(self::$mapDependencies) as $module) { $declared = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_DECLARED); $found = array_merge( $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_FOUND), - $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND) + $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND), + $schemaDependencyProvider->getDeclaredExistingModuleDependencies($module) ); + /** TODO: Remove this temporary solution after MC-5806 is closed */ + if (!empty($undetectedDependencies[$module])) { + $found = array_merge($found, $undetectedDependencies[$module]); + } + $found['Magento\Framework'] = 'Magento\Framework'; $this->_setDependencies($module, self::TYPE_HARD, self::MAP_TYPE_REDUNDANT, array_diff($declared, $found)); } @@ -458,7 +475,7 @@ public function testRedundant() foreach (array_keys(self::$mapDependencies) as $module) { $result = []; $redundant = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_REDUNDANT); - if (count($redundant)) { + if (!empty($redundant)) { $result[] = sprintf( "\r\nModule %s: %s [%s]", $module, @@ -467,11 +484,11 @@ public function testRedundant() ); } - if (count($result)) { + if (!empty($result)) { $output[] = implode(', ', $result); } } - if (count($output)) { + if (!empty($output)) { $this->fail("Redundant dependencies found!\r\n" . implode(' ', $output)); } } @@ -501,6 +518,7 @@ protected function _prepareFiles($fileType, $files, $skip = null) * Return all files * * @return array + * @throws \Exception */ public function getAllFiles() { @@ -522,12 +540,6 @@ public function getAllFiles() $this->_prepareFiles('config', Files::init()->getConfigFiles()) ); - // Get all configuration files - $files = array_merge( - $files, - $this->_prepareFiles('db_schema', Files::init()->getDbSchemaFiles()) - ); - //Get all layout updates files $files = array_merge( $files, @@ -557,20 +569,6 @@ protected static function _prepareListConfigXml() } } - /** - * Prepare list of db_schema.xml files (by modules) - */ - protected static function _prepareListDbSchemaXml() - { - $files = Files::init()->getDbSchemaFiles('db_schema.xml', [], false); - foreach ($files as $file) { - if (preg_match('/(?<namespace>[A-Z][a-z]+)[_\/\\\\](?<module>[A-Z][a-zA-Z]+)/', $file, $matches)) { - $module = $matches['namespace'] . '\\' . $matches['module']; - self::$_listDbSchemaXml[$module] = $file; - } - } - } - /** * Prepare list of routes.xml files (by modules) */ @@ -658,7 +656,7 @@ protected static function _prepareMapLayoutBlocks() $area = 'default'; if (preg_match('/[\/](?<area>adminhtml|frontend)[\/]/', $file, $matches)) { $area = $matches['area']; - self::$_mapLayoutBlocks[$area] = @(self::$_mapLayoutBlocks[$area] ?: []); + self::$_mapLayoutBlocks[$area] = self::$_mapLayoutBlocks[$area] ?? []; } if (preg_match('/(?<namespace>[A-Z][a-z]+)[_\/\\\\](?<module>[A-Z][a-zA-Z]+)/', $file, $matches)) { $module = $matches['namespace'] . '\\' . $matches['module']; @@ -668,7 +666,7 @@ protected static function _prepareMapLayoutBlocks() $attributes = $element->attributes(); $block = (string)$attributes->name; if (!empty($block)) { - self::$_mapLayoutBlocks[$area][$block] = @(self::$_mapLayoutBlocks[$area][$block] ?: []); + self::$_mapLayoutBlocks[$area][$block] = self::$_mapLayoutBlocks[$area][$block] ?? []; self::$_mapLayoutBlocks[$area][$block][$module] = $module; } } @@ -686,7 +684,7 @@ protected static function _prepareMapLayoutHandles() $area = 'default'; if (preg_match('/\/(?<area>adminhtml|frontend)\//', $file, $matches)) { $area = $matches['area']; - self::$_mapLayoutHandles[$area] = @(self::$_mapLayoutHandles[$area] ?: []); + self::$_mapLayoutHandles[$area] = self::$_mapLayoutHandles[$area] ?? []; } if (preg_match('/app\/code\/(?<namespace>[A-Z][a-z]+)[_\/\\\\](?<module>[A-Z][a-zA-Z]+)/', $file, $matches) ) { @@ -695,7 +693,7 @@ protected static function _prepareMapLayoutHandles() foreach ((array)$xml->xpath('/layout/child::*') as $element) { /** @var \SimpleXMLElement $element */ $handle = $element->getName(); - self::$_mapLayoutHandles[$area][$handle] = @(self::$_mapLayoutHandles[$area][$handle] ?: []); + self::$_mapLayoutHandles[$area][$handle] = self::$_mapLayoutHandles[$area][$handle] ?? []; self::$_mapLayoutHandles[$area][$handle][$module] = $module; } } @@ -755,6 +753,7 @@ protected static function _initDependencies() $contents = file_get_contents($file); $decodedJson = json_decode($contents); if (null == $decodedJson) { + //phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception("Invalid Json: $file"); } $json = new \Magento\Framework\Config\Composer\Package(json_decode($contents)); @@ -843,6 +842,7 @@ private static function getPackageModuleMapping(): array $contents = file_get_contents($file); $composerJson = json_decode($contents); if (null == $composerJson) { + //phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception("Invalid Json: $file"); } $moduleXml = simplexml_load_file(dirname($file) . '/etc/module.xml'); diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/blacklisted_dependencies_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/blacklisted_dependencies_ce.php new file mode 100644 index 0000000000000..270cb99c29caa --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/blacklisted_dependencies_ce.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +return [ + "Magento\InventorySales" => ["Magento\Inventory"], +]; diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php index 530f55504d009..3fb53be2ec400 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php @@ -96,7 +96,7 @@ 'catalogrule_website' => 'Magento\CatalogRule', 'catalogsearch_fulltext' => 'Magento\CatalogSearch', 'catalogsearch_result' => 'Magento\CatalogSearch', - 'search_query' => 'Magento\CatalogSearch', + 'search_query' => 'Magento\Search', 'checkout_agreement' => 'Magento\Checkout', 'checkout_agreement_store' => 'Magento\Checkout', 'cms_block' => 'Magento\Cms', diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php new file mode 100644 index 0000000000000..407f57ee51257 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); +return [ + "Magento\Search" => ["Magento\CatalogSearch"] +]; diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index 92f0302d93baf..6bdb74ef7b89a 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -47,7 +47,7 @@ class ConfigOptionsListConstants const CONFIG_PATH_SCD_ON_DEMAND_IN_PRODUCTION = 'static_content_on_demand_in_production'; /** - * Paramater for forcing HTML minification even if file is already minified. + * Parameter for forcing HTML minification even if file is already minified. */ const CONFIG_PATH_FORCE_HTML_MINIFICATION = 'force_html_minification'; diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManager.php b/lib/internal/Magento/Framework/Crontab/CrontabManager.php index 6049b7fba6d44..da81540faf477 100644 --- a/lib/internal/Magento/Framework/Crontab/CrontabManager.php +++ b/lib/internal/Magento/Framework/Crontab/CrontabManager.php @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ + namespace Magento\Framework\Crontab; use Magento\Framework\App\Filesystem\DirectoryList; @@ -40,31 +41,35 @@ public function __construct( } /** + * Build tasks block start text. + * * @return string */ private function getTasksBlockStart() { $tasksBlockStart = self::TASKS_BLOCK_START; if (defined('BP')) { - $tasksBlockStart .= ' ' . md5(BP); + $tasksBlockStart .= ' ' . hash("sha256", BP); } return $tasksBlockStart; } /** + * Build tasks block end text. + * * @return string */ private function getTasksBlockEnd() { $tasksBlockEnd = self::TASKS_BLOCK_END; if (defined('BP')) { - $tasksBlockEnd .= ' ' . md5(BP); + $tasksBlockEnd .= ' ' . hash("sha256", BP); } return $tasksBlockEnd; } /** - * {@inheritdoc} + * @inheritdoc */ public function getTasks() { @@ -82,7 +87,7 @@ public function getTasks() } /** - * {@inheritdoc} + * @inheritdoc */ public function saveTasks(array $tasks) { @@ -118,8 +123,7 @@ public function saveTasks(array $tasks) } /** - * {@inheritdoc} - * @throws LocalizedException + * @inheritdoc */ public function removeTasks() { @@ -182,7 +186,7 @@ private function cleanMagentoSection($content) private function getCrontabContent() { try { - $content = (string)$this->shell->execute('crontab -l'); + $content = (string)$this->shell->execute('crontab -l 2>/dev/null'); } catch (LocalizedException $e) { return ''; } @@ -203,6 +207,7 @@ private function save($content) try { $this->shell->execute('echo "' . $content . '" | crontab -'); + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (LocalizedException $e) { throw new LocalizedException( new Phrase('Error during saving of crontab: %1', [$e->getPrevious()->getMessage()]), diff --git a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php index b160eb0a7f95e..f6c863d9d9fad 100644 --- a/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php +++ b/lib/internal/Magento/Framework/Crontab/Test/Unit/CrontabManagerTest.php @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ + namespace Magento\Framework\Crontab\Test\Unit; use Magento\Framework\Crontab\CrontabManager; @@ -16,6 +17,9 @@ use Magento\Framework\Filesystem\Directory\ReadInterface; use Magento\Framework\Filesystem\DriverPool; +/** + * Tests crontab manager functionality. + */ class CrontabManagerTest extends \PHPUnit\Framework\TestCase { /** @@ -58,7 +62,7 @@ public function testGetTasksNoCrontab() $this->shellMock->expects($this->once()) ->method('execute') - ->with('crontab -l', []) + ->with('crontab -l 2>/dev/null', []) ->willThrowException($localizedException); $this->assertEquals([], $this->crontabManager->getTasks()); @@ -74,7 +78,7 @@ public function testGetTasks($content, $tasks) { $this->shellMock->expects($this->once()) ->method('execute') - ->with('crontab -l', []) + ->with('crontab -l 2>/dev/null', []) ->willReturn($content); $this->assertEquals($tasks, $this->crontabManager->getTasks()); @@ -88,17 +92,17 @@ public function getTasksDataProvider() return [ [ 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, 'tasks' => ['* * * * * /bin/php /var/www/magento/bin/magento cron:run'], ], [ 'content' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, 'tasks' => [ '* * * * * /bin/php /var/www/magento/bin/magento cron:run', '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run', @@ -127,7 +131,7 @@ public function testRemoveTasksWithException() $this->shellMock->expects($this->at(0)) ->method('execute') - ->with('crontab -l', []) + ->with('crontab -l 2>/dev/null', []) ->willReturn(''); $this->shellMock->expects($this->at(1)) @@ -148,7 +152,7 @@ public function testRemoveTasks($contentBefore, $contentAfter) { $this->shellMock->expects($this->at(0)) ->method('execute') - ->with('crontab -l', []) + ->with('crontab -l 2>/dev/null', []) ->willReturn($contentBefore); $this->shellMock->expects($this->at(1)) @@ -166,17 +170,17 @@ public function removeTasksDataProvider() return [ [ 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL ], [ 'contentBefore' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento setup:cron:run' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, 'contentAfter' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL ], [ @@ -276,7 +280,7 @@ public function testSaveTasks($tasks, $content, $contentToSave) $this->shellMock->expects($this->at(0)) ->method('execute') - ->with('crontab -l', []) + ->with('crontab -l 2>/dev/null', []) ->willReturn($content); $this->shellMock->expects($this->at(1)) @@ -292,9 +296,9 @@ public function testSaveTasks($tasks, $content, $contentToSave) public function saveTasksDataProvider() { $content = '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * /bin/php /var/www/magento/bin/magento cron:run' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL; + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL; return [ [ @@ -303,9 +307,9 @@ public function saveTasksDataProvider() ], 'content' => $content, 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * ' . PHP_BINARY . ' run.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, ], [ 'tasks' => [ @@ -313,9 +317,9 @@ public function saveTasksDataProvider() ], 'content' => $content, 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '1 2 3 4 5 ' . PHP_BINARY . ' run.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, ], [ 'tasks' => [ @@ -323,10 +327,10 @@ public function saveTasksDataProvider() ], 'content' => $content, 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php >>' . ' /var/www/magento2/var/log/cron.log' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, ], [ 'tasks' => [ @@ -334,10 +338,10 @@ public function saveTasksDataProvider() ], 'content' => $content, 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php' . ' %% cron:run | grep -v \"Ran \'jobs\' by schedule\"' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, ], [ 'tasks' => [ @@ -345,10 +349,10 @@ public function saveTasksDataProvider() ], 'content' => '* * * * * /bin/php /var/www/cron.php', 'contentToSave' => '* * * * * /bin/php /var/www/cron.php' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . md5(BP) . PHP_EOL + . CrontabManagerInterface::TASKS_BLOCK_START . ' ' . hash("sha256", BP) . PHP_EOL . '* * * * * ' . PHP_BINARY . ' /var/www/magento2/run.php' . ' %% cron:run | grep -v \"Ran \'jobs\' by schedule\"' . PHP_EOL - . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . md5(BP) . PHP_EOL, + . CrontabManagerInterface::TASKS_BLOCK_END . ' ' . hash("sha256", BP) . PHP_EOL, ], ]; } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Label.php b/lib/internal/Magento/Framework/Data/Form/Element/Label.php index 901dcb5289e8d..70b7885e7a0d0 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Label.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Label.php @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ -/** - * Data form abstract class - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Framework\Data\Form\Element; +use Magento\Framework\Phrase; + +/** + * Label form element. + */ class Label extends \Magento\Framework\Data\Form\Element\AbstractElement { /** @@ -37,8 +37,13 @@ public function __construct( public function getElementHtml() { $html = $this->getBold() ? '<div class="control-value special">' : '<div class="control-value">'; - $html .= $this->getEscapedValue() . '</div>'; + if (is_string($this->getValue()) || $this->getValue() instanceof Phrase) { + $html .= $this->getEscapedValue(); + } + + $html .= '</div>'; $html .= $this->getAfterElementHtml(); + return $html; } } diff --git a/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php b/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php index a7a387b5def81..acd0a61633557 100644 --- a/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php +++ b/lib/internal/Magento/Framework/Event/Invoker/InvokerDefault.php @@ -9,7 +9,12 @@ namespace Magento\Framework\Event\Invoker; use Magento\Framework\Event\Observer; +use Psr\Log\LoggerInterface; +use Magento\Framework\App\State; +/** + * Default Invoker. + */ class InvokerDefault implements \Magento\Framework\Event\InvokerInterface { /** @@ -22,20 +27,29 @@ class InvokerDefault implements \Magento\Framework\Event\InvokerInterface /** * Application state * - * @var \Magento\Framework\App\State + * @var State */ protected $_appState; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param \Magento\Framework\Event\ObserverFactory $observerFactory - * @param \Magento\Framework\App\State $appState + * @param State $appState + * @param LoggerInterface $logger */ public function __construct( \Magento\Framework\Event\ObserverFactory $observerFactory, - \Magento\Framework\App\State $appState + State $appState, + LoggerInterface $logger = null ) { $this->_observerFactory = $observerFactory; $this->_appState = $appState; + $this->logger = $logger ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(LoggerInterface::class); } /** @@ -61,6 +75,8 @@ public function dispatch(array $configuration, Observer $observer) } /** + * Execute Observer. + * * @param \Magento\Framework\Event\ObserverInterface $object * @param Observer $observer * @return $this @@ -70,7 +86,7 @@ protected function _callObserverMethod($object, $observer) { if ($object instanceof \Magento\Framework\Event\ObserverInterface) { $object->execute($observer); - } elseif ($this->_appState->getMode() == \Magento\Framework\App\State::MODE_DEVELOPER) { + } elseif ($this->_appState->getMode() == State::MODE_DEVELOPER) { throw new \LogicException( sprintf( 'Observer "%s" must implement interface "%s"', @@ -78,6 +94,12 @@ protected function _callObserverMethod($object, $observer) \Magento\Framework\Event\ObserverInterface::class ) ); + } else { + $this->logger->warning(sprintf( + 'Observer "%s" must implement interface "%s"', + get_class($object), + \Magento\Framework\Event\ObserverInterface::class + )); } return $this; } diff --git a/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php b/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php index 37f650dbef6a0..e6ec123823854 100644 --- a/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php +++ b/lib/internal/Magento/Framework/Event/Test/Unit/Invoker/InvokerDefaultTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Event\Test\Unit\Invoker; +/** + * Test for Magento\Framework\Event\Invoker\InvokerDefault. + */ class InvokerDefaultTest extends \PHPUnit\Framework\TestCase { /** @@ -32,6 +35,11 @@ class InvokerDefaultTest extends \PHPUnit\Framework\TestCase */ protected $_invokerDefault; + /** + * @var |Psr\Log|LoggerInterface + */ + private $loggerMock; + protected function setUp() { $this->_observerFactoryMock = $this->createMock(\Magento\Framework\Event\ObserverFactory::class); @@ -41,10 +49,12 @@ protected function setUp() ['execute'] ); $this->_appStateMock = $this->createMock(\Magento\Framework\App\State::class); + $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); $this->_invokerDefault = new \Magento\Framework\Event\Invoker\InvokerDefault( $this->_observerFactoryMock, - $this->_appStateMock + $this->_appStateMock, + $this->loggerMock ); } @@ -166,13 +176,15 @@ public function testWrongInterfaceCallWithDisabledDeveloperMode($shared) $this->returnValue($notObserver) ); $this->_appStateMock->expects( - $this->once() + $this->exactly(1) )->method( 'getMode' )->will( $this->returnValue(\Magento\Framework\App\State::MODE_PRODUCTION) ); + $this->loggerMock->expects($this->once())->method('warning'); + $this->_invokerDefault->dispatch( [ 'shared' => $shared, diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php index 51e7ea9be0c24..d4c98cda17e12 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Search\Adapter\Mysql\Query\Builder; use Magento\Framework\DB\Helper\Mysql\Fulltext; @@ -28,7 +30,7 @@ class Match implements QueryInterface */ const SPECIAL_CHARACTERS = '-+~/\\<>\'":*$#@()!,.?`=%&^'; - const MINIMAL_CHARACTER_LENGTH = 3; + const MINIMAL_CHARACTER_LENGTH = 1; /** * @var string[] diff --git a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd index 39cdec05a65ea..6486b39070788 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd @@ -313,6 +313,7 @@ </xs:choice> <xs:attribute type="elementNameType" name="name" use="required"/> <xs:attribute type="xs:string" name="template" use="optional"/> + <xs:attribute type="xs:string" name="class" use="optional"/> <xs:attribute type="xs:boolean" name="display" default="true" use="optional"/> <xs:attribute type="xs:boolean" name="remove" use="optional"/> </xs:complexType> diff --git a/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php b/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php new file mode 100644 index 0000000000000..8de30e92218c0 --- /dev/null +++ b/lib/internal/Magento/Framework/Webapi/CustomAttribute/PreprocessorInterface.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Webapi\CustomAttribute; + +/** + * Interface for attribute preprocessor + */ +interface PreprocessorInterface +{ + /** + * Check if this attribute data should be processed + * + * @param string $key + * @param mixed $attribute + * @return bool + */ + public function shouldBeProcessed(string $key, $attribute): bool; + + /** + * Process attribute object according to type rules + * + * @param string $key + * @param mixed $attribute + */ + public function process(string $key, &$attribute); + + /** + * Get list of affected attributes for the current preprocessor + * + * @return array + */ + public function getAffectedAttributes(): array; +} diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index a9b553f6dd6f9..c253a400bed93 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -20,6 +20,7 @@ use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Reflection\TypeProcessor; use Magento\Framework\Webapi\Exception as WebapiException; +use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Zend\Code\Reflection\ClassReflection; /** @@ -72,6 +73,16 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface */ private $config; + /** + * @var PreprocessorInterface[] + */ + private $customAttributePreprocessors; + + /** + * @var array + */ + private $attributesPreprocessorsMap = []; + /** * Initialize dependencies. * @@ -82,6 +93,7 @@ class ServiceInputProcessor implements ServicePayloadConverterInterface * @param MethodsMap $methodsMap * @param ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap * @param ConfigInterface $config + * @param array $customAttributePreprocessors */ public function __construct( TypeProcessor $typeProcessor, @@ -90,7 +102,8 @@ public function __construct( CustomAttributeTypeLocatorInterface $customAttributeTypeLocator, MethodsMap $methodsMap, ServiceTypeToEntityTypeMap $serviceTypeToEntityTypeMap = null, - ConfigInterface $config = null + ConfigInterface $config = null, + array $customAttributePreprocessors = [] ) { $this->typeProcessor = $typeProcessor; $this->objectManager = $objectManager; @@ -101,6 +114,7 @@ public function __construct( ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ServiceTypeToEntityTypeMap::class); $this->config = $config ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ConfigInterface::class); + $this->customAttributePreprocessors = $customAttributePreprocessors; } /** @@ -132,7 +146,6 @@ private function getNameFinder() * @param string $serviceMethodName name of the method that we are trying to call * @param array $inputArray data to send to method in key-value format * @return array list of parameters that can be used to call the service method - * @throws InputException if no value is provided for required parameters * @throws WebapiException */ public function process($serviceClassName, $serviceMethodName, array $inputArray) @@ -216,6 +229,7 @@ private function getConstructorData(string $className, array $data): array * @param array $data * @return object the newly created and populated object * @throws \Exception + * @throws SerializationException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _createFromArray($className, $data) @@ -261,6 +275,7 @@ protected function _createFromArray($className, $data) } else { $setterValue = $this->convertValue($value, $returnType); } + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (SerializationException $e) { throw new SerializationException( new Phrase( @@ -289,12 +304,11 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat $dataObjectClassName = ltrim($dataObjectClassName, '\\'); foreach ($customAttributesValueArray as $key => $customAttribute) { + $this->runCustomAttributePreprocessors($key, $customAttribute); if (!is_array($customAttribute)) { $customAttribute = [AttributeValue::ATTRIBUTE_CODE => $key, AttributeValue::VALUE => $customAttribute]; } - list($customAttributeCode, $customAttributeValue) = $this->processCustomAttribute($customAttribute); - $entityType = $this->serviceTypeToEntityTypeMap->getEntityType($dataObjectClassName); if ($entityType) { $type = $this->customAttributeTypeLocator->getType( @@ -310,6 +324,7 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat ) { try { $attributeValue = $this->convertValue($customAttributeValue, $type); + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (SerializationException $e) { throw new SerializationException( new Phrase( @@ -331,11 +346,49 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat return $result; } + /** + * Get map of preprocessors related to the custom attributes + * + * @return array + */ + private function getAttributesPreprocessorsMap(): array + { + if (!$this->attributesPreprocessorsMap) { + foreach ($this->customAttributePreprocessors as $attributePreprocessor) { + foreach ($attributePreprocessor->getAffectedAttributes() as $attributeKey) { + $this->attributesPreprocessorsMap[$attributeKey][] = $attributePreprocessor; + } + } + } + + return $this->attributesPreprocessorsMap; + } + + /** + * Prepare attribute value by loaded attribute preprocessors + * + * @param mixed $key + * @param mixed $customAttribute + */ + private function runCustomAttributePreprocessors($key, &$customAttribute) + { + $preprocessorsMap = $this->getAttributesPreprocessorsMap(); + if ($key && is_array($customAttribute) && array_key_exists($key, $preprocessorsMap)) { + $preprocessorsList = $preprocessorsMap[$key]; + foreach ($preprocessorsList as $attributePreprocessor) { + if ($attributePreprocessor->shouldBeProcessed($key, $customAttribute)) { + $attributePreprocessor->process($key, $customAttribute); + } + } + } + } + /** * Derive the custom attribute code and value. * * @param string[] $customAttribute * @return string[] + * @throws SerializationException */ private function processCustomAttribute($customAttribute) { diff --git a/lib/web/css/source/lib/variables/_colors.less b/lib/web/css/source/lib/variables/_colors.less index 9c694468e9f62..ffb0e8e797d81 100644 --- a/lib/web/css/source/lib/variables/_colors.less +++ b/lib/web/css/source/lib/variables/_colors.less @@ -7,9 +7,13 @@ // Color variables // _____________________________________________ +@color-blue-dodger: #008bdb; +@color-black_dark: #333333; + @color-white: #fff; @color-black: #000; +@color-darkie-gray: #8a837f; @color-gray19: #303030; @color-gray20: #333; @color-gray34: #575757; @@ -29,12 +33,16 @@ @color-gray79: #c9c9c9; @color-gray80: #ccc; @color-gray82: #d1d1d1; +@color-gray83: #d4d4d4; @color-gray89: #e3e3e3; @color-gray90: #e5e5e5; @color-gray91: #e8e8e8; @color-gray92: #ebebeb; @color-gray94: #f0f0f0; @color-gray95: #f2f2f2; +@color-gray_light: #cccccc; +@color-lighter-grayish: #cacaca; +@color-very-dark-gray: #666; @color-white-smoke: #f5f5f5; @color-white-dark-smoke: #efefef; @color-white-fog: #f8f8f8; @@ -80,6 +88,8 @@ @color-pink1: #fae5e5; @color-dark-pink1: #800080; // Legacy pink +@color-brownie: #514943; +@color-brownie-vanilla: #736963; @color-brownie1: #6f4400; @color-brownie-light1: #c07600; diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js index 18d71aad2071a..cfcdef0b701c9 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -132,7 +132,7 @@ define([ attributes.type = attributes.type.replace(/\\\\/g, '\\'); imageSrc = config.placeholders[attributes.type]; - if (config.types.indexOf(attributes['type_name']) > -1) { + if (imageSrc) { imageHtml += '<span class="magento-placeholder magento-widget mceNonEditable" ' + 'contenteditable="false">'; } else { @@ -147,8 +147,8 @@ define([ imageHtml += ' src="' + imageSrc + '"'; imageHtml += ' />'; - if (attributes['type_name']) { - imageHtml += attributes['type_name']; + if (config.types[attributes.type]) { + imageHtml += config.types[attributes.type]; } imageHtml += '</span>'; diff --git a/lib/web/mage/gallery/gallery.js b/lib/web/mage/gallery/gallery.js index 2ed3f8b0d620c..be78856b21fcd 100644 --- a/lib/web/mage/gallery/gallery.js +++ b/lib/web/mage/gallery/gallery.js @@ -141,7 +141,7 @@ define([ this.setupBreakpoints(); this.initFullscreenSettings(); - this.settings.$element.on('mousedown', '.fotorama__stage__frame', function () { + this.settings.$element.on('click', '.fotorama__stage__frame', function () { if ( !$(this).parents('.fotorama__shadows--left, .fotorama__shadows--right').length && !$(this).hasClass('fotorama-video-container') diff --git a/lib/web/mage/gallery/gallery.less b/lib/web/mage/gallery/gallery.less index 373708ac35a00..2c3476732caad 100644 --- a/lib/web/mage/gallery/gallery.less +++ b/lib/web/mage/gallery/gallery.less @@ -977,12 +977,9 @@ // While first time init .gallery-placeholder { - .loading-mask { - padding: 0 0 50%; - position: static; - } - .loader img { - position: absolute; + &__image { + display: block; + margin: auto; } } @@ -1003,6 +1000,7 @@ display: block; } } + .fotorama__product-video--loaded { .fotorama__img, .fotorama__img--full { display: none !important; diff --git a/pub/.htaccess b/pub/.htaccess index 85a204c85e8a8..6a97a6d14dc00 100644 --- a/pub/.htaccess +++ b/pub/.htaccess @@ -47,11 +47,6 @@ php_flag session.auto_start off ############################################ -## Enable resulting html compression - - #php_flag zlib.output_compression on - -########################################### # Disable user agent verification to not break multiple image upload php_flag suhosin.session.cryptua off diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 0e1860405e946..cfabe613a0ea1 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -34,6 +34,11 @@ <stringProp name="Argument.value">${__P(request_protocol,http)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="graphql_port_number" elementType="Argument"> + <stringProp name="Argument.name">graphql_port_number</stringProp> + <stringProp name="Argument.value">${__P(graphql_port_number,)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="admin_password" elementType="Argument"> <stringProp name="Argument.name">admin_password</stringProp> <stringProp name="Argument.value">${__P(admin_password,123123q)}</stringProp> @@ -354,6 +359,81 @@ <stringProp name="Argument.value">${__P(frontendPoolUsers,1)}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> + <elementProp name="graphQLPoolUsers" elementType="Argument"> + <stringProp name="Argument.name">graphQLPoolUsers</stringProp> + <stringProp name="Argument.value">${__P(graphQLPoolUsers,1)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlAddConfigurableProductToCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlAddConfigurableProductToCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlAddConfigurableProductToCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlAddSimpleProductToCartPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlAddSimpleProductToCartPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlAddSimpleProductToCartPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetCategoryListByCategoryIdPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetCategoryListByCategoryIdPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetCategoryListByCategoryIdPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetCmsBlockByIdentifierPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetCmsBlockByIdentifierPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetCmsBlockByIdentifierPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetCmsPageByIdPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetCmsPageByIdPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetCmsPageByIdPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetConfigurableProductDetailsByNamePercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetConfigurableProductDetailsByNamePercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetConfigurableProductDetailsByNamePercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetConfigurableProductDetailsByProductUrlKeyPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetConfigurableProductDetailsByProductUrlKeyPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetConfigurableProductDetailsByProductUrlKeyPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetListOfProductsByCategoryIdPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetListOfProductsByCategoryIdPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetListOfProductsByCategoryIdPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetNavigationMenuByCategoryIdPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetNavigationMenuByCategoryIdPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetNavigationMenuByCategoryIdPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetProductDetailByProductNamePercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetProductDetailByProductNamePercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetProductDetailByProductNamePercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetProductSearchByTextAndCategoryIdPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetProductSearchByTextAndCategoryIdPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetProductSearchByTextAndCategoryIdPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetSimpleProductDetailsByNamePercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetSimpleProductDetailsByNamePercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetSimpleProductDetailsByNamePercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlGetSimpleProductDetailsByProductUrlKeyPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlGetSimpleProductDetailsByProductUrlKeyPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlGetSimpleProductDetailsByProductUrlKeyPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + <elementProp name="graphqlUrlInfoByUrlKeyPercentage" elementType="Argument"> + <stringProp name="Argument.name">graphqlUrlInfoByUrlKeyPercentage</stringProp> + <stringProp name="Argument.value">${__P(graphqlUrlInfoByUrlKeyPercentage,0)}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> <elementProp name="guest_checkout_percent" elementType="Argument"> <stringProp name="Argument.name">guest_checkout_percent</stringProp> <stringProp name="Argument.value">${__P(guest_checkout_percent,100)}</stringProp> @@ -658,6 +738,9 @@ props.remove("configurable_products_list"); props.remove("configurable_products_list_for_edit"); props.remove("users"); props.remove("customer_emails_list"); +props.remove("categories"); +props.remove("cms_pages"); +props.remove("cms_blocks"); /* This is only used when admin is enabled. */ props.put("activeAdminThread", ""); @@ -821,167 +904,253 @@ if (!slash.equals(path.substring(path.length() -1)) || !slash.equals(path.substr </hashTree> </hashTree> - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract categories" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_categories.jmx</stringProp> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract admin users" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_admin_users.jmx</stringProp> </TestFragmentController> <hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Extract categories" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> - </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Extract Admin Users" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="form_key" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">${admin_form_key}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">form_key</stringProp> </elementProp> </collectionProp> - </HeaderManager> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/user/roleGrid/limit/200/?ajax=true&isAjax=true</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="BeanShell PostProcessor" enabled="true"> + <stringProp name="filename"/> + <stringProp name="parameters"/> + <boolProp name="resetInterpreter">false</boolProp> + <stringProp name="script">import java.util.regex.Pattern; + import java.util.regex.Matcher; + import java.util.LinkedList; + + LinkedList adminUserList = new LinkedList(); + String response = new String(data); + Pattern pattern = Pattern.compile("<td\\W*?data-column=.username[^>]*?>\\W*?(\\w+)\\W*?<"); + Matcher matcher = pattern.matcher(response); + + while (matcher.find()) { + adminUserList.add(matcher.group(1)); + } + + adminUserList.poll(); + props.put("adminUserList", adminUserList); + props.put("adminUserListIterator", adminUserList.descendingIterator()); + </stringProp> + </BeanShellPostProcessor> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> + </hashTree> + </hashTree> + + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract customers" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_customers.jmx</stringProp> +</TestFragmentController> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Open Customer Grid" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/customer/index/</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <BeanShellPreProcessor guiclass="TestBeanGUI" testclass="BeanShellPreProcessor" testname="BeanShell PreProcessor" enabled="true"> + <stringProp name="filename"/> + <stringProp name="parameters"/> + <boolProp name="resetInterpreter">true</boolProp> + <stringProp name="script">import org.apache.jmeter.protocol.http.control.CookieManager; +import org.apache.jmeter.protocol.http.control.Cookie; +CookieManager manager = sampler.getCookieManager(); +Cookie cookie = new Cookie("adminhtml",vars.get("COOKIE_adminhtml"),vars.get("host"),"/",false,0); +manager.add(cookie); </stringProp> + </BeanShellPreProcessor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert Customer Grid" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-679437259">Customers</stringProp> + <stringProp name="495525733"><title>Customers / Customers / Magento Admin</title></stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Search Customers" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="namespace" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">customer_listing</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">namespace</stringProp> + </elementProp> + <elementProp name="sorting[field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">entity_id</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">sorting[field]</stringProp> + </elementProp> + <elementProp name="sorting[direction]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">asc</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">sorting[direction]</stringProp> + </elementProp> + <elementProp name="isAjax" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">isAjax</stringProp> + </elementProp> + <elementProp name="filters[placeholder]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">customer_since[locale]=en_US</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">filters[placeholder]</stringProp> + </elementProp> + <elementProp name="filters[group_id]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">filters[group_id]</stringProp> + </elementProp> + <elementProp name="filters[website_id]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">filters[website_id]</stringProp> + </elementProp> + <elementProp name="paging[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${customers_page_size}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">paging[pageSize]</stringProp> + </elementProp> + <elementProp name="paging[current]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">paging[current]</stringProp> + </elementProp> + <elementProp name="sorting[field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">entity_id</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">sorting[field]</stringProp> + </elementProp> + <elementProp name="sorting[direction]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">asc</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">sorting[direction]</stringProp> + </elementProp> + <elementProp name="isAjax" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">isAjax</stringProp> </elementProp> </collectionProp> - </HeaderManager> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert total records is not 0" enabled="true"> + <stringProp name="JSON_PATH">$.totalRecords</stringProp> + <stringProp name="EXPECTED_VALUE">0</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">true</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer emails" enabled="true"> + <stringProp name="VAR">customer_emails</stringProp> + <stringProp name="JSONPATH">$.items[*].email</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer ids" enabled="true"> + <stringProp name="VAR">customer_ids</stringProp> + <stringProp name="JSONPATH">$.items[*].entity_id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="Make email list" enabled="true"> + <boolProp name="resetInterpreter">false</boolProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="script">import java.util.LinkedList; +LinkedList emailsList = new LinkedList(); +props.put("customer_emails_list", emailsList);</stringProp> + </BeanShellPostProcessor> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get Categories Names and skus" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">path</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1/2/%</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][0][filters][0][conditionType]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">like</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][conditionType]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">level</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][field]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][value]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">2</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][value]</stringProp> - </elementProp> - <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${categories_count}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/categories/list</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">false</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract category url key" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">category_url_keys</stringProp> - <stringProp name="RegexExtractor.regex">url_key\",\"value\":\"(.*?)\"</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> - <hashTree/> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract category name" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">category_names</stringProp> - <stringProp name="RegexExtractor.regex">name\":\"(.*?)\"</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> - <hashTree/> - </hashTree> </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: category url keys loop" enabled="true"> - <stringProp name="ForeachController.inputVal">category_url_keys</stringProp> - <stringProp name="ForeachController.returnVal">category_url_key</stringProp> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: customer emails loop (search result)" enabled="true"> + <stringProp name="ForeachController.inputVal">customer_emails</stringProp> + <stringProp name="ForeachController.returnVal">customer_email</stringProp> <boolProp name="ForeachController.useSeparator">true</boolProp> </ForeachController> <hashTree> @@ -989,32 +1158,31 @@ if (!slash.equals(path.substring(path.length() -1)) || !slash.equals(path.substr <stringProp name="CounterConfig.start">1</stringProp> <stringProp name="CounterConfig.end"/> <stringProp name="CounterConfig.incr">1</stringProp> - <stringProp name="CounterConfig.name">category_url_key_counter</stringProp> + <stringProp name="CounterConfig.name">email_counter</stringProp> <stringProp name="CounterConfig.format"/> <boolProp name="CounterConfig.per_user">false</boolProp> </CounterConfig> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect category url keys" enabled="true"> - <stringProp name="BeanShellSampler.query">import java.util.ArrayList; + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect customer emails" enabled="true"> + <stringProp name="BeanShellSampler.query"> +try { -// If it is first iteration of cycle then recreate category url key list -if (1 == Integer.parseInt(vars.get("category_url_key_counter"))) { - categoryUrlKeysList = new ArrayList(); - props.put("category_url_keys_list", categoryUrlKeysList); - props.put("category_url_key", vars.get("category_url_key")); -} else { - categoryUrlKeysList = props.get("category_url_keys_list"); +props.get("customer_emails_list").add(vars.get("customer_email")); + +} catch (java.lang.Exception e) { + log.error("error…", e); + SampleResult.setStopThread(true); } -categoryUrlKeysList.add(vars.get("category_url_key"));</stringProp> + </stringProp> <stringProp name="BeanShellSampler.filename"/> <stringProp name="BeanShellSampler.parameters"/> <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> </BeanShellSampler> <hashTree/> </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: category names loop" enabled="true"> - <stringProp name="ForeachController.inputVal">category_names</stringProp> - <stringProp name="ForeachController.returnVal">category_name</stringProp> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: customer ids loop (search result)" enabled="true"> + <stringProp name="ForeachController.inputVal">customer_ids</stringProp> + <stringProp name="ForeachController.returnVal">customer_id</stringProp> <boolProp name="ForeachController.useSeparator">true</boolProp> </ForeachController> <hashTree> @@ -1022,166 +1190,177 @@ categoryUrlKeysList.add(vars.get("category_url_key"));</stringProp> <stringProp name="CounterConfig.start">1</stringProp> <stringProp name="CounterConfig.end"/> <stringProp name="CounterConfig.incr">1</stringProp> - <stringProp name="CounterConfig.name">category_name_counter</stringProp> + <stringProp name="CounterConfig.name">id_counter</stringProp> <stringProp name="CounterConfig.format"/> <boolProp name="CounterConfig.per_user">false</boolProp> </CounterConfig> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect category names" enabled="true"> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect customer ids" enabled="true"> <stringProp name="BeanShellSampler.query">import java.util.ArrayList; -// If it is first iteration of cycle then recreate category name list -if (1 == Integer.parseInt(vars.get("category_name_counter"))) { - categoryNamesList = new ArrayList(); - props.put("category_names_list",categoryNamesList); - props.put("category_name", vars.get("category_name")); +// If it is first iteration of cycle then recreate idsList +if (1 == Integer.parseInt(vars.get("id_counter"))) { + idsList = new ArrayList(); + props.put("customer_ids_list", idsList); } else { - categoryNamesList = props.get("category_names_list"); + idsList = props.get("customer_ids_list"); } - -categoryNamesList.add(vars.get("category_name"));</stringProp> +idsList.add(vars.get("customer_id"));</stringProp> <stringProp name="BeanShellSampler.filename"/> <stringProp name="BeanShellSampler.parameters"/> <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> </BeanShellSampler> <hashTree/> </hashTree> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect category" enabled="true"> - <stringProp name="BeanShellSampler.query">props.put("category_url_key", vars.get("category_url_key")); -props.put("category_name", vars.get("category_name"));</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> </hashTree> - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract categories id of last level" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_categories_id_of_last_level.jmx</stringProp> -</TestFragmentController> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract region ids" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_region_ids.jmx</stringProp> + </TestFragmentController> <hashTree> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Clear Admin Category Management properties" enabled="true"> - <stringProp name="BeanShellSampler.query">props.remove("admin_category_ids_list");</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Get categories of last level" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> - </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Extract Region ids" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="parent" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">US</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">parent</stringProp> </elementProp> </collectionProp> - </HeaderManager> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}${admin_path}/directory/json/countryRegion/</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="Parse and put region id into variables" enabled="true"> + <stringProp name="scriptLanguage">groovy</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">import groovy.json.JsonSlurper +def jsonSlurper = new JsonSlurper(); +def regionResponse = jsonSlurper.parseText(prev.getResponseDataAsString()); + +regionResponse.each { region -> + if (region.label.toString() == "Alabama") { + props.put("alabama_region_id", region.value.toString()); + } else if (region.label.toString() == 'California') { + props.put("california_region_id", region.value.toString()); + } +}</stringProp> + </JSR223PostProcessor> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - API Get categories" enabled="true"> + </hashTree> + </hashTree> + + <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Api Data Retrieval" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/simple_controller.jmx</stringProp> +</GenericController> + <hashTree> + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/admin_token_retrieval.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> + <stringProp name="VAR">admin_token</stringProp> + <stringProp name="JSONPATH">$</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="484395188">^[a-z0-9-]+$</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">1</intProp> + <stringProp name="Assertion.scope">variable</stringProp> + <stringProp name="Scope.variable">admin_token</stringProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Authorization</stringProp> + <stringProp name="Header.value">Bearer ${admin_token}</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager.jmx</stringProp></HeaderManager> + <hashTree/> + + <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="SetUp - Get CMS pages" enabled="true"/> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get CMS pages" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"> - <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">children_count</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">0</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">level</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][field]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][value]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">2</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][value]</stringProp> - </elementProp> - <elementProp name="searchCriteria[filterGroups][1][filters][0][conditionType]" elementType="HTTPArgument"> + <elementProp name="searchCriteria[current_page]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">gt</stringProp> + <stringProp name="Argument.value">1</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][conditionType]</stringProp> + <stringProp name="Argument.name">searchCriteria[current_page]</stringProp> </elementProp> - <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${adminCategoryCount}</stringProp> + <elementProp name="searchCriteria[page_size]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">20</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + <stringProp name="Argument.name">searchCriteria[page_size]</stringProp> </elementProp> </collectionProp> </elementProp> @@ -1191,7 +1370,7 @@ props.put("category_name", vars.get("category_name"));</stringProp> <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/categories/list</stringProp> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/cmsPage/search</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -1199,118 +1378,42 @@ props.put("category_name", vars.get("category_name"));</stringProp> <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/get_cms_pages.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">category_list_id</stringProp> - <stringProp name="RegexExtractor.regex">\{\"id\":(\d+),</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert results are present" enabled="true"> + <stringProp name="JSON_PATH">$.total_count</stringProp> + <stringProp name="EXPECTED_VALUE">0</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">true</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> <hashTree/> - </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Category Id" enabled="true"> - <stringProp name="ForeachController.inputVal">category_list_id</stringProp> - <stringProp name="ForeachController.returnVal">category_id</stringProp> - <boolProp name="ForeachController.useSeparator">true</boolProp> - </ForeachController> - <hashTree> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="Process categories ids" enabled="true"> - <stringProp name="BeanShellSampler.query">import java.util.ArrayList; + <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="PostProcessor" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var data = JSON.parse(prev.getResponseDataAsString()); -adminCategoryIdsList = props.get("admin_category_ids_list"); -// If it is first iteration of cycle then recreate categories ids list -if (adminCategoryIdsList == null) { - adminCategoryIdsList = new ArrayList(); - props.put("admin_category_ids_list", adminCategoryIdsList); -} -adminCategoryIdsList.add(vars.get("category_id"));</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> +var cmsPages = []; + +for (var i in data.items) { + cmsPages.push({"id": data.items[i].id, "identifier": data.items[i].identifier}); + } + +props.put("cms_pages", cmsPages); +</stringProp> + </JSR223PostProcessor> <hashTree/> </hashTree> - </hashTree> </hashTree> <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract configurable products" enabled="true"> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_configurable_products.jmx</stringProp> </TestFragmentController> <hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Retrieve configurable products" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> - </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get configurable products" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"> @@ -1437,83 +1540,11 @@ productList.add(productMap);</stringProp> </BeanShellSampler> <hashTree/> </hashTree> - </hashTree> <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract configurable products for edit" enabled="true"> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_configurable_products_for_edit.jmx</stringProp> </TestFragmentController> <hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Retrieve configurable products for edit" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> - </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get configurable products for edit" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"> @@ -1645,36 +1676,49 @@ editProductList.add(editProductMap);</stringProp> <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> </BeanShellSampler> <hashTree/> - </hashTree> </hashTree> - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract simple products for edit" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_simple_products_for_edit.jmx</stringProp> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract simple products" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_simple_products.jmx</stringProp> </TestFragmentController> <hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Retrieve simple products for edit" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> - </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get simple products" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">type_id</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">simple</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> + </elementProp> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${simple_products_count}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][1][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">attribute_set_id</stringProp> + <stringProp name="Argument.metadata">!=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][1][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][0][filters][1][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">4</stringProp> <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][1][value]</stringProp> </elementProp> </collectionProp> </elementProp> @@ -1684,8 +1728,8 @@ editProductList.add(editProductMap);</stringProp> <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/products</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> <boolProp name="HTTPSampler.use_keepalive">true</boolProp> @@ -1694,35 +1738,94 @@ editProductList.add(editProductMap);</stringProp> <stringProp name="HTTPSampler.embedded_url_re"/> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product url keys" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">simple_products_url_keys</stringProp> + <stringProp name="RegexExtractor.regex">url_key\",\"value\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product ids" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">simple_product_ids</stringProp> + <stringProp name="RegexExtractor.regex">\"id\":(\d+),\"sku\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product titles" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">simple_product_names</stringProp> + <stringProp name="RegexExtractor.regex">name\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> + <hashTree/> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product skus" enabled="true"> + <stringProp name="RegexExtractor.useHeaders">false</stringProp> + <stringProp name="RegexExtractor.refname">simple_product_skus</stringProp> + <stringProp name="RegexExtractor.regex">sku\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.template">$1$</stringProp> + <stringProp name="RegexExtractor.default"/> + <stringProp name="RegexExtractor.match_number">-1</stringProp> + </RegexExtractor> <hashTree/> </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> - </elementProp> - </collectionProp> - </HeaderManager> + </hashTree> + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: Prepare simple products" enabled="true"> + <stringProp name="ForeachController.inputVal">simple_product_ids</stringProp> + <stringProp name="ForeachController.returnVal">simple_product_id</stringProp> + <boolProp name="ForeachController.useSeparator">true</boolProp> + </ForeachController> + <hashTree> + <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter" enabled="true"> + <stringProp name="CounterConfig.start">1</stringProp> + <stringProp name="CounterConfig.end"/> + <stringProp name="CounterConfig.incr">1</stringProp> + <stringProp name="CounterConfig.name">simple_products_counter</stringProp> + <stringProp name="CounterConfig.format"/> + <boolProp name="CounterConfig.per_user">false</boolProp> + </CounterConfig> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect simple product" enabled="true"> + <stringProp name="BeanShellSampler.query">import java.util.ArrayList; +import java.util.HashMap; +import org.apache.commons.codec.binary.Base64; + +// If it is first iteration of cycle then recreate productList +if (1 == Integer.parseInt(vars.get("simple_products_counter"))) { + productList = new ArrayList(); + props.put("simple_products_list", productList); +} else { + productList = props.get("simple_products_list"); +} +String productUrl = vars.get("request_protocol") + "://" + vars.get("host") + vars.get("base_path") + vars.get("simple_products_url_keys_" + vars.get("simple_products_counter"))+ vars.get("url_suffix"); +encodedUrl = Base64.encodeBase64(productUrl.getBytes()); +// Create product map +Map productMap = new HashMap(); +productMap.put("id", vars.get("simple_product_id")); +productMap.put("title", vars.get("simple_product_names_" + vars.get("simple_products_counter"))); +productMap.put("sku", vars.get("simple_product_skus_" + vars.get("simple_products_counter"))); +productMap.put("url_key", vars.get("simple_products_url_keys_" + vars.get("simple_products_counter"))); +productMap.put("uenc", new String(encodedUrl)); + +// Collect products map in products list +productList.add(productMap);</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> <hashTree/> + </hashTree> + + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract simple products for edit" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_simple_products_for_edit.jmx</stringProp> +</TestFragmentController> + <hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get simple products for edit" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> <collectionProp name="Arguments.arguments"> @@ -1869,120 +1972,162 @@ editProductList.add(editProductMap);</stringProp> </BeanShellSampler> <hashTree/> </hashTree> - </hashTree> - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract simple products" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_simple_products.jmx</stringProp> + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract categories (First Level)" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_categories.jmx</stringProp> </TestFragmentController> <hashTree> - <GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="SetUp - Retrieve simple products" enabled="true"/> - <hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Content-Type</stringProp> - <stringProp name="Header.value">application/json</stringProp> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get Categories Names and skus" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">path</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> </elementProp> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Accept</stringProp> - <stringProp name="Header.value">*/*</stringProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1/2/%</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> </elementProp> - </collectionProp> - </HeaderManager> - <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Admin Token Retrieval" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"username":"${admin_user}","password":"${admin_password}"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/integration/admin/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="jp@gc - JSON Path Extractor" enabled="true"> - <stringProp name="VAR">admin_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert token not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="484395188">^[a-z0-9-]+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">admin_token</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> - <collectionProp name="HeaderManager.headers"> - <elementProp name="" elementType="Header"> - <stringProp name="Header.name">Authorization</stringProp> - <stringProp name="Header.value">Bearer ${admin_token}</stringProp> + <elementProp name="searchCriteria[filterGroups][0][filters][0][conditionType]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">like</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][conditionType]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][0][field]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">level</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][field]</stringProp> + </elementProp> + <elementProp name="searchCriteria[filterGroups][1][filters][0][value]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">2</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][value]</stringProp> + </elementProp> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${categories_count}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> </elementProp> </collectionProp> - </HeaderManager> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/V1/categories/list</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">false</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + </HTTPSamplerProxy> + <hashTree> + <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="PostProcessor" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var data = JSON.parse(prev.getResponseDataAsString()); + +var categoryData = [], categoryNames = [], categoryUrls = []; + +for (var i in data.items) { + var cat = data.items[i], urlKey = getUrlKey(cat); + categoryData.push({"id": cat.id, "name": cat.name, "url_key": urlKey, "children": cat.children.split(",")}); + categoryNames.push(cat.name); + categoryUrls.push(urlKey); + } + +function getUrlKey(cat) { + for (var i in cat.custom_attributes) { + if (cat.custom_attributes[i].attribute_code == "url_key") { + return cat.custom_attributes[i].value; + } + } + return ""; +} + +props.put("categories", categoryData); +props.put("category_url_keys_list", categoryUrls); +props.put("category_names_list",categoryNames);</stringProp> + </JSR223PostProcessor> <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Get simple products" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> + </hashTree> + </hashTree> + + <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract categories id of last level" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_categories_id_of_last_level.jmx</stringProp> +</TestFragmentController> + <hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Clear Admin Category Management properties" enabled="true"> + <stringProp name="BeanShellSampler.query">props.remove("admin_category_ids_list");</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - API Get categories" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"> <elementProp name="searchCriteria[filterGroups][0][filters][0][field]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">type_id</stringProp> + <stringProp name="Argument.value">children_count</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][field]</stringProp> </elementProp> <elementProp name="searchCriteria[filterGroups][0][filters][0][value]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">simple</stringProp> + <stringProp name="Argument.value">0</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][0][value]</stringProp> </elementProp> - <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <elementProp name="searchCriteria[filterGroups][1][filters][0][field]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${simple_products_count}</stringProp> + <stringProp name="Argument.value">level</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][field]</stringProp> </elementProp> - <elementProp name="searchCriteria[filterGroups][0][filters][1][field]" elementType="HTTPArgument"> + <elementProp name="searchCriteria[filterGroups][1][filters][0][value]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">attribute_set_id</stringProp> - <stringProp name="Argument.metadata">!=</stringProp> + <stringProp name="Argument.value">2</stringProp> + <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][1][field]</stringProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][value]</stringProp> </elementProp> - <elementProp name="searchCriteria[filterGroups][0][filters][1][value]" elementType="HTTPArgument"> + <elementProp name="searchCriteria[filterGroups][1][filters][0][conditionType]" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">4</stringProp> + <stringProp name="Argument.value">gt</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">searchCriteria[filterGroups][0][filters][1][value]</stringProp> + <stringProp name="Argument.name">searchCriteria[filterGroups][1][filters][0][conditionType]</stringProp> + </elementProp> + <elementProp name="searchCriteria[pageSize]" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${adminCategoryCount}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">searchCriteria[pageSize]</stringProp> </elementProp> </collectionProp> </elementProp> @@ -1992,7 +2137,7 @@ editProductList.add(editProductMap);</stringProp> <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/V1/products</stringProp> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/categories/list</stringProp> <stringProp name="HTTPSampler.method">GET</stringProp> <boolProp name="HTTPSampler.follow_redirects">true</boolProp> <boolProp name="HTTPSampler.auto_redirects">false</boolProp> @@ -2002,648 +2147,238 @@ editProductList.add(editProductMap);</stringProp> <stringProp name="HTTPSampler.embedded_url_re"/> </HTTPSamplerProxy> <hashTree> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product url keys" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">simple_products_url_keys</stringProp> - <stringProp name="RegexExtractor.regex">url_key\",\"value\":\"(.*?)\"</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> - <hashTree/> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product ids" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">simple_product_ids</stringProp> - <stringProp name="RegexExtractor.regex">\"id\":(\d+),\"sku\"</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> - <hashTree/> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product titles" enabled="true"> - <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">simple_product_names</stringProp> - <stringProp name="RegexExtractor.regex">name\":\"(.*?)\"</stringProp> - <stringProp name="RegexExtractor.template">$1$</stringProp> - <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">-1</stringProp> - </RegexExtractor> - <hashTree/> - <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor: Extract product skus" enabled="true"> + <RegexExtractor guiclass="RegexExtractorGui" testclass="RegexExtractor" testname="Regular Expression Extractor" enabled="true"> <stringProp name="RegexExtractor.useHeaders">false</stringProp> - <stringProp name="RegexExtractor.refname">simple_product_skus</stringProp> - <stringProp name="RegexExtractor.regex">sku\":\"(.*?)\"</stringProp> + <stringProp name="RegexExtractor.refname">category_list_id</stringProp> + <stringProp name="RegexExtractor.regex">\{\"id\":(\d+),</stringProp> <stringProp name="RegexExtractor.template">$1$</stringProp> <stringProp name="RegexExtractor.default"/> <stringProp name="RegexExtractor.match_number">-1</stringProp> </RegexExtractor> <hashTree/> </hashTree> - </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: Prepare simple products" enabled="true"> - <stringProp name="ForeachController.inputVal">simple_product_ids</stringProp> - <stringProp name="ForeachController.returnVal">simple_product_id</stringProp> - <boolProp name="ForeachController.useSeparator">true</boolProp> - </ForeachController> - <hashTree> - <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter" enabled="true"> - <stringProp name="CounterConfig.start">1</stringProp> - <stringProp name="CounterConfig.end"/> - <stringProp name="CounterConfig.incr">1</stringProp> - <stringProp name="CounterConfig.name">simple_products_counter</stringProp> - <stringProp name="CounterConfig.format"/> - <boolProp name="CounterConfig.per_user">false</boolProp> - </CounterConfig> - <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect simple product" enabled="true"> - <stringProp name="BeanShellSampler.query">import java.util.ArrayList; -import java.util.HashMap; -import org.apache.commons.codec.binary.Base64; + <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Category Id" enabled="true"> + <stringProp name="ForeachController.inputVal">category_list_id</stringProp> + <stringProp name="ForeachController.returnVal">category_id</stringProp> + <boolProp name="ForeachController.useSeparator">true</boolProp> + </ForeachController> + <hashTree> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="Process categories ids" enabled="true"> + <stringProp name="BeanShellSampler.query">import java.util.ArrayList; -// If it is first iteration of cycle then recreate productList -if (1 == Integer.parseInt(vars.get("simple_products_counter"))) { - productList = new ArrayList(); - props.put("simple_products_list", productList); -} else { - productList = props.get("simple_products_list"); +adminCategoryIdsList = props.get("admin_category_ids_list"); +// If it is first iteration of cycle then recreate categories ids list +if (adminCategoryIdsList == null) { + adminCategoryIdsList = new ArrayList(); + props.put("admin_category_ids_list", adminCategoryIdsList); } -String productUrl = vars.get("request_protocol") + "://" + vars.get("host") + vars.get("base_path") + vars.get("simple_products_url_keys_" + vars.get("simple_products_counter"))+ vars.get("url_suffix"); -encodedUrl = Base64.encodeBase64(productUrl.getBytes()); -// Create product map -Map productMap = new HashMap(); -productMap.put("id", vars.get("simple_product_id")); -productMap.put("title", vars.get("simple_product_names_" + vars.get("simple_products_counter"))); -productMap.put("sku", vars.get("simple_product_skus_" + vars.get("simple_products_counter"))); -productMap.put("url_key", vars.get("simple_products_url_keys_" + vars.get("simple_products_counter"))); -productMap.put("uenc", new String(encodedUrl)); - -// Collect products map in products list -productList.add(productMap);</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> - </hashTree> +adminCategoryIdsList.add(vars.get("category_id"));</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + </BeanShellSampler> + <hashTree/> + </hashTree> </hashTree> + </hashTree> - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract admin users" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_admin_users.jmx</stringProp> -</TestFragmentController> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Validate properties and count users" enabled="true"> + <stringProp name="BeanShellSampler.query">Boolean stopTestOnError (String error) { + log.error(error); + System.out.println(error); + SampleResult.setStopTest(true); + return false; +} + +if (props.get("simple_products_list") == null) { + return stopTestOnError("Cannot find simple products. Test stopped."); +} +if (props.get("simple_products_list_for_edit") == null) { + return stopTestOnError("Cannot find simple products for edit. Test stopped."); +} +if (props.get("configurable_products_list") == null) { + return stopTestOnError("Cannot find configurable products. Test stopped."); +} +if (props.get("configurable_products_list_for_edit") == null) { + return stopTestOnError("Cannot find configurable products for edit. Test stopped."); +} +if (props.get("customer_emails_list") == null) { + return stopTestOnError("Cannot find customer emails. Test stopped."); +} +if (props.get("category_url_keys_list") == null) { + return stopTestOnError("Cannot find category url keys. Test stopped."); +} +if (props.get("category_names_list") == null) { + return stopTestOnError("Cannot find category names. Test stopped."); +} +if (props.get("cms_pages") == null) { + return stopTestOnError("Cannot find cms pages. Test stopped."); +} +</stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/validate_properties.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - WarmUp Add To Cart" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="product" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">product</stringProp> + </elementProp> + <elementProp name="related_product" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value"/> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">related_product</stringProp> + </elementProp> + <elementProp name="qty" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">1</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">qty</stringProp> + </elementProp> + <elementProp name="form_key" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">${form_key}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">form_key</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/warmup_add_to_cart.jmx</stringProp></HTTPSamplerProxy> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Ajax Load Login Form" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"> + <elementProp name="blocks" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">["customer_form_login"]</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">blocks</stringProp> + </elementProp> + <elementProp name="handles" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">["default","customer_account_login"]</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">handles</stringProp> + </elementProp> + <elementProp name="originalRequest" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">{"route":"customer","controller":"account","action":"login","uri":"/customer/account/login/"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">originalRequest</stringProp> + </elementProp> + <elementProp name="ajax" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">ajax</stringProp> + </elementProp> + <elementProp name="isAjax" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">true</boolProp> + <stringProp name="Argument.value">true</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + <boolProp name="HTTPArgument.use_equals">true</boolProp> + <stringProp name="Argument.name">isAjax</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}page_cache/block/render/</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/ajax_load_login_form.jmx</stringProp></HTTPSamplerProxy> <hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Extract Admin Users" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="form_key" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">${admin_form_key}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">form_key</stringProp> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="940598773">"customer_form_login"</stringProp> + <stringProp name="1951684663">Registered Customers</stringProp> + <stringProp name="474011748">form_key</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Frontend Pool" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">${loops}</stringProp> </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}/admin/user/roleGrid/limit/200/?ajax=true&isAjax=true</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="BeanShell PostProcessor" enabled="true"> - <stringProp name="filename"/> - <stringProp name="parameters"/> - <boolProp name="resetInterpreter">false</boolProp> - <stringProp name="script">import java.util.regex.Pattern; - import java.util.regex.Matcher; - import java.util.LinkedList; - - LinkedList adminUserList = new LinkedList(); - String response = new String(data); - Pattern pattern = Pattern.compile("<td\\W*?data-column=.username[^>]*?>\\W*?(\\w+)\\W*?<"); - Matcher matcher = pattern.matcher(response); + <stringProp name="ThreadGroup.num_threads">${frontendPoolUsers}</stringProp> + <stringProp name="ThreadGroup.ramp_time">${ramp_period}</stringProp> + <longProp name="ThreadGroup.start_time">1505803944000</longProp> + <longProp name="ThreadGroup.end_time">1505803944000</longProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"/> + <stringProp name="ThreadGroup.delay"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/thread_group.jmx</stringProp></ThreadGroup> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Cache hit miss" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var cacheHitPercent = vars.get("cache_hits_percentage"); - while (matcher.find()) { - adminUserList.add(matcher.group(1)); - } +if ( + cacheHitPercent < 100 && + sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + doCache(); +} - adminUserList.poll(); - props.put("adminUserList", adminUserList); - props.put("adminUserListIterator", adminUserList.descendingIterator()); - </stringProp> - </BeanShellPostProcessor> - <hashTree/> - </hashTree> - </hashTree> - - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract customers" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_customers.jmx</stringProp> -</TestFragmentController> - <hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Open Customer Grid" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"/> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}/customer/index/</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <BeanShellPreProcessor guiclass="TestBeanGUI" testclass="BeanShellPreProcessor" testname="BeanShell PreProcessor" enabled="true"> - <stringProp name="filename"/> - <stringProp name="parameters"/> - <boolProp name="resetInterpreter">true</boolProp> - <stringProp name="script">import org.apache.jmeter.protocol.http.control.CookieManager; -import org.apache.jmeter.protocol.http.control.Cookie; -CookieManager manager = sampler.getCookieManager(); -Cookie cookie = new Cookie("adminhtml",vars.get("COOKIE_adminhtml"),vars.get("host"),"/",false,0); -manager.add(cookie); </stringProp> - </BeanShellPreProcessor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert Customer Grid" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="-679437259">Customers</stringProp> - <stringProp name="495525733"><title>Customers / Customers / Magento Admin</title></stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">2</intProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Search Customers" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="namespace" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">customer_listing</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">namespace</stringProp> - </elementProp> - <elementProp name="sorting[field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">entity_id</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">sorting[field]</stringProp> - </elementProp> - <elementProp name="sorting[direction]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">asc</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">sorting[direction]</stringProp> - </elementProp> - <elementProp name="isAjax" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">true</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">isAjax</stringProp> - </elementProp> - <elementProp name="filters[placeholder]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">customer_since[locale]=en_US</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">filters[placeholder]</stringProp> - </elementProp> - <elementProp name="filters[group_id]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">filters[group_id]</stringProp> - </elementProp> - <elementProp name="filters[website_id]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">filters[website_id]</stringProp> - </elementProp> - <elementProp name="paging[pageSize]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${customers_page_size}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">paging[pageSize]</stringProp> - </elementProp> - <elementProp name="paging[current]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">paging[current]</stringProp> - </elementProp> - <elementProp name="sorting[field]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">entity_id</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">sorting[field]</stringProp> - </elementProp> - <elementProp name="sorting[direction]" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">asc</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">sorting[direction]</stringProp> - </elementProp> - <elementProp name="isAjax" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">true</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">isAjax</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}/mui/index/render/</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert total records is not 0" enabled="true"> - <stringProp name="JSON_PATH">$.totalRecords</stringProp> - <stringProp name="EXPECTED_VALUE">0</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">true</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer emails" enabled="true"> - <stringProp name="VAR">customer_emails</stringProp> - <stringProp name="JSONPATH">$.items[*].email</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer ids" enabled="true"> - <stringProp name="VAR">customer_ids</stringProp> - <stringProp name="JSONPATH">$.items[*].entity_id</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellPostProcessor guiclass="TestBeanGUI" testclass="BeanShellPostProcessor" testname="Make email list" enabled="true"> - <boolProp name="resetInterpreter">false</boolProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="script">import java.util.LinkedList; -LinkedList emailsList = new LinkedList(); -props.put("customer_emails_list", emailsList);</stringProp> - </BeanShellPostProcessor> - <hashTree/> - </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: customer emails loop (search result)" enabled="true"> - <stringProp name="ForeachController.inputVal">customer_emails</stringProp> - <stringProp name="ForeachController.returnVal">customer_email</stringProp> - <boolProp name="ForeachController.useSeparator">true</boolProp> - </ForeachController> - <hashTree> - <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter" enabled="true"> - <stringProp name="CounterConfig.start">1</stringProp> - <stringProp name="CounterConfig.end"/> - <stringProp name="CounterConfig.incr">1</stringProp> - <stringProp name="CounterConfig.name">email_counter</stringProp> - <stringProp name="CounterConfig.format"/> - <boolProp name="CounterConfig.per_user">false</boolProp> - </CounterConfig> - <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect customer emails" enabled="true"> - <stringProp name="BeanShellSampler.query"> -try { - -props.get("customer_emails_list").add(vars.get("customer_email")); - -} catch (java.lang.Exception e) { - log.error("error…", e); - SampleResult.setStopThread(true); -} - </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> - </hashTree> - <ForeachController guiclass="ForeachControlPanel" testclass="ForeachController" testname="ForEach Controller: customer ids loop (search result)" enabled="true"> - <stringProp name="ForeachController.inputVal">customer_ids</stringProp> - <stringProp name="ForeachController.returnVal">customer_id</stringProp> - <boolProp name="ForeachController.useSeparator">true</boolProp> - </ForeachController> - <hashTree> - <CounterConfig guiclass="CounterConfigGui" testclass="CounterConfig" testname="Counter" enabled="true"> - <stringProp name="CounterConfig.start">1</stringProp> - <stringProp name="CounterConfig.end"/> - <stringProp name="CounterConfig.incr">1</stringProp> - <stringProp name="CounterConfig.name">id_counter</stringProp> - <stringProp name="CounterConfig.format"/> - <boolProp name="CounterConfig.per_user">false</boolProp> - </CounterConfig> - <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Collect customer ids" enabled="true"> - <stringProp name="BeanShellSampler.query">import java.util.ArrayList; - -// If it is first iteration of cycle then recreate idsList -if (1 == Integer.parseInt(vars.get("id_counter"))) { - idsList = new ArrayList(); - props.put("customer_ids_list", idsList); -} else { - idsList = props.get("customer_ids_list"); -} -idsList.add(vars.get("customer_id"));</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - </BeanShellSampler> - <hashTree/> - </hashTree> - </hashTree> - - <TestFragmentController guiclass="TestFragmentControllerGui" testclass="TestFragmentController" testname="Extract region ids" enabled="true"> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/extract_region_ids.jmx</stringProp> - </TestFragmentController> - <hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Extract Region ids" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="parent" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">US</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">parent</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}${admin_path}/directory/json/countryRegion/</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - </HTTPSamplerProxy> - <hashTree> - <JSR223PostProcessor guiclass="TestBeanGUI" testclass="JSR223PostProcessor" testname="Parse and put region id into variables" enabled="true"> - <stringProp name="scriptLanguage">groovy</stringProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="cacheKey"/> - <stringProp name="script">import groovy.json.JsonSlurper -def jsonSlurper = new JsonSlurper(); -def regionResponse = jsonSlurper.parseText(prev.getResponseDataAsString()); - -regionResponse.each { region -> - if (region.label.toString() == "Alabama") { - props.put("alabama_region_id", region.value.toString()); - } else if (region.label.toString() == 'California') { - props.put("california_region_id", region.value.toString()); - } -}</stringProp> - </JSR223PostProcessor> - <hashTree/> - </hashTree> - </hashTree> - - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - BeanShell Sampler: Validate properties and count users" enabled="true"> - <stringProp name="BeanShellSampler.query">Boolean stopTestOnError (String error) { - log.error(error); - System.out.println(error); - SampleResult.setStopTest(true); - return false; -} - -if (props.get("simple_products_list") == null) { - return stopTestOnError("Cannot find simple products. Test stopped."); -} -if (props.get("simple_products_list_for_edit") == null) { - return stopTestOnError("Cannot find simple products for edit. Test stopped."); -} -if (props.get("configurable_products_list") == null) { - return stopTestOnError("Cannot find configurable products. Test stopped."); -} -if (props.get("configurable_products_list_for_edit") == null) { - return stopTestOnError("Cannot find configurable products for edit. Test stopped."); -} -if (props.get("customer_emails_list") == null) { - return stopTestOnError("Cannot find customer emails. Test stopped."); -} -if (props.get("category_url_keys_list") == null) { - return stopTestOnError("Cannot find category url keys. Test stopped."); -} -if (props.get("category_names_list") == null) { - return stopTestOnError("Cannot find category names. Test stopped."); -} -</stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/validate_properties.jmx</stringProp></BeanShellSampler> - <hashTree/> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - WarmUp Add To Cart" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="product" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">product</stringProp> - </elementProp> - <elementProp name="related_product" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value"/> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">related_product</stringProp> - </elementProp> - <elementProp name="qty" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">1</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">qty</stringProp> - </elementProp> - <elementProp name="form_key" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">${form_key}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">form_key</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}checkout/cart/add</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/warmup_add_to_cart.jmx</stringProp></HTTPSamplerProxy> - <hashTree/> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="SetUp - Ajax Load Login Form" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"> - <elementProp name="blocks" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">["customer_form_login"]</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">blocks</stringProp> - </elementProp> - <elementProp name="handles" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">["default","customer_account_login"]</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">handles</stringProp> - </elementProp> - <elementProp name="originalRequest" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"route":"customer","controller":"account","action":"login","uri":"/customer/account/login/"}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">originalRequest</stringProp> - </elementProp> - <elementProp name="ajax" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">true</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">ajax</stringProp> - </elementProp> - <elementProp name="isAjax" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">true</boolProp> - <stringProp name="Argument.value">true</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">isAjax</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> - <stringProp name="HTTPSampler.response_timeout">200000</stringProp> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}page_cache/block/render/</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/ajax_load_login_form.jmx</stringProp></HTTPSamplerProxy> - <hashTree> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="940598773">"customer_form_login"</stringProp> - <stringProp name="1951684663">Registered Customers</stringProp> - <stringProp name="474011748">form_key</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">2</intProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - </hashTree> - - <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Frontend Pool" enabled="true"> - <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> - <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> - <boolProp name="LoopController.continue_forever">false</boolProp> - <stringProp name="LoopController.loops">${loops}</stringProp> - </elementProp> - <stringProp name="ThreadGroup.num_threads">${frontendPoolUsers}</stringProp> - <stringProp name="ThreadGroup.ramp_time">${ramp_period}</stringProp> - <longProp name="ThreadGroup.start_time">1505803944000</longProp> - <longProp name="ThreadGroup.end_time">1505803944000</longProp> - <boolProp name="ThreadGroup.scheduler">false</boolProp> - <stringProp name="ThreadGroup.duration"/> - <stringProp name="ThreadGroup.delay"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/thread_group.jmx</stringProp></ThreadGroup> - <hashTree> - <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Cache hit miss" enabled="true"> - <stringProp name="scriptLanguage">javascript</stringProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="cacheKey"/> - <stringProp name="script">var cacheHitPercent = vars.get("cache_hits_percentage"); - -if ( - cacheHitPercent < 100 && - sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' -) { - doCache(); -} - -function doCache(){ - var random = Math.random() * 100; - if (cacheHitPercent < random) { - sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); - } -} -</stringProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/cache_hit_miss.jmx</stringProp></JSR223PreProcessor> - <hashTree/> +function doCache(){ + var random = Math.random() * 100; + if (cacheHitPercent < random) { + sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); + } +} +</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/cache_hit_miss.jmx</stringProp></JSR223PreProcessor> + <hashTree/> <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Catalog Browsing By Customer" enabled="true"> <intProp name="ThroughputController.style">1</intProp> @@ -2719,22 +2454,24 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <CriticalSectionController guiclass="CriticalSectionControllerGui" testclass="CriticalSectionController" testname="Get Customer Email" enabled="true"> <stringProp name="CriticalSectionController.lockName">get-email</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/lock_controller.jmx</stringProp></CriticalSectionController> @@ -3251,22 +2988,24 @@ vars.putObject("randomIntGenerator", random); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Home Page" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"/> @@ -4656,22 +4395,24 @@ vars.put("totalProductsAdded", "0"); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Home Page" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"/> @@ -5970,22 +5711,24 @@ vars.put("totalProductsAdded", "0"); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Category" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"/> @@ -6537,22 +6280,24 @@ vars.put("totalProductsAdded", "0"); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Home Page" enabled="true"> <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"/> @@ -7676,22 +7421,24 @@ vars.put("totalProductsAdded", "0"); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <CriticalSectionController guiclass="CriticalSectionControllerGui" testclass="CriticalSectionController" testname="Get Customer Email" enabled="true"> <stringProp name="CriticalSectionController.lockName">get-email</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/lock_controller.jmx</stringProp></CriticalSectionController> @@ -9500,22 +9247,24 @@ vars.put("totalProductsAdded", "0"); </BeanShellSampler> <hashTree/> - <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Category Data" enabled="true"> - <stringProp name="BeanShellSampler.query"> -import java.util.Random; + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); -Random random = vars.getObject("randomIntGenerator"); -number = random.nextInt(props.get("category_url_keys_list").size()); +var categories = props.get("categories"); +number = random.nextInt(categories.length); -vars.put("category_url_key", props.get("category_url_keys_list").get(number)); -vars.put("category_name", props.get("category_names_list").get(number)); +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); </stringProp> - <stringProp name="BeanShellSampler.filename"/> - <stringProp name="BeanShellSampler.parameters"/> - <boolProp name="BeanShellSampler.resetInterpreter">false</boolProp> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></BeanShellSampler> - <hashTree/> - + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + <CriticalSectionController guiclass="CriticalSectionControllerGui" testclass="CriticalSectionController" testname="Get Customer Email" enabled="true"> <stringProp name="CriticalSectionController.lockName">get-email</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/lock_controller.jmx</stringProp></CriticalSectionController> @@ -38502,7 +38251,7 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 200\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 20\n currentPage: 0\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38559,15 +38308,2651 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_simple_product_with_extensible_data_objects.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_simple_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract response" enabled="true"> + <stringProp name="VAR">graphql_multiple_products_query_response</stringProp> + <stringProp name="JSONPATH">$</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_simple_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) != 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; + } else { + Failure = false; + } +} +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert product sku" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> + <stringProp name="EXPECTED_VALUE">${simple_product_sku}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query configurable product" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_configurable_product_with_extensible_data_objects.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_configurable_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_configurable_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) != 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert configurable product sku" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> + <stringProp name="EXPECTED_VALUE">${configurable_product_sku}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search and filter" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:0\n search: \"configurable\"\n filter: {name: {like: \"Configurable Product%\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_and_filter.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count_fulltext_filter</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count_fulltext_filter"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search only" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:0\n search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_only.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search and filters" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:0\n search: \"Option 1\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_and_filters.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query bundle product" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_bundle_product_with_extensible_data_objects.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_bundle_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_bundle_products_query_total_count"); + + if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; + } else { + if (Integer.parseInt(totalCount) != 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; + } else { + Failure = false; + } + } + + + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert bundle product sku" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> + <stringProp name="EXPECTED_VALUE">${bundle_product_sku}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query downloadable product" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_downloadable_product_with_extensible_data_objects.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_downloadable_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_downloadable_products_query_total_count"); + + if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; + } else { + if (Integer.parseInt(totalCount) != 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; + } else { + Failure = false; + } + } + + + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert downloadable product sku" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> + <stringProp name="EXPECTED_VALUE">${downloadable_product_sku}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert link samples titles" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].downloadable_product_samples..title</stringProp> + <stringProp name="EXPECTED_VALUE">["sample1","sample2"]</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert sample titles" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].downloadable_product_samples..title</stringProp> + <stringProp name="EXPECTED_VALUE">["sample1","sample2"]</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query virtual product" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_virtual_product_with_extensible_data_objects.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_virtual_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_virtual_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) != 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; + } else { + Failure = false; + } +} +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert product sku" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> + <stringProp name="EXPECTED_VALUE">${virtual_product_sku}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query grouped product" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_grouped_product_with_extensible_data_objects.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_grouped_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_grouped_products_query_total_count"); + + if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; + } else { + if (Integer.parseInt(totalCount) != 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; + } else { + Failure = false; + } + } + + + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert grouped product sku" enabled="true"> + <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> + <stringProp name="EXPECTED_VALUE">${grouped_product_sku}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + + <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Query Customer" enabled="true"/> + <hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create customer" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ + "customer": { + + "email": "customer_${__time()}-${__threadNum}-${__Random(1,1000000)}@example.com", + "firstname": "test_${__time()}-${__threadNum}-${__Random(1,1000000)}", + "lastname": "Doe" + }, + "password": "test@123" + }</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_frontend_customer.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer id" enabled="true"> + <stringProp name="VAR">customer_id</stringProp> + <stringProp name="JSONPATH">$.id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert customer id not null" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="89649215">^\d+$</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">1</intProp> + <stringProp name="Assertion.scope">variable</stringProp> + <stringProp name="Scope.variable">customer_id</stringProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Check customer" enabled="true"> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> + <collectionProp name="Arguments.arguments"/> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers/${customer_id}</stringProp> + <stringProp name="HTTPSampler.method">GET</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/check_customer.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="jp@gc - JSON Path Assertion" enabled="true"> + <stringProp name="JSON_PATH">$.id</stringProp> + <stringProp name="EXPECTED_VALUE">${customer_id}</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer email" enabled="true"> + <stringProp name="VAR">customer_email</stringProp> + <stringProp name="JSONPATH">$.email</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create customer token" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{ + "username":"${customer_email}", + "password":"test@123" + } + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/integration/customer/token</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/create_customer.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer token" enabled="true"> + <stringProp name="VAR">customer_token</stringProp> + <stringProp name="JSONPATH">$</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query customer with token" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n customer {\n created_at\n group_id\n\n prefix\n firstname\n middlename\n lastname\n suffix\n email\n default_billing\n default_shipping\n\n dob\n taxvat\n\n id\n addresses {\n id\n customer_id\n region {\n region_code\n region\n region_id\n }\n region_id\n country_id\n street \n company\n telephone\n fax\n postcode\n city\n firstname\n lastname\n middlename\n prefix\n suffix\n vat_id\n default_shipping\n default_billing\n }\n is_subscribed\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_frontend_customer.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <BeanShellPreProcessor guiclass="TestBeanGUI" testclass="BeanShellPreProcessor" testname="Set Customer Token" enabled="true"> + <boolProp name="resetInterpreter">false</boolProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="script">import org.apache.jmeter.protocol.http.control.Header; + + sampler.getHeaderManager().removeHeaderNamed("Authorization"); + + sampler.getHeaderManager().add(new Header("Authorization","Bearer " + vars.get("customer_token")));</stringProp> + </BeanShellPreProcessor> + <hashTree/> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert customer lastname" enabled="true"> + <stringProp name="JSON_PATH">$.data.customer.lastname</stringProp> + <stringProp name="EXPECTED_VALUE">Doe</stringProp> + <boolProp name="JSONVALIDATION">true</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">true</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query Category" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n category(id: 1) {\n name\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_root_category.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_category_query_name</stringProp> + <stringProp name="JSONPATH">$.data.category.name</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="BeanShell Assertion" enabled="true"> + <stringProp name="BeanShellAssertion.query">String name = vars.get("graphql_category_query_name"); +if (name == null) { + Failure = true; + FailureMessage = "Not Expected \"children\" to be null"; +} else { + if (!name.equals("Root Catalog")) { + Failure = true; + FailureMessage = "Expected \"name\" to equal \"Root Catalog\", Actual: " + name; + } else { + Failure = false; + } +} +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + </hashTree> + </hashTree> + + </hashTree> + + + <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="GraphQL Pool" enabled="true"> + <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> + <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> + <boolProp name="LoopController.continue_forever">false</boolProp> + <stringProp name="LoopController.loops">${loops}</stringProp> + </elementProp> + <stringProp name="ThreadGroup.num_threads">${graphQLPoolUsers}</stringProp> + <stringProp name="ThreadGroup.ramp_time">${ramp_period}</stringProp> + <longProp name="ThreadGroup.start_time">1505803944000</longProp> + <longProp name="ThreadGroup.end_time">1505803944000</longProp> + <boolProp name="ThreadGroup.scheduler">false</boolProp> + <stringProp name="ThreadGroup.duration"/> + <stringProp name="ThreadGroup.delay"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/thread_group.jmx</stringProp></ThreadGroup> + <hashTree> + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get List of Products by category_id" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetListOfProductsByCategoryIdPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get List of Products by category_id"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get List of Products by category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query category($id: Int!, $currentPage: Int, $pageSize: Int) {\n category(id: $id) {\n product_count\n description\n url_key\n name\n id\n breadcrumbs {\n category_name\n category_url_key\n __typename\n }\n products(pageSize: $pageSize, currentPage: $currentPage) {\n total_count\n items {\n id\n name\n # small_image\n # short_description\n url_key\n special_price\n special_from_date\n special_to_date\n price {\n regularPrice {\n amount {\n value\n currency\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n","variables":{"id":${category_id},"currentPage":1,"pageSize":12},"operationName":"category"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_list_of_products_by_category_id.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"name":"${category_name}","id":${category_id},</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Simple Product Details by product_url_key" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetSimpleProductDetailsByProductUrlKeyPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Simple Product Details by product_url_key"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Simple Product Details by product_url_key" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_simple_product_details_by_product_url_key.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Simple Product Details by name" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetSimpleProductDetailsByNamePercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Simple Product Details by name"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Simple Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetail($name: String, $onServer: Boolean!) {\n productDetail: products(filter: { name: { eq: $name } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_simple_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Configurable Product Detail by product_url_key" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetConfigurableProductDetailsByProductUrlKeyPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Configurable Product Detail by product_url_key"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by product_url_key" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_product_url_key.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Configurable Product Detail by name" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetConfigurableProductDetailsByNamePercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Configurable Product Detail by name"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Product Detail by product_name" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetProductDetailByProductNamePercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Product Detail by product_name"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Product Detail by product_name" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_product_detail_by_product_name.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Product Search by text and category_id" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetProductSearchByTextAndCategoryIdPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Product Search by text and category_id"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Product Search by text and category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(search: $inputText, filter: { category_id: { eq: $categoryId } }) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_product_search_by_text_and_category_id.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); + +if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; +} else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } +} + + +</stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Category List by category_id" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetCategoryListByCategoryIdPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Category List by category_id"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Category List by category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value"> + {"query":"query categoryList($id: Int!) {\n category(id: $id) {\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"categoryList"} + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_category_list_by_category_id.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <JSR223Assertion guiclass="TestBeanGUI" testclass="JSR223Assertion" testname="Assert found categories" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">var category = vars.getObject("category"); +var response = JSON.parse(prev.getResponseDataAsString()); + +assertCategoryId(category, response); +assertCategoryChildren(category, response); + +function assertCategoryId(category, response) { + if (response.data == undefined || response.data.category == undefined || response.data.category.id != category.id) { + AssertionResult.setFailureMessage("Cannot find category with id \"" + category.id + "\""); + AssertionResult.setFailure(true); + } +} + +function assertCategoryChildren(category, response) { + foundCategory = response.data && response.data.category ? response.data.category : null; + if (foundCategory) { + var childrenFound = foundCategory.children.map(function (c) {return parseInt(c.id)}); + var children = category.children.map(function (c) {return parseInt(c)}); + if (JSON.stringify(children.sort()) != JSON.stringify(childrenFound.sort())) { + AssertionResult.setFailureMessage("Cannot math children categories \"" + JSON.stringify(children) + "\" for to found one: \"" + JSON.stringify(childrenFound) + "\""); + AssertionResult.setFailure(true); + } + } + +} + +</stringProp> + </JSR223Assertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Url Info by url_key" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlUrlInfoByUrlKeyPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Url Info by url_key"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Url Info by url_key" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value"> + {"query":"query resolveUrl($urlKey: String!) {\n urlResolver(url: $urlKey) {\n type\n id\n }\n}","variables":{"urlKey":"${category_url_key}${url_suffix}"},"operationName":"resolveUrl"} + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_url_info_by_url_key.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1062388959">{"type":"CATEGORY","id":${category_id}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Cms Page by id" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetCmsPageByIdPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Cms Page by id"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare CMS Page" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var cmsPages = props.get("cms_pages"); +var number = random.nextInt(cmsPages.length); + +vars.put("cms_page_id", cmsPages[number].id); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/setup/prepare_cms_page.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cms Page by id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value"> + {"query":"query getCmsPage($id: Int!, $onServer: Boolean!) {\n cmsPage(id: $id) {\n url_key\n content\n content_heading\n title\n page_layout\n meta_title @include(if: $onServer)\n meta_keywords @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n}","variables":{"id":${cms_page_id},"onServer":false},"operationName":"getCmsPage"} + </stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_get_cms_page_by_id.jmx</stringProp></HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="jp@gc - JSON Path Assertion" enabled="true"> + <stringProp name="JSON_PATH">$.data.cmsPage.url_key</stringProp> + <stringProp name="EXPECTED_VALUE">${cms_page_id}</stringProp> + <boolProp name="JSONVALIDATION">false</boolProp> + <boolProp name="EXPECT_NULL">false</boolProp> + <boolProp name="INVERT">false</boolProp> + <boolProp name="ISREGEX">false</boolProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Get Navigation Menu by category_id" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlGetNavigationMenuByCategoryIdPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Get Navigation Menu by category_id"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <JSR223Sampler guiclass="TestBeanGUI" testclass="JSR223Sampler" testname="SetUp - Prepare Category Data" enabled="true"> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="parameters"/> + <stringProp name="filename"/> + <stringProp name="cacheKey"/> + <stringProp name="script">random = vars.getObject("randomIntGenerator"); + +var categories = props.get("categories"); +number = random.nextInt(categories.length); + +vars.put("category_url_key", categories[number].url_key); +vars.put("category_name", categories[number].name); +vars.put("category_id", categories[number].id); +vars.putObject("category", categories[number]); + </stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/extract_category_setup.jmx</stringProp></JSR223Sampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Navigation Menu by category_id" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_navigation_menu_by_category_id.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"id":${category_id},"name":"${category_name}","product_count"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + </hashTree> + + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Add Simple Product To Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlAddSimpleProductToCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); +} + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Add Simple Product To Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} + +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Simple Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("simple_products_list").size()); +product = props.get("simple_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/simple_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Simple Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation { \n addSimpleProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n data: {\n qty: 2\n sku: \"${product_sku}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n qty\n product {\n sku\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_simple_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addSimpleProductsToCart</stringProp> + <stringProp name="-1173443935">"sku":"${product_sku}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n qty\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Simple Product qty In Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n updateCartItems(input: {\n cart_id: \"${quote_id}\"\n cart_items: [\n {\n cart_item_id: ${item_id}\n quantity: 5\n }\n ]\n }) {\n cart {\n items {\n id\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/update_simple_product_qty_in_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="664196114">{"data":{"updateCartItems":{"cart":{"items":[{"id":"${item_id}","qty":5}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Billing Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setBillingAddressOnCart(\n input: {\n cart_id: \"${quote_id}\"\n billing_address: {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n }\n ) {\n cart {\n billing_address {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n address_type\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_billing_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1147076914">{"data":{"setBillingAddressOnCart":{"cart":{"billing_address":{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"},"address_type":"BILLING"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingAddressesOnCart(\n input: {\n cart_id: \"${quote_id}\"\n shipping_addresses: [\n {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n ]\n }\n ) {\n cart {\n shipping_addresses {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n address_type\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1671866339">{"data":{"setShippingAddressesOnCart":{"cart":{"shipping_addresses":[{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"},"address_type":"SHIPPING"}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Payment Method On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setPaymentMethodOnCart(input: {\n cart_id: \"${quote_id}\", \n payment_method: {\n code: \"checkmo\"\n }\n }) {\n cart {\n selected_payment_method {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_payment_method_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1830199373">{"data":{"setPaymentMethodOnCart":{"cart":{"selected_payment_method":{"code":"checkmo"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Current Shipping Address" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n shipping_addresses {\n address_id\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -38578,72 +40963,34 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_simple_product_with_extensible_data_objects.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_current_shipping_address.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_simple_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">address_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.shipping_addresses[0].address_id</stringProp> <stringProp name="DEFAULT"/> <stringProp name="VARIABLE"/> <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract response" enabled="true"> - <stringProp name="VAR">graphql_multiple_products_query_response</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_simple_products_query_total_count"); - -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) != 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; - } else { - Failure = false; - } -} -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert product sku" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> - <stringProp name="EXPECTED_VALUE">${simple_product_sku}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query configurable product" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Method On Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingMethodsOnCart(input: \n {\n cart_id: \"${quote_id}\", \n shipping_methods: [{\n cart_address_id: ${address_id}\n carrier_code: \"flatrate\"\n method_code: \"flatrate\"\n }]\n }) {\n cart {\n shipping_addresses {\n selected_shipping_method {\n carrier_code\n method_code\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -38654,65 +41001,35 @@ if (totalCount == null) { <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_configurable_product_with_extensible_data_objects.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_method_on_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_configurable_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_configurable_products_query_total_count"); - -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) != 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; - } else { - Failure = false; - } -} - - -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert configurable product sku" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> - <stringProp name="EXPECTED_VALUE">${configurable_product_sku}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="644143859">{"data":{"setShippingMethodsOnCart":{"cart":{"shipping_addresses":[{"selected_shipping_method":{"carrier_code":"flatrate","method_code":"flatrate"}}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search and filter" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Simple Product From Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n search: \"configurable\"\n filter: {price: {gteq: \"1\"} }\n ) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -38723,56 +41040,120 @@ if (totalCount == null) { <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_and_filter.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_simple_product_from_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_search_products_query_total_count_fulltext_filter</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count_fulltext_filter"); + </hashTree> + </hashTree> -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) < 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; - } else { - Failure = false; - } + + <ThroughputController guiclass="ThroughputControllerGui" testclass="ThroughputController" testname="Add Configurable Product To Cart" enabled="true"> + <intProp name="ThroughputController.style">1</intProp> + <boolProp name="ThroughputController.perThread">false</boolProp> + <intProp name="ThroughputController.maxThroughput">1</intProp> + <stringProp name="ThroughputController.percentThroughput">${graphqlAddConfigurableProductToCartPercentage}</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx</stringProp></ThroughputController> + <hashTree> + <JSR223PreProcessor guiclass="TestBeanGUI" testclass="JSR223PreProcessor" testname="Set Test Label" enabled="true"> + <stringProp name="script"> +var testLabel = "${testLabel}" ? " (${testLabel})" : ""; +if (testLabel + && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' +) { + if (sampler.getName().indexOf(testLabel) == -1) { + sampler.setName(sampler.getName() + testLabel); + } +} else if (sampler.getName().indexOf("SetUp - ") == -1) { + sampler.setName("SetUp - " + sampler.getName()); } + </stringProp> + <stringProp name="scriptLanguage">javascript</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/setup_label.jmx</stringProp></JSR223PreProcessor> + <hashTree/> + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Set Label" enabled="true"> + <stringProp name="BeanShellSampler.query"> + vars.put("testLabel", "Add Configurable Product To Cart"); + </stringProp> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> + <hashTree/> + + <HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true"> + <collectionProp name="HeaderManager.headers"> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Content-Type</stringProp> + <stringProp name="Header.value">application/json</stringProp> + </elementProp> + <elementProp name="" elementType="Header"> + <stringProp name="Header.name">Accept</stringProp> + <stringProp name="Header.value">*/*</stringProp> + </elementProp> + </collectionProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/header_manager_before_token.jmx</stringProp></HeaderManager> + <hashTree/> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Init Random Generator" enabled="true"> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/init_random_generator_setup.jmx</stringProp> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; +Random random = new Random(); +if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); +} -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> +vars.putObject("randomIntGenerator", random); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + </BeanShellSampler> <hashTree/> - </hashTree> + + <BeanShellSampler guiclass="BeanShellSamplerGui" testclass="BeanShellSampler" testname="SetUp - Prepare Configurable Product Data" enabled="true"> + <stringProp name="BeanShellSampler.query"> +import java.util.Random; + +Random random = vars.getObject("randomIntGenerator"); +number = random.nextInt(props.get("configurable_products_list").size()); +product = props.get("configurable_products_list").get(number); + +vars.put("product_url_key", product.get("url_key")); +vars.put("product_id", product.get("id")); +vars.put("product_name", product.get("title")); +vars.put("product_uenc", product.get("uenc")); +vars.put("product_sku", product.get("sku")); + </stringProp> + <stringProp name="BeanShellSampler.filename"/> + <stringProp name="BeanShellSampler.parameters"/> + <boolProp name="BeanShellSampler.resetInterpreter">true</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/product_browsing_and_adding_items_to_the_cart/configurable_products_setup.jmx</stringProp></BeanShellSampler> + <hashTree/> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search only" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Configurable Product Details by name" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -38783,56 +41164,44 @@ if (totalCount == null) { <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_only.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_configurable_product_details_by_name.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); - -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) < 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; - } else { - Failure = false; - } -} - - -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1201352014">"sku":"${product_sku}","name":"${product_name}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> <hashTree/> - </hashTree> + + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract Configurable Product option" enabled="true"> + <stringProp name="VAR">product_option</stringProp> + <stringProp name="JSONPATH">$.data.products.items[0].variants[0].product.sku</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/extract_configurable_product_option.jmx</stringProp></com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search and filters" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create Empty Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(search: \"Option 1\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n createEmptyCart\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -38843,212 +41212,112 @@ if (totalCount == null) { <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_and_filters.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/create_empty_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract cart id" enabled="true"> + <stringProp name="VAR">quote_id</stringProp> + <stringProp name="JSONPATH">$.data.createEmptyCart</stringProp> <stringProp name="DEFAULT"/> <stringProp name="VARIABLE"/> <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); - -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) < 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; - } else { - Failure = false; - } -} - - -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Add Configurable Product To Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n addConfigurableProductsToCart(\n input: {\n cart_id: \"${quote_id}\"\n cartItems: [\n {\n variant_sku: \"${product_option}\"\n data: {\n qty: 2\n sku: \"${product_option}\"\n }\n }\n ]\n }\n ) {\n cart {\n items {\n id\n qty\n product {\n name\n sku\n }\n ... on ConfigurableCartItem {\n configurable_options {\n option_label\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/add_configurable_product_to_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1421843282">addConfigurableProductsToCart</stringProp> + <stringProp name="675049292">"sku":"${product_option}"</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">2</intProp> + </ResponseAssertion> <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query bundle product" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_bundle_product_with_extensible_data_objects.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_bundle_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_bundle_products_query_total_count"); - - if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; - } else { - if (Integer.parseInt(totalCount) != 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; - } else { - Failure = false; - } - } - - - </stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert bundle product sku" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> - <stringProp name="EXPECTED_VALUE">${bundle_product_sku}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">false</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - </hashTree> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query downloadable product" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_downloadable_product_with_extensible_data_objects.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_downloadable_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_downloadable_products_query_total_count"); - - if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; - } else { - if (Integer.parseInt(totalCount) != 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; - } else { - Failure = false; - } - } - - - </stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert downloadable product sku" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> - <stringProp name="EXPECTED_VALUE">${downloadable_product_sku}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert link samples titles" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].downloadable_product_samples..title</stringProp> - <stringProp name="EXPECTED_VALUE">["sample1","sample2"]</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">false</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert sample titles" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].downloadable_product_samples..title</stringProp> - <stringProp name="EXPECTED_VALUE">["sample1","sample2"]</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">false</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - </hashTree> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query virtual product" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n items {\n id\n qty\n product {\n sku\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">item_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.items[0].id</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Update Configurable Product qty In Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n updateCartItems(input: {\n cart_id: \"${quote_id}\"\n cart_items: [\n {\n cart_item_id: ${item_id}\n quantity: 5\n }\n ]\n }) {\n cart {\n items {\n id\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -39059,321 +41328,113 @@ if (totalCount == null) { <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_virtual_product_with_extensible_data_objects.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/update_configurable_product_qty_in_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_virtual_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="664196114">{"data":{"updateCartItems":{"cart":{"items":[{"id":"${item_id}","qty":5}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_virtual_products_query_total_count"); - -if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; -} else { - if (Integer.parseInt(totalCount) != 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; - } else { - Failure = false; - } -} -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Billing Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setBillingAddressOnCart(\n input: {\n cart_id: \"${quote_id}\"\n billing_address: {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n }\n ) {\n cart {\n billing_address {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n address_type\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_billing_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1147076914">{"data":{"setBillingAddressOnCart":{"cart":{"billing_address":{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"},"address_type":"BILLING"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert product sku" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> - <stringProp name="EXPECTED_VALUE">${virtual_product_sku}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Address On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingAddressesOnCart(\n input: {\n cart_id: \"${quote_id}\"\n shipping_addresses: [\n {\n address: {\n firstname: \"test firstname\"\n lastname: \"test lastname\"\n company: \"test company\"\n street: [\"test street 1\", \"test street 2\"]\n city: \"test city\"\n region: \"test region\"\n postcode: \"887766\"\n country_code: \"US\"\n telephone: \"88776655\"\n save_in_address_book: false\n }\n }\n ]\n }\n ) {\n cart {\n shipping_addresses {\n firstname\n lastname\n company\n street\n city\n postcode\n telephone\n country {\n code\n label\n }\n address_type\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_address_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1671866339">{"data":{"setShippingAddressesOnCart":{"cart":{"shipping_addresses":[{"firstname":"test firstname","lastname":"test lastname","company":"test company","street":["test street 1","test street 2"],"city":"test city","postcode":"887766","telephone":"88776655","country":{"code":"US","label":"US"},"address_type":"SHIPPING"}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> <hashTree/> </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query grouped product" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_grouped_product_with_extensible_data_objects.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_grouped_products_query_total_count</stringProp> - <stringProp name="JSONPATH">$.data.products.total_count</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count = 1" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_grouped_products_query_total_count"); - - if (totalCount == null) { - Failure = true; - FailureMessage = "Not Expected \"totalCount\" to be null"; - } else { - if (Integer.parseInt(totalCount) != 1) { - Failure = true; - FailureMessage = "Expected \"totalCount\" to be equal to 1, Actual: " + totalCount; - } else { - Failure = false; - } - } - - - </stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert grouped product sku" enabled="true"> - <stringProp name="JSON_PATH">$.data.products.items[0].sku</stringProp> - <stringProp name="EXPECTED_VALUE">${grouped_product_sku}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - </hashTree> - - <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Query Customer" enabled="true"/> - <hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create customer" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{ - "customer": { - - "email": "customer_${__time()}-${__threadNum}-${__Random(1,1000000)}@example.com", - "firstname": "test_${__time()}-${__threadNum}-${__Random(1,1000000)}", - "lastname": "Doe" - }, - "password": "test@123" - }</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_frontend_customer.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer id" enabled="true"> - <stringProp name="VAR">customer_id</stringProp> - <stringProp name="JSONPATH">$.id</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert customer id not null" enabled="true"> - <collectionProp name="Asserion.test_strings"> - <stringProp name="89649215">^\d+$</stringProp> - </collectionProp> - <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> - <boolProp name="Assertion.assume_success">false</boolProp> - <intProp name="Assertion.test_type">1</intProp> - <stringProp name="Assertion.scope">variable</stringProp> - <stringProp name="Scope.variable">customer_id</stringProp> - </ResponseAssertion> - <hashTree/> - </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Check customer" enabled="true"> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> - <collectionProp name="Arguments.arguments"/> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/customers/${customer_id}</stringProp> - <stringProp name="HTTPSampler.method">GET</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/check_customer.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="jp@gc - JSON Path Assertion" enabled="true"> - <stringProp name="JSON_PATH">$.id</stringProp> - <stringProp name="EXPECTED_VALUE">${customer_id}</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer email" enabled="true"> - <stringProp name="VAR">customer_email</stringProp> - <stringProp name="JSONPATH">$.email</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Create customer token" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{ - "username":"${customer_email}", - "password":"test@123" - } - </stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}rest/default/V1/integration/customer/token</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/api/create_customer.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract customer token" enabled="true"> - <stringProp name="VAR">customer_token</stringProp> - <stringProp name="JSONPATH">$</stringProp> - <stringProp name="DEFAULT"/> - <stringProp name="VARIABLE"/> - <stringProp name="SUBJECT">BODY</stringProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> - <hashTree/> - </hashTree> - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query customer with token" enabled="true"> - <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> - <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> - <collectionProp name="Arguments.arguments"> - <elementProp name="" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n customer {\n created_at\n group_id\n\n prefix\n firstname\n middlename\n lastname\n suffix\n email\n default_billing\n default_shipping\n\n dob\n taxvat\n\n id\n addresses {\n id\n customer_id\n region {\n region_code\n region\n region_id\n }\n region_id\n country_id\n street \n company\n telephone\n fax\n postcode\n city\n firstname\n lastname\n middlename\n prefix\n suffix\n vat_id\n default_shipping\n default_billing\n }\n is_subscribed\n }\n}","variables":null,"operationName":null}</stringProp> - <stringProp name="Argument.metadata">=</stringProp> - </elementProp> - </collectionProp> - </elementProp> - <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> - <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> - <stringProp name="HTTPSampler.contentEncoding"/> - <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> - <stringProp name="HTTPSampler.method">POST</stringProp> - <boolProp name="HTTPSampler.follow_redirects">true</boolProp> - <boolProp name="HTTPSampler.auto_redirects">false</boolProp> - <boolProp name="HTTPSampler.use_keepalive">true</boolProp> - <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> - <boolProp name="HTTPSampler.monitor">false</boolProp> - <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_frontend_customer.jmx</stringProp> - </HTTPSamplerProxy> - <hashTree> - <BeanShellPreProcessor guiclass="TestBeanGUI" testclass="BeanShellPreProcessor" testname="Set Customer Token" enabled="true"> - <boolProp name="resetInterpreter">false</boolProp> - <stringProp name="parameters"/> - <stringProp name="filename"/> - <stringProp name="script">import org.apache.jmeter.protocol.http.control.Header; - - sampler.getHeaderManager().removeHeaderNamed("Authorization"); - - sampler.getHeaderManager().add(new Header("Authorization","Bearer " + vars.get("customer_token")));</stringProp> - </BeanShellPreProcessor> - <hashTree/> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.gui.JSONPathAssertionGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion" testname="Assert customer lastname" enabled="true"> - <stringProp name="JSON_PATH">$.data.customer.lastname</stringProp> - <stringProp name="EXPECTED_VALUE">Doe</stringProp> - <boolProp name="JSONVALIDATION">true</boolProp> - <boolProp name="EXPECT_NULL">false</boolProp> - <boolProp name="INVERT">false</boolProp> - <boolProp name="ISREGEX">true</boolProp> - </com.atlantbh.jmeter.plugins.jsonutils.jsonpathassertion.JSONPathAssertion> - <hashTree/> - </hashTree> - </hashTree> - - <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query Category" enabled="true"> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Payment Method On Cart" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n category(id: 1) {\n name\n id\n level\n description\n path\n path_in_store\n url_key\n url_path\n children {\n id\n description\n default_sort_by\n children {\n id\n description\n level\n children {\n level\n id\n children {\n id\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"mutation {\n setPaymentMethodOnCart(input: {\n cart_id: \"${quote_id}\", \n payment_method: {\n code: \"checkmo\"\n }\n }) {\n cart {\n selected_payment_method {\n code\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> </elementProp> <stringProp name="HTTPSampler.domain"/> - <stringProp name="HTTPSampler.port"/> - <stringProp name="HTTPSampler.connect_timeout"/> - <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> <stringProp name="HTTPSampler.contentEncoding"/> <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> @@ -39384,38 +41445,135 @@ if (totalCount == null) { <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> <boolProp name="HTTPSampler.monitor">false</boolProp> <stringProp name="HTTPSampler.embedded_url_re"/> - <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_root_category.jmx</stringProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_payment_method_on_cart.jmx</stringProp> </HTTPSamplerProxy> <hashTree> - <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> - <stringProp name="VAR">graphql_category_query_name</stringProp> - <stringProp name="JSONPATH">$.data.category.name</stringProp> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="-1830199373">{"data":{"setPaymentMethodOnCart":{"cart":{"selected_payment_method":{"code":"checkmo"}}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Current Shipping Address" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n cart(cart_id: \"${quote_id}\") {\n shipping_addresses {\n address_id\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/get_current_shipping_address.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract item id" enabled="true"> + <stringProp name="VAR">address_id</stringProp> + <stringProp name="JSONPATH">$.data.cart.shipping_addresses[0].address_id</stringProp> <stringProp name="DEFAULT"/> <stringProp name="VARIABLE"/> <stringProp name="SUBJECT">BODY</stringProp> </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> - <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="BeanShell Assertion" enabled="true"> - <stringProp name="BeanShellAssertion.query">String name = vars.get("graphql_category_query_name"); -if (name == null) { - Failure = true; - FailureMessage = "Not Expected \"children\" to be null"; -} else { - if (!name.equals("Root Catalog")) { - Failure = true; - FailureMessage = "Expected \"name\" to equal \"Root Catalog\", Actual: " + name; - } else { - Failure = false; - } -} -</stringProp> - <stringProp name="BeanShellAssertion.filename"/> - <stringProp name="BeanShellAssertion.parameters"/> - <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> - </BeanShellAssertion> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Set Shipping Method On Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n setShippingMethodsOnCart(input: \n {\n cart_id: \"${quote_id}\", \n shipping_methods: [{\n cart_address_id: ${address_id}\n carrier_code: \"flatrate\"\n method_code: \"flatrate\"\n }]\n }) {\n cart {\n shipping_addresses {\n selected_shipping_method {\n carrier_code\n method_code\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/set_shipping_method_on_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="644143859">{"data":{"setShippingMethodsOnCart":{"cart":{"shipping_addresses":[{"selected_shipping_method":{"carrier_code":"flatrate","method_code":"flatrate"}}]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> + <hashTree/> + </hashTree> + + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Remove Configurable Product From Cart" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"mutation {\n removeItemFromCart(\n input: {\n cart_id: \"${quote_id}\"\n cart_item_id: ${item_id}\n }\n ) {\n cart {\n items {\n qty\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port">${graphql_port_number}</stringProp> + <stringProp name="HTTPSampler.connect_timeout">60000</stringProp> + <stringProp name="HTTPSampler.response_timeout">200000</stringProp> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/remove_configurable_product_from_cart.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true"> + <collectionProp name="Asserion.test_strings"> + <stringProp name="1452665323">{"data":{"removeItemFromCart":{"cart":{"items":[]}}}}</stringProp> + </collectionProp> + <stringProp name="Assertion.test_field">Assertion.response_data</stringProp> + <boolProp name="Assertion.assume_success">false</boolProp> + <intProp name="Assertion.test_type">8</intProp> + </ResponseAssertion> <hashTree/> </hashTree> - </hashTree> </hashTree> </hashTree> @@ -39552,6 +41710,14 @@ if (name == null) { <stringProp name="filename">${response_time_file_name}</stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/aggregate_graph.jmx</stringProp></ResultCollector> <hashTree/> + + <DebugPostProcessor guiclass="TestBeanGUI" testclass="DebugPostProcessor" testname="Debug PostProcessor" enabled="false"> + <boolProp name="displayJMeterProperties">false</boolProp> + <boolProp name="displayJMeterVariables">true</boolProp> + <boolProp name="displaySamplerProperties">true</boolProp> + <boolProp name="displaySystemProperties">false</boolProp> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/_system/debug.jmx</stringProp></DebugPostProcessor> + <hashTree/> </hashTree> <WorkBench guiclass="WorkBenchGui" testclass="WorkBench" testname="WorkBench" enabled="true"> <boolProp name="WorkBench.save">true</boolProp>