fix(medusa): Shipping profile CRUD (#3154)
**What** - Fixes wrong payload class for `POST /admin/shipping-profiles` - Fixes wrong payload class for `POST /admin/shipping-profiles/:id` - Fixes an issue where updating a shipping profile with products and/or shipping options would fail. - Fixes an issue where passing `profile_id` to `ShippingOptionService.update()` would not update the shipping profile of the option. **Testing** - Adds new `simpleshippingProfileFactory` - Adds new integration test suite for shipping profiles operations. Resolves CORE-1065
This commit is contained in:
committed by
GitHub
parent
4d6e63d68f
commit
d0adaf57ed
346
integration-tests/api/__tests__/admin/shipping-profile.js
Normal file
346
integration-tests/api/__tests__/admin/shipping-profile.js
Normal file
@@ -0,0 +1,346 @@
|
||||
const path = require("path")
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server")
|
||||
const { useApi } = require("../../../helpers/use-api")
|
||||
const { initDb, useDb } = require("../../../helpers/use-db")
|
||||
const {
|
||||
simpleProductFactory,
|
||||
simpleShippingOptionFactory,
|
||||
simpleShippingProfileFactory,
|
||||
} = require("../../factories")
|
||||
const adminSeeder = require("../../helpers/admin-seeder")
|
||||
|
||||
const adminReqConfig = {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("/admin/shipping-profiles", () => {
|
||||
let medusaProcess
|
||||
let dbConnection
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
medusaProcess = await setupServer({ cwd, verbose: false })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
|
||||
medusaProcess.kill()
|
||||
})
|
||||
|
||||
describe("GET /admin/shipping-profiles", () => {
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("lists shipping profiles", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const {
|
||||
data: { shipping_profiles },
|
||||
status,
|
||||
} = await api.get("/admin/shipping-profiles", adminReqConfig)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
|
||||
// Should contain default and gift_card profiles
|
||||
expect(shipping_profiles.length).toEqual(2)
|
||||
})
|
||||
|
||||
it("gets a shipping profile by id", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const profile = await simpleShippingProfileFactory(dbConnection)
|
||||
|
||||
const {
|
||||
data: { shipping_profile },
|
||||
status,
|
||||
} = await api.get(
|
||||
`/admin/shipping-profiles/${profile.id}`,
|
||||
adminReqConfig
|
||||
)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(shipping_profile).toEqual(
|
||||
expect.objectContaining({
|
||||
...profile,
|
||||
updated_at: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/shipping-profiles", () => {
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("creates a custom shipping profile", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const payload = {
|
||||
name: "test-profile-2023",
|
||||
type: "custom",
|
||||
}
|
||||
|
||||
const {
|
||||
data: { shipping_profile },
|
||||
status,
|
||||
} = await api.post("/admin/shipping-profiles", payload, adminReqConfig)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(shipping_profile).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
...payload,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("creates a default shipping profile", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const payload = {
|
||||
name: "test-profile-2023",
|
||||
type: "default",
|
||||
}
|
||||
|
||||
const {
|
||||
data: { shipping_profile },
|
||||
status,
|
||||
} = await api.post("/admin/shipping-profiles", payload, adminReqConfig)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(shipping_profile).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
...payload,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("creates a gift_card shipping profile", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const payload = {
|
||||
name: "test-profile-2023",
|
||||
type: "gift_card",
|
||||
}
|
||||
|
||||
const {
|
||||
data: { shipping_profile },
|
||||
status,
|
||||
} = await api.post("/admin/shipping-profiles", payload, adminReqConfig)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(shipping_profile).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
...payload,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("creates a shipping profile with metadata", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const payload = {
|
||||
name: "test-profile-2023",
|
||||
type: "default",
|
||||
metadata: {
|
||||
custom_key: "custom_value",
|
||||
},
|
||||
}
|
||||
|
||||
const {
|
||||
data: { shipping_profile },
|
||||
status,
|
||||
} = await api.post("/admin/shipping-profiles", payload, adminReqConfig)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(shipping_profile).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
deleted_at: null,
|
||||
...payload,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("fails to create a shipping profile with invalid type", async () => {
|
||||
const api = useApi()
|
||||
expect.assertions(2)
|
||||
|
||||
const payload = {
|
||||
name: "test-profile-2023",
|
||||
type: "invalid",
|
||||
}
|
||||
|
||||
await api
|
||||
.post("/admin/shipping-profiles", payload, adminReqConfig)
|
||||
.catch((err) => {
|
||||
expect(err.response.status).toEqual(400)
|
||||
expect(err.response.data.message).toEqual(
|
||||
"type must be one of 'default', 'custom', 'gift_card'"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it("updates a shipping profile", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const testProducts = await Promise.all(
|
||||
[...Array(5).keys()].map(async () => {
|
||||
return await simpleProductFactory(dbConnection)
|
||||
})
|
||||
)
|
||||
|
||||
const testShippingOptions = await Promise.all(
|
||||
[...Array(5).keys()].map(async () => {
|
||||
return await simpleShippingOptionFactory(dbConnection)
|
||||
})
|
||||
)
|
||||
|
||||
const payload = {
|
||||
name: "test-profile-2023",
|
||||
type: "custom",
|
||||
metadata: {
|
||||
my_key: "my_value",
|
||||
},
|
||||
}
|
||||
|
||||
const {
|
||||
data: { shipping_profile: created },
|
||||
} = await api.post("/admin/shipping-profiles", payload, adminReqConfig)
|
||||
|
||||
const updatePayload = {
|
||||
name: "test-profile-2023-updated",
|
||||
products: testProducts.map((p) => p.id),
|
||||
shipping_options: testShippingOptions.map((o) => o.id),
|
||||
metadata: {
|
||||
my_key: "",
|
||||
my_new_key: "my_new_value",
|
||||
},
|
||||
}
|
||||
|
||||
const {
|
||||
data: { shipping_profile },
|
||||
status,
|
||||
} = await api.post(
|
||||
`/admin/shipping-profiles/${created.id}`,
|
||||
updatePayload,
|
||||
adminReqConfig
|
||||
)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(shipping_profile).toEqual(
|
||||
expect.objectContaining({
|
||||
name: "test-profile-2023-updated",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
metadata: {
|
||||
my_new_key: "my_new_value",
|
||||
},
|
||||
deleted_at: null,
|
||||
type: "custom",
|
||||
})
|
||||
)
|
||||
|
||||
const {
|
||||
data: { products },
|
||||
} = await api.get(`/admin/products`, adminReqConfig)
|
||||
|
||||
expect(products.length).toEqual(5)
|
||||
expect(products).toEqual(
|
||||
expect.arrayContaining(
|
||||
testProducts.map((p) => {
|
||||
return expect.objectContaining({
|
||||
id: p.id,
|
||||
profile_id: shipping_profile.id,
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
const {
|
||||
data: { shipping_options },
|
||||
} = await api.get(`/admin/shipping-options`, adminReqConfig)
|
||||
|
||||
const numberOfShippingOptionsWithProfile = shipping_options.filter(
|
||||
(so) => so.profile_id === shipping_profile.id
|
||||
).length
|
||||
|
||||
expect(numberOfShippingOptionsWithProfile).toEqual(5)
|
||||
expect(shipping_options).toEqual(
|
||||
expect.arrayContaining(
|
||||
testShippingOptions.map((o) => {
|
||||
return expect.objectContaining({
|
||||
id: o.id,
|
||||
profile_id: shipping_profile.id,
|
||||
})
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/shipping-profiles", () => {
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("deletes a shipping profile", async () => {
|
||||
expect.assertions(2)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
const profile = await simpleShippingProfileFactory(dbConnection)
|
||||
|
||||
const { status } = await api.delete(
|
||||
`/admin/shipping-profiles/${profile.id}`,
|
||||
adminReqConfig
|
||||
)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
await api
|
||||
.get(`/admin/shipping-profiles/${profile.id}`, adminReqConfig)
|
||||
.catch((err) => {
|
||||
expect(err.response.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,25 +1,25 @@
|
||||
export * from "./simple-gift-card-factory"
|
||||
export * from "./simple-payment-factory"
|
||||
export * from "./simple-batch-job-factory"
|
||||
export * from "./simple-discount-factory"
|
||||
export * from "./simple-order-factory"
|
||||
export * from "./simple-cart-factory"
|
||||
export * from "./simple-region-factory"
|
||||
export * from "./simple-custom-shipping-option-factory"
|
||||
export * from "./simple-customer-factory"
|
||||
export * from "./simple-discount-factory"
|
||||
export * from "./simple-gift-card-factory"
|
||||
export * from "./simple-line-item-factory"
|
||||
export * from "./simple-order-edit-factory"
|
||||
export * from "./simple-order-factory"
|
||||
export * from "./simple-order-item-change-factory"
|
||||
export * from "./simple-payment-collection-factory"
|
||||
export * from "./simple-payment-factory"
|
||||
export * from "./simple-price-list-factory"
|
||||
export * from "./simple-product-category-factory"
|
||||
export * from "./simple-product-factory"
|
||||
export * from "./simple-product-variant-factory"
|
||||
export * from "./simple-product-tax-rate-factory"
|
||||
export * from "./simple-product-type-tax-rate-factory"
|
||||
export * from "./simple-product-variant-factory"
|
||||
export * from "./simple-region-factory"
|
||||
export * from "./simple-sales-channel-factory"
|
||||
export * from "./simple-shipping-method-factory"
|
||||
export * from "./simple-shipping-option-factory"
|
||||
export * from "./simple-shipping-profile-factory"
|
||||
export * from "./simple-shipping-tax-rate-factory"
|
||||
export * from "./simple-tax-rate-factory"
|
||||
export * from "./simple-shipping-option-factory"
|
||||
export * from "./simple-shipping-method-factory"
|
||||
export * from "./simple-product-type-tax-rate-factory"
|
||||
export * from "./simple-price-list-factory"
|
||||
export * from "./simple-batch-job-factory"
|
||||
export * from "./simple-sales-channel-factory"
|
||||
export * from "./simple-custom-shipping-option-factory"
|
||||
export * from "./simple-payment-collection-factory"
|
||||
export * from "./simple-order-edit-factory"
|
||||
export * from "./simple-order-item-change-factory"
|
||||
export * from "./simple-customer-factory"
|
||||
export * from "./simple-product-category-factory"
|
||||
|
||||
@@ -7,28 +7,29 @@ import {
|
||||
} from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
import { simpleRegionFactory } from "./simple-region-factory"
|
||||
|
||||
export type ShippingOptionFactoryData = {
|
||||
id?: string
|
||||
name?: string
|
||||
region_id: string
|
||||
region_id?: string
|
||||
is_return?: boolean
|
||||
is_giftcard?: boolean
|
||||
price?: number
|
||||
price_type?: ShippingOptionPriceType
|
||||
includes_tax?: boolean
|
||||
data?: object
|
||||
requirements: ShippingOptionRequirementData[]
|
||||
requirements?: ShippingOptionRequirementData[]
|
||||
}
|
||||
|
||||
type ShippingOptionRequirementData = {
|
||||
type: 'min_subtotal' | 'max_subtotal'
|
||||
type: "min_subtotal" | "max_subtotal"
|
||||
amount: number
|
||||
}
|
||||
|
||||
export const simpleShippingOptionFactory = async (
|
||||
connection: Connection,
|
||||
data: ShippingOptionFactoryData,
|
||||
data: ShippingOptionFactoryData = {},
|
||||
seed?: number
|
||||
): Promise<ShippingOption> => {
|
||||
if (typeof seed !== "undefined") {
|
||||
@@ -44,11 +45,18 @@ export const simpleShippingOptionFactory = async (
|
||||
type: ShippingProfileType.GIFT_CARD,
|
||||
})
|
||||
|
||||
let region_id = data.region_id
|
||||
|
||||
if (!region_id) {
|
||||
const { id } = await simpleRegionFactory(connection)
|
||||
region_id = id
|
||||
}
|
||||
|
||||
const shippingOptionData = {
|
||||
id: data.id ?? `simple-so-${Math.random() * 1000}`,
|
||||
name: data.name || "Test Method",
|
||||
is_return: data.is_return ?? false,
|
||||
region_id: data.region_id,
|
||||
region_id: region_id,
|
||||
provider_id: "test-ful",
|
||||
profile_id: data.is_giftcard ? gcProfile.id : defaultProfile.id,
|
||||
price_type: data.price_type ?? ShippingOptionPriceType.FLAT_RATE,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ShippingProfile, ShippingProfileType } from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { Connection } from "typeorm"
|
||||
|
||||
export type ShippingProfileFactoryData = {
|
||||
id?: string
|
||||
name?: string
|
||||
type?: ShippingProfileType
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const simpleShippingProfileFactory = async (
|
||||
connection: Connection,
|
||||
data: ShippingOptionFactoryData = {},
|
||||
seed?: number
|
||||
): Promise<ShippingProfile> => {
|
||||
if (typeof seed !== "undefined") {
|
||||
faker.seed(seed)
|
||||
}
|
||||
|
||||
const manager = connection.manager
|
||||
|
||||
const shippingProfileData = {
|
||||
id: data.id ?? `simple-sp-${Math.random() * 1000}`,
|
||||
name: data.name || `sp-${Math.random() * 1000}`,
|
||||
type: data.type || ShippingProfileType.DEFAULT,
|
||||
metadata: data.metadata,
|
||||
products: [],
|
||||
shipping_options: [],
|
||||
}
|
||||
|
||||
const created = manager.create(ShippingProfile, shippingProfileData)
|
||||
|
||||
return await manager.save(created)
|
||||
}
|
||||
Reference in New Issue
Block a user