Add nonces to Store API endpoints (https://github.com/woocommerce/woocommerce-blocks/pull/1992)
* Disable authentication for the Store API completely. This may also resolve woocommerce/woocommerce-blocks#1991 * Add nonce handling to the abstract route * Default state * Add shared controls including nonce api fetch * Use shared controls * Hydrate inital nonce * Update data stores * Update nonce validation * Fix tests by setting nonces * Remove print_r debug * Revert useStoreCart change * Add nonce middleware Co-Authored-By: Darren Ethier <darren@roughsmootheng.in> * Switch back to apiFetchWithHeaders * Docs Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
This commit is contained in:
parent
cba3b9712f
commit
2ff854e212
|
@ -1,13 +1,14 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { apiFetch, select } from '@wordpress/data-controls';
|
||||
import { select } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ACTION_TYPES as types } from './action-types';
|
||||
import { STORE_KEY as CART_STORE_KEY } from './constants';
|
||||
import { apiFetchWithHeaders } from '../shared-controls';
|
||||
|
||||
/**
|
||||
* Returns an action object used in updating the store with the provided items
|
||||
|
@ -69,80 +70,6 @@ export function receiveRemovingCoupon( couponCode ) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a coupon code and either invalidates caches, or receives an error if
|
||||
* the coupon cannot be applied.
|
||||
*
|
||||
* @throws Will throw an error if there is an API problem.
|
||||
* @param {string} couponCode The coupon code to apply to the cart.
|
||||
*/
|
||||
export function* applyCoupon( couponCode ) {
|
||||
yield receiveApplyingCoupon( couponCode );
|
||||
|
||||
try {
|
||||
const result = yield apiFetch( {
|
||||
path: '/wc/store/cart/apply-coupon',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: couponCode,
|
||||
},
|
||||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
if ( result ) {
|
||||
yield receiveCart( result );
|
||||
}
|
||||
|
||||
// Finished handling the coupon.
|
||||
yield receiveApplyingCoupon( '' );
|
||||
} catch ( error ) {
|
||||
// Store the error message in state.
|
||||
yield receiveError( error );
|
||||
// Re-throw the error.
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a coupon code and either invalidates caches, or receives an error if
|
||||
* the coupon cannot be removed.
|
||||
*
|
||||
* @throws Will throw an error if there is an API problem.
|
||||
* @param {string} couponCode The coupon code to remove from the cart.
|
||||
*/
|
||||
export function* removeCoupon( couponCode ) {
|
||||
yield receiveRemovingCoupon( couponCode );
|
||||
|
||||
try {
|
||||
const result = yield apiFetch( {
|
||||
path: '/wc/store/cart/remove-coupon',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: couponCode,
|
||||
},
|
||||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
if ( result ) {
|
||||
yield receiveCart( result );
|
||||
}
|
||||
|
||||
// Finished handling the coupon.
|
||||
yield receiveRemovingCoupon( '' );
|
||||
} catch ( error ) {
|
||||
// Store the error message in state.
|
||||
yield receiveError( error );
|
||||
// Finished handling the coupon.
|
||||
yield receiveRemovingCoupon( '' );
|
||||
// Re-throw the error.
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an action object for updating a single cart item in the store.
|
||||
*
|
||||
|
@ -185,6 +112,85 @@ export function receiveRemovedItem( cartItemKey ) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an action object used to track what shipping address are we updating to.
|
||||
*
|
||||
* @param {boolean} isResolving if we're loading shipping address or not.
|
||||
* @return {Object} Object for action.
|
||||
*/
|
||||
export function shippingRatesAreResolving( isResolving ) {
|
||||
return {
|
||||
type: types.UPDATING_SHIPPING_ADDRESS,
|
||||
isResolving,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a coupon code and either invalidates caches, or receives an error if
|
||||
* the coupon cannot be applied.
|
||||
*
|
||||
* @throws Will throw an error if there is an API problem.
|
||||
* @param {string} couponCode The coupon code to apply to the cart.
|
||||
*/
|
||||
export function* applyCoupon( couponCode ) {
|
||||
yield receiveApplyingCoupon( couponCode );
|
||||
|
||||
try {
|
||||
const { response } = yield apiFetchWithHeaders( {
|
||||
path: '/wc/store/cart/apply-coupon',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: couponCode,
|
||||
},
|
||||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
yield receiveCart( response );
|
||||
yield receiveApplyingCoupon( '' );
|
||||
} catch ( error ) {
|
||||
// Store the error message in state.
|
||||
yield receiveError( error );
|
||||
// Re-throw the error.
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a coupon code and either invalidates caches, or receives an error if
|
||||
* the coupon cannot be removed.
|
||||
*
|
||||
* @throws Will throw an error if there is an API problem.
|
||||
* @param {string} couponCode The coupon code to remove from the cart.
|
||||
*/
|
||||
export function* removeCoupon( couponCode ) {
|
||||
yield receiveRemovingCoupon( couponCode );
|
||||
|
||||
try {
|
||||
const { response } = yield apiFetchWithHeaders( {
|
||||
path: '/wc/store/cart/remove-coupon',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: couponCode,
|
||||
},
|
||||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
yield receiveCart( response );
|
||||
yield receiveRemovingCoupon( '' );
|
||||
} catch ( error ) {
|
||||
// Store the error message in state.
|
||||
yield receiveError( error );
|
||||
// Finished handling the coupon.
|
||||
yield receiveRemovingCoupon( '' );
|
||||
// Re-throw the error.
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified item from the cart:
|
||||
* - Calls API to remove item.
|
||||
|
@ -198,13 +204,13 @@ export function* removeItemFromCart( cartItemKey ) {
|
|||
yield itemQuantityPending( cartItemKey, true );
|
||||
|
||||
try {
|
||||
const cart = yield apiFetch( {
|
||||
const { response } = yield apiFetchWithHeaders( {
|
||||
path: `/wc/store/cart/remove-item/?key=${ cartItemKey }`,
|
||||
method: 'POST',
|
||||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
yield receiveCart( cart );
|
||||
yield receiveCart( response );
|
||||
} catch ( error ) {
|
||||
yield receiveError( error );
|
||||
}
|
||||
|
@ -228,8 +234,8 @@ export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
const cart = yield apiFetch( {
|
||||
path: `/wc/store/cart/update-item`,
|
||||
const { response } = yield apiFetchWithHeaders( {
|
||||
path: '/wc/store/cart/update-item',
|
||||
method: 'POST',
|
||||
data: {
|
||||
key: cartItemKey,
|
||||
|
@ -238,7 +244,7 @@ export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
|||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
yield receiveCart( cart );
|
||||
yield receiveCart( response );
|
||||
} catch ( error ) {
|
||||
yield receiveError( error );
|
||||
}
|
||||
|
@ -252,7 +258,7 @@ export function* changeCartItemQuantity( cartItemKey, quantity ) {
|
|||
*/
|
||||
export function* selectShippingRate( rateId, packageId = 0 ) {
|
||||
try {
|
||||
const result = yield apiFetch( {
|
||||
const { response } = yield apiFetchWithHeaders( {
|
||||
path: `/wc/store/cart/select-shipping-rate/${ packageId }`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
|
@ -261,9 +267,7 @@ export function* selectShippingRate( rateId, packageId = 0 ) {
|
|||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
if ( result ) {
|
||||
yield receiveCart( result );
|
||||
}
|
||||
yield receiveCart( response );
|
||||
} catch ( error ) {
|
||||
yield receiveError( error );
|
||||
// Re-throw the error.
|
||||
|
@ -272,19 +276,6 @@ export function* selectShippingRate( rateId, packageId = 0 ) {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an action object used to track what shipping address are we updating to.
|
||||
*
|
||||
* @param {boolean} isResolving if we're loading shipping address or not.
|
||||
* @return {Object} Object for action.
|
||||
*/
|
||||
export function shippingRatesAreResolving( isResolving ) {
|
||||
return {
|
||||
type: types.UPDATING_SHIPPING_ADDRESS,
|
||||
isResolving,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a coupon code and either invalidates caches, or receives an error if
|
||||
the coupon cannot be applied.
|
||||
|
@ -294,16 +285,14 @@ the coupon cannot be applied.
|
|||
export function* updateShippingAddress( address ) {
|
||||
yield shippingRatesAreResolving( true );
|
||||
try {
|
||||
const result = yield apiFetch( {
|
||||
const { response } = yield apiFetchWithHeaders( {
|
||||
path: '/wc/store/cart/update-shipping',
|
||||
method: 'POST',
|
||||
data: address,
|
||||
cache: 'no-store',
|
||||
} );
|
||||
|
||||
if ( result ) {
|
||||
yield receiveCart( result );
|
||||
}
|
||||
yield receiveCart( response );
|
||||
} catch ( error ) {
|
||||
yield receiveError( error );
|
||||
yield shippingRatesAreResolving( false );
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { registerStore } from '@wordpress/data';
|
||||
import { controls } from '@wordpress/data-controls';
|
||||
import { controls as dataControls } from '@wordpress/data-controls';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -12,11 +12,12 @@ import * as selectors from './selectors';
|
|||
import * as actions from './actions';
|
||||
import * as resolvers from './resolvers';
|
||||
import reducer from './reducers';
|
||||
import { controls } from '../shared-controls';
|
||||
|
||||
registerStore( STORE_KEY, {
|
||||
reducer,
|
||||
actions,
|
||||
controls,
|
||||
controls: { ...dataControls, ...controls },
|
||||
selectors,
|
||||
resolvers,
|
||||
} );
|
||||
|
|
|
@ -12,7 +12,7 @@ import * as selectors from './selectors';
|
|||
import * as actions from './actions';
|
||||
import * as resolvers from './resolvers';
|
||||
import reducer from './reducers';
|
||||
import { controls } from './controls';
|
||||
import { controls } from '../shared-controls';
|
||||
|
||||
registerStore( STORE_KEY, {
|
||||
reducer,
|
||||
|
|
|
@ -10,7 +10,7 @@ import { addQueryArgs } from '@wordpress/url';
|
|||
import { receiveCollection, receiveCollectionError } from './actions';
|
||||
import { STORE_KEY as SCHEMA_STORE_KEY } from '../schema/constants';
|
||||
import { STORE_KEY, DEFAULT_EMPTY_ARRAY } from './constants';
|
||||
import { apiFetchWithHeaders } from './controls';
|
||||
import { apiFetchWithHeaders } from '../shared-controls';
|
||||
|
||||
/**
|
||||
* Check if the store needs invalidating due to a change in last modified headers.
|
||||
|
@ -52,9 +52,9 @@ export function* getCollection( namespace, resourceName, query, ids ) {
|
|||
|
||||
try {
|
||||
const {
|
||||
items = DEFAULT_EMPTY_ARRAY,
|
||||
response = DEFAULT_EMPTY_ARRAY,
|
||||
headers,
|
||||
} = yield apiFetchWithHeaders( route + queryString );
|
||||
} = yield apiFetchWithHeaders( { path: route + queryString } );
|
||||
|
||||
if ( headers && headers.get && headers.has( 'last-modified' ) ) {
|
||||
// Do any invalidation before the collection is received to prevent
|
||||
|
@ -65,7 +65,7 @@ export function* getCollection( namespace, resourceName, query, ids ) {
|
|||
}
|
||||
|
||||
yield receiveCollection( namespace, resourceName, queryString, ids, {
|
||||
items,
|
||||
items: response,
|
||||
headers,
|
||||
} );
|
||||
} catch ( error ) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { getCollection, getCollectionHeader } from '../resolvers';
|
|||
import { receiveCollection } from '../actions';
|
||||
import { STORE_KEY as SCHEMA_STORE_KEY } from '../../schema/constants';
|
||||
import { STORE_KEY } from '../constants';
|
||||
import { apiFetchWithHeaders } from '../controls';
|
||||
import { apiFetchWithHeaders } from '../../shared-controls';
|
||||
|
||||
jest.mock( '@wordpress/data-controls' );
|
||||
|
||||
|
@ -73,7 +73,9 @@ describe( 'getCollection', () => {
|
|||
fulfillment.next();
|
||||
const { value } = fulfillment.next( 'https://example.org' );
|
||||
expect( value ).toEqual(
|
||||
apiFetchWithHeaders( 'https://example.org?foo=bar' )
|
||||
apiFetchWithHeaders( {
|
||||
path: 'https://example.org?foo=bar',
|
||||
} )
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -101,7 +103,7 @@ describe( 'getCollection', () => {
|
|||
fulfillment.next();
|
||||
fulfillment.next( 'https://example.org' );
|
||||
const { value } = fulfillment.next( {
|
||||
items: [ '42', 'cheeseburgers' ],
|
||||
response: [ '42', 'cheeseburgers' ],
|
||||
headers: { foo: 'bar' },
|
||||
} );
|
||||
expect( value ).toEqual(
|
||||
|
|
|
@ -7,14 +7,14 @@ import triggerFetch from '@wordpress/api-fetch';
|
|||
* Dispatched a control action for triggering an api fetch call with no parsing.
|
||||
* Typically this would be used in scenarios where headers are needed.
|
||||
*
|
||||
* @param {string} path The path for the request.
|
||||
* @param {Object} options The options for the API request.
|
||||
*
|
||||
* @return {Object} The control action descriptor.
|
||||
*/
|
||||
export const apiFetchWithHeaders = ( path ) => {
|
||||
export const apiFetchWithHeaders = ( options ) => {
|
||||
return {
|
||||
type: 'API_FETCH_WITH_HEADERS',
|
||||
path,
|
||||
options,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -25,12 +25,13 @@ export const apiFetchWithHeaders = ( path ) => {
|
|||
* the controls property of the registration object.
|
||||
*/
|
||||
export const controls = {
|
||||
API_FETCH_WITH_HEADERS( { path } ) {
|
||||
API_FETCH_WITH_HEADERS( { options } ) {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
triggerFetch( { path, parse: false } )
|
||||
.then( ( response ) => {
|
||||
response.json().then( ( items ) => {
|
||||
resolve( { items, headers: response.headers } );
|
||||
triggerFetch( { ...options, parse: false } )
|
||||
.then( ( fetchResponse ) => {
|
||||
fetchResponse.json().then( ( response ) => {
|
||||
resolve( { response, headers: fetchResponse.headers } );
|
||||
triggerFetch.setNonce( fetchResponse.headers );
|
||||
} );
|
||||
} )
|
||||
.catch( ( error ) => {
|
|
@ -1,2 +1,3 @@
|
|||
export * from './store-api-nonce';
|
||||
export * from './constants';
|
||||
export { ENDPOINTS } from './endpoints';
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getSetting } from '../shared';
|
||||
|
||||
// Cache for the initial nonce initialized from hydration.
|
||||
let nonce = getSetting( 'storeApiNonce' );
|
||||
|
||||
/**
|
||||
* Returns whether or not this is a non GET wc/store API request.
|
||||
*
|
||||
* @param {Object} options Fetch options.
|
||||
*
|
||||
* @return {boolean} Returns true if this is a store request.
|
||||
*/
|
||||
const isStoreApiGetRequest = ( options ) => {
|
||||
const url = options.url || options.path;
|
||||
if ( ! url || ! options.method || options.method === 'GET' ) {
|
||||
return false;
|
||||
}
|
||||
return /wc\/store\//.exec( url ) !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the current nonce from a header object.
|
||||
*
|
||||
* @param {Object} headers Headers object.
|
||||
*/
|
||||
const setNonce = ( headers ) => {
|
||||
const newNonce = headers?.get( 'X-WC-Store-API-Nonce' );
|
||||
if ( newNonce ) {
|
||||
nonce = newNonce;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Nonce middleware which updates the nonce after a request, if given.
|
||||
*
|
||||
* @param {Object} options Fetch options.
|
||||
* @param {Function} next The next middleware or fetchHandler to call.
|
||||
*
|
||||
* @return {*} The evaluated result of the remaining middleware chain.
|
||||
*/
|
||||
export const storeNonceMiddleware = ( options, next ) => {
|
||||
if ( isStoreApiGetRequest( options ) ) {
|
||||
const existingHeaders = options.headers || {};
|
||||
options.headers = {
|
||||
...existingHeaders,
|
||||
'X-WC-Store-API-Nonce': nonce,
|
||||
};
|
||||
}
|
||||
return next( options, next );
|
||||
};
|
||||
|
||||
apiFetch.use( storeNonceMiddleware );
|
||||
apiFetch.setNonce = setNonce;
|
|
@ -141,6 +141,7 @@ class Assets {
|
|||
'restApiRoutes' => [
|
||||
'/wc/store' => array_keys( \Automattic\WooCommerce\Blocks\RestApi::get_routes_from_namespace( 'wc/store' ) ),
|
||||
],
|
||||
'storeApiNonce' => wp_create_nonce( 'wc_store_api' ),
|
||||
'homeUrl' => esc_url( home_url( '/' ) ),
|
||||
'storePages' => [
|
||||
'shop' => self::format_page_resource( $page_ids['shop'] ),
|
||||
|
|
|
@ -21,6 +21,7 @@ class RestApi {
|
|||
add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ), 10 );
|
||||
add_action( 'rest_api_init', array( '\Automattic\WooCommerce\Blocks\RestApi\StoreApi\RoutesController', 'register_routes' ), 10 );
|
||||
add_filter( 'rest_authentication_errors', array( __CLASS__, 'maybe_init_cart_session' ), 1 );
|
||||
add_filter( 'rest_authentication_errors', array( __CLASS__, 'store_api_authentication' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,6 +56,37 @@ class RestApi {
|
|||
return isset( $response_data['routes'] ) ? $response_data['routes'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is request to the Store API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function is_request_to_store_api() {
|
||||
if ( empty( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rest_prefix = trailingslashit( rest_get_url_prefix() );
|
||||
$request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
|
||||
return false !== strpos( $request_uri, $rest_prefix . 'wc/store' );
|
||||
}
|
||||
|
||||
/**
|
||||
* The Store API does not require authentication.
|
||||
*
|
||||
* @param \WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not.
|
||||
* @return \WP_Error|null|bool
|
||||
*/
|
||||
public static function store_api_authentication( $result ) {
|
||||
// Pass through errors from other authentication methods used before this one.
|
||||
if ( ! empty( $result ) || ! self::is_request_to_store_api() ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're making a cart request, we may need to load some additional classes from WC Core so we're ready to deal with requests.
|
||||
*
|
||||
|
|
|
@ -50,6 +50,9 @@ abstract class AbstractRoute implements RouteInterface {
|
|||
public function get_response( \WP_REST_Request $request ) {
|
||||
$response = null;
|
||||
try {
|
||||
if ( 'GET' !== $request->get_method() ) {
|
||||
$this->check_nonce( $request );
|
||||
}
|
||||
switch ( $request->get_method() ) {
|
||||
case 'POST':
|
||||
$response = $this->get_route_post_response( $request );
|
||||
|
@ -65,6 +68,9 @@ abstract class AbstractRoute implements RouteInterface {
|
|||
$response = $this->get_route_response( $request );
|
||||
break;
|
||||
}
|
||||
if ( 'GET' !== $request->get_method() && ! is_wp_error( $response ) ) {
|
||||
$response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
}
|
||||
} catch ( RouteException $error ) {
|
||||
$response = new \WP_Error( $error->getErrorCode(), $error->getMessage(), [ 'status' => $error->getCode() ] );
|
||||
} catch ( Exception $error ) {
|
||||
|
@ -73,6 +79,30 @@ abstract class AbstractRoute implements RouteInterface {
|
|||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-GET endpoints, require and validate a nonce to prevent CSRF attacks.
|
||||
*
|
||||
* Nonces will mismatch if the logged in session cookie is different! If using a client to test, set this cookie
|
||||
* to match the logged in cookie in your browser.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*/
|
||||
protected function check_nonce( \WP_REST_Request $request ) {
|
||||
$nonce = $request->get_header( 'X-WC-Store-API-Nonce' );
|
||||
|
||||
if ( null === $nonce ) {
|
||||
throw new RouteException( 'woocommerce_rest_missing_nonce', __( 'Missing the X-WC-Store-API-Nonce header. This endpoint requires a valid nonce.', 'woo-gutenberg-products-block' ), 403 );
|
||||
}
|
||||
|
||||
$valid_nonce = wp_verify_nonce( $nonce, 'wc_store_api' );
|
||||
|
||||
if ( ! $valid_nonce ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_nonce', __( 'X-WC-Store-API-Nonce is invalid.', 'woo-gutenberg-products-block' ), 403 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response for GET requests.
|
||||
*
|
||||
|
|
|
@ -98,6 +98,7 @@ class Cart extends TestCase {
|
|||
public function test_remove_bad_cart_item() {
|
||||
// Test removing a bad cart item - should return 404.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/remove-item' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'key' => 'bad_item_key_123',
|
||||
|
@ -116,6 +117,7 @@ class Cart extends TestCase {
|
|||
public function test_remove_cart_item() {
|
||||
// Test removing a valid cart item - should return updated cart.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/remove-item' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'key' => $this->keys[0],
|
||||
|
@ -143,6 +145,7 @@ class Cart extends TestCase {
|
|||
*/
|
||||
public function test_update_item() {
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-item' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'key' => $this->keys[0],
|
||||
|
@ -163,6 +166,7 @@ class Cart extends TestCase {
|
|||
*/
|
||||
public function test_update_shipping() {
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-shipping' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'country' => 'US',
|
||||
|
@ -188,6 +192,7 @@ class Cart extends TestCase {
|
|||
public function test_get_items_address_validation() {
|
||||
// US address.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-shipping' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'address_1' => 'Test address 1',
|
||||
|
@ -211,6 +216,7 @@ class Cart extends TestCase {
|
|||
|
||||
// Address with empty country.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-shipping' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'country' => ''
|
||||
|
@ -223,6 +229,7 @@ class Cart extends TestCase {
|
|||
|
||||
// Address with invalid country.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-shipping' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'country' => 'ZZZZZZZZ'
|
||||
|
@ -235,6 +242,7 @@ class Cart extends TestCase {
|
|||
|
||||
// US address with named state.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-shipping' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'state' =>'Alabama',
|
||||
|
@ -250,6 +258,7 @@ class Cart extends TestCase {
|
|||
|
||||
// US address with invalid state.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/update-shipping' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'state' =>'ZZZZZZZZ',
|
||||
|
@ -270,6 +279,7 @@ class Cart extends TestCase {
|
|||
wc()->cart->remove_coupon( $this->coupon->get_code() );
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/apply-coupon' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => $this->coupon->get_code(),
|
||||
|
@ -283,6 +293,7 @@ class Cart extends TestCase {
|
|||
// Test coupons with different case.
|
||||
$newcoupon = CouponHelper::create_coupon( 'testCoupon' );
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/apply-coupon' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => 'testCoupon',
|
||||
|
@ -295,6 +306,7 @@ class Cart extends TestCase {
|
|||
// Test coupons with special chars in the code.
|
||||
$newcoupon = CouponHelper::create_coupon( '$5 off' );
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/apply-coupon' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => '$5 off',
|
||||
|
@ -311,6 +323,7 @@ class Cart extends TestCase {
|
|||
public function test_remove_coupon() {
|
||||
// Invalid coupon.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/remove-coupon' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => 'doesnotexist',
|
||||
|
@ -322,6 +335,7 @@ class Cart extends TestCase {
|
|||
|
||||
// Applied coupon.
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/remove-coupon' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => $this->coupon->get_code(),
|
||||
|
|
|
@ -74,6 +74,7 @@ class CartCoupons extends TestCase {
|
|||
wc()->cart->remove_coupons();
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/coupons' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => $this->coupon->get_code(),
|
||||
|
@ -93,6 +94,7 @@ class CartCoupons extends TestCase {
|
|||
wc()->cart->remove_coupons();
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/coupons' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'code' => 'IDONOTEXIST',
|
||||
|
@ -109,6 +111,7 @@ class CartCoupons extends TestCase {
|
|||
*/
|
||||
public function test_delete_item() {
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons/' . $this->coupon->get_code() );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
@ -116,12 +119,14 @@ class CartCoupons extends TestCase {
|
|||
$this->assertEmpty( $data );
|
||||
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons/' . $this->coupon->get_code() );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 404, $response->get_status() );
|
||||
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons/i-do-not-exist' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
@ -133,6 +138,7 @@ class CartCoupons extends TestCase {
|
|||
*/
|
||||
public function test_delete_items() {
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/coupons' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ class CartCreateOrder extends TestCase {
|
|||
*/
|
||||
public function test_create_item() {
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/create-order' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_param(
|
||||
'billing_address',
|
||||
[
|
||||
|
|
|
@ -92,6 +92,7 @@ class CartItems extends TestCase {
|
|||
$this->assertEquals( '2000', $data['totals']->line_total );
|
||||
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/XXX815416f775098fe977004015c6193' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
@ -105,6 +106,7 @@ class CartItems extends TestCase {
|
|||
wc_empty_cart();
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/items' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'id' => $this->products[0]->get_id(),
|
||||
|
@ -137,6 +139,7 @@ class CartItems extends TestCase {
|
|||
$invalid_product->save();
|
||||
|
||||
$request = new WP_REST_Request( 'POST', '/wc/store/cart/items' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'id' => $invalid_product->get_id(),
|
||||
|
@ -153,6 +156,7 @@ class CartItems extends TestCase {
|
|||
*/
|
||||
public function test_update_item() {
|
||||
$request = new WP_REST_Request( 'PUT', '/wc/store/cart/items/' . $this->keys[0] );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'quantity' => '10',
|
||||
|
@ -170,6 +174,7 @@ class CartItems extends TestCase {
|
|||
*/
|
||||
public function test_delete_item() {
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/' . $this->keys[0] );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
@ -177,6 +182,7 @@ class CartItems extends TestCase {
|
|||
$this->assertEmpty( $data );
|
||||
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/' . $this->keys[0] );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
@ -188,6 +194,7 @@ class CartItems extends TestCase {
|
|||
*/
|
||||
public function test_delete_items() {
|
||||
$request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items' );
|
||||
$request->set_header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
|
|
Loading…
Reference in New Issue