Files
stemcell/dev/tests/api-functional/testsuite/Magento/GraphQl/Eav/CustomAttributesMetadataV2CacheTest.php
T
2026-09-15 10:26:34 +08:00

367 lines
12 KiB
PHP

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\GraphQl\Eav;
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Customer\Api\Data\AttributeMetadataInterface;
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Eav\Model\AttributeRepository;
use Magento\Eav\Test\Fixture\Attribute;
use Magento\GraphQl\PageCache\GraphQLPageCacheAbstract;
use Magento\TestFramework\Fixture\Config as ConfigFixture;
use Magento\TestFramework\Fixture\DataFixture;
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException;
use Magento\PageCache\Model\Config;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Test\Fixture\Group as StoreGroupFixture;
use Magento\Store\Test\Fixture\Store as StoreFixture;
use Magento\Store\Test\Fixture\Website as WebsiteFixture;
/**
* Test caching for custom attribute metadata GraphQL query.
*/
class CustomAttributesMetadataV2CacheTest extends GraphQLPageCacheAbstract
{
/**
* @var AttributeRepository
*/
private $attributeRepository;
/**
* @inheritdoc
*/
public function setUp(): void
{
$this->attributeRepository = Bootstrap::getObjectManager()->get(AttributeRepository::class);
parent::setUp();
}
#[
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'text'
],
'attribute'
),
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH)
]
public function testCacheHitMiss(): void
{
/** @var AttributeInterface $attribute */
$attribute = DataFixtureStorageManager::getStorage()->get('attribute');
$query = $this->getAttributeQuery($attribute->getAttributeCode(), "customer");
$response = $this->assertCacheMissAndReturnResponse($query, []);
$assertionMap = [
['response_field' => 'code', 'expected_value' => $attribute->getAttributeCode()],
['response_field' => 'entity_type', 'expected_value' => 'CUSTOMER'],
['response_field' => 'frontend_input', 'expected_value' => 'TEXT']
];
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
$response = $this->assertCacheHitAndReturnResponse($query, []);
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
}
#[
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(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'text'
],
'attribute'
),
]
public function testCacheMissAndHitDifferentStores(): void
{
/** @var StoreInterface $store2 */
$store2 = DataFixtureStorageManager::getStorage()->get('store2');
/** @var AttributeMetadataInterface $attribute */
$attribute = DataFixtureStorageManager::getStorage()->get('attribute');
$query = $this->getAttributeQuery($attribute->getAttributeCode(), "customer");
$response = $this->assertCacheMissAndReturnResponse($query, []);
$assertionMap = [
['response_field' => 'code', 'expected_value' => $attribute->getAttributeCode()],
['response_field' => 'entity_type', 'expected_value' => 'CUSTOMER'],
['response_field' => 'frontend_input', 'expected_value' => 'TEXT']
];
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
$response = $this->assertCacheHitAndReturnResponse($query, []);
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
// First query execution for a different store should result in a cache miss, while second one should be a hit
$response = $this->assertCacheMissAndReturnResponse($query, ['Store' => $store2->getCode()]);
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
$response = $this->assertCacheHitAndReturnResponse($query, ['Store' => $store2->getCode()]);
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
}
#[
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'text'
],
'attribute_1'
),
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'boolean'
],
'attribute_2'
),
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH)
]
public function testCacheInvalidation(): void
{
/** @var AttributeInterface $attribute */
$attribute = DataFixtureStorageManager::getStorage()->get('attribute_1');
/** @var AttributeInterface $attribute2 */
$attribute2 = DataFixtureStorageManager::getStorage()->get('attribute_2');
$query = $this->getAttributeQuery($attribute->getAttributeCode(), "customer");
// check cache missed on first query
$this->assertCacheMissAndReturnResponse($query, []);
// assert cache hit on second query
$this->assertCacheHitAndReturnResponse($query, []);
$attribute->setIsRequired(true);
$this->attributeRepository->save($attribute);
// assert cache miss after changes
$this->assertCacheMissAndReturnResponse($query, []);
$attribute2->setIsRequired(true);
$this->attributeRepository->save($attribute2);
// assert cache hits on second query after changes, and cache is not invalidated when another entity changed
$this->assertCacheHitAndReturnResponse($query, []);
}
#[
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'text'
],
'attribute'
),
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH)
]
public function testCacheInvalidationOnAttributeDelete()
{
/** @var AttributeInterface $attribute */
$attribute = DataFixtureStorageManager::getStorage()->get('attribute');
$attributeCode = $attribute->getAttributeCode();
$query = $this->getAttributeQuery($attributeCode, "customer");
// check cache missed on first query
$response = $this->assertCacheMissAndReturnResponse($query, []);
$assertionMap = [
['response_field' => 'code', 'expected_value' => $attributeCode],
['response_field' => 'entity_type', 'expected_value' => 'CUSTOMER'],
['response_field' => 'frontend_input', 'expected_value' => 'TEXT']
];
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
// assert cache hit on second query
$response = $this->assertCacheHitAndReturnResponse($query, []);
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['items'][0], $assertionMap);
$this->attributeRepository->delete($attribute);
$assertionMap = [
['response_field' => 'type', 'expected_value' => 'ATTRIBUTE_NOT_FOUND'],
['response_field' => 'message', 'expected_value' => sprintf(
'Attribute code "%s" could not be found.',
$attributeCode
)]
];
$response = $this->assertCacheMissAndReturnResponse($query, []);
$this->assertResponseFields($response['body']['customAttributeMetadataV2']['errors'][0], $assertionMap);
}
#[
DataFixture(
Attribute::class,
[
'entity_type_id' => CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER,
'frontend_input' => 'text'
],
'attribute'
),
ConfigFixture(Config::XML_PAGECACHE_TYPE, Config::VARNISH)
]
public function testCacheMissingAttributeParam(): void
{
/** @var AttributeInterface $attribute */
$attribute = DataFixtureStorageManager::getStorage()->get('attribute');
$query = $this->getAttributeQueryNoCode("customer");
// check cache missed on each query
$this->assertQueryResultIsCacheMissWithError(
$query,
"Missing attribute_code for the input entity_type: customer."
);
$this->assertQueryResultIsCacheMissWithError(
$query,
"Missing attribute_code for the input entity_type: customer."
);
$query = $this->getAttributeQueryNoEntityType($attribute->getAttributeCode());
// check cache missed on each query
$this->assertQueryResultIsCacheMissWithError(
$query,
sprintf("Missing entity_type for the input attribute_code: %s.", $attribute->getAttributeCode())
);
$this->assertQueryResultIsCacheMissWithError(
$query,
sprintf("Missing entity_type for the input attribute_code: %s.", $attribute->getAttributeCode())
);
}
/**
* Assert that query produces an error and the cache is missed.
*
* @param string $query
* @param string $expectedError
* @return void
* @throws \Exception
*/
private function assertQueryResultIsCacheMissWithError(string $query, string $expectedError)
{
$caughtException = null;
try {
// query for response, expect response to be present in exception
$this->graphQlQueryWithResponseHeaders($query, []);
} catch (ResponseContainsErrorsException $exception) {
$caughtException = $exception;
}
$this->assertInstanceOf(
ResponseContainsErrorsException::class,
$caughtException
);
// cannot use expectException because need to assert the headers
$this->assertStringContainsString(
$expectedError,
$caughtException->getMessage()
);
// assert that it's a miss
$this->assertEquals(
'MISS',
$caughtException->getResponseHeaders()['X-Magento-Cache-Debug']
);
}
/**
* Prepare and return GraphQL query for given entity type and code.
*
* @param string $code
* @param string $entityType
* @return string
*/
private function getAttributeQuery(string $code, string $entityType) : string
{
return <<<QUERY
{
customAttributeMetadataV2(attributes: [{attribute_code:"{$code}", entity_type:"{$entityType}"}]) {
items {
code
label
entity_type
frontend_input
is_required
default_value
is_unique
options {
label
value
}
}
errors {
type
message
}
}
}
QUERY;
}
/**
* Prepare and return GraphQL query for given entity type with no code.
*
* @param string $entityType
*
* @return string
*/
private function getAttributeQueryNoCode(string $entityType) : string
{
return <<<QUERY
{
customAttributeMetadata(attributes:
[
{
entity_type:"{$entityType}"
}
]
)
{
items
{
attribute_code
entity_type
}
}
}
QUERY;
}
/**
* Prepare and return GraphQL query for given code with no entity type.
*
* @param string $code
*
* @return string
*/
private function getAttributeQueryNoEntityType(string $code) : string
{
return <<<QUERY
{
customAttributeMetadata(attributes:
[
{
attribute_code:"{$code}"
}
]
)
{
items
{
attribute_code
entity_type
}
}
}
QUERY;
}
}