-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core-flows,medusa): Add customer validation on cart update (#9662)
- Loading branch information
Showing
6 changed files
with
231 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils" | ||
import { | ||
Modules, | ||
PriceListStatus, | ||
|
@@ -6,39 +7,19 @@ import { | |
PromotionRuleOperator, | ||
PromotionType, | ||
} from "@medusajs/utils" | ||
import { medusaIntegrationTestRunner } from "@medusajs/test-utils" | ||
import { | ||
createAdminUser, | ||
generatePublishableKey, | ||
generateStoreHeaders, | ||
} from "../../../../helpers/create-admin-user" | ||
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures" | ||
import { createAuthenticatedCustomer } from "../../../../modules/helpers/create-authenticated-customer" | ||
|
||
jest.setTimeout(100000) | ||
|
||
const env = { MEDUSA_FF_MEDUSA_V2: true } | ||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } } | ||
|
||
const generateStoreHeadersWithCustomer = async ({ | ||
api, | ||
storeHeaders, | ||
customer, | ||
}) => { | ||
const registeredCustomerToken = ( | ||
await api.post("/auth/customer/emailpass/register", { | ||
email: customer.email, | ||
password: "password", | ||
}) | ||
).data.token | ||
|
||
return { | ||
headers: { | ||
...storeHeaders.headers, | ||
authorization: `Bearer ${registeredCustomerToken}`, | ||
}, | ||
} | ||
} | ||
|
||
const shippingAddressData = { | ||
address_1: "test address 1", | ||
address_2: "test address 2", | ||
|
@@ -136,23 +117,20 @@ medusaIntegrationTestRunner({ | |
const publishableKey = await generatePublishableKey(appContainer) | ||
storeHeaders = generateStoreHeaders({ publishableKey }) | ||
|
||
customer = ( | ||
await api.post( | ||
"/admin/customers", | ||
{ | ||
first_name: "tony", | ||
email: "[email protected]", | ||
}, | ||
adminHeaders | ||
) | ||
).data.customer | ||
|
||
storeHeadersWithCustomer = await generateStoreHeadersWithCustomer({ | ||
storeHeaders, | ||
api, | ||
customer, | ||
const result = await createAuthenticatedCustomer(appContainer, { | ||
first_name: "tony", | ||
last_name: "stark", | ||
email: "[email protected]", | ||
}) | ||
|
||
customer = result.customer | ||
storeHeadersWithCustomer = { | ||
headers: { | ||
...storeHeaders.headers, | ||
authorization: `Bearer ${result.jwt}`, | ||
}, | ||
} | ||
|
||
await setupTaxStructure(appContainer.resolve(Modules.TAX)) | ||
|
||
region = ( | ||
|
@@ -579,23 +557,23 @@ medusaIntegrationTestRunner({ | |
}) | ||
|
||
describe("POST /store/carts/:id", () => { | ||
let otherRegion | ||
let otherRegion, cartWithCustomer | ||
|
||
beforeEach(async () => { | ||
cart = ( | ||
await api.post( | ||
`/store/carts`, | ||
{ | ||
email: "[email protected]", | ||
currency_code: "usd", | ||
sales_channel_id: salesChannel.id, | ||
region_id: region.id, | ||
shipping_address: shippingAddressData, | ||
items: [{ variant_id: product.variants[0].id, quantity: 1 }], | ||
promo_codes: [promotion.code], | ||
}, | ||
storeHeadersWithCustomer | ||
) | ||
const cartData = { | ||
currency_code: "usd", | ||
sales_channel_id: salesChannel.id, | ||
region_id: region.id, | ||
shipping_address: shippingAddressData, | ||
items: [{ variant_id: product.variants[0].id, quantity: 1 }], | ||
promo_codes: [promotion.code], | ||
} | ||
|
||
cart = (await api.post(`/store/carts`, cartData, storeHeaders)).data | ||
.cart | ||
|
||
cartWithCustomer = ( | ||
await api.post(`/store/carts`, cartData, storeHeadersWithCustomer) | ||
).data.cart | ||
|
||
otherRegion = ( | ||
|
@@ -751,7 +729,7 @@ medusaIntegrationTestRunner({ | |
it("should not generate tax lines if automatic taxes is false", async () => { | ||
let updated = await api.post( | ||
`/store/carts/${cart.id}`, | ||
{ email: "[email protected]" }, | ||
{}, | ||
storeHeaders | ||
) | ||
|
||
|
@@ -776,7 +754,7 @@ medusaIntegrationTestRunner({ | |
|
||
updated = await api.post( | ||
`/store/carts/${cart.id}`, | ||
{ email: "[email protected]", region_id: noAutomaticRegion.id }, | ||
{ region_id: noAutomaticRegion.id }, | ||
storeHeaders | ||
) | ||
|
||
|
@@ -1236,6 +1214,103 @@ medusaIntegrationTestRunner({ | |
}) | ||
) | ||
}) | ||
|
||
it("should update email if cart customer_id is not set", async () => { | ||
const updated = await api.post( | ||
`/store/carts/${cart.id}`, | ||
{ email: "[email protected]" }, | ||
storeHeaders | ||
) | ||
|
||
expect(updated.status).toEqual(200) | ||
expect(updated.data.cart).toEqual( | ||
expect.objectContaining({ | ||
email: "[email protected]", | ||
customer: expect.objectContaining({ | ||
email: "[email protected]", | ||
}), | ||
}) | ||
) | ||
}) | ||
|
||
it("should update customer_id if cart customer_id if not already set", async () => { | ||
const updated = await api.post( | ||
`/store/carts/${cart.id}`, | ||
{ customer_id: customer.id }, | ||
storeHeadersWithCustomer | ||
) | ||
|
||
expect(updated.status).toEqual(200) | ||
expect(updated.data.cart).toEqual( | ||
expect.objectContaining({ | ||
email: customer.email, | ||
customer: expect.objectContaining({ | ||
id: customer.id, | ||
email: customer.email, | ||
}), | ||
}) | ||
) | ||
}) | ||
|
||
it("should throw when trying to set customer_id if customer is not logged in", async () => { | ||
const { response } = await api | ||
.post( | ||
`/store/carts/${cartWithCustomer.id}`, | ||
{ customer_id: customer.id }, | ||
storeHeaders | ||
) | ||
.catch((e) => e) | ||
|
||
expect(response.status).toEqual(400) | ||
expect(response.data.message).toEqual( | ||
"auth_customer_id is required when customer_id is set" | ||
) | ||
}) | ||
|
||
it("should throw when trying to set customer_id if customer_id is already set", async () => { | ||
const newCustomer = ( | ||
await api.post( | ||
"/admin/customers", | ||
{ | ||
first_name: "new tony", | ||
email: "[email protected]", | ||
}, | ||
adminHeaders | ||
) | ||
).data.customer | ||
|
||
const { response } = await api | ||
.post( | ||
`/store/carts/${cartWithCustomer.id}`, | ||
{ customer_id: newCustomer.id }, | ||
storeHeadersWithCustomer | ||
) | ||
.catch((e) => e) | ||
|
||
expect(response.status).toEqual(400) | ||
expect(response.data.message).toEqual( | ||
"Cannot update cart customer when customer_id is set" | ||
) | ||
}) | ||
|
||
it("should update email when email is already set and customer is logged in", async () => { | ||
const updated = await api.post( | ||
`/store/carts/${cart.id}`, | ||
{ customer_id: customer.id }, | ||
storeHeadersWithCustomer | ||
) | ||
|
||
expect(updated.status).toEqual(200) | ||
expect(updated.data.cart).toEqual( | ||
expect.objectContaining({ | ||
email: customer.email, | ||
customer: expect.objectContaining({ | ||
id: customer.id, | ||
email: customer.email, | ||
}), | ||
}) | ||
) | ||
}) | ||
}) | ||
}) | ||
}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
packages/core/core-flows/src/cart/workflows/update-cart-with-customer-validation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import { | ||
AdditionalData, | ||
UpdateCartWorkflowInputDTO, | ||
} from "@medusajs/framework/types" | ||
import { isDefined, isPresent, MedusaError } from "@medusajs/framework/utils" | ||
import { | ||
createStep, | ||
createWorkflow, | ||
when, | ||
WorkflowData, | ||
WorkflowResponse, | ||
} from "@medusajs/framework/workflows-sdk" | ||
import { useRemoteQueryStep } from "../../common" | ||
import { updateCartWorkflow } from "./update-cart" | ||
|
||
/** | ||
* This step validates rules of engagement when customer_id or email is | ||
* requested to be updated. | ||
*/ | ||
export const validateCartCustomerOrEmailStep = createStep( | ||
"validate-cart-customer-or-email", | ||
async function ({ | ||
input, | ||
cart, | ||
}: { | ||
input: { | ||
customer_id?: string | null | ||
email?: string | null | ||
auth_customer_id: string | undefined | null | ||
} | ||
cart: { customer_id: string | null; email: string | null } | ||
}) { | ||
if (isPresent(cart.customer_id) && cart.customer_id !== input.customer_id) { | ||
throw new MedusaError( | ||
MedusaError.Types.INVALID_DATA, | ||
`Cannot update cart customer when customer_id is set` | ||
) | ||
} | ||
|
||
if (isDefined(input.customer_id) && !isDefined(input.auth_customer_id)) { | ||
throw new MedusaError( | ||
MedusaError.Types.INVALID_DATA, | ||
`auth_customer_id is required when customer_id is set` | ||
) | ||
} | ||
|
||
const isInputCustomerIdDifferent = | ||
input.auth_customer_id !== input.customer_id | ||
|
||
if (isDefined(input.customer_id) && isInputCustomerIdDifferent) { | ||
throw new MedusaError( | ||
MedusaError.Types.INVALID_DATA, | ||
`Cannot update cart customer_id to a different customer` | ||
) | ||
} | ||
} | ||
) | ||
|
||
export const updateCartWorkflowWithCustomerValidationId = | ||
"update-cart-with-customer-validation" | ||
/** | ||
* This workflow wraps updateCartWorkflow with customer validations | ||
*/ | ||
export const updateCartWorkflowWithCustomerValidation = createWorkflow( | ||
updateCartWorkflowWithCustomerValidationId, | ||
( | ||
input: WorkflowData< | ||
UpdateCartWorkflowInputDTO & | ||
AdditionalData & { auth_customer_id: string | undefined } | ||
> | ||
) => { | ||
const cart = useRemoteQueryStep({ | ||
entry_point: "cart", | ||
variables: { id: input.id }, | ||
fields: ["id", "customer_id", "email"], | ||
list: false, | ||
throw_if_key_not_found: true, | ||
}).config({ name: "get-cart" }) | ||
|
||
when({ input }, ({ input }) => { | ||
return !!input.customer_id || !!input.email | ||
}).then(() => { | ||
validateCartCustomerOrEmailStep({ input, cart }) | ||
}) | ||
|
||
const updatedCart = updateCartWorkflow.runAsStep({ input }) | ||
|
||
return new WorkflowResponse(updatedCart) | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters