Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expired Guest Cart Errors #1150

Merged
merged 15 commits into from
Apr 26, 2019
Merged

Conversation

supernova-at
Copy link
Contributor

@supernova-at supernova-at commented Apr 19, 2019

Description

There were a handful of errors relating to stale data, specifically the cartId. This PR:

  • Captures errors during addItemToCart, updateItemInCart, and removeItemFromCart so that the backstop reducer doesn't display an error toast
  • Fixes bugs that lead to infinite retry loops (not clearing out data properly)
  • Updates retry logic in updateItemInCart and removeItemFromCart to not actually retry these operations because when we determine that the guest cart has expired we get a new cart. Retrying these operations on a new cart doesn't make sense, as the cart will be empty. updateItemInCart instead attempts to addItemToCart to the new cart.

Related Issue

Closes #1143 .

Verification Steps

For each test case below, ensure that:

  1. The red error toast does not appear
  2. The action ultimately resolves (the app does not hang / infinite loop)

We will be testing a bad cartId in Local Storage and then in Redux.
For each, we will be testing adding, updating, and removing items from the cart.
We will also test the flows for both guest users and authenticated users.

Guest Users (Not Signed In)

Perform the following tests as a guest user. You may have to sign out first.

Test Case: Bad cartId in Local Storage

Screen Shot 2019-04-23 at 2 27 11 PM

Page Load

  1. Change the value of the cartId cookie to something bogus
  2. Fully refresh the page
  3. See that you get a new cartId and empty cart

Add Item

  1. Change the value of the cartId cookie to something bogus
  2. Attempt to add an item to your cart
  3. See that the operation is successful

Update Item

  1. Change the value of the cartId cookie to something bogus
  2. Attempt to update an item in your cart
  3. See that the operation is successful

Remove Item

  1. Change the value of the cartId cookie to something bogus
  2. Attempt to remove an item from your cart
  3. See that the operation is successful

Test Case: Bad cartId in Redux

Prerequisite

  1. Install the Redux DevTools Extension

Add Item

  1. Launch Venia and wait for a successful initialization (look for a successful CART/GET_DETAILS/RECEIVE message in the console)
  2. Open Redux DevTools
  3. In the text area at the very bottom, enter the following:
{
type: 'CART/GET_CART/RECEIVE',
payload: 'Bogus!'
}

and click the small dispatch button in the lower right to dispatch an action that will set cart.cartId to "Bogus!"

  1. Attempt to add an item to your cart
  2. See that the item is eventually added, and that it is the only item in the cart (the old cart no longer exists).

Update Item

  1. Open the cart item edit dialog from the mini cart
  2. Open Redux DevTools
  3. In the text area at the very bottom, enter the following:
{
type: 'CART/GET_CART/RECEIVE',
payload: 'Bogus!'
}

and click the small dispatch button in the lower right to dispatch an action that will set cart.cartId to "Bogus!"

  1. Select some product options and click the Update Cart button
  2. See that the item is eventually added to the cart, and that it is the only item in the cart (the old cart no longer exists).

Remove Item

  1. Open the mini cart
  2. Open Redux DevTools
  3. In the text area at the very bottom, enter the following:
{
type: 'CART/GET_CART/RECEIVE',
payload: 'Bogus!'
}

and click the small dispatch button in the lower right to dispatch an action that will set cart.cartId to "Bogus!"

  1. Select Remove Item for any item in the cart
  2. See that you get a new, empty cart (the old cart no longer exists).

Authenticated Users (Signed In)

Perform the following tests as an authenticated user. You may have to sign in first.
You may also want to add an item to your cart so that you can be sure you're getting "your" cart back.

Test Case: Bad cartId in Local Storage

⚠️ Warning ⚠️
The cartId for authenticated users is a Number (ex: "2209"). For these steps either change it to another number or be sure to include escaped quotes if you change it to a string. Ex: {"value":"\"bogus\"" ...}. If you change it to the literal string "bogus" you will get JSON parse errors in the console and will invalidate the tests.

Screen Shot 2019-04-23 at 2 27 11 PM

Page Load

  1. Change the value of the cartId cookie to something bogus
  2. Fully refresh the page
  3. See that you (eventually) get your cart and that the cartId in local storage is updated back to the correct ID (yours).

