From a796656b1f4c5b6b99f3b61aecb4b1d28b35fcb6 Mon Sep 17 00:00:00 2001 From: Rafal Janicki Date: Wed, 5 Jul 2023 14:04:12 +0100 Subject: [PATCH] LYNX-176: Fix unrelated caches invalidation (#123) * LYNX-151: Cache identity for attributeForms query * LYNX-151: Tests [in progress] * LYNX-151: Cache identity for attributeForms query - tests * LYNX-151: Cache identity for attributeForms query - pre CR changes * LYNX-151: Cache identity for attributeForms query - pre CR changes * LYNX-151: Cache identity for attributeForms query - pre CR changes * LYNX-151: Cache identity for attributeForms query - pre CR changes * LYNX-151: Cache identity for attributeForms query - pre CR changes * LYNX-151: Add cache invalidation test * LYNX-151: Add cache invalidation test * LYNX-151: Add tests for cahce invalidation for shared attribute (two forms) * LYNX-151: Add cache invalidation test - refactoring (cont.) * LYNX-151: Test for multiple stores * LYNX-151: Refactoring * LYNX-151: Work in progress * LYNX-151: Add cache tag for AttributesForm query * LYNX-151: Cache identity for attributeForms query - bugfixes, refactoring * LYNX-151: CR changes * LYNX-151: CR changes * LYNX-151: CR changes * LYNX-151: Refactoring, bugfixing * LYNX-151: Refactoring, bugfixing; adding more specific cache tagging for attributesForm * LYNX-151: Refactoring; bugfixing; test coverage improvements * LNX-151: Fix static tests; cleanup the code * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring; bugfixing; revert unnecessary changes * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-151: pre-CR changes * LYNX-151: Bugfixing * LYNX-176: Move invalidation of attributesList caches to InvalidateAttributeRelatedCaches plugin * LYNX-171: Some refactoring; remove cache tag creation for attributesList from AttributePlugin * LYNX-151: Fix for Static tests; refactoring * LYNX-151: Refactoring * LYNX-151: Refactoring * LYNX-176: Refactoring; added Cache Id headers to AttributesListCache tests * LYNX-176: Refactoring; remove unnecessary tag(s) invalidation from InvalidateAttributeRelatedCaches * LYNX-176: Refactoring; added tests to cover additional scenarios * LYNX-176: Refacting; fix issue with tag for entity types * LYNX-176: Refactoring * LYNX-176: Refactoring * LYNX-176: Tests fixes; refactoring * LYNX-176: CR changes; refactoring * LYNX-176: delete temp file * LYNX-176: Formatting --- .../Model/Metadata/AttributeMetadataCache.php | 2 +- app/code/Magento/Eav/Model/Attribute.php | 34 +- .../Model/Cache/AttributesFormIdentity.php | 50 ++ app/code/Magento/Eav/Model/Config.php | 2 +- .../Magento/Eav/Model/Entity/Attribute.php | 32 +- .../Model/Resolver/AttributesForm.php | 20 +- .../Resolver/Cache/AttributesListIdentity.php | 29 +- .../EavGraphQl/Plugin/Eav/AttributePlugin.php | 4 +- .../Magento/EavGraphQl/etc/schema.graphqls | 5 +- .../Attribute/AttributesFormCacheTest.php | 688 ++++++++++++++++++ .../EavGraphQl/AttributesListCacheTest.php | 251 ++++++- 11 files changed, 1053 insertions(+), 64 deletions(-) create mode 100644 app/code/Magento/Eav/Model/Cache/AttributesFormIdentity.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/Attribute/AttributesFormCacheTest.php diff --git a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php index 3654da67db530..23ce32b9e217d 100644 --- a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php +++ b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php @@ -156,7 +156,7 @@ public function clean() $this->cache->clean( [ Type::CACHE_TAG, - Attribute::CACHE_TAG, + Attribute::CACHE_TAG ] ); } diff --git a/app/code/Magento/Eav/Model/Attribute.php b/app/code/Magento/Eav/Model/Attribute.php index 40f9a4ae4e934..7699577113211 100644 --- a/app/code/Magento/Eav/Model/Attribute.php +++ b/app/code/Magento/Eav/Model/Attribute.php @@ -3,15 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** - * EAV attribute resource model (Using Forms) - * - * @method \Magento\Eav\Model\Attribute\Data\AbstractData|null getDataModel() - * Get data model linked to attribute or null. - * - * @author Magento Core Team - */ namespace Magento\Eav\Model; use Magento\Store\Model\Website; @@ -23,14 +16,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute { /** - * Name of the module - * Override it - */ - //const MODULE_NAME = 'Magento_Eav'; - - /** - * Name of the module - * Override it + * @var string */ protected $_eventObject = 'attribute'; @@ -80,7 +66,7 @@ public function afterSave() } /** - * Return forms in which the attribute + * Return forms in which the attribute is being used * * @return array */ @@ -110,6 +96,18 @@ public function getValidateRules() return []; } + /** + * @inheritdoc + */ + public function setData($key, $value = null): Attribute + { + if ($key === 'used_in_forms') { + $this->setOrigData('used_in_forms', $this->getData('used_in_forms') ?? []); + } + parent::setData($key, $value); + return $this; + } + /** * Set validate rules * @@ -188,7 +186,7 @@ public function getMultilineCount() } /** - * {@inheritdoc} + * @inheritdoc */ public function afterDelete() { diff --git a/app/code/Magento/Eav/Model/Cache/AttributesFormIdentity.php b/app/code/Magento/Eav/Model/Cache/AttributesFormIdentity.php new file mode 100644 index 0000000000000..9317426addadc --- /dev/null +++ b/app/code/Magento/Eav/Model/Cache/AttributesFormIdentity.php @@ -0,0 +1,50 @@ +getAttributeId() + ); + } + } + return $identities; + } +} diff --git a/app/code/Magento/Eav/Model/Config.php b/app/code/Magento/Eav/Model/Config.php index 37a25fb94a327..ab4cb121fd166 100644 --- a/app/code/Magento/Eav/Model/Config.php +++ b/app/code/Magento/Eav/Model/Config.php @@ -218,7 +218,7 @@ public function clear() $this->_cache->clean( [ \Magento\Eav\Model\Cache\Type::CACHE_TAG, - \Magento\Eav\Model\Entity\Attribute::CACHE_TAG, + \Magento\Eav\Model\Entity\Attribute::CACHE_TAG ] ); return $this; diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index e1094a331149e..65c2b8d0220b1 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -12,6 +12,8 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Cache\AttributesFormIdentity; /** * EAV Entity attribute model @@ -522,7 +524,35 @@ public function getSortWeight($setId) */ public function getIdentities() { - return [self::CACHE_TAG . '_' . $this->getId()]; + $identities = [self::CACHE_TAG . '_' . $this->getId()]; + + if (($this->hasDataChanges() || $this->isDeleted())) { + $identities[] = sprintf( + "%s_%s_ENTITY", + Config::ENTITIES_CACHE_ID, + strtoupper($this->getEntityType()->getEntityTypeCode()) + ); + + $formsUsedBeforeChange = $this->getOrigData('used_in_forms') ?? []; + $usedInForms = $this->getUsedInForms() ?? []; + + if ($formsUsedBeforeChange != $usedInForms) { + $formsToInvalidate = array_merge( + array_diff($formsUsedBeforeChange, $usedInForms), + array_diff($usedInForms, $formsUsedBeforeChange) + ); + + foreach ($formsToInvalidate as $form) { + $identities[] = sprintf( + "%s_%s_FORM", + AttributesFormIdentity::CACHE_TAG, + $form + ); + }; + } + } + + return $identities; } /** diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/AttributesForm.php b/app/code/Magento/EavGraphQl/Model/Resolver/AttributesForm.php index 8b86fc606ff74..257c7c00da588 100644 --- a/app/code/Magento/EavGraphQl/Model/Resolver/AttributesForm.php +++ b/app/code/Magento/EavGraphQl/Model/Resolver/AttributesForm.php @@ -51,26 +51,34 @@ public function resolve( array $value = null, array $args = null ) { + if (empty($args['formCode'])) { throw new GraphQlInputException(__('Required parameter "%1" of type string.', 'formCode')); } - $attributes = $this->getAttributesFormComposite->execute($args['formCode']); - if ($this->isAnAdminForm($args['formCode']) || $attributes === null) { + $formCode = $args['formCode']; + + $attributes = $this->getAttributesFormComposite->execute($formCode); + if ($this->isAnAdminForm($formCode) || $attributes === null) { return [ 'items' => [], 'errors' => [ [ 'type' => 'ENTITY_NOT_FOUND', - 'message' => (string) __('Form "%form" could not be found.', ['form' => $args['formCode']]) + 'message' => (string) __('Form "%form" could not be found.', ['form' => $formCode]) ] ] ]; } - return $this->getAttributesMetadata->execute( - $attributes, - (int)$context->getExtensionAttributes()->getStore()->getId() + return array_merge( + [ + 'formCode' => $formCode + ], + $this->getAttributesMetadata->execute( + $attributes, + (int)$context->getExtensionAttributes()->getStore()->getId() + ) ); } diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/Cache/AttributesListIdentity.php b/app/code/Magento/EavGraphQl/Model/Resolver/Cache/AttributesListIdentity.php index c48f451d43d5f..0771903de18d9 100644 --- a/app/code/Magento/EavGraphQl/Model/Resolver/Cache/AttributesListIdentity.php +++ b/app/code/Magento/EavGraphQl/Model/Resolver/Cache/AttributesListIdentity.php @@ -8,31 +8,40 @@ namespace Magento\EavGraphQl\Model\Resolver\Cache; use Magento\Framework\GraphQl\Query\Resolver\IdentityInterface; +use Magento\Framework\Api\AttributeInterface; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute; /** * Cache identity provider for attributes list query results. */ class AttributesListIdentity implements IdentityInterface { - public const CACHE_TAG = 'ATTRIBUTES_LIST'; - /** * @inheritDoc */ public function getIdentities(array $resolvedData): array { - if (empty($resolvedData['items'])) { + if (empty($resolvedData['items']) || !is_array($resolvedData['items'][0])) { return []; } - if (!is_array($resolvedData['items'][0])) { - return []; + $item = $resolvedData['items'][0]; + $identities = []; + + if ($item['entity_type'] !== '') { + $identities[] = Config::ENTITIES_CACHE_ID . "_" . $item['entity_type'] . "_ENTITY"; } - return [sprintf( - "%s_%s", - self::CACHE_TAG, - $resolvedData['items'][0]['entity_type'] - )]; + foreach ($resolvedData['items'] as $item) { + if ($item['attribute'] instanceof AttributeInterface) { + $identities[] = sprintf( + "%s_%s", + Attribute::CACHE_TAG, + $item['attribute']->getAttributeId() + ); + } + } + return $identities; } } diff --git a/app/code/Magento/EavGraphQl/Plugin/Eav/AttributePlugin.php b/app/code/Magento/EavGraphQl/Plugin/Eav/AttributePlugin.php index d4b30a7bd55b0..dd713f4f69acd 100644 --- a/app/code/Magento/EavGraphQl/Plugin/Eav/AttributePlugin.php +++ b/app/code/Magento/EavGraphQl/Plugin/Eav/AttributePlugin.php @@ -8,7 +8,6 @@ namespace Magento\EavGraphQl\Plugin\Eav; use Magento\Eav\Model\Entity\Attribute; -use Magento\EavGraphQl\Model\Resolver\Cache\AttributesListIdentity; use Magento\Framework\Api\AttributeInterface; /** @@ -36,8 +35,7 @@ public function afterGetIdentities(Attribute $subject, array $result): array $subject->getOrigData(AttributeInterface::ATTRIBUTE_CODE) ?? $subject->getData(AttributeInterface::ATTRIBUTE_CODE) ) - ], - [AttributesListIdentity::CACHE_TAG.'_'.strtoupper($subject->getEntityType()->getEntityTypeCode())] + ] ); } } diff --git a/app/code/Magento/EavGraphQl/etc/schema.graphqls b/app/code/Magento/EavGraphQl/etc/schema.graphqls index 673640db9ce09..291aa8feee441 100644 --- a/app/code/Magento/EavGraphQl/etc/schema.graphqls +++ b/app/code/Magento/EavGraphQl/etc/schema.graphqls @@ -9,7 +9,10 @@ type Query { @cache(cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\Cache\\CustomAttributeMetadataIdentity") @deprecated(reason: "Use `customAttributeMetadataV2` query instead.") customAttributeMetadataV2(attributes: [AttributeInput!]): AttributesMetadataOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesMetadata") @doc(description: "Retrieve EAV attributes metadata.") @cache(cacheIdentity: "Magento\\EavGraphQl\\Model\\Resolver\\Cache\\CustomAttributeMetadataV2Identity") - attributesForm(formCode: String! @doc(description: "Form code.")): AttributesFormOutput! @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesForm") @doc(description: "Retrieve EAV attributes associated to a frontend form.") + attributesForm(formCode: String! @doc(description: "Form code.")): AttributesFormOutput! + @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesForm") + @doc(description: "Retrieve EAV attributes associated to a frontend form.") + @cache(cacheIdentity: "Magento\\Eav\\Model\\Cache\\AttributesFormIdentity") attributesList(entityType: AttributeEntityTypeEnum! @doc(description: "Entity type.")): AttributesMetadataOutput @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributesList") diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/Attribute/AttributesFormCacheTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/Attribute/AttributesFormCacheTest.php new file mode 100644 index 0000000000000..cdd105e6a5557 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/Attribute/AttributesFormCacheTest.php @@ -0,0 +1,688 @@ +objectManager = Bootstrap::getObjectManager(); + $this->attributesToRemove = []; + parent::setUp(); + } + + /** + * @inheritdoc + */ + protected function tearDown(): void + { + $eavAttributeRepo = $this->objectManager->get(AttributeRepository::class); + array_walk($this->attributesToRemove, function ($attribute) use ($eavAttributeRepo) { + $eavAttributeRepo->delete($attribute); + }); + parent::tearDown(); + } + + /** + * Obtains cache ID header from response + * + * @param string $query + * @return string + */ + private function getCacheIdHeader(string $query, array $headers = []): string + { + $response = $this->graphQlQueryWithResponseHeaders( + $query, + [], + '', + $headers + ); + $this->assertArrayHasKey(CacheIdCalculator::CACHE_ID_HEADER, $response['headers']); + return $response['headers'][CacheIdCalculator::CACHE_ID_HEADER]; + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => ['customer_register_address'] + ], + 'attribute_1' + ) + ] + public function testAttributesFormCacheMissAndHit() + { + /** @var AttributeInterface $attribute1 */ + $attribute1 = DataFixtureStorageManager::getStorage()->get('attribute_1'); + $cacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + + /** First response should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Second response should be a HIT and attribute should be present in a cached response */ + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + foreach ($response['body']['attributesForm']['items'] as $item) { + if (in_array($attribute1->getAttributeCode(), $item)) { + return; + } + } + $this->fail(sprintf( + "Attribute '%s' not found in query_CUSTOMER_REGISTER_ADDRESS response", + $attribute1->getAttributeCode() + )); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture(WebsiteFixture::class, as: 'website2'), + DataFixture( + StoreGroupFixture::class, + [ + 'website_id' => '$website2.id$' + ], + 'store_group2' + ), + DataFixture( + StoreFixture::class, + [ + 'store_group_id' => '$store_group2.id$' + ], + 'store2' + ), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => ['customer_register_address'] + ], + 'attribute_1' + ) + ] + public function testAttributesFormCacheMissAndHitDifferentWebsites() + { + /** @var StoreInterface $store2 */ + $store2 = DataFixtureStorageManager::getStorage()->get('store2'); + /** @var AttributeInterface $attribute1 */ + $attribute1 = DataFixtureStorageManager::getStorage()->get('attribute_1'); + $cacheIdStore1 = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + + $response = $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore1] + ); + + $this->assertContains($attribute1->getAttributeCode(), array_map(function ($attribute) { + return $attribute['code']; + }, $response['body']['attributesForm']['items'])); + + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore1] + ); + + // obtain CacheID for Store 2 - has to be different than for Store 1: + $cacheIdStore2 = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS, ['Store' => $store2->getCode()]); + + // First query execution for a different store should result in a cache miss, while second one should be a hit + $response = $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [ + 'Store' => $store2->getCode(), + CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore2 + ] + ); + + $this->assertContains($attribute1->getAttributeCode(), array_map(function ($attribute) { + return $attribute['code']; + }, $response['body']['attributesForm']['items'])); + + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [ + 'Store' => $store2->getCode(), + CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore2 + ] + ); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => ['customer_register_address'] + ], + 'attribute_1' + ) + ] + public function testAttributesFormCacheInvalidateOnAttributeEdit() + { + /** @var AttributeInterface $attribute1 */ + $attribute1 = DataFixtureStorageManager::getStorage()->get('attribute_1'); + + $cacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + + /** First response should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Second response should be a HIT */ + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Modify attribute to invalidate cache */ + $eavAttributeRepo = $this->objectManager->get(AttributeRepository::class); + $attribute1->setDefaultValue("default_value"); + $eavAttributeRepo->save($attribute1); + + /** Response after the change should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Second response should be a HIT */ + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + foreach ($response['body']['attributesForm']['items'] as $item) { + if (in_array($attribute1->getAttributeCode(), $item)) { + return; + } + } + $this->fail(sprintf( + "Attribute '%s' not found in query_CUSTOMER_REGISTER_ADDRESS response", + $attribute1->getAttributeCode() + )); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH) + ] + public function testAttributesFormCacheInvalidateOnAttributeCreate() + { + $cacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + + /** First response should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Second response should be a HIT */ + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Create new attribute and assign it to customer_register_address */ + $attributeCreate = $this->objectManager->get(CustomerAttribute::class); + $attribute = $attributeCreate->apply([ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => ['customer_register_address'] + ]); + $this->attributesToRemove[] = $attribute; + + /** Response after the creation of new attribute should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Second response should be a HIT */ + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + // verify created attribute is present in result + foreach ($response['body']['attributesForm']['items'] as $item) { + if (in_array($attribute->getAttributeCode(), $item)) { + return; + } + } + $this->fail(sprintf( + "Attribute '%s' not found in QUERY_CUSTOMER_REGISTER_ADDRESS response", + $attribute->getAttributeCode() + )); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => ['customer_register_address'] + ], + 'attribute_1' + ) + ] + public function testAttributesFormCacheInvalidateOnAttributeDelete() + { + /** @var AttributeInterface $attribute1 */ + $attribute1 = DataFixtureStorageManager::getStorage()->get('attribute_1'); + $cacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + + /** First response should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Second response should be a HIT */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + /** Delete attribute to invalidate cache */ + $eavAttributeRepo = $this->objectManager->get(AttributeRepository::class); + $deletedAttributeCode = $attribute1->getAttributeCode(); + $eavAttributeRepo->delete($attribute1); + + /** First response should be a MISS and attribute should NOT be present in a cached response */ + $response = $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + foreach ($response['body']['attributesForm']['items'] as $item) { + if (in_array($deletedAttributeCode, $item)) { + $this->fail(sprintf( + "Deleted attribute '%s' found in cached query_CUSTOMER_REGISTER_ADDRESS response", + $deletedAttributeCode + )); + } + } + + /** Second response should be a HIT */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + ], + 'attribute' + ) + ] + public function testAttributesFormCacheInvalidateOnAttributeAssignToForm() + { + /** @var AttributeInterface $attribute */ + $attribute = DataFixtureStorageManager::getStorage()->get('attribute'); + + $eavAttributeRepo = $this->objectManager->get(AttributeRepository::class); + $queryEditCacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + $queryRegisterCacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_REGISTER_ADDRESS); + + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + /** Second response should be a HIT*/ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Assign $attribute to the 'customer_account_edit' form */ + $attribute->setData('used_in_forms', ['customer_account_edit']); + $eavAttributeRepo->save($attribute); + + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Non-affected "customer_register_address" form -> MISS, then cached and HIT */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + + /** Add $attribute to the 'customer_register_address' form */ + $attribute->setData('used_in_forms', ['customer_account_edit', 'customer_register_address']); + $eavAttributeRepo->save($attribute); + + /** 'customer_register_address' form should be invalidated first now */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + + /** Remove $attribute from the 'customer_account_edit' form */ + $attribute->setData('used_in_forms', ['customer_register_address']); + $eavAttributeRepo->save($attribute); + + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + + /** Remove $attribute from remaining form(s) */ + $attribute->setData('used_in_forms', []); + $eavAttributeRepo->save($attribute); + + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => [ + 'customer_register_address', + 'customer_account_edit' + ] + ], + 'shared_attribute' + ) + ] + public function testAttributesFormCacheInvalidateOnDeletedSharedAttribute() + { + /** @var AttributeInterface $sharedAttribute */ + $sharedAttribute = DataFixtureStorageManager::getStorage()->get('shared_attribute'); + $queryEditCacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + $queryRegisterCacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_REGISTER_ADDRESS); + + /** First response should be a MISS from both queries */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + // /** Second response should be a HIT from both queries */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Delete attribute to invalidate both cached queries */ + $eavAttributeRepo = $this->objectManager->get(AttributeRepository::class); + $deletedAttributeCode = $sharedAttribute->getAttributeCode(); + $eavAttributeRepo->delete($sharedAttribute); + + /** First response after deleting should be a MISS from both queries */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Second response should be a HIT from both queries as they are both cached back */ + $responseRegisterAddress = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $responseEditAddress = $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + // verify created attribute is NOT present in results + foreach ($responseRegisterAddress['body']['attributesForm']['items'] as $item) { + if (in_array($deletedAttributeCode, $item)) { + $this->fail( + sprintf( + "Attribute '%s' found in QUERY_CUSTOMER_REGISTER_ADDRESS response", + $deletedAttributeCode + ) + ); + } + } + + foreach ($responseEditAddress['body']['attributesForm']['items'] as $item) { + if (in_array($deletedAttributeCode, $item)) { + $this->fail( + sprintf( + "Attribute '%s' found in QUERY_CUSTOMER_EDIT_ADDRESS response", + $deletedAttributeCode + ) + ); + } + } + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => [ + 'customer_account_edit' + ] + ], + 'non_shared_attribute_2' + ), + DataFixture( + CustomerAttribute::class, + [ + 'entity_type_id' => AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS, + 'used_in_forms' => [ + 'customer_register_address' + ] + ], + 'non_shared_attribute_1' + ) + ] + public function testAttributesFormCacheInvalidateOnDeletedNonSharedAttribute() + { + /** @var AttributeInterface $nonSharedAttribute1 */ + $nonSharedAttribute1 = DataFixtureStorageManager::getStorage()->get('non_shared_attribute_1'); + /** @var AttributeInterface $nonSharedAttribute2 */ + $nonSharedAttribute2 = DataFixtureStorageManager::getStorage()->get('non_shared_attribute_2'); + $queryEditCacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_EDIT_ADDRESS); + $queryRegisterCacheId = $this->getCacheIdHeader(self::QUERY_CUSTOMER_REGISTER_ADDRESS); + + $eavAttributeRepo = $this->objectManager->get(AttributeRepository::class); + + /** First response should be a MISS from both queries */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Second response should be a HIT from all queries */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Delete nonSharedAttribute1 to invalidate cache of 'customer_register_address' ONLY*/ + $eavAttributeRepo->delete($nonSharedAttribute1); + + /** First response from QUERY_CUSTOMER_REGISTER_ADDRESS after deleting $nonSharedAttribute1 should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + /** other cached queries should not be affected */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Second response should be a HIT from all queries */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + + /** Delete nonSharedAttribute2 to invalidate cache of 'customer_account_edit' ONLY*/ + $eavAttributeRepo->delete($nonSharedAttribute2); + + /** First response from QUERY_CUSTOMER_EDIT_ADDRESS after deleting $nonSharedAttribute2 should be a MISS */ + $this->assertCacheMissAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + /** other cached queries should not be affected */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + + /** Second response should be a HIT from all queries */ + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_REGISTER_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryRegisterCacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_CUSTOMER_EDIT_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $queryEditCacheId] + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/EavGraphQl/AttributesListCacheTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/EavGraphQl/AttributesListCacheTest.php index b10a560a7a814..bd2a84320c33c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/EavGraphQl/AttributesListCacheTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/EavGraphQl/AttributesListCacheTest.php @@ -24,6 +24,7 @@ use Magento\TestFramework\Fixture\DataFixture; use Magento\TestFramework\Fixture\DataFixtureStorageManager; use Magento\TestFramework\Helper\Bootstrap; +use Magento\GraphQlCache\Model\CacheId\CacheIdCalculator; /** * Test caching for attributes list GraphQL query. @@ -81,6 +82,24 @@ public function setUp(): void parent::setUp(); } + /** + * Obtains cache ID header from response + * + * @param string $query + * @return string + */ + private function getCacheIdHeader(string $query, array $headers = []): string + { + $response = $this->graphQlQueryWithResponseHeaders( + $query, + [], + '', + $headers + ); + $this->assertArrayHasKey(CacheIdCalculator::CACHE_ID_HEADER, $response['headers']); + return $response['headers'][CacheIdCalculator::CACHE_ID_HEADER]; + } + #[ ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), DataFixture( @@ -97,20 +116,33 @@ public function testAttributesListCacheMissAndHit() { /** @var AttributeInterface $attribute0 */ $attribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0'); + $cacheId = $this->getCacheIdHeader(self::QUERY); - $this->assertCacheMissAndReturnResponse(self::QUERY, []); - $response = $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); $attribute = end($response['body']['attributesList']['items']); $this->assertEquals($attribute0->getAttributeCode(), $attribute['code']); // Modify an attribute present in the response of the previous query to check cache invalidation - $attribute0->setAttributeCode($attribute0->getAttributeCode() . '_modified'); + $attribute0->setDefaultValue('default_value'); $this->eavAttributeRepo->save($attribute0); // First query execution should result in a cache miss, while second one should be a cache hit - $this->assertCacheMissAndReturnResponse(self::QUERY, []); - $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); } #[ @@ -132,22 +164,148 @@ public function testAttributesListCacheMissAndHitDifferentStores() { /** @var StoreInterface $store2 */ $store2 = DataFixtureStorageManager::getStorage()->get('store2'); + $cacheIdStore1 = $this->getCacheIdHeader(self::QUERY); + $cacheIdStore2 = $this->getCacheIdHeader(self::QUERY, ['Store' => $store2->getCode()]); /** @var AttributeInterface $attribute0 */ $attribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0'); - $response = $this->assertCacheMissAndReturnResponse(self::QUERY, []); + $response = $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore1] + ); $attribute = end($response['body']['attributesList']['items']); $this->assertEquals($attribute0->getAttributeCode(), $attribute['code']); - $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore1] + ); // First query execution for a different store should result in a cache miss, while second one should be a hit - $response = $this->assertCacheMissAndReturnResponse(self::QUERY, ['Store' => $store2->getCode()]); + $response = $this->assertCacheMissAndReturnResponse( + self::QUERY, + [ + 'Store' => $store2->getCode(), + CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore2 + ] + ); $attribute = end($response['body']['attributesList']['items']); $this->assertEquals($attribute0->getAttributeCode(), $attribute['code']); - $this->assertCacheHitAndReturnResponse(self::QUERY, ['Store' => $store2->getCode()]); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [ + 'Store' => $store2->getCode(), + CacheIdCalculator::CACHE_ID_HEADER => $cacheIdStore2 + ] + ); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + Attribute::class, + [ + 'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, + 'frontend_input' => 'boolean', + 'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean' + ], + 'customer_attribute_0' + ) + ] + public function testAttributeListCacheInvalidateOnAttributeDelete() + { + /** @var AttributeInterface $customerAttribute0 */ + $customerAttribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0'); + $cacheId = $this->getCacheIdHeader(self::QUERY); + + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + $deletedAttributeCode = $customerAttribute0->getAttributeCode(); + $this->eavAttributeRepo->delete($customerAttribute0); + + $response = $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + foreach ($response['body']['attributesList']['items'] as $item) { + if (in_array($deletedAttributeCode, $item)) { + $this->fail(sprintf( + "Deleted attribute '%s' found in query response", + $deletedAttributeCode + )); + } + } + + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + } + + #[ + ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH), + DataFixture( + Attribute::class, + [ + 'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, + 'frontend_input' => 'boolean', + 'default_value' => 'initial value', + 'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean' + ], + 'customer_attribute_0' + ) + ] + public function testAttributeListCacheInvalidateOnAttributeEdit() + { + /** @var AttributeInterface $customerAttribute0 */ + $customerAttribute0 = DataFixtureStorageManager::getStorage()->get('customer_attribute_0'); + $cacheId = $this->getCacheIdHeader(self::QUERY); + $cacheAddressId = $this->getCacheIdHeader(self::QUERY_ADDRESS); + + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + $this->assertCacheMissAndReturnResponse( + self::QUERY_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheAddressId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheAddressId] + ); + + $customerAttribute0->setDefaultValue('after change default value'); + $this->eavAttributeRepo->save($customerAttribute0); + + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + $this->assertCacheHitAndReturnResponse( + self::QUERY_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheAddressId] + ); } #[ @@ -178,20 +336,47 @@ public function testAttributeListChangeOnlyAffectsResponsesWithEntity() /** @var AttributeInterface $customerAttribute0 */ $customerAddressAttribute0 = DataFixtureStorageManager::getStorage()->get('customer_address_attribute_0'); + $cacheId = $this->getCacheIdHeader(self::QUERY); + $cacheAddressId = $this->getCacheIdHeader(self::QUERY_ADDRESS); - $this->assertCacheMissAndReturnResponse(self::QUERY, []); - $response = $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); $attribute = end($response['body']['attributesList']['items']); $this->assertEquals($customerAttribute0->getAttributeCode(), $attribute['code']); - $this->assertCacheMissAndReturnResponse(self::QUERY_ADDRESS, []); - $this->assertCacheHitAndReturnResponse(self::QUERY_ADDRESS, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheAddressId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheAddressId] + ); $customerAttribute0->setAttributeCode($customerAttribute0->getAttributeCode() . '_modified'); $this->eavAttributeRepo->save($customerAttribute0); - $response = $this->assertCacheHitAndReturnResponse(self::QUERY_ADDRESS, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + + $response = $this->assertCacheHitAndReturnResponse( + self::QUERY_ADDRESS, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheAddressId] + ); + $attribute = end($response['body']['attributesList']['items']); $this->assertEquals($customerAddressAttribute0->getAttributeCode(), $attribute['code']); } @@ -201,25 +386,45 @@ public function testAttributeListChangeOnlyAffectsResponsesWithEntity() ] public function testAttributesListCacheMissAndHitNewAttribute() { - $this->assertCacheMissAndReturnResponse(self::QUERY, []); - $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $cacheId = $this->getCacheIdHeader(self::QUERY); + + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); $newAttributeCreate = Bootstrap::getObjectManager()->get(CustomerAttribute::class); /** @var AttributeInterface $newAttribute */ $newAttribute = $newAttributeCreate->apply([ - 'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, - 'frontend_input' => 'boolean', - 'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean' + 'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER, + 'frontend_input' => 'boolean', + 'source_model' => 'Magento\Eav\Model\Entity\Attribute\Source\Boolean' ]); // First query execution should result in a cache miss, while second one should be a cache hit - $this->assertCacheMissAndReturnResponse(self::QUERY, []); - $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); $this->eavAttributeRepo->delete($newAttribute); // Check that the same mentioned above applies if we delete an attribute present in the response - $this->assertCacheMissAndReturnResponse(self::QUERY, []); - $this->assertCacheHitAndReturnResponse(self::QUERY, []); + $this->assertCacheMissAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); + $this->assertCacheHitAndReturnResponse( + self::QUERY, + [CacheIdCalculator::CACHE_ID_HEADER => $cacheId] + ); } }