Skip to content

Commit ff0bc28

Browse files
committed
B2B-2206: [Spike] Find All Areas in GraphQL That Break When Session is Disabled
1 parent f5198e6 commit ff0bc28

File tree

4 files changed

+309
-9
lines changed

4 files changed

+309
-9
lines changed

app/code/Magento/GraphQl/Test/Unit/Plugin/DisableSessionTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ public function setUp(): void
5555
);
5656
}
5757

58-
5958
/**
6059
* Test afterCheck plugin result over original method result.
6160
*

dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ public function getWithResponseHeaders(
139139
string $query,
140140
array $variables = [],
141141
string $operationName = '',
142-
array $headers = []
142+
array $headers = [],
143+
bool $flushCookies = false
143144
): array {
144145
$url = $this->getEndpointUrl();
145146
$requestArray = [
@@ -149,11 +150,12 @@ public function getWithResponseHeaders(
149150
];
150151
array_filter($requestArray);
151152

152-
$response = $this->curlClient->getWithFullResponse($url, $requestArray, $headers);
153+
$response = $this->curlClient->getWithFullResponse($url, $requestArray, $headers, $flushCookies);
153154
$responseBody = $this->processResponse($response['body']);
154155
$responseHeaders = !empty($response['header']) ? $this->processResponseHeaders($response['header']) : [];
156+
$responseCookies = !empty($response['header']) ? $this->processResponseCookies($response['header']) : [];
155157

156-
return ['headers' => $responseHeaders, 'body' => $responseBody];
158+
return ['headers' => $responseHeaders, 'body' => $responseBody, 'cookies' => $responseCookies];
157159
}
158160

159161
/**
@@ -169,7 +171,8 @@ public function postWithResponseHeaders(
169171
string $query,
170172
array $variables = [],
171173
string $operationName = '',
172-
array $headers = []
174+
array $headers = [],
175+
bool $flushCookies = false
173176
): array {
174177
$url = $this->getEndpointUrl();
175178
$headers = array_merge($headers, ['Accept: application/json', 'Content-Type: application/json']);
@@ -180,11 +183,12 @@ public function postWithResponseHeaders(
180183
];
181184
$postData = $this->json->jsonEncode($requestArray);
182185

183-
$response = $this->curlClient->postWithFullResponse($url, $postData, $headers);
186+
$response = $this->curlClient->postWithFullResponse($url, $postData, $headers, $flushCookies);
184187
$responseBody = $this->processResponse($response['body']);
185188
$responseHeaders = !empty($response['header']) ? $this->processResponseHeaders($response['header']) : [];
189+
$responseCookies = !empty($response['header']) ? $this->processResponseCookies($response['header']) : [];
186190

187-
return ['headers' => $responseHeaders, 'body' => $responseBody];
191+
return ['headers' => $responseHeaders, 'body' => $responseBody, 'cookies' => $responseCookies];
188192
}
189193

190194
/**
@@ -254,4 +258,25 @@ private function processResponseHeaders(string $headers): array
254258

255259
return $headersArray;
256260
}
261+
262+
/**
263+
* Prepare separate array of cookies.
264+
*
265+
* @param string $headers
266+
* @return array
267+
*/
268+
private function processResponseCookies(string $headers): array
269+
{
270+
$cookiesArray = [];
271+
$headers = preg_split('/((\r?\n)|(\r\n?))/', $headers);
272+
foreach ($headers as $header) {
273+
if (strpos($header, 'Set-Cookie:') === 0) {
274+
$cookie = preg_split('/: /', $header, 2);
275+
if (isset($cookie[1]) && !empty($cookie[1])) {
276+
$cookiesArray[] = $cookie[1];
277+
}
278+
}
279+
}
280+
return $cookiesArray;
281+
}
257282
}

dev/tests/api-functional/framework/Magento/TestFramework/TestCase/HttpClient/CurlClient.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,20 @@ public function get($url, $data = [], $headers = [])
3838
* @param string $url
3939
* @param array $data
4040
* @param array $headers
41+
* @param bool $flushCookies
4142
* @return array
4243
*/
43-
public function getWithFullResponse($url, $data = [], $headers = []): array
44+
public function getWithFullResponse($url, $data = [], $headers = [], $flushCookies = false): array
4445
{
4546
if (!empty($data)) {
4647
$url .= '?' . http_build_query($data);
4748
}
4849

4950
$curlOpts = [];
5051
$curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET;
52+
if ($flushCookies) {
53+
$curlOpts[CURLOPT_COOKIELIST] = 'ALL';
54+
}
5155
return $this->invokeApi($url, $curlOpts, $headers);
5256
}
5357

