Files
Frane Polić 864d772e34 feat(core-flows, dashboard, link-modules,medusa, types, utils): fulfillment shipping changes (#10902)
**What**
- product <> shipping profile link
- create and update product workflows/endpoints accepts shipping profile
- pass shipping option id when creating fulfillment to allow overriding customer selected SO
- validate shipping profile delete
- dashboard
  - set shipping profile on product create
  - manage shipping profile for a product
  - **update the create fulfillment form**
- other
  - fix create product form infinite rerenders
 
---

CLOSES CMRC-831 CMRC-834 CMRC-836 CMRC-837 CMRC-838 CMRC-857 TRI-761
2025-01-27 12:00:20 +00:00

332 lines
10 KiB
TypeScript

import {
createLinksWorkflow,
createLinksWorkflowId,
dismissLinksWorkflow,
dismissLinksWorkflowId,
updateLinksWorkflow,
updateLinksWorkflowId,
} from "@medusajs/core-flows"
import { Modules } from "@medusajs/utils"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import {
adminHeaders,
createAdminUser,
} from "../../../helpers/create-admin-user"
jest.setTimeout(50000)
medusaIntegrationTestRunner({
env: {},
testSuite: ({ getContainer, api, dbConnection }) => {
describe("Workflows: Common", () => {
let appContainer
let product
let variant
beforeAll(async () => {
appContainer = getContainer()
})
beforeEach(async () => {
await createAdminUser(dbConnection, adminHeaders, getContainer())
const shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "Test", type: "default" },
adminHeaders
)
).data.shipping_profile
product = (
await api.post(
"/admin/products",
{
title: "product 1",
shipping_profile_id: shippingProfile.id,
options: [{ title: "size", values: ["x", "l"] }],
variants: [
{
title: "variant 1",
options: { size: "x" },
prices: [{ currency_code: "usd", amount: 100 }],
},
],
},
adminHeaders
)
).data.product
variant = product.variants[0]
})
describe("createLinksWorkflow", () => {
describe("compensation", () => {
it("should dismiss links when step throws an error", async () => {
const workflow = createLinksWorkflow(appContainer)
const workflowId = createLinksWorkflowId
const inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "12345" },
adminHeaders
)
).data.inventory_item
workflow.appendAction("throw", workflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const { errors } = await workflow.run({
input: [
{
[Modules.PRODUCT]: { variant_id: variant.id },
[Modules.INVENTORY]: { inventory_item_id: inventoryItem.id },
data: { required_quantity: 10 },
},
],
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: expect.objectContaining({
message: `Fail`,
}),
},
])
const updatedVariant = (
await api.get(
`/admin/products/${product.id}/variants/${variant.id}?fields=inventory_items.inventory.*,inventory_items.*`,
adminHeaders
)
).data.variant
// The default inventory item remains that was created as a part of create product
expect(updatedVariant.inventory_items).toHaveLength(1)
})
})
})
describe("updateLinksWorkflow", () => {
describe("compensation", () => {
it("should revert link data when step throws an error", async () => {
const workflow = updateLinksWorkflow(appContainer)
const workflowId = updateLinksWorkflowId
const originalQuantity = 5
const newQuantity = 10
const inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "12345" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/products/${product.id}/variants/${variant.id}/inventory-items`,
{
inventory_item_id: inventoryItem.id,
required_quantity: originalQuantity,
},
adminHeaders
)
workflow.appendAction("throw", workflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const { errors } = await workflow.run({
input: [
{
[Modules.PRODUCT]: { variant_id: variant.id },
[Modules.INVENTORY]: { inventory_item_id: inventoryItem.id },
data: { required_quantity: newQuantity },
},
],
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: expect.objectContaining({
message: `Fail`,
}),
},
])
const updatedVariant = (
await api.get(
`/admin/products/${product.id}/variants/${variant.id}?fields=inventory_items.inventory.*,inventory_items.*`,
adminHeaders
)
).data.variant
expect(updatedVariant.inventory_items).toHaveLength(2)
expect(updatedVariant.inventory_items).toEqual(
expect.arrayContaining([
expect.objectContaining({
required_quantity: originalQuantity,
}),
])
)
})
it("should throw an error when a link is not found", async () => {
const workflow = updateLinksWorkflow(appContainer)
const workflowId = updateLinksWorkflowId
workflow.appendAction("throw", workflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const { errors } = await workflow.run({
input: [
{
[Modules.PRODUCT]: { variant_id: variant.id },
[Modules.INVENTORY]: {
inventory_item_id: "does-not-exist-id",
},
data: { required_quantity: 10 },
},
],
throwOnError: false,
})
expect(errors).toEqual([
{
action: "update-remote-links-step",
handlerType: "invoke",
error: expect.objectContaining({
message: `Could not find all existing links from data`,
}),
},
])
})
})
})
describe("dismissLinksWorkflow", () => {
describe("compensation", () => {
it("should recreate dismissed links when step throws an error", async () => {
const originalQuantity = 10
const workflow = dismissLinksWorkflow(appContainer)
const workflowId = dismissLinksWorkflowId
const inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "12345" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/products/${product.id}/variants/${variant.id}/inventory-items`,
{
inventory_item_id: inventoryItem.id,
required_quantity: originalQuantity,
},
adminHeaders
)
workflow.appendAction("throw", workflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const { errors } = await workflow.run({
input: [
{
[Modules.PRODUCT]: { variant_id: variant.id },
[Modules.INVENTORY]: { inventory_item_id: inventoryItem.id },
},
],
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: expect.objectContaining({
message: `Fail`,
}),
},
])
const updatedVariant = (
await api.get(
`/admin/products/${product.id}/variants/${variant.id}?fields=inventory_items.inventory.*,inventory_items.*`,
adminHeaders
)
).data.variant
expect(updatedVariant.inventory_items).toHaveLength(2)
expect(updatedVariant.inventory_items).toEqual(
expect.arrayContaining([
expect.objectContaining({
required_quantity: originalQuantity,
}),
])
)
})
it("should pass dismiss step if link not found if next step throws error", async () => {
const workflow = dismissLinksWorkflow(appContainer)
const workflowId = dismissLinksWorkflowId
workflow.appendAction("throw", workflowId, {
invoke: async function failStep() {
throw new Error(`Fail`)
},
})
const { errors } = await workflow.run({
input: [
{
[Modules.PRODUCT]: { variant_id: variant.id },
[Modules.INVENTORY]: {
inventory_item_id: "does-not-exist-id",
},
},
],
throwOnError: false,
})
expect(errors).toEqual([
{
action: "throw",
handlerType: "invoke",
error: expect.objectContaining({
message: `Fail`,
}),
},
])
const updatedVariant = (
await api.get(
`/admin/products/${product.id}/variants/${variant.id}?fields=inventory_items.inventory.*,inventory_items.*`,
adminHeaders
)
).data.variant
expect(updatedVariant.inventory_items).toHaveLength(1)
})
})
})
})
},
})