ConfigurableProductViewTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\GraphQl\ConfigurableProduct;
  8. use Magento\Catalog\Api\Data\ProductInterface;
  9. use Magento\Catalog\Api\ProductRepositoryInterface;
  10. use Magento\Catalog\Pricing\Price\FinalPrice;
  11. use Magento\Catalog\Pricing\Price\RegularPrice;
  12. use Magento\Framework\EntityManager\MetadataPool;
  13. use Magento\TestFramework\ObjectManager;
  14. use Magento\TestFramework\TestCase\GraphQlAbstract;
  15. class ConfigurableProductViewTest extends GraphQlAbstract
  16. {
  17. /**
  18. * @var array
  19. */
  20. private $configurableOptions = [];
  21. /**
  22. * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_with_category_and_weight.php
  23. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  24. */
  25. public function testQueryConfigurableProductLinks()
  26. {
  27. $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/361');
  28. $productSku = 'configurable';
  29. $query
  30. = <<<QUERY
  31. {
  32. products(filter: {sku: {eq: "{$productSku}"}}) {
  33. items {
  34. id
  35. attribute_set_id
  36. created_at
  37. name
  38. sku
  39. type_id
  40. updated_at
  41. ... on PhysicalProductInterface {
  42. weight
  43. }
  44. price {
  45. minimalPrice {
  46. amount {
  47. value
  48. currency
  49. }
  50. adjustments {
  51. amount {
  52. value
  53. currency
  54. }
  55. code
  56. description
  57. }
  58. }
  59. maximalPrice {
  60. amount {
  61. value
  62. currency
  63. }
  64. adjustments {
  65. amount {
  66. value
  67. currency
  68. }
  69. code
  70. description
  71. }
  72. }
  73. regularPrice {
  74. amount {
  75. value
  76. currency
  77. }
  78. adjustments {
  79. amount {
  80. value
  81. currency
  82. }
  83. code
  84. description
  85. }
  86. }
  87. }
  88. ... on ConfigurableProduct {
  89. configurable_options {
  90. id
  91. attribute_id
  92. label
  93. position
  94. use_default
  95. attribute_code
  96. values {
  97. value_index
  98. label
  99. store_label
  100. default_label
  101. use_default_value
  102. }
  103. product_id
  104. }
  105. variants {
  106. product {
  107. id
  108. name
  109. sku
  110. attribute_set_id
  111. ... on PhysicalProductInterface {
  112. weight
  113. }
  114. created_at
  115. updated_at
  116. price {
  117. minimalPrice {
  118. amount {
  119. value
  120. currency
  121. }
  122. adjustments {
  123. amount {
  124. value
  125. currency
  126. }
  127. code
  128. description
  129. }
  130. }
  131. maximalPrice {
  132. amount {
  133. value
  134. currency
  135. }
  136. adjustments {
  137. amount {
  138. value
  139. currency
  140. }
  141. code
  142. description
  143. }
  144. }
  145. regularPrice {
  146. amount {
  147. value
  148. currency
  149. }
  150. adjustments {
  151. amount {
  152. value
  153. currency
  154. }
  155. code
  156. description
  157. }
  158. }
  159. }
  160. categories {
  161. id
  162. }
  163. media_gallery_entries {
  164. disabled
  165. file
  166. id
  167. label
  168. media_type
  169. position
  170. types
  171. content {
  172. base64_encoded_data
  173. type
  174. name
  175. }
  176. video_content {
  177. media_type
  178. video_description
  179. video_metadata
  180. video_provider
  181. video_title
  182. video_url
  183. }
  184. }
  185. }
  186. attributes {
  187. label
  188. code
  189. value_index
  190. }
  191. }
  192. }
  193. }
  194. }
  195. }
  196. QUERY;
  197. $response = $this->graphQlQuery($query);
  198. /**
  199. * @var ProductRepositoryInterface $productRepository
  200. */
  201. $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
  202. $product = $productRepository->get($productSku, false, null, true);
  203. $this->assertArrayHasKey('products', $response);
  204. $this->assertArrayHasKey('items', $response['products']);
  205. $this->assertEquals(1, count($response['products']['items']));
  206. $this->assertArrayHasKey(0, $response['products']['items']);
  207. $this->assertBaseFields($product, $response['products']['items'][0]);
  208. $this->assertConfigurableProductOptions($response['products']['items'][0]);
  209. $this->assertConfigurableVariants($response['products']['items'][0]);
  210. }
  211. /**
  212. * @param ProductInterface $product
  213. * @param array $actualResponse
  214. */
  215. private function assertBaseFields($product, $actualResponse)
  216. {
  217. /** @var \Magento\Framework\Pricing\PriceInfo\Factory $priceInfoFactory */
  218. $priceInfoFactory = ObjectManager::getInstance()->get(\Magento\Framework\Pricing\PriceInfo\Factory::class);
  219. $priceInfo = $priceInfoFactory->create($product);
  220. /** @var \Magento\Catalog\Pricing\Price\FinalPriceInterface $finalPrice */
  221. $finalPrice = $priceInfo->getPrice(FinalPrice::PRICE_CODE);
  222. $minimalPriceAmount = $finalPrice->getMinimalPrice();
  223. $maximalPriceAmount = $finalPrice->getMaximalPrice();
  224. $regularPriceAmount = $priceInfo->getPrice(RegularPrice::PRICE_CODE)->getAmount();
  225. /** @var MetadataPool $metadataPool */
  226. $metadataPool = ObjectManager::getInstance()->get(MetadataPool::class);
  227. // ['product_object_field_name', 'expected_value']
  228. $assertionMap = [
  229. ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()],
  230. ['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()],
  231. [
  232. 'response_field' => 'id',
  233. 'expected_value' => $product->getData(
  234. $metadataPool->getMetadata(
  235. ProductInterface::class
  236. )->getLinkField()
  237. )
  238. ],
  239. ['response_field' => 'name', 'expected_value' => $product->getName()],
  240. ['response_field' => 'sku', 'expected_value' => $product->getSku()],
  241. ['response_field' => 'type_id', 'expected_value' => $product->getTypeId()],
  242. ['response_field' => 'updated_at', 'expected_value' => $product->getUpdatedAt()],
  243. ['response_field' => 'weight', 'expected_value' => $product->getWeight()],
  244. [
  245. 'response_field' => 'price',
  246. 'expected_value' => [
  247. 'minimalPrice' => [
  248. 'amount' => [
  249. 'value' => $minimalPriceAmount->getValue(),
  250. 'currency' => 'USD'
  251. ],
  252. 'adjustments' => []
  253. ],
  254. 'regularPrice' => [
  255. 'amount' => [
  256. 'value' => $maximalPriceAmount->getValue(),
  257. 'currency' => 'USD'
  258. ],
  259. 'adjustments' => []
  260. ],
  261. 'maximalPrice' => [
  262. 'amount' => [
  263. 'value' => $regularPriceAmount->getValue(),
  264. 'currency' => 'USD'
  265. ],
  266. 'adjustments' => []
  267. ],
  268. ]
  269. ],
  270. ];
  271. $this->assertResponseFields($actualResponse, $assertionMap);
  272. }
  273. /**
  274. * Asserts various fields for child products for a configurable products
  275. *
  276. * @param $actualResponse
  277. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  278. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  279. */
  280. private function assertConfigurableVariants($actualResponse)
  281. {
  282. $this->assertNotEmpty(
  283. $actualResponse['variants'],
  284. "Precondition failed: 'variants' must not be empty"
  285. );
  286. foreach ($actualResponse['variants'] as $variantKey => $variantArray) {
  287. $this->assertNotEmpty($variantArray);
  288. $this->assertNotEmpty($variantArray['product']);
  289. $this->assertTrue(
  290. isset($variantArray['product']['id']),
  291. 'variant product elements don\'t contain id key'
  292. );
  293. $indexValue = $variantArray['product']['sku'];
  294. unset($variantArray['product']['id']);
  295. $this->assertTrue(
  296. isset($variantArray['product']['categories']),
  297. 'variant product doesn\'t contain categories key'
  298. );
  299. $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
  300. /** @var \Magento\Catalog\Model\Product $childProduct */
  301. $childProduct = $productRepository->get($indexValue);
  302. /** @var \Magento\Catalog\Api\Data\ProductLinkInterface[] */
  303. $links = $childProduct->getExtensionAttributes()->getCategoryLinks();
  304. $this->assertCount(1, $links, "Precondition failed, incorrect number of categories.");
  305. $id =$links[0]->getCategoryId();
  306. $actualValue
  307. = $actualResponse['variants'][$variantKey]['product']['categories'][0];
  308. $this->assertEquals($actualValue, ['id' => $id]);
  309. unset($variantArray['product']['categories']);
  310. $mediaGalleryEntries = $childProduct->getMediaGalleryEntries();
  311. $this->assertCount(
  312. 1,
  313. $mediaGalleryEntries,
  314. "Precondition failed since there are incorrect number of media gallery entries"
  315. );
  316. $this->assertTrue(
  317. is_array(
  318. $actualResponse['variants']
  319. [$variantKey]
  320. ['product']
  321. ['media_gallery_entries']
  322. )
  323. );
  324. $this->assertCount(
  325. 1,
  326. $actualResponse['variants'][$variantKey]['product']['media_gallery_entries'],
  327. "there must be 1 record in the media gallery"
  328. );
  329. $mediaGalleryEntry = $mediaGalleryEntries[0];
  330. $this->assertResponseFields(
  331. $actualResponse['variants']
  332. [$variantKey]
  333. ['product']
  334. ['media_gallery_entries'][0],
  335. [
  336. 'disabled' => (bool)$mediaGalleryEntry->isDisabled(),
  337. 'file' => $mediaGalleryEntry->getFile(),
  338. 'id' => $mediaGalleryEntry->getId(),
  339. 'label' => $mediaGalleryEntry->getLabel(),
  340. 'media_type' => $mediaGalleryEntry->getMediaType(),
  341. 'position' => $mediaGalleryEntry->getPosition()
  342. ]
  343. );
  344. $videoContent = $mediaGalleryEntry->getExtensionAttributes()->getVideoContent();
  345. $this->assertResponseFields(
  346. $actualResponse['variants']
  347. [$variantKey]
  348. ['product']
  349. ['media_gallery_entries']
  350. [0]
  351. ['video_content'],
  352. [
  353. 'media_type' =>$videoContent->getMediaType(),
  354. 'video_description' => $videoContent->getVideoDescription(),
  355. 'video_metadata' =>$videoContent->getVideoMetadata(),
  356. 'video_provider' => $videoContent->getVideoProvider(),
  357. 'video_title' => $videoContent->getVideoTitle(),
  358. 'video_url' => $videoContent->getVideoUrl()
  359. ]
  360. );
  361. unset($variantArray['product']['media_gallery_entries']);
  362. foreach ($variantArray['product'] as $key => $value) {
  363. if ($key !== 'price') {
  364. $this->assertEquals($value, $childProduct->getData($key));
  365. }
  366. }
  367. //assert prices
  368. $this->assertEquals(
  369. [
  370. 'minimalPrice' => [
  371. 'amount' => [
  372. 'value' => $childProduct->getFinalPrice(),
  373. 'currency' => 'USD'
  374. ],
  375. 'adjustments' => []
  376. ],
  377. 'regularPrice' => [
  378. 'amount' => [
  379. 'value' => $childProduct->getFinalPrice(),
  380. 'currency' => 'USD'
  381. ],
  382. 'adjustments' => []
  383. ],
  384. 'maximalPrice' => [
  385. 'amount' => [
  386. 'value' => $childProduct->getFinalPrice(),
  387. 'currency' => 'USD'
  388. ],
  389. 'adjustments' => []
  390. ],
  391. ],
  392. $variantArray['product']['price']
  393. );
  394. $configurableOptions = $this->getConfigurableOptions();
  395. $this->assertEquals(1, count($variantArray['attributes']));
  396. foreach ($variantArray['attributes'] as $attribute) {
  397. $hasAssertion = false;
  398. foreach ($configurableOptions as $option) {
  399. foreach ($option['options'] as $value) {
  400. if ((int)$value['value_index'] === (int)$attribute['value_index']) {
  401. $this->assertEquals((int)$attribute['value_index'], (int)$value['value_index']);
  402. $this->assertEquals($attribute['label'], $value['label']);
  403. $hasAssertion = true;
  404. }
  405. }
  406. $this->assertEquals($attribute['code'], $option['attribute_code']);
  407. }
  408. if (!$hasAssertion) {
  409. $this->fail('variant did not contain correct attributes');
  410. }
  411. }
  412. }
  413. }
  414. private function assertConfigurableProductOptions($actualResponse)
  415. {
  416. $this->assertNotEmpty(
  417. $actualResponse['configurable_options'],
  418. "Precondition failed: 'configurable_options' must not be empty"
  419. );
  420. $configurableAttributeOptions = $this->getConfigurableOptions();
  421. $configurableAttributeOption = array_shift($configurableAttributeOptions);
  422. $this->assertEquals(
  423. $actualResponse['configurable_options'][0]['id'],
  424. $configurableAttributeOption['id']
  425. );
  426. $this->assertEquals(
  427. $actualResponse['configurable_options'][0]['use_default'],
  428. (bool)$configurableAttributeOption['use_default']
  429. );
  430. $this->assertEquals(
  431. $actualResponse['configurable_options'][0]['attribute_id'],
  432. $configurableAttributeOption['attribute_id']
  433. );
  434. $this->assertEquals(
  435. $actualResponse['configurable_options'][0]['label'],
  436. $configurableAttributeOption['label']
  437. );
  438. $this->assertEquals(
  439. $actualResponse['configurable_options'][0]['position'],
  440. $configurableAttributeOption['position']
  441. );
  442. $this->assertEquals(
  443. $actualResponse['configurable_options'][0]['product_id'],
  444. $configurableAttributeOption['product_id']
  445. );
  446. $this->assertEquals(
  447. $actualResponse['configurable_options'][0]['attribute_code'],
  448. $configurableAttributeOption['attribute_code']
  449. );
  450. foreach ($actualResponse['configurable_options'][0]['values'] as $key => $value) {
  451. $this->assertEquals(
  452. $value['label'],
  453. $configurableAttributeOption['options'][$key]['label']
  454. );
  455. $this->assertEquals(
  456. $value['store_label'],
  457. $configurableAttributeOption['options'][$key]['store_label']
  458. );
  459. $this->assertEquals(
  460. $value['default_label'],
  461. $configurableAttributeOption['options'][$key]['default_label']
  462. );
  463. $this->assertEquals(
  464. $value['use_default_value'],
  465. $configurableAttributeOption['options'][$key]['use_default_value']
  466. );
  467. $this->assertEquals(
  468. (int)$value['value_index'],
  469. (int)$configurableAttributeOption['options'][$key]['value_index']
  470. );
  471. }
  472. }
  473. private function getConfigurableOptions()
  474. {
  475. if (!empty($this->configurableOptions)) {
  476. return $this->configurableOptions;
  477. }
  478. $productSku = 'configurable';
  479. /** @var ProductRepositoryInterface $productRepo */
  480. $productRepo = ObjectManager::getInstance()->get(ProductRepositoryInterface::class);
  481. $product = $productRepo->get($productSku);
  482. $configurableAttributeOptions = $product->getExtensionAttributes()->getConfigurableProductOptions();
  483. $configurableAttributeOptionsData = [];
  484. foreach ($configurableAttributeOptions as $option) {
  485. $configurableAttributeOptionsData[$option->getId()] = $option->getData();
  486. $configurableAttributeOptionsData[$option->getId()]['id'] = $option->getId();
  487. $configurableAttributeOptionsData[$option->getId()]['attribute_code']
  488. = $option->getProductAttribute()->getAttributeCode();
  489. unset($configurableAttributeOptionsData[$option->getId()]['values']);
  490. foreach ($option->getValues() as $value) {
  491. $configurableAttributeOptionsData[$option->getId()]['values'][$value->getId()] = $value->getData();
  492. $configurableAttributeOptionsData[$option->getId()]['values'][$value->getId()]['label']
  493. = $value->getLabel();
  494. }
  495. }
  496. return $this->configurableOptions = $configurableAttributeOptionsData;
  497. }
  498. }