@@ -57,14 +61,18 @@ public function getWithFullResponse($url, $data = [], $headers = []): array
5761
* @param string $url
5862
* @param array|string $data
5963
* @param array $headers
64+
* @param bool $flushCookies
6065
* @return array
6166
*/
62-
public function postWithFullResponse($url, $data, $headers = []): array
67+
public function postWithFullResponse($url, $data, $headers = [], $flushCookies = false): array
6368
{
6469
$curlOpts = [];
6570
$curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST;
6671
$headers[] = 'Content-Length: ' . strlen($data);
6772
$curlOpts[CURLOPT_POSTFIELDS] = $data;
73+
if ($flushCookies) {
74+
$curlOpts[CURLOPT_COOKIELIST] = 'ALL';
75+
}
6876

6977
return $this->invokeApi($url, $curlOpts, $headers);
7078
}
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\GraphQl;
9+
10+
use Magento\Framework\Exception\AuthenticationException;
11+
use Magento\Integration\Api\CustomerTokenServiceInterface;
12+
use Magento\TestFramework\Helper\Bootstrap;
13+
use Magento\TestFramework\TestCase\GraphQl\Client;
14+
use Magento\TestFramework\TestCase\GraphQlAbstract;
15+
use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
16+
17+
/**
18+
* Test class to verify category uid, available as product aggregation type
19+
*/
20+
class GraphQlSessionTest extends GraphQlAbstract
21+
{
22+
/**
23+
* @var Client
24+
*/
25+
private $graphQlClient;
26+
27+
/**
28+
* @var \Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId
29+
*/
30+
private $getMaskedQuoteIdByReservedOrderId;
31+
32+
/**
33+
* @var CustomerTokenServiceInterface
34+
*/
35+
private $customerTokenService;
36+
37+
/**
38+
* @inheirtdoc
39+
*/
40+
public function setUp(): void
41+
{
42+
$objectManager = Bootstrap::getObjectManager();
43+
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
44+
$this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
45+
$this->graphQlClient = $objectManager->get(Client::class);
46+
}
47+
48+
/**
49+
* Test for checking if graphQL query sets session cookies
50+
*
51+
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
52+
* @magentoConfigFixture graphql/session/disable 0
53+
*/
54+
public function testCheckSessionCookieWithGetCategoryList(): void
55+
{
56+
$query = <<<QUERY
57+
{
58+
categoryList{
59+
id
60+
uid
61+
name
62+
url_key
63+
url_path
64+
children_count
65+
path
66+
position
67+
}
68+
}
69+
QUERY;
70+
// Using cURL feature of flushing cookies upon completion of request
71+
$this->graphQlClient->getWithResponseHeaders($query, [], '', [], true);
72+
// perform secondary request after cookies have been flushed
73+
$result = $this->graphQlClient->getWithResponseHeaders($query, [], '', []);
74+
$this->assertNotEmpty($result['cookies']);
75+
$this->assertAnyCookieMatchesRegex('/PHPSESSID=[a-z0-9]+;/', $result['cookies']);
76+
$this->assertCount(1, $result['body']['categoryList']);
77+
}
78+
79+
/**
80+
* Test for checking if graphQL query does not set session cookies when session is disabled
81+
*
82+
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
83+
* @magentoConfigFixture graphql/session/disable 1
84+
*/
85+
public function testCheckSessionCookieNotPresentWithGetCategoryList(): void
86+
{
87+
$query = <<<QUERY
88+
{
89+
categoryList{
90+
id
91+
uid
92+
name
93+
url_key
94+
url_path
95+
children_count
96+
path
97+
position
98+
}
99+
}
100+
QUERY;
101+
// CURL flushes cookies only upon completion of the request is the flag is set
102+
// perform graphql request with flushing cookies upon completion
103+
$this->graphQlClient->getWithResponseHeaders($query, [], '', [], true);
104+
// perform secondary request after cookies have been flushed
105+
$result = $this->graphQlClient->getWithResponseHeaders($query, [], '', []);
106+
// may have other cookies than session
107+
$this->assertNoCookiesMatchRegex('/PHPSESSID=[a-z0-9]+;/', $result['cookies']);
108+
$this->assertCount(1, $result['body']['categoryList']);
109+
}
110+
111+
/**
112+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
113+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
114+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
115+
* @magentoConfigFixture graphql/session/disable 0
116+
*/
117+
public function testSessionStartsInAddProductToCartMutation()
118+
{
119+
$sku = 'simple_product';
120+
$quantity = 2;
121+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
122+
$query = $this->getQuery($maskedQuoteId, $sku, $quantity);
123+
124+
$this->graphQlClient->postWithResponseHeaders($query, [], '', $this->getAuthHeaders(), true);
125+
$result = $this->graphQlClient->postWithResponseHeaders($query, [], '', $this->getAuthHeaders());
126+
$this->assertNotEmpty($result['cookies']);
127+
$this->assertAnyCookieMatchesRegex('/PHPSESSID=[a-z0-9]+;/', $result['cookies']);
128+
}
129+
130+
/**
131+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
132+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
133+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
134+
* @magentoConfigFixture graphql/session/disable 1
135+
*/
136+
public function testSessionDoesNotStartInAddProductToCartMutation()
137+
{
138+
$sku = 'simple_product';
139+
$quantity = 2;
140+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
141+
$query = $this->getQuery($maskedQuoteId, $sku, $quantity);
142+
143+
$this->graphQlClient->postWithResponseHeaders($query, [], '', $this->getAuthHeaders(), true);
144+
$result = $this->graphQlClient->postWithResponseHeaders($query, [], '', $this->getAuthHeaders());
145+
$this->assertNoCookiesMatchRegex('/PHPSESSID=[a-z0-9]+;/', $result['cookies']);
146+
}
147+
148+
/**
149+
* Retrieve customer authorization headers
150+
*
151+
* @param string $username
152+
* @param string $password
153+
* @return array
154+
* @throws AuthenticationException
155+
*/
156+
private function getAuthHeaders(string $username = '[email protected]', string $password = 'password'): array
157+
{
158+
$customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password);
159+
return [sprintf('%s: %s', 'Authorization', 'Bearer ' . $customerToken)];
160+
}
161+
162+
/**
163+
* @param string $maskedQuoteId
164+
* @param string $sku
165+
* @param float $quantity
166+
* @return string
167+
*/
168+
private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string
169+
{
170+
return <<<QUERY
171+
mutation {
172+
addSimpleProductsToCart(input: {
173+
cart_id: "{$maskedQuoteId}",
174+
cart_items: [
175+
{
176+
data: {
177+
quantity: {$quantity}
178+
sku: "{$sku}"
179+
}
180+
}
181+
]
182+
}) {
183+
cart {
184+
items {
185+
id
186+
quantity
187+
product {
188+
sku
189+
}
190+
prices {
191+
price {
192+
value
193+
currency
194+
}
195+
row_total {
196+
value
197+
currency
198+
}
199+
row_total_including_tax {
200+
value
201+
currency
202+
}
203+
}
204+
}
205+
shipping_addresses {
206+
firstname
207+
lastname
208+
company
209+
street
210+
city
211+
postcode
212+
telephone
213+
country {
214+
code
215+
label
216+
}
217+
__typename
218+
}
219+
}
220+
}
221+
}
222+
QUERY;
223+
}
224+
225+
/**
226+
* Assert that at least one cookie in the array matches pattern.
227+
*
228+
* @param string $pattern
229+
* @param array $cookies
230+
* @return void
231+
*/
232+
private function assertAnyCookieMatchesRegex(string $pattern, array $cookies): void
233+
{
234+
if (empty($cookies)) {
235+
return;
236+
}
237+
$result = false;
238+
foreach ($cookies as $cookie) {
239+
if (preg_match($pattern, $cookie)) {
240+
$result = true;
241+
break;
242+
}
243+
}
244+
$this->assertTrue($result, 'Failed asserting that any cookie in the array matches pattern: ' . $pattern);
245+
}
246+
247+
/**
248+
* Assert that no cookie in the array matches pattern.
249+
*
250+
* @param string $pattern
251+
* @param array $cookies
252+
* @return void
253+
*/
254+
private function assertNoCookiesMatchRegex(string $pattern, array $cookies): void
255+
{
256+
if (empty($cookies)) {
257+
return;
258+
}
259+
$result = true;
260+
foreach ($cookies as $cookie) {
261+
if (preg_match($pattern, $cookie)) {
262+
$result = false;
263+
break;
264+
}
265+
}
266+
$this->assertTrue($result, 'Failed assertion. At least one cookie in the array matches pattern: ' . $pattern);
267+
}
268+
}

0 commit comments

Comments
 (0)