RulesTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. namespace Dotdigitalgroup\Email\Model;
  3. use Dotdigitalgroup\Email\Model\Config\Json;
  4. use Dotdigitalgroup\Email\Model\ResourceModel\Rules as RulesResource;
  5. use Dotdigitalgroup\Email\Model\Rules;
  6. use Magento\Catalog\Model\Product;
  7. use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
  8. use Magento\Customer\Api\AddressRepositoryInterface;
  9. use Magento\Customer\Api\CustomerRepositoryInterface;
  10. use Magento\Quote\Model\Quote;
  11. use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
  12. use Magento\Quote\Model\ResourceModel\Quote\Collection as QuoteCollection;
  13. use Magento\Store\Model\StoreManagerInterface;
  14. use Magento\TestFramework\ObjectManager;
  15. /**
  16. * @magentoDataFixture Magento/Customer/_files/customer.php
  17. * @magentoDataFixture Magento/Customer/_files/customer_address.php
  18. * @magentoDataFixture Magento/Catalog/_files/products.php
  19. *
  20. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  21. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  22. */
  23. class RulesTest extends \PHPUnit\Framework\TestCase
  24. {
  25. const RULE_OPERATOR_AND = 1;
  26. const RULE_OPERATOR_OR = 2;
  27. /**
  28. * @var QuoteCollection
  29. */
  30. private $quoteCollection;
  31. /**
  32. * @var int
  33. */
  34. private $currentWebsiteId;
  35. /**
  36. * @param string $type
  37. * @return Quote\Address
  38. */
  39. private function createQuoteAddress($type)
  40. {
  41. /** @var AddressRepositoryInterface $addressRepository */
  42. $addressRepository = ObjectManager::getInstance()->create(AddressRepositoryInterface::class);
  43. /** @var Quote\Address $quoteAddress */
  44. $quoteAddress = ObjectManager::getInstance()->create(Quote\Address::class);
  45. $quoteAddress->importCustomerAddressData($addressRepository->getById(1));
  46. $quoteAddress->setData('address_type', $type);
  47. return $quoteAddress;
  48. }
  49. /**
  50. * @return \Magento\Customer\Api\Data\CustomerInterface
  51. */
  52. private function getCustomer()
  53. {
  54. /** @var CustomerRepositoryInterface $customerRepository */
  55. $customerRepository = ObjectManager::getInstance()->create(CustomerRepositoryInterface::class);
  56. return $customerRepository->getById(1);
  57. }
  58. /**
  59. * @return Product
  60. */
  61. private function getProduct()
  62. {
  63. /** @var ProductResource $resourceModel */
  64. $resourceModel = ObjectManager::getInstance()->create(ProductResource::class);
  65. /** @var Product $product */
  66. $product = ObjectManager::getInstance()->create(Product::class);
  67. $resourceModel->load($product, 1);
  68. return $product;
  69. }
  70. /**
  71. * @return Quote
  72. */
  73. private function createQuote()
  74. {
  75. /** @var Quote $quote */
  76. $quote = ObjectManager::getInstance()->create(Quote::class);
  77. $quote->setStoreId(1);
  78. $quote->setIsActive(true);
  79. $quote->setData('is_multi_shipping', false);
  80. $quote->assignCustomerWithAddressChange($this->getCustomer());
  81. $quote->setShippingAddress($this->createQuoteAddress('shipping'));
  82. $quote->setBillingAddress($this->createQuoteAddress('billing'));
  83. $quote->setCheckoutMethod('customer');
  84. $quote->setReservedOrderId('test_order_1');
  85. $quote->addProduct($this->getProduct(), 2);
  86. return $quote;
  87. }
  88. private function createQuoteWithCityAddress($city)
  89. {
  90. /** @var Quote $quote */
  91. $quote = ObjectManager::getInstance()->create(Quote::class);
  92. $quote->setStoreId(1);
  93. $quote->setIsActive(true);
  94. $quote->setData('is_multi_shipping', false);
  95. $quote->assignCustomerWithAddressChange($this->getCustomer());
  96. $shippingAddress = $this->createQuoteAddress('shipping');
  97. $shippingAddress->setCity($city);
  98. $billingAddress = $this->createQuoteAddress('billing');
  99. $billingAddress->setCity($city);
  100. $quote->setShippingAddress($shippingAddress);
  101. $quote->setBillingAddress($billingAddress);
  102. $quote->setCheckoutMethod('customer');
  103. $quote->setReservedOrderId('test_order_no_address_1');
  104. $quote->addProduct($this->getProduct(), 2);
  105. $quoteResource = ObjectManager::getInstance()->create(QuoteResource::class);
  106. $quoteResource->save($quote);
  107. return $quote;
  108. }
  109. /**
  110. * @return Quote
  111. */
  112. public function createQuoteWithoutPayment()
  113. {
  114. $quote = $this->createQuote();
  115. /** @var QuoteResource $quoteResource */
  116. $quoteResource = ObjectManager::getInstance()->create(QuoteResource::class);
  117. $quoteResource->save($quote);
  118. return $quote;
  119. }
  120. /**
  121. * @param int $operator
  122. *
  123. * @return Rules
  124. */
  125. private function createAbandonedCartRuleWithOperator($operator)
  126. {
  127. if (! in_array($operator, [self::RULE_OPERATOR_AND, self::RULE_OPERATOR_OR])) {
  128. throw new \InvalidArgumentException('Invalid rule operator, must be 1 (AND) or 2 (OR)');
  129. }
  130. /** @var Rules $rule */
  131. $rule = ObjectManager::getInstance()->create(Rules::class);
  132. $rule->setData('status', 1);
  133. $rule->setData('type', Rules::ABANDONED);
  134. $rule->setData('combination', $operator);
  135. $rule->setData('website_ids', [$this->currentWebsiteId]);
  136. return $rule;
  137. }
  138. /**
  139. * @param string $attribute
  140. * @param string $condition
  141. * @param string $value
  142. * @param int $operator
  143. *
  144. * @return Rules
  145. */
  146. private function createAbandonedCartRuleWithCondition($attribute, $condition, $value, $operator)
  147. {
  148. $rule = $this->createAbandonedCartRuleWithOperator($operator);
  149. $this->addConditionToRule($rule, $attribute, $condition, $value);
  150. return $rule;
  151. }
  152. /**
  153. * @param Rules $rule
  154. * @param string $attribute
  155. * @param string $condition
  156. * @param string $value
  157. *
  158. * @return null
  159. */
  160. private function addConditionToRule(Rules $rule, $attribute, $condition, $value)
  161. {
  162. $conditions = $this->getConditionsFromRule($rule);
  163. $conditions[] = [
  164. 'attribute' => $attribute,
  165. 'conditions' => $condition,
  166. 'cvalue' => $value,
  167. ];
  168. $rule->setData('website_ids', $this->getWebsiteIdsFromRule($rule));
  169. $rule->setData('conditions', $conditions);
  170. /** @var RulesResource $rulesResource */
  171. $rulesResource = ObjectManager::getInstance()->get(RulesResource::class);
  172. $rulesResource->save($rule);
  173. }
  174. /**
  175. * @param Rules $rule
  176. * @return array
  177. */
  178. private function getWebsiteIdsFromRule(Rules $rule)
  179. {
  180. $websiteIds = $rule->getData('website_ids');
  181. return is_array($websiteIds) ?
  182. $websiteIds :
  183. explode(',', $websiteIds);
  184. }
  185. /**
  186. * @param Rules $rule
  187. *
  188. * @return array
  189. */
  190. private function getConditionsFromRule(Rules $rule)
  191. {
  192. $serializer = ObjectManager::getInstance()->create(Json::class);
  193. $conditions = $rule->getData('conditions') ? $rule->getData('conditions') : [];
  194. if (is_string($conditions)) {
  195. $conditions = $serializer->unserialize($conditions);
  196. }
  197. return $conditions;
  198. }
  199. /**
  200. * @return void
  201. */
  202. protected function setUp()
  203. {
  204. $this->quoteCollection = ObjectManager::getInstance()->create(QuoteCollection::class);
  205. /** @var StoreManagerInterface $storeManager */
  206. $storeManager = ObjectManager::getInstance()->get(StoreManagerInterface::class);
  207. $this->currentWebsiteId = $storeManager->getStore()->getWebsiteId();
  208. }
  209. /**
  210. * @param Quote $expected
  211. * @return void
  212. */
  213. private function assertQuoteCollectionNotContains(Quote $expected)
  214. {
  215. $message = sprintf(
  216. 'The quote with ID "%s" is contained in the quote collection, but was expected to be absent',
  217. $expected->getId()
  218. );
  219. $this->assertNotContains($expected->getId(), $this->quoteCollection->getAllIds(), $message);
  220. }
  221. /**
  222. * @param Quote $expected
  223. */
  224. private function assertQuoteCollectionContains(Quote $expected)
  225. {
  226. $message = sprintf(
  227. 'The quote with ID "%s" is not contained in the quote collection, but is absent',
  228. $expected->getId()
  229. );
  230. $this->assertContains($expected->getId(), $this->quoteCollection->getAllIds(), $message);
  231. }
  232. /**
  233. * @param float $subtotal
  234. *
  235. * @return Quote
  236. */
  237. private function createQuoteWithSubtotal($subtotal)
  238. {
  239. /** @var \Magento\Quote\Model\Quote $quote */
  240. $quote = $this->createQuote();
  241. $quote->setSubtotal($subtotal);
  242. $quoteResource = ObjectManager::getInstance()->create(QuoteResource::class);
  243. $quoteResource->save($quote);
  244. return $quote;
  245. }
  246. /**
  247. * @return void
  248. */
  249. public function testRuleWithSubtotalCondition()
  250. {
  251. //subtotal
  252. $conditionValue = '300.00';
  253. $attribute = 'subtotal';
  254. $this->createAbandonedCartRuleWithCondition($attribute, 'gteq', $conditionValue, self::RULE_OPERATOR_AND);
  255. $quote = $this->createQuoteWithSubtotal('500.00');
  256. $quote1 = $this->createQuoteWithSubtotal('1000.11');
  257. $quote2 = $this->createQuoteWithSubtotal('999.11');
  258. /** @var Rules $ruleService */
  259. $ruleService = ObjectManager::getInstance()->create(Rules::class);
  260. $ruleService->process($this->quoteCollection, Rules::ABANDONED, $this->currentWebsiteId);
  261. $this->assertQuoteCollectionNotContains($quote);
  262. $this->assertQuoteCollectionNotContains($quote1);
  263. $this->assertQuoteCollectionNotContains($quote2);
  264. }
  265. /**
  266. * @return void
  267. */
  268. public function testRuleWithCustomerGroupANDPaymentMethod()
  269. {
  270. $attribute1 = 'method';
  271. $attribute2 = 'customer_group_id';
  272. $value1 = '1';
  273. $value2 = 'payflow_advanced';
  274. $this->createAbandonedCartRuleWithCondition($attribute1, 'neq', $value1, self::RULE_OPERATOR_AND);
  275. $this->createAbandonedCartRuleWithCondition($attribute2, 'eq', $value2, self::RULE_OPERATOR_AND);
  276. }
  277. /**
  278. * Test shipping city AC is excluded by the exclusion rules.
  279. */
  280. public function testShippingCityNullValue()
  281. {
  282. //create a rule to exclude the city attribute from the abandoned carts
  283. $attribute = 'city';
  284. $value = $cityOne = 'CityM';
  285. $city = null;
  286. $this->createAbandonedCartRuleWithCondition($attribute, 'eq', $value, self::RULE_OPERATOR_AND);
  287. $quote = $this->createQuoteWithCityAddress($city);
  288. $quote1 = $this->createQuoteWithCityAddress($cityOne);
  289. /** @var Rules $ruleService */
  290. $ruleService = ObjectManager::getInstance()->create(Rules::class);
  291. $ruleService->process($this->quoteCollection, Rules::ABANDONED, $this->currentWebsiteId);
  292. $this->assertQuoteCollectionContains($quote);
  293. $this->assertQuoteCollectionNotContains($quote1);
  294. }
  295. }