feat: List shipping options for cart (#6677)

This commit is contained in:
Oli Juhl
2024-03-13 09:08:47 +00:00
committed by GitHub
parent db26dafa85
commit 25d176b851
23 changed files with 855 additions and 142 deletions
@@ -7,6 +7,7 @@ import {
deleteLineItemsWorkflow,
findOrCreateCustomerStepId,
linkCartAndPaymentCollectionsStepId,
listShippingOptionsForCartWorkflow,
refreshPaymentCollectionForCartWorkflow,
updateLineItemInCartWorkflow,
updateLineItemsStepId,
@@ -22,7 +23,9 @@ import {
IProductModuleService,
IRegionModuleService,
ISalesChannelModuleService,
IStockLocationServiceNext,
} from "@medusajs/types"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import adminSeeder from "../../../../helpers/admin-seeder"
@@ -43,6 +46,7 @@ medusaIntegrationTestRunner({
let pricingModule: IPricingModuleService
let paymentModule: IPaymentModuleService
let fulfillmentModule: IFulfillmentModuleService
let locationModule: IStockLocationServiceNext
let remoteLink, remoteQuery
let defaultRegion
@@ -63,8 +67,13 @@ medusaIntegrationTestRunner({
fulfillmentModule = appContainer.resolve(
ModuleRegistrationName.FULFILLMENT
)
remoteLink = appContainer.resolve("remoteLink")
remoteQuery = appContainer.resolve("remoteQuery")
locationModule = appContainer.resolve(
ModuleRegistrationName.STOCK_LOCATION
)
remoteLink = appContainer.resolve(ContainerRegistrationKeys.REMOTE_LINK)
remoteQuery = appContainer.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
})
beforeEach(async () => {
@@ -672,12 +681,84 @@ medusaIntegrationTestRunner({
expect(updatedItem).not.toBeUndefined()
})
})
})
describe("createPaymentCollectionForCart", () => {
it("should create a payment collection and link it to cart", async () => {
const cart = await cartModuleService.create({
currency_code: "dkk",
describe("createPaymentCollectionForCart", () => {
it("should create a payment collection and link it to cart", async () => {
const cart = await cartModuleService.create({
currency_code: "dkk",
region_id: defaultRegion.id,
items: [
{
quantity: 1,
unit_price: 5000,
title: "Test item",
},
],
})
await createPaymentCollectionForCartWorkflow(appContainer).run({
input: {
cart_id: cart.id,
region_id: defaultRegion.id,
currency_code: "dkk",
amount: 5000,
},
throwOnError: false,
})
const result = await remoteQuery(
{
cart: {
fields: ["id"],
payment_collection: {
fields: ["id", "amount", "currency_code"],
},
},
},
{
cart: {
id: cart.id,
},
}
)
expect(result).toEqual([
expect.objectContaining({
id: cart.id,
payment_collection: expect.objectContaining({
amount: 5000,
currency_code: "dkk",
}),
}),
])
})
describe("compensation", () => {
it("should dismiss cart <> payment collection link and delete created payment collection", async () => {
const workflow =
createPaymentCollectionForCartWorkflow(appContainer)
workflow.appendAction(
"throw",
linkCartAndPaymentCollectionsStepId,
{
invoke: async function failStep() {
throw new Error(
`Failed to do something after linking cart and payment collection`
)
},
}
)
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const cart = await cartModuleService.create({
currency_code: "usd",
region_id: region.id,
items: [
{
quantity: 1,
@@ -687,17 +768,27 @@ medusaIntegrationTestRunner({
],
})
await createPaymentCollectionForCartWorkflow(appContainer).run({
const { errors } = await workflow.run({
input: {
cart_id: cart.id,
region_id: defaultRegion.id,
currency_code: "dkk",
region_id: region.id,
currency_code: "usd",
amount: 5000,
},
throwOnError: false,
})
const result = await remoteQuery(
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: new Error(
`Failed to do something after linking cart and payment collection`
),
},
])
const carts = await remoteQuery(
{
cart: {
fields: ["id"],
@@ -713,101 +804,19 @@ medusaIntegrationTestRunner({
}
)
expect(result).toEqual([
const payCols = await remoteQuery({
payment_collection: {
fields: ["id"],
},
})
expect(carts).toEqual([
expect.objectContaining({
id: cart.id,
payment_collection: expect.objectContaining({
amount: 5000,
currency_code: "dkk",
}),
payment_collection: undefined,
}),
])
})
describe("compensation", () => {
it("should dismiss cart <> payment collection link and delete created payment collection", async () => {
const workflow =
createPaymentCollectionForCartWorkflow(appContainer)
workflow.appendAction(
"throw",
linkCartAndPaymentCollectionsStepId,
{
invoke: async function failStep() {
throw new Error(
`Failed to do something after linking cart and payment collection`
)
},
}
)
const region = await regionModuleService.create({
name: "US",
currency_code: "usd",
})
const cart = await cartModuleService.create({
currency_code: "usd",
region_id: region.id,
items: [
{
quantity: 1,
unit_price: 5000,
title: "Test item",
},
],
})
const { errors } = await workflow.run({
input: {
cart_id: cart.id,
region_id: region.id,
currency_code: "usd",
amount: 5000,
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: new Error(
`Failed to do something after linking cart and payment collection`
),
},
])
const carts = await remoteQuery(
{
cart: {
fields: ["id"],
payment_collection: {
fields: ["id", "amount", "currency_code"],
},
},
},
{
cart: {
id: cart.id,
},
}
)
const payCols = await remoteQuery({
payment_collection: {
fields: ["id"],
},
})
expect(carts).toEqual([
expect.objectContaining({
id: cart.id,
payment_collection: undefined,
}),
])
expect(payCols.length).toEqual(0)
})
expect(payCols.length).toEqual(0)
})
})
})
@@ -995,24 +1004,26 @@ medusaIntegrationTestRunner({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
})
const serviceZone = await fulfillmentModule.createServiceZones({
name: "Test",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
service_zones: [
{
type: "country",
country_code: "us",
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: serviceZone.id,
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
@@ -1077,6 +1088,337 @@ medusaIntegrationTestRunner({
)
})
})
describe("listShippingOptionsForCartWorkflow", () => {
it("should list shipping options for cart", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await locationModule.create({
name: "Europe",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
shipping_address: {
city: "CPH",
province: "Sjaelland",
country_code: "dk",
},
})
const shippingProfile =
await fulfillmentModule.createShippingProfiles({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
service_zones: [
{
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
})
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
shipping_option_id: shippingOption.id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id"],
relations: ["shipping_address"],
})
const { result } = await listShippingOptionsForCartWorkflow(
appContainer
).run({
input: {
cart_id: cart.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
shipping_address: {
city: cart.shipping_address?.city,
province: cart.shipping_address?.province,
country_code: cart.shipping_address?.country_code,
},
},
})
expect(result).toEqual([
expect.objectContaining({
amount: 3000,
name: "Test shipping option",
id: shippingOption.id,
}),
])
})
it("should list no shipping options for cart, if sales channel is not associated with location", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await locationModule.create({
name: "Europe",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
shipping_address: {
city: "CPH",
province: "Sjaelland",
country_code: "dk",
},
})
const shippingProfile =
await fulfillmentModule.createShippingProfiles({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
service_zones: [
{
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
})
const priceSet = await pricingModule.create({
prices: [
{
amount: 3000,
currency_code: "usd",
},
],
})
await remoteLink.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
shipping_option_id: shippingOption.id,
},
[Modules.PRICING]: {
price_set_id: priceSet.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id"],
relations: ["shipping_address"],
})
const { result } = await listShippingOptionsForCartWorkflow(
appContainer
).run({
input: {
cart_id: cart.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
shipping_address: {
city: cart.shipping_address?.city,
province: cart.shipping_address?.province,
country_code: cart.shipping_address?.country_code,
},
},
})
expect(result).toEqual([])
})
it("should throw when shipping options are missing prices", async () => {
const salesChannel = await scModuleService.create({
name: "Webshop",
})
const location = await locationModule.create({
name: "Europe",
})
let cart = await cartModuleService.create({
currency_code: "usd",
sales_channel_id: salesChannel.id,
shipping_address: {
city: "CPH",
province: "Sjaelland",
country_code: "dk",
},
})
const shippingProfile =
await fulfillmentModule.createShippingProfiles({
name: "Test",
type: "default",
})
const fulfillmentSet = await fulfillmentModule.create({
name: "Test",
type: "test-type",
service_zones: [
{
name: "Test",
geo_zones: [
{
type: "country",
country_code: "us",
},
],
},
],
})
const shippingOption = await fulfillmentModule.createShippingOptions({
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
})
await remoteLink.create([
{
[Modules.SALES_CHANNEL]: {
sales_channel_id: salesChannel.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: location.id,
},
},
])
cart = await cartModuleService.retrieve(cart.id, {
select: ["id"],
relations: ["shipping_address"],
})
const { errors } = await listShippingOptionsForCartWorkflow(
appContainer
).run({
input: {
cart_id: cart.id,
sales_channel_id: salesChannel.id,
currency_code: "usd",
shipping_address: {
city: cart.shipping_address?.city,
province: cart.shipping_address?.province,
country_code: cart.shipping_address?.country_code,
},
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "get-shipping-option-price-sets",
error: new Error(
`Shipping options with IDs ${shippingOption.id} do not have a price`
),
handlerType: "invoke",
},
])
})
})
})
},
})
@@ -0,0 +1,87 @@
import { ModuleRegistrationName, Modules } from "@medusajs/modules-sdk"
import {
IFulfillmentModuleService,
ISalesChannelModuleService,
IStockLocationServiceNext,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
jest.setTimeout(50000)
const env = { MEDUSA_FF_MEDUSA_V2: true }
medusaIntegrationTestRunner({
env,
testSuite: ({ getContainer }) => {
describe("FulfillmentSet and Location", () => {
let appContainer
let fulfillmentModule: IFulfillmentModuleService
let locationModule: IStockLocationServiceNext
let scService: ISalesChannelModuleService
let remoteQuery
let remoteLink
beforeAll(async () => {
appContainer = getContainer()
fulfillmentModule = appContainer.resolve(
ModuleRegistrationName.FULFILLMENT
)
locationModule = appContainer.resolve(
ModuleRegistrationName.STOCK_LOCATION
)
scService = appContainer.resolve(ModuleRegistrationName.SALES_CHANNEL)
remoteQuery = appContainer.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
remoteLink = appContainer.resolve(ContainerRegistrationKeys.REMOTE_LINK)
})
it("should query fulfillment set and location link with remote query", async () => {
const fulfillmentSet = await fulfillmentModule.create({
name: "Test fulfillment set",
type: "delivery",
})
const euWarehouse = await locationModule.create({
name: "EU Warehouse",
})
await remoteLink.create([
{
[Modules.FULFILLMENT]: {
fulfillment_set_id: fulfillmentSet.id,
},
[Modules.STOCK_LOCATION]: {
stock_location_id: euWarehouse.id,
},
},
])
const linkQuery = remoteQueryObjectFromString({
entryPoint: "fulfillment_sets",
fields: ["id", "stock_locations.id"],
})
const link = await remoteQuery(linkQuery)
expect(link).toHaveLength(1)
expect(link).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: fulfillmentSet.id,
stock_locations: expect.arrayContaining([
expect.objectContaining({
id: euWarehouse.id,
}),
]),
}),
])
)
})
})
},
})
@@ -56,7 +56,7 @@ medusaIntegrationTestRunner({
sales_channel_id: scWebshop.id,
},
[Modules.STOCK_LOCATION]: {
location_id: euWarehouse.id,
stock_location_id: euWarehouse.id,
},
},
{
@@ -64,7 +64,7 @@ medusaIntegrationTestRunner({
sales_channel_id: scCphStore.id,
},
[Modules.STOCK_LOCATION]: {
location_id: euWarehouse.id,
stock_location_id: euWarehouse.id,
},
},
{
@@ -72,7 +72,7 @@ medusaIntegrationTestRunner({
sales_channel_id: scNycStore.id,
},
[Modules.STOCK_LOCATION]: {
location_id: usWarehouse.id,
stock_location_id: usWarehouse.id,
},
},
])