fix: Customer registration (#8896)

* fix: Customer registration

* update test

* one mroe test

* chore: add transformation
This commit is contained in:
Oli Juhl
2024-09-01 11:41:39 +02:00
committed by GitHub
parent 99461e24ab
commit cbb0a6adc7
7 changed files with 269 additions and 10 deletions

View File

@@ -0,0 +1,196 @@
import { MedusaContainer } from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
jest.setTimeout(30000)
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, api, getContainer }) => {
let appContainer: MedusaContainer
beforeEach(async () => {
appContainer = getContainer()
await createAdminUser(dbConnection, adminHeaders, appContainer)
})
describe("POST /admin/customers", () => {
it("should fails to create a customer without an identity", async () => {
const customer = await api
.post("/store/customers", {
email: "newcustomer@medusa.js",
first_name: "John",
last_name: "Doe",
})
.catch((e) => e)
expect(customer.response.status).toEqual(401)
})
it("should successfully create a customer with an identity", async () => {
const signup = await api.post("/auth/customer/emailpass/register", {
email: "newcustomer@medusa.js",
password: "secret_password",
})
expect(signup.status).toEqual(200)
expect(signup.data).toEqual({ token: expect.any(String) })
const customer = await api.post(
"/store/customers",
{
email: "newcustomer@medusa.js",
first_name: "John",
last_name: "Doe",
},
{
headers: {
authorization: `Bearer ${signup.data.token}`,
},
}
)
expect(customer.status).toEqual(200)
expect(customer.data).toEqual({
customer: expect.objectContaining({
email: "newcustomer@medusa.js",
first_name: "John",
last_name: "Doe",
has_account: true,
}),
})
})
it("should successfully create a customer with an identity even if the email is already taken by a non-registered customer", async () => {
const nonRegisteredCustomer = await api.post(
"/admin/customers",
{
email: "newcustomer@medusa.js",
first_name: "John",
last_name: "Doe",
},
adminHeaders
)
expect(nonRegisteredCustomer.status).toEqual(200)
expect(nonRegisteredCustomer.data).toEqual({
customer: expect.objectContaining({
email: "newcustomer@medusa.js",
first_name: "John",
last_name: "Doe",
has_account: false,
}),
})
const signup = await api.post("/auth/customer/emailpass/register", {
email: "newcustomer@medusa.js",
password: "secret_password",
})
expect(signup.status).toEqual(200)
expect(signup.data).toEqual({ token: expect.any(String) })
const customer = await api.post(
"/store/customers",
{
email: "newcustomer@medusa.js",
first_name: "Jane",
last_name: "Doe",
},
{
headers: {
authorization: `Bearer ${signup.data.token}`,
},
}
)
expect(customer.status).toEqual(200)
expect(customer.data).toEqual({
customer: expect.objectContaining({
email: "newcustomer@medusa.js",
first_name: "Jane",
last_name: "Doe",
has_account: true,
}),
})
// Check that customers co-exist
const customers = await api.get("/admin/customers", adminHeaders)
expect(customers.status).toEqual(200)
expect(customers.data.customers).toHaveLength(2)
expect(customers.data.customers).toEqual(
expect.arrayContaining([
expect.objectContaining({
first_name: "Jane",
last_name: "Doe",
email: "newcustomer@medusa.js",
has_account: true,
}),
expect.objectContaining({
first_name: "John",
last_name: "Doe",
email: "newcustomer@medusa.js",
has_account: false,
}),
])
)
})
it("should fail to create a customer with an identity when the email is already taken by a registered customer", async () => {
const firstSignup = await api.post(
"/auth/customer/emailpass/register",
{
email: "newcustomer@medusa.js",
password: "secret_password",
}
)
expect(firstSignup.status).toEqual(200)
expect(firstSignup.data).toEqual({ token: expect.any(String) })
await api.post(
"/store/customers",
{
email: "newcustomer@medusa.js",
first_name: "John",
last_name: "Doe",
},
{
headers: {
authorization: `Bearer ${firstSignup.data.token}`,
},
}
)
const firstSignin = await api.post("/auth/customer/emailpass", {
email: "newcustomer@medusa.js",
password: "secret_password",
})
const customer = await api
.post(
"/store/customers",
{
email: "newcustomer@medusa.js",
first_name: "Jane",
last_name: "Doe",
},
{
headers: {
authorization: `Bearer ${firstSignin.data.token}`,
},
}
)
.catch((e) => e)
expect(customer.response.status).toEqual(400)
expect(customer.response.data.message).toEqual(
"Request already authenticated as a customer."
)
})
})
},
})

