2023-05-22 13:43:10 -04:00
|
|
|
<?php
|
2026-03-26 17:20:55 -04:00
|
|
|
|
2023-08-02 23:54:46 +03:00
|
|
|
use WPGraphQL\WooCommerce\Vendor\Firebase\JWT\JWT;
|
|
|
|
|
use WPGraphQL\WooCommerce\Vendor\Firebase\JWT\Key;
|
2026-03-26 17:20:55 -04:00
|
|
|
use Tests\WPGraphQL\Logger\CodeceptLogger as Signal;
|
2023-05-22 13:43:10 -04:00
|
|
|
|
|
|
|
|
class ProtectedRouterCest {
|
|
|
|
|
private $product_catalog;
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
public function _before( FunctionalTester $I ) {
|
2023-05-22 13:43:10 -04:00
|
|
|
$this->product_catalog = $I->getCatalog();
|
|
|
|
|
|
|
|
|
|
if ( ! defined( 'GRAPHQL_WOOCOMMERCE_SECRET_KEY' ) ) {
|
2026-03-26 17:20:55 -04:00
|
|
|
define( 'GRAPHQL_WOOCOMMERCE_SECRET_KEY', 'testestestestestestestestestest!!' );
|
2023-05-22 13:43:10 -04:00
|
|
|
}
|
2026-03-30 21:42:00 -04:00
|
|
|
|
|
|
|
|
// Enable authorizing URL fields so checkoutNonce, accountNonce, and
|
|
|
|
|
// addPaymentMethodNonce fields are registered on the Customer type.
|
|
|
|
|
$I->setWooGraphQLSetting(
|
|
|
|
|
'enable_authorizing_url_fields',
|
|
|
|
|
[
|
|
|
|
|
'cart_url' => 'cart_url',
|
|
|
|
|
'checkout_url' => 'checkout_url',
|
|
|
|
|
'account_url' => 'account_url',
|
|
|
|
|
'add_payment_method_url' => 'add_payment_method_url',
|
|
|
|
|
]
|
|
|
|
|
);
|
2023-05-22 13:43:10 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
/**
|
|
|
|
|
* Helper: Starts a guest session by adding a product to the cart.
|
|
|
|
|
* Returns the cart item key and raw session token.
|
|
|
|
|
*/
|
|
|
|
|
private function startNewSession( FunctionalTester $I ): array {
|
2023-05-22 13:43:10 -04:00
|
|
|
$success = $I->addToCart(
|
|
|
|
|
[
|
|
|
|
|
'clientMutationId' => 'someId',
|
|
|
|
|
'productId' => $this->product_catalog['t-shirt'],
|
|
|
|
|
'quantity' => 5,
|
2026-03-26 17:20:55 -04:00
|
|
|
]
|
2023-05-22 13:43:10 -04:00
|
|
|
);
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->assertQuerySuccessful(
|
|
|
|
|
$success,
|
|
|
|
|
[ $I->expectField( 'addToCart.cartItem.key', Signal::NOT_NULL ) ]
|
|
|
|
|
);
|
2023-05-22 13:43:10 -04:00
|
|
|
|
|
|
|
|
$session_token = $I->grabHttpHeader( 'woocommerce-session' );
|
|
|
|
|
|
2023-11-30 15:41:32 +02:00
|
|
|
return [
|
2026-03-26 17:20:55 -04:00
|
|
|
'key' => $I->lodashGet( $success, 'data.addToCart.cartItem.key' ),
|
|
|
|
|
'session_token' => $session_token,
|
2023-05-22 13:43:10 -04:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
/**
|
|
|
|
|
* Helper: Decodes the session token and returns the customer_id (session_id).
|
|
|
|
|
*/
|
|
|
|
|
private function getSessionId( string $session_token ): string {
|
|
|
|
|
JWT::$leeway = 60;
|
|
|
|
|
$token_data = JWT::decode( $session_token, new Key( GRAPHQL_WOOCOMMERCE_SECRET_KEY, 'HS256' ) );
|
|
|
|
|
|
|
|
|
|
return $token_data->data->customer_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that a valid nonce redirects to the checkout page.
|
|
|
|
|
*/
|
|
|
|
|
public function testValidNonceRedirectsToCheckout( FunctionalTester $I ) {
|
|
|
|
|
$session_data = $this->startNewSession( $I );
|
|
|
|
|
$session_id = $this->getSessionId( $session_data['session_token'] );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
|
|
|
|
$query = 'query { customer { checkoutNonce } }';
|
|
|
|
|
$success = $I->sendGraphQLRequest(
|
|
|
|
|
$query,
|
|
|
|
|
null,
|
2026-03-26 17:20:55 -04:00
|
|
|
[ 'woocommerce-session' => "Session {$session_data['session_token']}" ]
|
2023-05-22 13:43:10 -04:00
|
|
|
);
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$checkout_nonce = $I->lodashGet( $success, 'data.customer.checkoutNonce' );
|
|
|
|
|
$I->assertNotEmpty( $checkout_nonce );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
|
|
|
|
$I->stopFollowingRedirects();
|
|
|
|
|
|
|
|
|
|
$wp_url = getenv( 'WORDPRESS_URL' );
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session_id}&_wc_checkout={$checkout_nonce}" );
|
2023-05-22 13:43:10 -04:00
|
|
|
$I->seeResponseCodeIs( 302 );
|
|
|
|
|
$I->followRedirect();
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->seeInCurrentUrl( '/checkout' );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->startFollowingRedirects();
|
2023-05-22 13:43:10 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
/**
|
|
|
|
|
* Test that an invalid nonce does NOT redirect to checkout.
|
|
|
|
|
*/
|
|
|
|
|
public function testInvalidNonceDoesNotRedirectToCheckout( FunctionalTester $I ) {
|
|
|
|
|
$session_data = $this->startNewSession( $I );
|
|
|
|
|
$session_id = $this->getSessionId( $session_data['session_token'] );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->stopFollowingRedirects();
|
|
|
|
|
|
|
|
|
|
$wp_url = getenv( 'WORDPRESS_URL' );
|
|
|
|
|
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session_id}&_wc_checkout=invalid_nonce" );
|
|
|
|
|
$I->seeResponseCodeIs( 302 );
|
|
|
|
|
$I->followRedirect();
|
|
|
|
|
$I->dontSeeInCurrentUrl( '/checkout' );
|
|
|
|
|
|
|
|
|
|
$I->startFollowingRedirects();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that an expired nonce (after client_session_id change) does NOT redirect to checkout.
|
|
|
|
|
*/
|
|
|
|
|
public function testExpiredNonceDoesNotRedirectToCheckout( FunctionalTester $I ) {
|
|
|
|
|
$this->startNewSession( $I );
|
|
|
|
|
|
|
|
|
|
$session_token = $I->grabHttpHeader( 'woocommerce-session' );
|
|
|
|
|
|
|
|
|
|
$query = '
|
|
|
|
|
mutation($input: UpdateSessionInput!) {
|
|
|
|
|
updateSession(input: $input) {
|
|
|
|
|
session { key value }
|
|
|
|
|
customer { checkoutUrl }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
';
|
|
|
|
|
|
|
|
|
|
// Set client_session_id and get checkout URL.
|
2023-05-22 13:43:10 -04:00
|
|
|
$success = $I->sendGraphQLRequest(
|
|
|
|
|
$query,
|
2026-03-26 17:20:55 -04:00
|
|
|
[
|
2025-11-15 19:02:41 -05:00
|
|
|
'input' => [
|
|
|
|
|
'sessionData' => [
|
2026-03-26 17:20:55 -04:00
|
|
|
[ 'key' => 'client_session_id', 'value' => 'original-session-id' ],
|
2023-05-22 13:43:10 -04:00
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
2026-03-26 17:20:55 -04:00
|
|
|
[ 'woocommerce-session' => "Session {$session_token}" ]
|
2023-05-22 13:43:10 -04:00
|
|
|
);
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$expired_checkout_url = $I->lodashGet( $success, 'data.updateSession.customer.checkoutUrl' );
|
|
|
|
|
$I->assertNotEmpty( $expired_checkout_url );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
// Change client_session_id to invalidate the nonce.
|
|
|
|
|
$I->sendGraphQLRequest(
|
2023-05-22 13:43:10 -04:00
|
|
|
$query,
|
2026-03-26 17:20:55 -04:00
|
|
|
[
|
2025-11-15 19:02:41 -05:00
|
|
|
'input' => [
|
|
|
|
|
'sessionData' => [
|
2026-03-26 17:20:55 -04:00
|
|
|
[ 'key' => 'client_session_id', 'value' => 'new-session-id' ],
|
2023-05-22 13:43:10 -04:00
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
],
|
2026-03-26 17:20:55 -04:00
|
|
|
[ 'woocommerce-session' => "Session {$session_token}" ]
|
2023-05-22 13:43:10 -04:00
|
|
|
);
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
// The old checkout URL should no longer redirect to checkout.
|
2023-05-22 13:43:10 -04:00
|
|
|
$I->stopFollowingRedirects();
|
|
|
|
|
$I->amOnUrl( $expired_checkout_url );
|
|
|
|
|
$I->seeResponseCodeIs( 302 );
|
|
|
|
|
$I->followRedirect();
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->dontSeeInCurrentUrl( '/checkout' );
|
2023-05-22 13:43:10 -04:00
|
|
|
$I->startFollowingRedirects();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
/**
|
|
|
|
|
* Test that the session cart URL redirects correctly.
|
|
|
|
|
*/
|
|
|
|
|
public function testGetTheSessionCartUrl( FunctionalTester $I ) {
|
|
|
|
|
$session_data = $this->startNewSession( $I );
|
|
|
|
|
$session_id = $this->getSessionId( $session_data['session_token'] );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$query = 'query { customer { cartNonce } }';
|
2023-05-22 13:43:10 -04:00
|
|
|
$success = $I->sendGraphQLRequest(
|
|
|
|
|
$query,
|
|
|
|
|
null,
|
2026-03-26 17:20:55 -04:00
|
|
|
[ 'woocommerce-session' => "Session {$session_data['session_token']}" ]
|
2023-05-22 13:43:10 -04:00
|
|
|
);
|
|
|
|
|
|
2026-03-26 17:20:55 -04:00
|
|
|
$cart_nonce = $I->lodashGet( $success, 'data.customer.cartNonce' );
|
|
|
|
|
$I->assertNotEmpty( $cart_nonce );
|
2023-05-22 13:43:10 -04:00
|
|
|
|
|
|
|
|
$I->stopFollowingRedirects();
|
|
|
|
|
|
|
|
|
|
$wp_url = getenv( 'WORDPRESS_URL' );
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session_id}&_wc_cart={$cart_nonce}" );
|
2023-05-22 13:43:10 -04:00
|
|
|
$I->seeResponseCodeIs( 302 );
|
|
|
|
|
$I->followRedirect();
|
2026-03-26 17:20:55 -04:00
|
|
|
$I->seeInCurrentUrl( '/cart' );
|
|
|
|
|
|
|
|
|
|
$I->startFollowingRedirects();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Helper: Sets up a logged-in user session and creates the my-account page.
|
|
|
|
|
* Returns auth_token, session_token, and session_id.
|
|
|
|
|
*/
|
|
|
|
|
private function setupAuthenticatedSession( FunctionalTester $I ): array {
|
|
|
|
|
$I->setupStoreAndUsers();
|
|
|
|
|
|
|
|
|
|
// Create the my-account page and set it as the WooCommerce account page.
|
|
|
|
|
$account_page_id = $I->havePostInDatabase(
|
|
|
|
|
[
|
|
|
|
|
'post_type' => 'page',
|
|
|
|
|
'post_title' => 'My Account',
|
|
|
|
|
'post_name' => 'my-account',
|
|
|
|
|
'post_status' => 'publish',
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
$I->haveOptionInDatabase( 'woocommerce_myaccount_page_id', $account_page_id );
|
|
|
|
|
|
|
|
|
|
$login = $I->login(
|
|
|
|
|
[
|
|
|
|
|
'clientMutationId' => 'login',
|
|
|
|
|
'username' => 'jimbo1234@example.com',
|
|
|
|
|
'password' => 'password',
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$auth_token = $I->lodashGet( $login, 'data.login.authToken' );
|
|
|
|
|
$customer_id = $I->lodashGet( $login, 'data.login.customer.databaseId' );
|
|
|
|
|
$session_token = $I->grabHttpHeader( 'woocommerce-session' );
|
|
|
|
|
$session_id = $this->getSessionId( $session_token );
|
|
|
|
|
|
|
|
|
|
// For registered users, the session_id should be the user's database ID.
|
|
|
|
|
$I->assertEquals( (string) $customer_id, $session_id );
|
|
|
|
|
|
|
|
|
|
return compact( 'auth_token', 'session_token', 'session_id' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that the session account URL redirects correctly.
|
|
|
|
|
*/
|
|
|
|
|
public function testGetTheSessionAccountUrl( FunctionalTester $I ) {
|
|
|
|
|
$session = $this->setupAuthenticatedSession( $I );
|
|
|
|
|
|
|
|
|
|
$query = 'query { customer { accountNonce } }';
|
|
|
|
|
$success = $I->sendGraphQLRequest(
|
|
|
|
|
$query,
|
|
|
|
|
null,
|
|
|
|
|
[
|
|
|
|
|
'Authorization' => "Bearer {$session['auth_token']}",
|
|
|
|
|
'woocommerce-session' => "Session {$session['session_token']}",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$account_nonce = $I->lodashGet( $success, 'data.customer.accountNonce' );
|
|
|
|
|
$I->assertNotEmpty( $account_nonce );
|
|
|
|
|
|
|
|
|
|
$I->stopFollowingRedirects();
|
|
|
|
|
|
|
|
|
|
$wp_url = getenv( 'WORDPRESS_URL' );
|
|
|
|
|
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session['session_id']}&_wc_account={$account_nonce}" );
|
|
|
|
|
$I->seeResponseCodeIs( 302 );
|
|
|
|
|
$I->followRedirect();
|
|
|
|
|
$I->seeInCurrentUrl( '/my-account' );
|
|
|
|
|
|
|
|
|
|
$I->startFollowingRedirects();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test that the session add payment method URL redirects correctly.
|
|
|
|
|
*/
|
|
|
|
|
public function testGetTheSessionAddPaymentMethodUrl( FunctionalTester $I ) {
|
|
|
|
|
$session = $this->setupAuthenticatedSession( $I );
|
|
|
|
|
|
|
|
|
|
$query = 'query { customer { addPaymentMethodNonce } }';
|
|
|
|
|
$success = $I->sendGraphQLRequest(
|
|
|
|
|
$query,
|
|
|
|
|
null,
|
|
|
|
|
[
|
|
|
|
|
'Authorization' => "Bearer {$session['auth_token']}",
|
|
|
|
|
'woocommerce-session' => "Session {$session['session_token']}",
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$payment_nonce = $I->lodashGet( $success, 'data.customer.addPaymentMethodNonce' );
|
|
|
|
|
$I->assertNotEmpty( $payment_nonce );
|
|
|
|
|
|
|
|
|
|
$I->stopFollowingRedirects();
|
|
|
|
|
|
|
|
|
|
$wp_url = getenv( 'WORDPRESS_URL' );
|
|
|
|
|
$I->amOnUrl( "{$wp_url}/transfer-session?session_id={$session['session_id']}&_wc_payment={$payment_nonce}" );
|
|
|
|
|
$I->seeResponseCodeIs( 302 );
|
|
|
|
|
$I->followRedirect();
|
|
|
|
|
$I->seeInCurrentUrl( 'add-payment-method' );
|
|
|
|
|
|
2023-05-22 13:43:10 -04:00
|
|
|
$I->startFollowingRedirects();
|
|
|
|
|
}
|
|
|
|
|
}
|