CarrierTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Ups\Test\Unit\Model;
  7. use Magento\Directory\Model\Country;
  8. use Magento\Directory\Model\CountryFactory;
  9. use Magento\Framework\App\Config\ScopeConfigInterface;
  10. use Magento\Framework\DataObject;
  11. use Magento\Framework\HTTP\ClientFactory;
  12. use Magento\Framework\HTTP\ClientInterface;
  13. use Magento\Framework\Model\AbstractModel;
  14. use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
  15. use Magento\Quote\Model\Quote\Address\RateRequest;
  16. use Magento\Quote\Model\Quote\Address\RateResult\Error;
  17. use Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory;
  18. use Magento\Shipping\Model\Rate\Result;
  19. use Magento\Shipping\Model\Rate\ResultFactory;
  20. use Magento\Shipping\Model\Simplexml\Element;
  21. use Magento\Shipping\Model\Simplexml\ElementFactory;
  22. use Magento\Ups\Model\Carrier;
  23. use PHPUnit_Framework_MockObject_MockObject as MockObject;
  24. use Psr\Log\LoggerInterface;
  25. /**
  26. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  27. */
  28. class CarrierTest extends \PHPUnit\Framework\TestCase
  29. {
  30. const FREE_METHOD_NAME = 'free_method';
  31. const PAID_METHOD_NAME = 'paid_method';
  32. /**
  33. * Model under test
  34. *
  35. * @var Error|MockObject
  36. */
  37. private $error;
  38. /**
  39. * @var ObjectManager
  40. */
  41. private $helper;
  42. /**
  43. * Model under test
  44. *
  45. * @var Carrier|MockObject
  46. */
  47. private $model;
  48. /**
  49. * @var ErrorFactory|MockObject
  50. */
  51. private $errorFactory;
  52. /**
  53. * @var ScopeConfigInterface|MockObject
  54. */
  55. private $scope;
  56. /**
  57. * @var CountryFactory
  58. */
  59. private $countryFactory;
  60. /**
  61. * @var Country|MockObject
  62. */
  63. private $country;
  64. /**
  65. * @var AbstractModel
  66. */
  67. private $abstractModel;
  68. /**
  69. * @var Result
  70. */
  71. private $rate;
  72. /**
  73. * @var ClientInterface|MockObject
  74. */
  75. private $httpClient;
  76. /**
  77. * @var LoggerInterface|MockObject
  78. */
  79. private $logger;
  80. protected function setUp()
  81. {
  82. $this->helper = new ObjectManager($this);
  83. $this->scope = $this->getMockBuilder(ScopeConfigInterface::class)
  84. ->disableOriginalConstructor()
  85. ->getMock();
  86. $this->scope->method('getValue')
  87. ->willReturnCallback([$this, 'scopeConfigGetValue']);
  88. $this->error = $this->getMockBuilder(Error::class)
  89. ->setMethods(['setCarrier', 'setCarrierTitle', 'setErrorMessage'])
  90. ->getMock();
  91. $this->errorFactory = $this->getMockBuilder(ErrorFactory::class)
  92. ->disableOriginalConstructor()
  93. ->setMethods(['create'])
  94. ->getMock();
  95. $this->errorFactory->method('create')
  96. ->willReturn($this->error);
  97. $rateFactory = $this->getRateFactory();
  98. $this->country = $this->getMockBuilder(Country::class)
  99. ->disableOriginalConstructor()
  100. ->setMethods(['load', 'getData'])
  101. ->getMock();
  102. $this->abstractModel = $this->getMockBuilder(AbstractModel::class)
  103. ->disableOriginalConstructor()
  104. ->setMethods(['getData'])
  105. ->getMock();
  106. $this->country->method('load')
  107. ->willReturn($this->abstractModel);
  108. $this->countryFactory = $this->getMockBuilder(CountryFactory::class)
  109. ->disableOriginalConstructor()
  110. ->setMethods(['create'])
  111. ->getMock();
  112. $this->countryFactory->method('create')
  113. ->willReturn($this->country);
  114. $xmlFactory = $this->getXmlFactory();
  115. $httpClientFactory = $this->getHttpClientFactory();
  116. $this->logger = $this->getMockForAbstractClass(LoggerInterface::class);
  117. $this->model = $this->helper->getObject(
  118. Carrier::class,
  119. [
  120. 'scopeConfig' => $this->scope,
  121. 'rateErrorFactory' => $this->errorFactory,
  122. 'countryFactory' => $this->countryFactory,
  123. 'rateFactory' => $rateFactory,
  124. 'xmlElFactory' => $xmlFactory,
  125. 'logger' => $this->logger,
  126. 'httpClientFactory' => $httpClientFactory,
  127. ]
  128. );
  129. }
  130. /**
  131. * Callback function, emulates getValue function.
  132. *
  133. * @param string $path
  134. * @return null|string
  135. */
  136. public function scopeConfigGetValue(string $path)
  137. {
  138. $pathMap = [
  139. 'carriers/ups/free_method' => 'free_method',
  140. 'carriers/ups/free_shipping_subtotal' => 5,
  141. 'carriers/ups/showmethod' => 1,
  142. 'carriers/ups/title' => 'ups Title',
  143. 'carriers/ups/specificerrmsg' => 'ups error message',
  144. 'carriers/ups/min_package_weight' => 2,
  145. 'carriers/ups/type' => 'UPS',
  146. 'carriers/ups/debug' => 1,
  147. 'carriers/ups/username' => 'user',
  148. 'carriers/ups/password' => 'pass',
  149. 'carriers/ups/access_license_number' => 'acn',
  150. ];
  151. return isset($pathMap[$path]) ? $pathMap[$path] : null;
  152. }
  153. /**
  154. * @dataProvider getMethodPriceProvider
  155. * @param int $cost
  156. * @param string $shippingMethod
  157. * @param bool $freeShippingEnabled
  158. * @param int $requestSubtotal
  159. * @param int $expectedPrice
  160. */
  161. public function testGetMethodPrice(
  162. $cost,
  163. $shippingMethod,
  164. $freeShippingEnabled,
  165. $requestSubtotal,
  166. $expectedPrice
  167. ) {
  168. $path = 'carriers/' . $this->model->getCarrierCode() . '/';
  169. $this->scope->method('isSetFlag')
  170. ->with($path . 'free_shipping_enable')
  171. ->willReturn($freeShippingEnabled);
  172. $request = new RateRequest();
  173. $request->setBaseSubtotalInclTax($requestSubtotal);
  174. $this->model->setRawRequest($request);
  175. $price = $this->model->getMethodPrice($cost, $shippingMethod);
  176. $this->assertEquals($expectedPrice, $price);
  177. }
  178. /**
  179. * Data provider for testGenerate method
  180. *
  181. * @return array
  182. */
  183. public function getMethodPriceProvider()
  184. {
  185. return [
  186. [3, self::FREE_METHOD_NAME, true, 6, 0],
  187. [3, self::FREE_METHOD_NAME, true, 4, 3],
  188. [3, self::FREE_METHOD_NAME, false, 6, 3],
  189. [3, self::FREE_METHOD_NAME, false, 4, 3],
  190. [3, self::PAID_METHOD_NAME, true, 6, 3],
  191. [3, self::PAID_METHOD_NAME, true, 4, 3],
  192. [3, self::PAID_METHOD_NAME, false, 6, 3],
  193. [3, self::PAID_METHOD_NAME, false, 4, 3],
  194. [7, self::FREE_METHOD_NAME, true, 6, 0],
  195. [7, self::FREE_METHOD_NAME, true, 4, 7],
  196. [7, self::FREE_METHOD_NAME, false, 6, 7],
  197. [7, self::FREE_METHOD_NAME, false, 4, 7],
  198. [7, self::PAID_METHOD_NAME, true, 6, 7],
  199. [7, self::PAID_METHOD_NAME, true, 4, 7],
  200. [7, self::PAID_METHOD_NAME, false, 6, 7],
  201. [7, self::PAID_METHOD_NAME, false, 4, 7],
  202. [3, self::FREE_METHOD_NAME, true, 0, 3],
  203. [3, self::FREE_METHOD_NAME, true, 0, 3],
  204. [3, self::FREE_METHOD_NAME, false, 0, 3],
  205. [3, self::FREE_METHOD_NAME, false, 0, 3],
  206. [3, self::PAID_METHOD_NAME, true, 0, 3],
  207. [3, self::PAID_METHOD_NAME, true, 0, 3],
  208. [3, self::PAID_METHOD_NAME, false, 0, 3],
  209. [3, self::PAID_METHOD_NAME, false, 0, 3]
  210. ];
  211. }
  212. public function testCollectRatesErrorMessage()
  213. {
  214. $this->scope->method('isSetFlag')
  215. ->willReturn(false);
  216. $this->error->method('setCarrier')
  217. ->with('ups');
  218. $this->error->method('setCarrierTitle');
  219. $this->error->method('setErrorMessage');
  220. $request = new RateRequest();
  221. $request->setPackageWeight(1);
  222. $this->assertSame($this->error, $this->model->collectRates($request));
  223. }
  224. public function testCollectRatesFail()
  225. {
  226. $this->scope->method('isSetFlag')
  227. ->willReturn(true);
  228. $request = new RateRequest();
  229. $request->setPackageWeight(1);
  230. $this->assertSame($this->rate, $this->model->collectRates($request));
  231. }
  232. /**
  233. * @param string $data
  234. * @param array $maskFields
  235. * @param string $expected
  236. * @dataProvider logDataProvider
  237. */
  238. public function testFilterDebugData($data, array $maskFields, $expected)
  239. {
  240. $refClass = new \ReflectionClass(Carrier::class);
  241. $property = $refClass->getProperty('_debugReplacePrivateDataKeys');
  242. $property->setAccessible(true);
  243. $property->setValue($this->model, $maskFields);
  244. $refMethod = $refClass->getMethod('filterDebugData');
  245. $refMethod->setAccessible(true);
  246. $result = $refMethod->invoke($this->model, $data);
  247. $expectedXml = new \SimpleXMLElement($expected);
  248. $resultXml = new \SimpleXMLElement($result);
  249. $this->assertEquals($expectedXml->asXML(), $resultXml->asXML());
  250. }
  251. /**
  252. * Get list of variations
  253. */
  254. public function logDataProvider()
  255. {
  256. return [
  257. [
  258. '<?xml version="1.0" encoding="UTF-8"?>
  259. <RateRequest>
  260. <UserId>42121</UserId>
  261. <Password>TestPassword</Password>
  262. <Package ID="0">
  263. <Service>ALL</Service>
  264. </Package>
  265. </RateRequest>',
  266. ['UserId', 'Password'],
  267. '<?xml version="1.0" encoding="UTF-8"?>
  268. <RateRequest>
  269. <UserId>****</UserId>
  270. <Password>****</Password>
  271. <Package ID="0">
  272. <Service>ALL</Service>
  273. </Package>
  274. </RateRequest>',
  275. ],
  276. [
  277. '<?xml version="1.0" encoding="UTF-8"?>
  278. <RateRequest>
  279. <Auth>
  280. <UserId>1231</UserId>
  281. </Auth>
  282. <Package ID="0">
  283. <Service>ALL</Service>
  284. </Package>
  285. </RateRequest>',
  286. ['UserId'],
  287. '<?xml version="1.0" encoding="UTF-8"?>
  288. <RateRequest>
  289. <Auth>
  290. <UserId>****</UserId>
  291. </Auth>
  292. <Package ID="0">
  293. <Service>ALL</Service>
  294. </Package>
  295. </RateRequest>',
  296. ]
  297. ];
  298. }
  299. /**
  300. * @param string $countryCode
  301. * @param string $foundCountryCode
  302. * @dataProvider countryDataProvider
  303. */
  304. public function testSetRequest($countryCode, $foundCountryCode)
  305. {
  306. /** @var RateRequest $request */
  307. $request = $this->helper->getObject(RateRequest::class);
  308. $request->setData([
  309. 'orig_country' => 'USA',
  310. 'orig_region_code' => 'CA',
  311. 'orig_post_code' => 90230,
  312. 'orig_city' => 'Culver City',
  313. 'dest_country_id' => $countryCode,
  314. ]);
  315. $this->country->expects($this->at(1))
  316. ->method('load')
  317. ->with($countryCode)
  318. ->willReturnSelf();
  319. $this->country->method('getData')
  320. ->with('iso2_code')
  321. ->willReturn($foundCountryCode);
  322. $this->model->setRequest($request);
  323. }
  324. /**
  325. * Get list of country variations
  326. * @return array
  327. */
  328. public function countryDataProvider()
  329. {
  330. return [
  331. ['countryCode' => 'PR', 'foundCountryCode' => null],
  332. ['countryCode' => 'US', 'foundCountryCode' => 'US'],
  333. ];
  334. }
  335. /**
  336. * Checks a case when UPS processes request to create shipment.
  337. *
  338. * @return void
  339. */
  340. public function testRequestToShipment()
  341. {
  342. // the same tracking number is specified in the fixtures XML file.
  343. $trackingNumber = '1Z207W886698856557';
  344. $packages = $this->getPackages();
  345. $request = new DataObject(['packages' => $packages]);
  346. $shipmentResponse = simplexml_load_file(__DIR__ . '/../Fixtures/ShipmentConfirmResponse.xml');
  347. $acceptResponse = simplexml_load_file(__DIR__ . '/../Fixtures/ShipmentAcceptResponse.xml');
  348. $this->httpClient->method('getBody')
  349. ->willReturnOnConsecutiveCalls($shipmentResponse->asXML(), $acceptResponse->asXML());
  350. $this->logger->expects($this->atLeastOnce())
  351. ->method('debug')
  352. ->with($this->stringContains('<UserId>****</UserId>'));
  353. $result = $this->model->requestToShipment($request);
  354. $this->assertEmpty($result->getErrors());
  355. $info = $result->getInfo()[0];
  356. $this->assertEquals($trackingNumber, $info['tracking_number'], 'Tracking Number must match.');
  357. }
  358. /**
  359. * Creates mock for XML factory.
  360. *
  361. * @return ElementFactory|MockObject
  362. */
  363. private function getXmlFactory(): MockObject
  364. {
  365. $xmlElFactory = $this->getMockBuilder(ElementFactory::class)
  366. ->disableOriginalConstructor()
  367. ->setMethods(['create'])
  368. ->getMock();
  369. $xmlElFactory->method('create')
  370. ->willReturnCallback(
  371. function ($data) {
  372. $helper = new ObjectManager($this);
  373. return $helper->getObject(
  374. Element::class,
  375. ['data' => $data['data']]
  376. );
  377. }
  378. );
  379. return $xmlElFactory;
  380. }
  381. /**
  382. * @return array
  383. */
  384. private function getPackages(): array
  385. {
  386. $packages = [
  387. 'package' => [
  388. 'params' => [
  389. 'width' => '3',
  390. 'length' => '3',
  391. 'height' => '3',
  392. 'dimension_units' => 'INCH',
  393. 'weight_units' => 'POUND',
  394. 'weight' => '0.454000000001',
  395. 'customs_value' => '10.00',
  396. 'container' => 'Small Express Box',
  397. ],
  398. 'items' => [
  399. 'item1' => [
  400. 'name' => 'item_name',
  401. ],
  402. ],
  403. ],
  404. ];
  405. return $packages;
  406. }
  407. /**
  408. * Creates mocks for http client factory and client.
  409. *
  410. * @return ClientFactory|MockObject
  411. */
  412. private function getHttpClientFactory(): MockObject
  413. {
  414. $httpClientFactory = $this->getMockBuilder(ClientFactory::class)
  415. ->disableOriginalConstructor()
  416. ->setMethods(['create'])
  417. ->getMock();
  418. $this->httpClient = $this->getMockForAbstractClass(ClientInterface::class);
  419. $httpClientFactory->method('create')
  420. ->willReturn($this->httpClient);
  421. return $httpClientFactory;
  422. }
  423. /**
  424. * @return MockObject
  425. */
  426. private function getRateFactory(): MockObject
  427. {
  428. $this->rate = $this->createPartialMock(Result::class, ['getError']);
  429. $rateFactory = $this->createPartialMock(ResultFactory::class, ['create']);
  430. $rateFactory->method('create')
  431. ->willReturn($this->rate);
  432. return $rateFactory;
  433. }
  434. }