View File

@@ -0,0 +1,45 @@
import { MedusaError, ModuleRegistrationName } from "@medusajs/utils"
import { createStep } from "@medusajs/workflows-sdk"
import { CreateCustomerAccountWorkflowInput } from "../workflows"
export const validateCustomerAccountCreationStepId =
"validate-customer-account-creation"
export const validateCustomerAccountCreation = createStep(
validateCustomerAccountCreationStepId,
async (input: CreateCustomerAccountWorkflowInput, { container }) => {
const customerService = container.resolve(ModuleRegistrationName.CUSTOMER)
const { email } = input.customerData
if (!email) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Email is required to create a customer"
)
}
// Check if customer with email already exists
const existingCustomers = await customerService.listCustomers({ email })
if (existingCustomers?.length) {
const hasExistingAccount = existingCustomers.some(
(customer) => customer.has_account
)
if (hasExistingAccount && input.authIdentityId) {
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
"Customer with this email already has an account"
)
}
if (!hasExistingAccount && !input.authIdentityId) {
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
"Guest customer with this email already exists"
)
}
}
}
)

View File

@@ -1,16 +1,17 @@
import { CreateCustomerDTO, CustomerDTO } from "@medusajs/types"
import {
createWorkflow,
transform,
WorkflowData,
WorkflowResponse,
} from "@medusajs/workflows-sdk"
import { createCustomersStep } from "../steps"
import { transform } from "@medusajs/workflows-sdk"
import { setAuthAppMetadataStep } from "../../auth"
import { createCustomersStep } from "../steps"
import { validateCustomerAccountCreation } from "../steps/validate-customer-account-creation"
export type CreateCustomerAccountWorkflowInput = {
authIdentityId: string
customersData: CreateCustomerDTO
customerData: CreateCustomerDTO
}
export const createCustomerAccountWorkflowId = "create-customer-account"
@@ -19,8 +20,19 @@ export const createCustomerAccountWorkflowId = "create-customer-account"
*/
export const createCustomerAccountWorkflow = createWorkflow(
createCustomerAccountWorkflowId,
(input: WorkflowData<CreateCustomerAccountWorkflowInput>): WorkflowResponse<CustomerDTO> => {
const customers = createCustomersStep([input.customersData])
(
input: WorkflowData<CreateCustomerAccountWorkflowInput>
): WorkflowResponse<CustomerDTO> => {
validateCustomerAccountCreation(input)
const customerData = transform({ input }, (data) => {
return {
...data.input.customerData,
has_account: !!data.input.authIdentityId,
}
})
const customers = createCustomersStep([customerData])
const customer = transform(
customers,

View File

@@ -199,6 +199,11 @@ export interface CreateCustomerDTO {
*/
created_by?: string | null
/**
* Whether the customer has an account.
*/
has_account?: boolean
/**
* The addresses of the customer.
*/

View File

@@ -1,3 +1,4 @@
import { HttpTypes } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
@@ -6,7 +7,6 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { HttpTypes } from "@medusajs/types"
export const GET = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminGetFulfillmentProvidersParams>,

View File

@@ -6,6 +6,7 @@ const defaultStoreCustomersFields = [
"last_name",
"phone",
"metadata",
"has_account",
"created_by",
"deleted_at",
"created_at",

View File

@@ -1,12 +1,12 @@
import { MedusaError } from "@medusajs/utils"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import { MedusaError } from "@medusajs/utils"
import { createCustomerAccountWorkflow } from "@medusajs/core-flows"
import { refetchCustomer } from "./helpers"
import { HttpTypes } from "@medusajs/types"
import { refetchCustomer } from "./helpers"
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.StoreCreateCustomer>,
@@ -21,10 +21,10 @@ export const POST = async (
}
const createCustomers = createCustomerAccountWorkflow(req.scope)
const customersData = req.validatedBody
const customerData = req.validatedBody
const { result } = await createCustomers.run({
input: { customersData, authIdentityId: req.auth_context.auth_identity_id },
input: { customerData, authIdentityId: req.auth_context.auth_identity_id },
})
const customer = await refetchCustomer(