diff --git a/app/code/Magento/SalesRule/Model/Plugin/FrontController.php b/app/code/Magento/SalesRule/Model/Plugin/FrontController.php
new file mode 100644
index 0000000000000..bf01769630284
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Plugin/FrontController.php
@@ -0,0 +1,37 @@
+getMethod());
+ $isReadOnly = ($method === 'GET');
+
+ $this->requestTypeRegistry->setIsGetRequestOrQuery($isReadOnly);
+ }
+}
diff --git a/app/code/Magento/SalesRule/Model/Plugin/GraphQlController.php b/app/code/Magento/SalesRule/Model/Plugin/GraphQlController.php
new file mode 100644
index 0000000000000..7c98b73d7ee88
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Plugin/GraphQlController.php
@@ -0,0 +1,38 @@
+requestTypeRegistry->reset();
+ return $result;
+ }
+}
diff --git a/app/code/Magento/SalesRule/Model/Plugin/QueryParser.php b/app/code/Magento/SalesRule/Model/Plugin/QueryParser.php
new file mode 100644
index 0000000000000..48d3e519055a0
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Plugin/QueryParser.php
@@ -0,0 +1,47 @@
+definitions as $definition) {
+ if ($definition instanceof \GraphQL\Language\AST\OperationDefinitionNode) {
+ $operation = $definition;
+ break;
+ }
+ }
+
+ if ($operation) {
+ $isOperationTypeQuery = ($operation->operation === 'query');
+ $this->requestTypeRegistry->setIsGetRequestOrQuery($isOperationTypeQuery);
+ }
+
+ return $documentNode;
+ }
+}
diff --git a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php
index 764658b3ac00a..9b96cee5b43b2 100644
--- a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php
+++ b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php
@@ -1,12 +1,16 @@
ruleResource = $ruleResource;
}
@@ -40,8 +61,24 @@ public function __construct(RuleResource $ruleResource)
*/
public function afterGetProductAttributes(Config $subject, array $attributeKeys): array
{
- if ($this->activeAttributeCodes === null) {
- $this->activeAttributeCodes = array_column($this->ruleResource->getActiveAttributes(), 'attribute_code');
+ if ($this->requestTypeRegistry->isGetRequestOrQuery()) {
+ return $attributeKeys;
+ }
+
+ $cachedData = $this->cache->load(self::CACHE_KEY);
+
+ if ($cachedData !== false) {
+ $this->activeAttributeCodes = $this->serializer->unserialize($cachedData);
+ } else {
+ $this->activeAttributeCodes = array_column(
+ $this->ruleResource->getActiveAttributes(),
+ 'attribute_code'
+ );
+ $this->cache->save(
+ $this->serializer->serialize($this->activeAttributeCodes),
+ self::CACHE_KEY,
+ [self::CACHE_TAG]
+ );
}
return array_merge($attributeKeys, $this->activeAttributeCodes);
diff --git a/app/code/Magento/SalesRule/Model/Plugin/QuoteItemCollection.php b/app/code/Magento/SalesRule/Model/Plugin/QuoteItemCollection.php
new file mode 100644
index 0000000000000..be5720d9d57de
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Plugin/QuoteItemCollection.php
@@ -0,0 +1,36 @@
+getTriggerRecollect() == 1 && $this->requestTypeRegistry->isGetRequestOrQuery()) {
+ $this->requestTypeRegistry->setIsGetRequestOrQuery(false);
+ }
+ }
+}
diff --git a/app/code/Magento/SalesRule/Model/Plugin/RequestTypeRegistry.php b/app/code/Magento/SalesRule/Model/Plugin/RequestTypeRegistry.php
new file mode 100644
index 0000000000000..d536deed37d86
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Plugin/RequestTypeRegistry.php
@@ -0,0 +1,53 @@
+isGetRequestOrQuery = $state;
+ }
+
+ /**
+ * Check if request is a get or query
+ *
+ * @return bool
+ */
+ public function isGetRequestOrQuery(): bool
+ {
+ return $this->isGetRequestOrQuery;
+ }
+
+ /**
+ * Resets the state of the object by setting the isGetRequestOrQuery property to false.
+ *
+ * @return void
+ */
+ public function reset(): void
+ {
+ $this->isGetRequestOrQuery = false;
+ }
+}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/FrontControllerTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/FrontControllerTest.php
new file mode 100644
index 0000000000000..d15dd20b4c707
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/FrontControllerTest.php
@@ -0,0 +1,81 @@
+requestTypeRegistry = $this->createMock(RequestTypeRegistry::class);
+ $this->plugin = new FrontController($this->requestTypeRegistry);
+ $this->request = $this->getMockBuilder(RequestInterface::class)
+ ->addMethods(['getMethod'])
+ ->getMockForAbstractClass();
+ $this->subject = $this->createMock(FrontControllerInterface::class);
+ }
+
+ public function testBeforeDispatchSetsTrueForGetRequests(): void
+ {
+ $this->request->method('getMethod')->willReturn('GET');
+
+ $this->requestTypeRegistry
+ ->expects($this->once())
+ ->method('setIsGetRequestOrQuery')
+ ->with(true);
+
+ $this->plugin->beforeDispatch($this->subject, $this->request);
+ }
+
+ public function testBeforeDispatchSetsFalseForPostRequests(): void
+ {
+ $this->request->method('getMethod')->willReturn('POST');
+
+ $this->requestTypeRegistry
+ ->expects($this->once())
+ ->method('setIsGetRequestOrQuery')
+ ->with(false);
+
+ $this->plugin->beforeDispatch($this->subject, $this->request);
+ }
+
+ public function testBeforeDispatchIsCaseInsensitive(): void
+ {
+
+ $this->request->method('getMethod')->willReturn('get'); // lowercase
+
+ $this->requestTypeRegistry
+ ->expects($this->once())
+ ->method('setIsGetRequestOrQuery')
+ ->with(true);
+
+ $this->plugin->beforeDispatch($this->subject, $this->request);
+ }
+}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/GraphQlControllerTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/GraphQlControllerTest.php
new file mode 100644
index 0000000000000..9bf94fbe15805
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/GraphQlControllerTest.php
@@ -0,0 +1,68 @@
+requestTypeRegistry = $this->createMock(RequestTypeRegistry::class);
+ $this->graphQlController = new GraphQlController($this->requestTypeRegistry);
+ $this->frontController = $this->createMock(FrontControllerInterface::class);
+ $this->response = $this->createMock(ResponseInterface::class);
+ }
+
+ /**
+ * Test afterDispatch calls reset on RequestTypeRegistry
+ */
+ public function testAfterDispatchResetsRequestTypeRegistry(): void
+ {
+ // Expect reset to be called on the RequestTypeRegistry
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('reset');
+
+ // Call the afterDispatch method
+ $result = $this->graphQlController->afterDispatch($this->frontController, $this->response);
+
+ // Verify the response is returned unchanged
+ $this->assertSame($this->response, $result);
+ }
+}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QueryParserTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QueryParserTest.php
new file mode 100644
index 0000000000000..277a0cccf4677
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QueryParserTest.php
@@ -0,0 +1,147 @@
+requestTypeRegistry = $this->createMock(RequestTypeRegistry::class);
+ $this->queryParser = new QueryParser($this->requestTypeRegistry);
+ $this->subject = $this->createMock(FrameworkQueryParser::class);
+ $this->documentNode = $this->createMock(DocumentNode::class);
+ }
+
+ /**
+ * Test afterParse method with query operation
+ */
+ public function testAfterParseWithQueryOperation(): void
+ {
+ // Create a mock operation definition node with 'query' operation
+ $operationNode = $this->createMock(OperationDefinitionNode::class);
+ $operationNode->operation = 'query';
+
+ // Create a mock definitions list with the operation node
+ $definitions = new NodeList([$operationNode]);
+ $this->documentNode->definitions = $definitions;
+
+ // Expect RequestTypeRegistry to be called with true
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('setIsGetRequestOrQuery')
+ ->with(true);
+
+ // Call the method
+ $result = $this->queryParser->afterParse($this->subject, $this->documentNode);
+
+ // Assert the document node is returned unchanged
+ $this->assertSame($this->documentNode, $result);
+ }
+
+ /**
+ * Test afterParse method with mutation operation
+ */
+ public function testAfterParseWithMutationOperation(): void
+ {
+ // Create a mock operation definition node with 'mutation' operation
+ $operationNode = $this->createMock(OperationDefinitionNode::class);
+ $operationNode->operation = 'mutation';
+
+ // Create a mock definitions list with the operation node
+ $definitions = new NodeList([$operationNode]);
+ $this->documentNode->definitions = $definitions;
+
+ // Expect RequestTypeRegistry to be called with false
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('setIsGetRequestOrQuery')
+ ->with(false);
+
+ // Call the method
+ $result = $this->queryParser->afterParse($this->subject, $this->documentNode);
+
+ // Assert the document node is returned unchanged
+ $this->assertSame($this->documentNode, $result);
+ }
+
+ /**
+ * Test afterParse method with no operation definitions
+ */
+ public function testAfterParseWithNoOperationDefinitions(): void
+ {
+ // Create an empty definitions list
+ $definitions = new NodeList([]);
+ $this->documentNode->definitions = $definitions;
+
+ // Expect RequestTypeRegistry not to be called
+ $this->requestTypeRegistry->expects($this->never())
+ ->method('setIsGetRequestOrQuery');
+
+ // Call the method
+ $result = $this->queryParser->afterParse($this->subject, $this->documentNode);
+
+ // Assert the document node is returned unchanged
+ $this->assertSame($this->documentNode, $result);
+ }
+
+ /**
+ * Test afterParse method with non-operation definition nodes
+ */
+ public function testAfterParseWithNonOperationDefinitions(): void
+ {
+ // Create a mock node that is not an OperationDefinitionNode
+ $nonOperationNode = $this->createMock(\GraphQL\Language\AST\Node::class);
+
+ // Create a mock definitions list with the non-operation node
+ $definitions = new NodeList([$nonOperationNode]);
+ $this->documentNode->definitions = $definitions;
+
+ // Expect RequestTypeRegistry not to be called
+ $this->requestTypeRegistry->expects($this->never())
+ ->method('setIsGetRequestOrQuery');
+
+ // Call the method
+ $result = $this->queryParser->afterParse($this->subject, $this->documentNode);
+
+ // Assert the document node is returned unchanged
+ $this->assertSame($this->documentNode, $result);
+ }
+}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php
index a06584cec2273..c855d678d622d 100644
--- a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteConfigProductAttributesTest.php
@@ -1,15 +1,17 @@
ruleResource = $this->createMock(Rule::class);
+ $this->requestTypeRegistry = $this->createMock(RequestTypeRegistry::class);
+ $this->cache = $this->createMock(CacheInterface::class);
+ $this->serializer = $this->createMock(SerializerInterface::class);
+ $this->subject = $this->createMock(Config::class);
- $this->plugin = $objectManager->getObject(
- QuoteConfigProductAttributes::class,
- [
- 'ruleResource' => $this->ruleResource
- ]
+ $this->plugin = new QuoteConfigProductAttributes(
+ $this->ruleResource,
+ $this->requestTypeRegistry,
+ $this->cache,
+ $this->serializer
);
}
- public function testAfterGetProductAttributes()
+ public function testAfterGetProductAttributesWithCache()
+ {
+ $attributeCode = 'code of the attribute';
+ $expected = [0 => $attributeCode];
+ $serializedData = '["' . $attributeCode . '"]';
+
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('isGetRequestOrQuery')
+ ->willReturn(false);
+
+ $this->cache->expects($this->once())
+ ->method('load')
+ ->with('salesrule_active_product_attributes')
+ ->willReturn($serializedData);
+
+ $this->serializer->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedData)
+ ->willReturn([$attributeCode]);
+
+ $this->ruleResource->expects($this->never())
+ ->method('getActiveAttributes');
+
+ $this->assertEquals($expected, $this->plugin->afterGetProductAttributes($this->subject, []));
+ }
+
+ public function testAfterGetProductAttributesWithoutCache()
{
- $subject = $this->createMock(Config::class);
$attributeCode = 'code of the attribute';
$expected = [0 => $attributeCode];
+ $serializedData = '["' . $attributeCode . '"]';
+
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('isGetRequestOrQuery')
+ ->willReturn(false);
+
+ $this->cache->expects($this->once())
+ ->method('load')
+ ->with('salesrule_active_product_attributes')
+ ->willReturn(false);
$this->ruleResource->expects($this->once())
->method('getActiveAttributes')
@@ -53,6 +113,24 @@ public function testAfterGetProductAttributes()
]
);
- $this->assertEquals($expected, $this->plugin->afterGetProductAttributes($subject, []));
+ $this->serializer->expects($this->once())
+ ->method('serialize')
+ ->with([$attributeCode])
+ ->willReturn($serializedData);
+
+ $this->cache->expects($this->once())
+ ->method('save')
+ ->with($serializedData, 'salesrule_active_product_attributes', ['salesrule']);
+
+ $this->assertEquals($expected, $this->plugin->afterGetProductAttributes($this->subject, []));
+ }
+
+ public function testAfterGetProductAttributesRequestTypePostOrMutation()
+ {
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('isGetRequestOrQuery')
+ ->willReturn(true);
+
+ $this->assertEquals([], $this->plugin->afterGetProductAttributes($this->subject, []));
}
}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteItemCollectionTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteItemCollectionTest.php
new file mode 100644
index 0000000000000..8b9ff3a39ddf7
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/QuoteItemCollectionTest.php
@@ -0,0 +1,122 @@
+requestTypeRegistry = $this->createMock(RequestTypeRegistry::class);
+ $this->plugin = new QuoteItemCollection($this->requestTypeRegistry);
+ $this->quoteItemCollection = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->quote = $this->getMockBuilder(Quote::class)
+ ->disableOriginalConstructor()
+ ->addMethods(['getTriggerRecollect'])
+ ->getMock();
+ }
+
+ /**
+ * Test beforeSetQuote when triggerRecollect is 1 and request is GET/Query
+ */
+ public function testBeforeSetQuoteWithTriggerRecollectAndGetRequest(): void
+ {
+ // Set up the quote mock to return 1 for getTriggerRecollect
+ $this->quote->expects($this->once())
+ ->method('getTriggerRecollect')
+ ->willReturn(1);
+
+ // Set up the request type registry to indicate this is a GET request
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('isGetRequestOrQuery')
+ ->willReturn(true);
+
+ // Expect setIsGetRequestOrQuery to be called with false
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('setIsGetRequestOrQuery')
+ ->with(false);
+
+ // Execute the method
+ $this->plugin->beforeSetQuote($this->quoteItemCollection, $this->quote);
+ }
+
+ /**
+ * Test beforeSetQuote when triggerRecollect is 0 and request is GET/Query
+ */
+ public function testBeforeSetQuoteWithNoTriggerRecollectAndGetRequest(): void
+ {
+ // Set up the quote mock to return 0 for getTriggerRecollect
+ $this->quote->expects($this->once())
+ ->method('getTriggerRecollect')
+ ->willReturn(0);
+
+ // Expect setIsGetRequestOrQuery NOT to be called
+ $this->requestTypeRegistry->expects($this->never())
+ ->method('setIsGetRequestOrQuery');
+
+ // Execute the method
+ $this->plugin->beforeSetQuote($this->quoteItemCollection, $this->quote);
+ }
+
+ /**
+ * Test beforeSetQuote when triggerRecollect is 1 but request is not GET/Query
+ */
+ public function testBeforeSetQuoteWithTriggerRecollectAndNonGetRequest(): void
+ {
+ // Set up the quote mock to return 1 for getTriggerRecollect
+ $this->quote->expects($this->once())
+ ->method('getTriggerRecollect')
+ ->willReturn(1);
+
+ // Set up the request type registry to indicate this is NOT a GET request
+ $this->requestTypeRegistry->expects($this->once())
+ ->method('isGetRequestOrQuery')
+ ->willReturn(false);
+
+ // Expect setIsGetRequestOrQuery NOT to be called
+ $this->requestTypeRegistry->expects($this->never())
+ ->method('setIsGetRequestOrQuery');
+
+ // Execute the method
+ $this->plugin->beforeSetQuote($this->quoteItemCollection, $this->quote);
+ }
+}
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/RequestTypeRegistryTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/RequestTypeRegistryTest.php
new file mode 100644
index 0000000000000..3dcd6b16ad626
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/Plugin/RequestTypeRegistryTest.php
@@ -0,0 +1,56 @@
+state = new RequestTypeRegistry();
+ }
+
+ public function testDefaultStateIsFalse()
+ {
+ $this->assertFalse($this->state->isGetRequestOrQuery());
+ }
+
+ public function testIsGetRequestOrQueryTrue()
+ {
+ $this->state->setIsGetRequestOrQuery(true);
+ $this->assertTrue($this->state->isGetRequestOrQuery());
+ }
+
+ public function testIsGetRequestOrQueryFalse()
+ {
+ $this->state->setIsGetRequestOrQuery(true);
+ $this->state->setIsGetRequestOrQuery(false);
+ $this->assertFalse($this->state->isGetRequestOrQuery());
+ }
+
+ public function testResetAndMultipleToggle()
+ {
+ $this->assertFalse($this->state->isGetRequestOrQuery());
+
+ $this->state->setIsGetRequestOrQuery(true);
+ $this->assertTrue($this->state->isGetRequestOrQuery());
+
+ $this->state->reset();
+ $this->assertFalse($this->state->isGetRequestOrQuery());
+
+ $this->state->setIsGetRequestOrQuery(true);
+ $this->assertTrue($this->state->isGetRequestOrQuery());
+ }
+}
diff --git a/app/code/Magento/SalesRule/etc/di.xml b/app/code/Magento/SalesRule/etc/di.xml
index cd841b36338e4..b3aa5b68191d3 100644
--- a/app/code/Magento/SalesRule/etc/di.xml
+++ b/app/code/Magento/SalesRule/etc/di.xml
@@ -54,6 +54,11 @@
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/frontend/di.xml b/app/code/Magento/SalesRule/etc/frontend/di.xml
index 2f5251e27f021..256f3ca51401f 100644
--- a/app/code/Magento/SalesRule/etc/frontend/di.xml
+++ b/app/code/Magento/SalesRule/etc/frontend/di.xml
@@ -1,7 +1,7 @@
@@ -32,4 +32,9 @@
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/graphql/di.xml b/app/code/Magento/SalesRule/etc/graphql/di.xml
new file mode 100644
index 0000000000000..c844dbf6d64c6
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/graphql/di.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/webapi_rest/di.xml b/app/code/Magento/SalesRule/etc/webapi_rest/di.xml
new file mode 100644
index 0000000000000..377063359a35a
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/webapi_rest/di.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+