Add Item

  1. Change the value of the cartId cookie to something bogus
  2. Attempt to add an item to your cart
  3. See that the operation is successful

Update Item

  1. Change the value of the cartId cookie to something bogus
  2. Attempt to update an item in your cart
  3. See that the operation is successful

Remove Item

  1. Change the value of the cartId cookie to something bogus
  2. Attempt to remove an item from your cart
  3. See that the operation is successful

Test Case: Bad cartId in Redux

Add Item

  1. Launch Venia and wait for a successful initialization (look for a successful CART/GET_DETAILS/RECEIVE message in the console)
  2. Open Redux DevTools
  3. In the text area at the very bottom, enter the following:
{
type: 'CART/GET_CART/RECEIVE',
payload: 'Bogus!'
}

and click the small dispatch button in the lower right to dispatch an action that will set cart.cartId to "Bogus!"

  1. Attempt to add an item to your cart
  2. See that the item is eventually added
  3. See that the items previously in the cart are still there too

Update Item

  1. Open the cart item edit dialog from the mini cart
  2. Open Redux DevTools
  3. In the text area at the very bottom, enter the following:
{
type: 'CART/GET_CART/RECEIVE',
payload: 'Bogus!'
}

and click the small dispatch button in the lower right to dispatch an action that will set cart.cartId to "Bogus!"

  1. Select some product options and click the Update Cart button
  2. See that the item is eventually updated
  3. See that the items previously in the cart are still there too

Remove Item

  1. Open the mini cart
  2. Open Redux DevTools
  3. In the text area at the very bottom, enter the following:
{
type: 'CART/GET_CART/RECEIVE',
payload: 'Bogus!'
}

and click the small dispatch button in the lower right to dispatch an action that will set cart.cartId to "Bogus!"

  1. Select Remove Item for any item in the cart
  2. See that the item is removed
  3. See that the items previously in the cart are still there too

How Have YOU Tested this?

  1. yarn test
  2. Followed the reproduction steps above

Screenshots / Screen Captures (if appropriate):

This is what I'm referring to when I say "the red error toast":

Screen Shot 2019-04-19 at 4 53 40 PM

Proposed Labels for Change Type/Package

  • major (e.g x.0.0 - a breaking change)
  • minor (e.g 0.x.0 - a backwards compatible addition)
  • patch (e.g 0.0.x - a bug fix)

Checklist:

  • I have updated the documentation accordingly, if necessary.
  • I have added tests to cover my changes, if necessary.

@vercel
Copy link

vercel bot commented Apr 19, 2019

This pull request is automatically deployed with Now.
To access deployments, click Details below or on the icon next to each push.

@coveralls
Copy link

coveralls commented Apr 19, 2019

Coverage Status

Coverage decreased (-0.002%) to 77.128% when pulling 5c3e147 on supernova/1143-expired_guest_cart into 3cc4deb on develop.

@@ -529,24 +532,9 @@ test('updateItemInCart thunk dispatches special failure if guestCartId is not pr
2,
actions.updateItem.receive(error)
);
// and now, the createGuestCart thunk
expect(dispatch).toHaveBeenNthCalledWith(3, expect.any(Function));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually care about all the other calls to dispatch in this test ... the previous assertion is the only thing we're after here.

.mockImplementationOnce(() => ({
cart: {},
user: { isSignedIn: false }
}));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getState is now only called once and cart and user are destructured from there.

The tweak to the retry logic made it so that we don't need this third mock implementation.


// if a guest cart exists in storage, act like we just received it
const guestCartId = await retrieveGuestCartId();
Copy link
Contributor Author

@supernova-at supernova-at Apr 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't anything special, I just rearranged the dispatches for consistency with other actions.

Previously we'd get an actions.getGuestCart.receive without a preceeding actions.getGuestCart.request in the case where we already had a guestCartId.

// complete before dispatching the error--you don't want an
// upstream action to try and reuse the known-bad ID.
await clearGuestCartId();
await dispatch(removeCart());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeCart is new - it calls clearGuestCartId AND actions.reset to remove the guestCartId from redux.

// if so, then delete the cached ID...
// in contrast to the save, make sure storage deletion is
// Delete the cached ID from local storage.
// The reducer handles clearing out the bad ID from Redux.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removeItemFromCart is a little different in that the reducer handles clearing out the bad ID from redux, so we don't have to do it here.

