chore: V2 core loader + modules integration-tests (#6544)

This commit is contained in:
Oli Juhl
2024-02-29 16:46:30 +01:00
committed by GitHub
parent dc025302a1
commit 296d7faad4
100 changed files with 318 additions and 78 deletions
@@ -1,307 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
describe("Store Carts API: Add promotions to cart", () => {
let dbConnection
let appContainer
let shutdownServer
let cartModuleService: ICartModuleService
let promotionModuleService: IPromotionModuleService
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
promotionModuleService = appContainer.resolve(
ModuleRegistrationName.PROMOTION
)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
describe("POST /store/carts/:id/promotions", () => {
it("should add line item adjustments to a cart based on promotions", async () => {
const appliedPromotion = await promotionModuleService.create({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "each",
value: "300",
apply_to_quantity: 1,
max_quantity: 1,
target_rules: [
{
attribute: "product_id",
operator: "eq",
values: "prod_tshirt",
},
],
},
})
const createdPromotion = await promotionModuleService.create({
code: "PROMOTION_TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
value: "1000",
apply_to_quantity: 1,
target_rules: [
{
attribute: "product_id",
operator: "eq",
values: "prod_mat",
},
],
},
})
const cart = await cartModuleService.create({
currency_code: "usd",
items: [
// Adjustment to add
{
id: "item-1",
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_mat",
} as any,
// This adjustment will be removed and recreated
{
id: "item-2",
unit_price: 1000,
quantity: 1,
title: "Test item",
product_id: "prod_tshirt",
} as any,
],
})
// Adjustment to keep
const [lineItemAdjustment] =
await cartModuleService.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
item_id: "item-2",
promotion_id: appliedPromotion.id,
},
])
const api = useApi() as any
const created = await api.post(`/store/carts/${cart.id}/promotions`, {
promo_codes: [createdPromotion.code],
})
expect(created.status).toEqual(200)
expect(created.data.cart).toEqual(
expect.objectContaining({
id: expect.any(String),
items: expect.arrayContaining([
expect.objectContaining({
id: "item-1",
adjustments: expect.arrayContaining([
expect.objectContaining({
code: createdPromotion.code,
amount: 1000,
}),
]),
}),
expect.objectContaining({
adjustments: expect.arrayContaining([
expect.objectContaining({
id: expect.not.stringContaining(lineItemAdjustment.id),
code: appliedPromotion.code,
amount: 300,
}),
]),
}),
]),
})
)
})
it("should add shipping method adjustments to a cart based on promotions", async () => {
const [appliedPromotion] = await promotionModuleService.create([
{
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
rules: [
{
attribute: "customer_id",
operator: "in",
values: ["cus_test"],
},
{
attribute: "currency_code",
operator: "in",
values: ["eur"],
},
],
application_method: {
type: "fixed",
target_type: "shipping_methods",
allocation: "each",
value: "100",
max_quantity: 1,
target_rules: [
{
attribute: "name",
operator: "in",
values: ["express"],
},
],
},
},
])
const [newPromotion] = await promotionModuleService.create([
{
code: "PROMOTION_NEW",
type: PromotionType.STANDARD,
rules: [
{
attribute: "customer_id",
operator: "in",
values: ["cus_test"],
},
{
attribute: "currency_code",
operator: "in",
values: ["eur"],
},
],
application_method: {
type: "fixed",
target_type: "shipping_methods",
allocation: "each",
value: "200",
max_quantity: 1,
target_rules: [
{
attribute: "name",
operator: "in",
values: ["express", "standard"],
},
],
},
},
])
const cart = await cartModuleService.create({
currency_code: "eur",
customer_id: "cus_test",
items: [
{
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_mat",
} as any,
],
})
const [express, standard] = await cartModuleService.addShippingMethods(
cart.id,
[
{
amount: 500,
name: "express",
},
{
amount: 500,
name: "standard",
},
]
)
const [adjustment] = await cartModuleService.addShippingMethodAdjustments(
cart.id,
[
{
shipping_method_id: express.id,
amount: 100,
code: appliedPromotion.code!,
},
]
)
const api = useApi() as any
const created = await api.post(`/store/carts/${cart.id}/promotions`, {
promo_codes: [newPromotion.code],
})
expect(created.status).toEqual(200)
expect(created.data.cart).toEqual(
expect.objectContaining({
id: expect.any(String),
items: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
}),
]),
shipping_methods: expect.arrayContaining([
expect.objectContaining({
id: express.id,
adjustments: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
amount: 200,
code: newPromotion.code,
}),
expect.objectContaining({
id: expect.not.stringContaining(adjustment.id),
amount: 100,
code: appliedPromotion.code,
}),
]),
}),
expect.objectContaining({
id: standard.id,
adjustments: [
expect.objectContaining({
id: expect.any(String),
amount: 200,
code: newPromotion.code,
}),
],
}),
]),
})
)
})
})
})
@@ -1,672 +0,0 @@
import {
addToCartWorkflow,
createCartWorkflow,
deleteLineItemsStepId,
deleteLineItemsWorkflow,
findOrCreateCustomerStepId,
updateLineItemInCartWorkflow,
updateLineItemsStepId,
} from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
ICartModuleService,
ICustomerModuleService,
IPricingModuleService,
IProductModuleService,
IRegionModuleService,
ISalesChannelModuleService,
} from "@medusajs/types"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
describe("Carts workflows", () => {
let dbConnection
let appContainer
let shutdownServer
let cartModuleService: ICartModuleService
let regionModuleService: IRegionModuleService
let scModuleService: ISalesChannelModuleService
let customerModule: ICustomerModuleService
let productModule: IProductModuleService
let pricingModule: IPricingModuleService
let remoteLink
let defaultRegion
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION)
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
remoteLink = appContainer.resolve("remoteLink")
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
beforeEach(async () => {
await adminSeeder(dbConnection)
await regionModuleService.createDefaultCountries()
// Here, so we don't have to create a region for each test
defaultRegion = await regionModuleService.create({
name: "Default Region",
currency_code: "dkk",
})
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
describe("CreateCartWorkflow", () => {
it("should create a cart", async () => {
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
])
const { result } = await createCartWorkflow(appContainer).run({
input: {
email: "tony@stark.com",
currency_code: "usd",
region_id: region.id,
sales_channel_id: salesChannel.id,
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
},
})
const cart = await cartModuleService.retrieve(result.id, {
relations: ["items"],
})
expect(cart).toEqual(
expect.objectContaining({
currency_code: "usd",
email: "tony@stark.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
customer_id: expect.any(String),
items: expect.arrayContaining([
expect.objectContaining({
quantity: 1,
unit_price: 3000,
}),
]),
})
)
})
it("should throw when no regions exist", async () => {
await regionModuleService.delete(defaultRegion.id)
const { errors } = await createCartWorkflow(appContainer).run({
input: {
email: "tony@stark.com",
currency_code: "usd",
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "find-one-or-any-region",
handlerType: "invoke",
error: new Error("No regions found"),
},
])
})
it("should throw if sales channel is disabled", async () => {
const api = useApi() as any
const salesChannel = await scModuleService.create({
name: "Webshop",
is_disabled: true,
})
const { errors } = await createCartWorkflow(appContainer).run({
input: {
sales_channel_id: salesChannel.id,
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "find-sales-channel",
handlerType: "invoke",
error: new Error(
`Unable to assign cart to disabled Sales Channel: Webshop`
),
},
])
})
describe("compensation", () => {
it("should delete created customer if cart-creation fails", async () => {
expect.assertions(2)
const workflow = createCartWorkflow(appContainer)
workflow.appendAction("throw", findOrCreateCustomerStepId, {
invoke: async function failStep() {
throw new Error(`Failed to create cart`)
},
})
const { errors } = await workflow.run({
input: {
currency_code: "usd",
email: "tony@stark-industries.com",
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: new Error(`Failed to create cart`),
},
])
const customers = await customerModule.list({
email: "tony@stark-industries.com",
})
expect(customers).toHaveLength(0)
})
it("should not delete existing customer if cart-creation fails", async () => {
expect.assertions(2)
const workflow = createCartWorkflow(appContainer)
workflow.appendAction("throw", findOrCreateCustomerStepId, {
invoke: async function failStep() {
throw new Error(`Failed to create cart`)
},
})
const customer = await customerModule.create({
email: "tony@stark-industries.com",
})
const { errors } = await workflow.run({
input: {
currency_code: "usd",
customer_id: customer.id,
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: new Error(`Failed to create cart`),
},
])
const customers = await customerModule.list({
email: "tony@stark-industries.com",
})
expect(customers).toHaveLength(1)
})
})
})
describe("AddToCartWorkflow", () => {
it("should add item to cart", async () => {
let cart = await cartModuleService.create({
currency_code: "usd",
})
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id", "region_id", "currency_code"],
})
await addToCartWorkflow(appContainer).run({
input: {
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
cart,
},
})
cart = await cartModuleService.retrieve(cart.id, {
relations: ["items"],
})
expect(cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 3000,
quantity: 1,
title: "Test variant",
}),
]),
})
)
})
it("should throw if no price sets for variant exist", async () => {
const cart = await cartModuleService.create({
currency_code: "usd",
})
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
const { errors } = await addToCartWorkflow(appContainer).run({
input: {
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
],
cart,
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "get-variant-price-sets",
handlerType: "invoke",
error: new Error(
`Variants with IDs ${product.variants[0].id} do not have a price`
),
},
])
})
it("should throw if variant does not exist", async () => {
const cart = await cartModuleService.create({
currency_code: "usd",
})
const { errors } = await addToCartWorkflow(appContainer).run({
input: {
items: [
{
variant_id: "prva_foo",
quantity: 1,
},
],
cart,
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "validate-variants-exist",
handlerType: "invoke",
error: new Error(`Variants with IDs prva_foo do not exist`),
},
])
})
})
describe("updateLineItemInCartWorkflow", () => {
it("should update item in cart", async () => {
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
])
let cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
unit_price: 5000,
title: "Test item",
},
],
})
cart = await cartModuleService.retrieve(cart.id, {
select: ["id", "region_id", "currency_code"],
relations: ["items", "items.variant_id", "items.metadata"],
})
const item = cart.items?.[0]!
await updateLineItemInCartWorkflow(appContainer).run({
input: {
cart,
item,
update: {
metadata: {
foo: "bar",
},
quantity: 2,
},
},
throwOnError: false,
})
const updatedItem = await cartModuleService.retrieveLineItem(item.id)
expect(updatedItem).toEqual(
expect.objectContaining({
id: item.id,
unit_price: 3000,
quantity: 2,
title: "Test item",
})
)
})
describe("compensation", () => {
it("should revert line item update to original state", async () => {
expect.assertions(2)
const workflow = updateLineItemInCartWorkflow(appContainer)
workflow.appendAction("throw", updateLineItemsStepId, {
invoke: async function failStep() {
throw new Error(`Failed to update something after line items`)
},
})
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
let cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
unit_price: 3000,
title: "Test item",
},
],
})
const priceSet = await pricingModule.create({
prices: [
{
amount: 5000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id", "region_id", "currency_code"],
relations: ["items", "items.variant_id", "items.metadata"],
})
const item = cart.items?.[0]!
const { errors } = await workflow.run({
input: {
cart,
item,
update: {
metadata: {
foo: "bar",
},
title: "Test item updated",
quantity: 2,
},
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: new Error(`Failed to update something after line items`),
},
])
const updatedItem = await cartModuleService.retrieveLineItem(item.id)
expect(updatedItem).toEqual(
expect.objectContaining({
id: item.id,
unit_price: 3000,
quantity: 1,
title: "Test item",
})
)
})
})
})
describe("deleteLineItems", () => {
it("should delete items in cart", async () => {
const cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
quantity: 1,
unit_price: 5000,
title: "Test item",
},
],
})
const items = await cartModuleService.listLineItems({ cart_id: cart.id })
await deleteLineItemsWorkflow(appContainer).run({
input: {
ids: items.map((i) => i.id),
},
throwOnError: false,
})
const [deletedItem] = await cartModuleService.listLineItems({
id: items.map((i) => i.id),
})
expect(deletedItem).toBeUndefined()
})
describe("compensation", () => {
it("should restore line item if delete fails", async () => {
const workflow = deleteLineItemsWorkflow(appContainer)
workflow.appendAction("throw", deleteLineItemsStepId, {
invoke: async function failStep() {
throw new Error(`Failed to do something after deleting line items`)
},
})
const cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
quantity: 1,
unit_price: 3000,
title: "Test item",
},
],
})
const items = await cartModuleService.listLineItems({
cart_id: cart.id,
})
const { errors } = await workflow.run({
input: {
ids: items.map((i) => i.id),
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: new Error(
`Failed to do something after deleting line items`
),
},
])
const updatedItem = await cartModuleService.retrieveLineItem(
items[0].id
)
expect(updatedItem).not.toBeUndefined()
})
})
})
})
@@ -1,673 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import {
ICartModuleService,
ICustomerModuleService,
IPricingModuleService,
IProductModuleService,
IPromotionModuleService,
IRegionModuleService,
ISalesChannelModuleService,
} from "@medusajs/types"
import { PromotionRuleOperator, PromotionType } from "@medusajs/utils"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"
import { createAuthenticatedCustomer } from "../../../helpers/create-authenticated-customer"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
describe("Store Carts API", () => {
let dbConnection
let appContainer
let shutdownServer
let cartModuleService: ICartModuleService
let regionModuleService: IRegionModuleService
let scModuleService: ISalesChannelModuleService
let customerModule: ICustomerModuleService
let productModule: IProductModuleService
let pricingModule: IPricingModuleService
let remoteLink
let promotionModule: IPromotionModuleService
let defaultRegion
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
regionModuleService = appContainer.resolve(ModuleRegistrationName.REGION)
scModuleService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
customerModule = appContainer.resolve(ModuleRegistrationName.CUSTOMER)
productModule = appContainer.resolve(ModuleRegistrationName.PRODUCT)
pricingModule = appContainer.resolve(ModuleRegistrationName.PRICING)
remoteLink = appContainer.resolve("remoteLink")
promotionModule = appContainer.resolve(ModuleRegistrationName.PROMOTION)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
beforeEach(async () => {
await adminSeeder(dbConnection)
await regionModuleService.createDefaultCountries()
// Here, so we don't have to create a region for each test
defaultRegion = await regionModuleService.create({
name: "Default Region",
currency_code: "dkk",
})
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
describe("POST /store/carts", () => {
it("should create a cart", async () => {
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
{
title: "Test variant 2",
},
],
},
])
const [priceSet, priceSetTwo] = await pricingModule.create([
{
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
},
{
prices: [
{
amount: 4000,
currency_code: "usd",
},
],
},
])
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
{
productService: {
variant_id: product.variants[1].id,
},
pricingService: {
price_set_id: priceSetTwo.id,
},
},
])
const api = useApi() as any
const created = await api.post(`/store/carts`, {
email: "tony@stark.com",
currency_code: "usd",
region_id: region.id,
sales_channel_id: salesChannel.id,
items: [
{
variant_id: product.variants[0].id,
quantity: 1,
},
{
variant_id: product.variants[1].id,
quantity: 2,
},
],
})
expect(created.status).toEqual(200)
expect(created.data.cart).toEqual(
expect.objectContaining({
id: created.data.cart.id,
currency_code: "usd",
email: "tony@stark.com",
region: expect.objectContaining({
id: region.id,
currency_code: "usd",
}),
sales_channel_id: salesChannel.id,
customer: expect.objectContaining({
email: "tony@stark.com",
}),
items: expect.arrayContaining([
expect.objectContaining({
quantity: 1,
unit_price: 3000,
}),
expect.objectContaining({
quantity: 2,
unit_price: 4000,
}),
]),
})
)
})
it("should create cart with customer from email", async () => {
const api = useApi() as any
const created = await api.post(`/store/carts`, {
currency_code: "usd",
email: "tony@stark-industries.com",
})
expect(created.status).toEqual(200)
expect(created.data.cart).toEqual(
expect.objectContaining({
id: created.data.cart.id,
currency_code: "usd",
email: "tony@stark-industries.com",
customer: expect.objectContaining({
id: expect.any(String),
email: "tony@stark-industries.com",
}),
})
)
})
it("should create cart with any region", async () => {
await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const api = useApi() as any
const response = await api.post(`/store/carts`, {
email: "tony@stark.com",
currency_code: "usd",
})
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: response.data.cart.id,
currency_code: "usd",
email: "tony@stark.com",
region: expect.objectContaining({
id: expect.any(String),
}),
})
)
})
it("should create cart with region currency code", async () => {
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const api = useApi() as any
const response = await api.post(`/store/carts`, {
email: "tony@stark.com",
region_id: region.id,
})
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: response.data.cart.id,
currency_code: "usd",
email: "tony@stark.com",
region: expect.objectContaining({
id: region.id,
}),
})
)
})
it("should create cart with logged-in customer", async () => {
const { customer, jwt } = await createAuthenticatedCustomer(appContainer)
const api = useApi() as any
const response = await api.post(
`/store/carts`,
{},
{
headers: { authorization: `Bearer ${jwt}` },
}
)
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: response.data.cart.id,
currency_code: "dkk",
email: customer.email,
customer: expect.objectContaining({
id: customer.id,
email: customer.email,
}),
})
)
})
it("should respond 400 bad request on unknown props", async () => {
const api = useApi() as any
await expect(
api.post(`/store/carts`, {
foo: "bar",
})
).rejects.toThrow()
})
})
describe("POST /store/carts/:id", () => {
it("should update a cart with promo codes with a replace action", async () => {
const targetRules = [
{
attribute: "product_id",
operator: PromotionRuleOperator.IN,
values: ["prod_tshirt"],
},
]
const appliedPromotion = await promotionModule.create({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "each",
value: "300",
apply_to_quantity: 1,
max_quantity: 1,
target_rules: targetRules,
},
})
const createdPromotion = await promotionModule.create({
code: "PROMOTION_TEST",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
value: "1000",
apply_to_quantity: 1,
target_rules: targetRules,
},
})
const cart = await cartModuleService.create({
currency_code: "usd",
email: "tony@stark.com",
items: [
{
id: "item-1",
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_tshirt",
} as any,
],
})
const [adjustment] = await cartModuleService.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
const api = useApi() as any
// Should remove earlier adjustments from other promocodes
let updated = await api.post(`/store/carts/${cart.id}`, {
promo_codes: [createdPromotion.code],
})
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
items: [
expect.objectContaining({
id: "item-1",
adjustments: [
expect.objectContaining({
id: expect.not.stringContaining(adjustment.id),
code: createdPromotion.code,
}),
],
}),
],
})
)
// Should remove all adjustments from other promo codes
updated = await api.post(`/store/carts/${cart.id}`, {
promo_codes: [],
})
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
items: [
expect.objectContaining({
id: "item-1",
adjustments: [],
}),
],
})
)
})
it("should update a cart's region, sales channel and customer data", async () => {
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const cart = await cartModuleService.create({
currency_code: "eur",
})
const api = useApi() as any
let updated = await api.post(`/store/carts/${cart.id}`, {
region_id: region.id,
email: "tony@stark.com",
sales_channel_id: salesChannel.id,
})
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
region: expect.objectContaining({
id: region.id,
currency_code: "usd",
}),
email: "tony@stark.com",
customer: expect.objectContaining({
email: "tony@stark.com",
}),
sales_channel_id: salesChannel.id,
})
)
updated = await api.post(`/store/carts/${cart.id}`, {
email: null,
sales_channel_id: null,
})
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
email: null,
customer_id: null,
region: expect.objectContaining({
id: region.id,
currency_code: "usd",
}),
sales_channel_id: null,
})
)
})
})
describe("GET /store/carts/:id", () => {
it("should create and update a cart", async () => {
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const api = useApi() as any
const created = await api.post(`/store/carts`, {
email: "tony@stark.com",
currency_code: "usd",
region_id: region.id,
sales_channel_id: salesChannel.id,
})
expect(created.status).toEqual(200)
expect(created.data.cart).toEqual(
expect.objectContaining({
id: created.data.cart.id,
currency_code: "usd",
email: "tony@stark.com",
region: expect.objectContaining({
id: region.id,
currency_code: "usd",
}),
sales_channel_id: salesChannel.id,
})
)
const updated = await api.post(`/store/carts/${created.data.cart.id}`, {
email: "tony@stark-industries.com",
})
expect(updated.status).toEqual(200)
expect(updated.data.cart).toEqual(
expect.objectContaining({
id: updated.data.cart.id,
currency_code: "usd",
email: "tony@stark-industries.com",
})
)
})
})
describe("GET /store/carts", () => {
it("should get cart", async () => {
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
unit_price: 1000,
quantity: 1,
title: "Test item",
},
],
region_id: region.id,
sales_channel_id: salesChannel.id,
})
const api = useApi() as any
const response = await api.get(`/store/carts/${cart.id}`)
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 1000,
quantity: 1,
title: "Test item",
}),
]),
region: expect.objectContaining({
id: region.id,
currency_code: "usd",
}),
sales_channel_id: salesChannel.id,
})
)
})
})
describe("POST /store/carts/:id/line-items", () => {
it("should add item to cart", async () => {
const [product] = await productModule.create([
{
title: "Test product",
variants: [
{
title: "Test variant",
},
],
},
])
const cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
id: "item-1",
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_mat",
} as any,
],
})
const appliedPromotion = await promotionModule.create({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "across",
value: "300",
apply_to_quantity: 2,
target_rules: [
{
attribute: "product_id",
operator: "in",
values: ["prod_mat", product.id],
},
],
},
})
const [lineItemAdjustment] =
await cartModuleService.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
item_id: "item-1",
promotion_id: appliedPromotion.id,
},
])
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
productService: {
variant_id: product.variants[0].id,
},
pricingService: {
price_set_id: priceSet.id,
},
},
])
const api = useApi() as any
const response = await api.post(`/store/carts/${cart.id}/line-items`, {
variant_id: product.variants[0].id,
quantity: 1,
})
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: cart.id,
currency_code: "usd",
items: expect.arrayContaining([
expect.objectContaining({
unit_price: 3000,
quantity: 1,
title: "Test variant",
adjustments: [
expect.objectContaining({
code: "PROMOTION_APPLIED",
amount: 180,
}),
],
}),
expect.objectContaining({
unit_price: 2000,
quantity: 1,
title: "Test item",
adjustments: [
expect.objectContaining({
id: expect.not.stringContaining(lineItemAdjustment.id),
code: "PROMOTION_APPLIED",
amount: 120,
}),
],
}),
]),
})
)
})
})
})
@@ -1,300 +0,0 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICartModuleService, IPromotionModuleService } from "@medusajs/types"
import { PromotionType } from "@medusajs/utils"
import path from "path"
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
import { useApi } from "../../../../environment-helpers/use-api"
import { getContainer } from "../../../../environment-helpers/use-container"
import { initDb, useDb } from "../../../../environment-helpers/use-db"
import adminSeeder from "../../../../helpers/admin-seeder"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
describe("Store Carts API: Remove promotions from cart", () => {
let dbConnection
let appContainer
let shutdownServer
let cartModuleService: ICartModuleService
let promotionModuleService: IPromotionModuleService
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
dbConnection = await initDb({ cwd, env } as any)
shutdownServer = await startBootstrapApp({ cwd, env })
appContainer = getContainer()
cartModuleService = appContainer.resolve(ModuleRegistrationName.CART)
promotionModuleService = appContainer.resolve(
ModuleRegistrationName.PROMOTION
)
})
afterAll(async () => {
const db = useDb()
await db.shutdown()
await shutdownServer()
})
beforeEach(async () => {
await adminSeeder(dbConnection)
})
afterEach(async () => {
const db = useDb()
await db.teardown()
})
describe("DELETE /store/carts/:id/promotions", () => {
it("should remove line item adjustments from a cart based on promotions", async () => {
const appliedPromotion = await promotionModuleService.create({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "each",
value: "300",
apply_to_quantity: 1,
max_quantity: 1,
target_rules: [
{
attribute: "product_id",
operator: "eq",
values: "prod_tshirt",
},
],
},
})
const appliedPromotionToRemove = await promotionModuleService.create({
code: "PROMOTION_APPLIED_TO_REMOVE",
type: PromotionType.STANDARD,
application_method: {
type: "fixed",
target_type: "items",
allocation: "each",
value: "300",
apply_to_quantity: 1,
max_quantity: 1,
target_rules: [
{
attribute: "product_id",
operator: "eq",
values: "prod_tshirt",
},
],
},
})
const cart = await cartModuleService.create({
currency_code: "usd",
items: [
{
id: "item-1",
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_mat",
} as any,
{
id: "item-2",
unit_price: 1000,
quantity: 1,
title: "Test item",
product_id: "prod_tshirt",
} as any,
],
})
const [adjustment1, adjustment2] =
await cartModuleService.addLineItemAdjustments([
{
code: appliedPromotion.code!,
amount: 300,
item_id: "item-2",
promotion_id: appliedPromotionToRemove.id,
},
{
code: appliedPromotionToRemove.code!,
amount: 150,
item_id: "item-1",
promotion_id: appliedPromotionToRemove.id,
},
{
code: appliedPromotionToRemove.code!,
amount: 150,
item_id: "item-2",
promotion_id: appliedPromotionToRemove.id,
},
])
const api = useApi() as any
const response = await api.delete(`/store/carts/${cart.id}/promotions`, {
data: {
promo_codes: [appliedPromotionToRemove.code],
},
})
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: expect.any(String),
items: expect.arrayContaining([
expect.objectContaining({
id: "item-1",
adjustments: [],
}),
expect.objectContaining({
id: "item-2",
adjustments: [
expect.objectContaining({
amount: 300,
code: appliedPromotion.code,
}),
],
}),
]),
})
)
})
it("should add shipping method adjustments to a cart based on promotions", async () => {
const appliedPromotion = await promotionModuleService.create({
code: "PROMOTION_APPLIED",
type: PromotionType.STANDARD,
rules: [
{
attribute: "customer_id",
operator: "in",
values: ["cus_test"],
},
{
attribute: "currency_code",
operator: "in",
values: ["eur"],
},
],
application_method: {
type: "fixed",
target_type: "shipping_methods",
allocation: "each",
value: "100",
max_quantity: 1,
target_rules: [
{
attribute: "name",
operator: "in",
values: ["express"],
},
],
},
})
const appliedPromotionToRemove = await promotionModuleService.create({
code: "PROMOTION_APPLIED_TO_REMOVE",
type: PromotionType.STANDARD,
rules: [
{
attribute: "customer_id",
operator: "in",
values: ["cus_test"],
},
{
attribute: "currency_code",
operator: "in",
values: ["eur"],
},
],
application_method: {
type: "fixed",
target_type: "shipping_methods",
allocation: "each",
value: "100",
max_quantity: 1,
target_rules: [
{
attribute: "name",
operator: "in",
values: ["express"],
},
],
},
})
const cart = await cartModuleService.create({
currency_code: "eur",
customer_id: "cus_test",
items: [
{
unit_price: 2000,
quantity: 1,
title: "Test item",
product_id: "prod_mat",
},
],
})
const [express, standard] = await cartModuleService.addShippingMethods(
cart.id,
[
{
amount: 500,
name: "express",
},
{
amount: 500,
name: "standard",
},
]
)
await cartModuleService.addShippingMethodAdjustments(cart.id, [
{
shipping_method_id: express.id,
amount: 100,
code: appliedPromotion.code!,
},
{
shipping_method_id: express.id,
amount: 100,
code: appliedPromotionToRemove.code!,
},
])
const api = useApi() as any
const response = await api.delete(`/store/carts/${cart.id}/promotions`, {
data: { promo_codes: [appliedPromotionToRemove.code] },
})
expect(response.status).toEqual(200)
expect(response.data.cart).toEqual(
expect.objectContaining({
id: expect.any(String),
items: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
}),
]),
shipping_methods: expect.arrayContaining([
expect.objectContaining({
id: express.id,
adjustments: [
expect.objectContaining({
amount: 100,
code: appliedPromotion.code,
}),
],
}),
expect.objectContaining({
id: standard.id,
adjustments: [],
}),
]),
})
)
})
})
})