// complete before dispatching the error--you don't want an
// upstream action to try and reuse the known-bad ID.
await clearGuestCartId();
// then create a new one
await dispatch(createGuestCart());
// then retry this operation
return thunk(...arguments);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No sense attempting to retry removing an item - we just got a brand new cart with nothing in it!

// if so, then delete the cached ID...
// in contrast to the save, make sure storage deletion is
// if so, then delete the cached ID from local storage.
// The reducer handles clearing out the bad ID from Redux.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing changed here, I just updated the comment to make this part clear because this function and removeItemFromCart differ from the others (they don't call removeCart).

shippingMethods: [],
totals: {}
totals: {},
updateItemError: null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We capture all these errors now to stop the backstop reducer from kicking in.

expect(result).toEqual({
...initialState,
removeItemError: action.payload
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other tests here pass but should probably be updated.

// then retry this operation
return thunk(...arguments);
// and add the updated item to the new cart.
await dispatch(addItemToCart(payload));
Copy link
Contributor Author

@supernova-at supernova-at Apr 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may or may not be controversial so I'll point it out:

Here we've failed to update an item in the cart due to the cart having expired. We've just gotten a brand new (empty) cart, so there's no sense in retrying the update to this item because it won't exist. But we do know what the user wanted, so we add the updated item to the new cart.

Sucks that they lose anything else they had in the cart, but 🤷‍♂️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we'll have to think this one through.

Copy link
Contributor

@jimbo jimbo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me. Let's discuss what we want to do about existing cart items (and pending cart modifications) when the cart expires.

// then retry this operation
return thunk(...arguments);
// and add the updated item to the new cart.
await dispatch(addItemToCart(payload));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we'll have to think this one through.

.set('notFound', '404 Not Found')
.set('internalError', '500 Internal Server Error');
.set('notFound', 'That page could not be found. Please try again.')
.set('internalError', 'Something went wrong. Please try again.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't think this change helps much—if anything, it makes it more obscure. If we want to make the message friendlier, let's do it right with a real design and some imagery. Until then, I'd rather leave it as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I do think the 500 Internal Server Error is misleading though, especially since that codepath can be executed without a 500 actually occurring.

Also misleading is that these are server-side error codes, when the problem (aside from the 404) can be in the client.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. They're not literally server error codes; they're intended to mimic what you'd see if this were a server-rendered app, so we know what case we're in. Ultimately, I don't mind changing the copy if it bothers you.

jimbo
jimbo previously approved these changes Apr 22, 2019
.set('notFound', '404 Not Found')
.set('internalError', '500 Internal Server Error');
.set('notFound', 'That page could not be found. Please try again.')
.set('internalError', 'Something went wrong. Please try again.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. They're not literally server error codes; they're intended to mimic what you'd see if this were a server-rendered app, so we know what case we're in. Ultimately, I don't mind changing the copy if it bothers you.

@supernova-at supernova-at added the version: Minor This changeset includes functionality added in a backwards compatible manner. label Apr 23, 2019
@supernova-at
Copy link
Contributor Author

PR Updated:

  • Merged changes from develop and resolved conflicts
  • Updated verification steps

cc @dpatil-magento

@vercel vercel bot temporarily deployed to staging April 24, 2019 17:34 Inactive
@supernova-at
Copy link
Contributor Author

PR Updated:

  • We are actually able to retry some of the operations for authenticated users because we their cart doesn't get completely wiped out. If they have a cart, we retrieve it and then retry the operations (update & remove item)

@dpatil-magento
Copy link
Contributor

QA Pass. @jimbo Can you please review the new changes and approve?

jimbo
jimbo previously approved these changes Apr 25, 2019
Copy link
Contributor

@jimbo jimbo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes make sense. If this passes QA, thumbs up from me. 👍

@dpatil-magento
Copy link
Contributor

pr-test and pr-build both pass bundlesize check, seems like webhook update is pending. Merging this.

@dpatil-magento dpatil-magento merged commit 3107e4e into develop Apr 26, 2019
@supernova-at supernova-at deleted the supernova/1143-expired_guest_cart branch May 2, 2019 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
version: Minor This changeset includes functionality added in a backwards compatible manner.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[bug]: Expired guest cart for Venia (server error)
5 participants