test(core-flows): Ensure productVariantUpdated failure compensate the workflow (#12381)

This commit is contained in:
Adrien de Peretti
2025-05-09 13:03:22 +02:00
committed by GitHub
parent 221c73f4a8
commit 091041f2da
2 changed files with 226 additions and 99 deletions

View File

@@ -0,0 +1,190 @@
import { updateProductVariantsWorkflow } from "@medusajs/core-flows"
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { IProductModuleService } from "@medusajs/types"
import { MedusaError, Modules } from "@medusajs/utils"
jest.setTimeout(50000)
medusaIntegrationTestRunner({
env: {},
testSuite: ({ getContainer }) => {
describe("Workflows: Update product variants", () => {
let appContainer
let service: IProductModuleService
beforeAll(async () => {
appContainer = getContainer()
service = appContainer.resolve(Modules.PRODUCT)
})
describe("updateProductVariantsWorkflow", () => {
beforeAll(() => {
updateProductVariantsWorkflow.hooks.productVariantsUpdated(() => {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"product variants updated hook failed"
)
})
})
describe("compensation", () => {
it("should revert the updated variants using product_variants if the hook fails", async () => {
const workflow = updateProductVariantsWorkflow(appContainer)
const [product1, product2] = await service.createProducts([
{
title: "test1",
variants: [
{
title: "variant1",
sku: "variant1",
},
],
},
{
title: "test2",
variants: [
{
title: "variant2",
sku: "variant2",
},
],
},
])
const { errors } = await workflow.run({
input: {
product_variants: [
{
id: product1.variants[0].id,
title: "variant1-updated",
},
],
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "productVariantsUpdated",
handlerType: "invoke",
error: expect.objectContaining({
message: `product variants updated hook failed`,
}),
},
])
const products = await service.listProducts(
{},
{
relations: ["variants"],
}
)
expect(products).toHaveLength(2)
expect(products).toEqual(
expect.arrayContaining([
expect.objectContaining({
title: product1.title,
variants: [
expect.objectContaining({
title: product1.variants[0].title,
sku: product1.variants[0].sku,
}),
],
}),
expect.objectContaining({
title: product2.title,
variants: [
expect.objectContaining({
title: product2.variants[0].title,
sku: product2.variants[0].sku,
}),
],
}),
])
)
})
it("should revert the updated variants using selector/update if the hook fails", async () => {
const workflow = updateProductVariantsWorkflow(appContainer)
const [product1, product2] = await service.createProducts([
{
title: "test1",
variants: [
{
title: "variant1",
sku: "variant1",
},
],
},
{
title: "test2",
variants: [
{
title: "variant2",
sku: "variant2",
},
],
},
])
const { errors } = await workflow.run({
input: {
selector: {
id: product1.variants[0].id,
},
update: {
title: "variant1-updated",
},
},
throwOnError: false,
})
expect(errors).toEqual([
{
action: "productVariantsUpdated",
handlerType: "invoke",
error: expect.objectContaining({
message: `product variants updated hook failed`,
}),
},
])
const products = await service.listProducts(
{},
{
relations: ["variants"],
}
)
expect(products).toHaveLength(2)
expect(products).toEqual(
expect.arrayContaining([
expect.objectContaining({
title: product1.title,
variants: [
expect.objectContaining({
title: product1.variants[0].title,
sku: product1.variants[0].sku,
}),
],
}),
expect.objectContaining({
title: product2.title,
variants: [
expect.objectContaining({
title: product2.variants[0].title,
sku: product2.variants[0].sku,
}),
],
}),
])
)
})
})
})
})
},
})

View File

@@ -1,117 +1,54 @@
import { z } from "zod"
import {
createStep,
createWorkflow,
StepResponse,
WorkflowData,
} from "./composer"
import { createStep, createWorkflow, StepResponse, transform } from "./composer"
import { createHook } from "./composer/create-hook"
import { WorkflowResponse } from "./composer/helpers/workflow-response"
const step1 = createStep("step1", async (input: {}, context) => {
return new StepResponse({ step1: "step1" })
})
type Step2Input = { filters: { id: string[] } } | { filters: { id: string } }
const step2 = createStep("step2", async (input: Step2Input, context) => {
return new StepResponse({ step2: input })
})
const step3 = createStep("step3", async function (_, context) {
return new StepResponse({ step3: "step3" })
})
const step4 = createStep("step4", async function (_, context) {
return new StepResponse<undefined | { id: number }>({ id: 1 })
})
const step5 = createStep("step5", async function (_, context) {
return new StepResponse(void 0)
})
const workflow = createWorkflow(
"sub-workflow",
function (input: WorkflowData<{ outsideWorkflowData: string }>) {
step1()
step2({ filters: { id: [] } })
let somethingHook = createHook(
"something",
{ id: "1" },
{
resultValidator: z.object({
id: z.number(),
}),
}
)
const step4Result = step4().config({ name: "foo" })
step4Result
const step5Result = step5().config({ name: "foo" })
step5Result
return new WorkflowResponse(
{ r: somethingHook.getResult(), step3: step3() },
{ hooks: [somethingHook] }
)
const step1 = createStep(
"step1",
() => {
return new StepResponse("step1")
},
() => {
console.log("compensate step1")
}
)
workflow.hooks.something((input, context) => {
console.log("input>", input)
console.log("context>", context)
return new StepResponse({ id: 2, foo: "bar" })
})
const step2 = createStep(
"step2",
(input: any) => {
return new StepResponse(input)
},
(input) => {
console.log("compensate step2", input)
}
)
workflow.run().then((res) => {
console.log("res", res)
})
const workflow = createWorkflow("workflow", () => {
const step1Result = step1()
/*const workflow2 = createWorkflow("workflow", function () {
const step1Res = step1()
const step3Res = when({ value: true }, ({ value }) => {
return value
}).then(() => {
return step3()
const step2Input = transform({ step1Result }, (input) => {
return input
})
transform({ step3Res }, ({ step3Res }) => {
console.log(step3Res)
const step2Result = step2(step2Input)
const hook = createHook("hook", {
step2Result,
})
const workflowRes = workflow.asStep({ outsideWorkflowData: step1Res.step1 })
return workflowRes
})*/
// workflow()
// .run({})
// .then((res) => {
// console.log(res.result)
// })
/*const step1 = createStep("step1", async (input: {}, context) => {
return new StepResponse({ step1: ["step1"] })
return new WorkflowResponse(void 0, {
hooks: [hook],
})
})
const step2 = createStep("step2", async (input: string[], context) => {
return new StepResponse({ step2: input })
})
const step3 = createStep("step3", async () => {
return new StepResponse({ step3: "step3" })
})
const workflow = createWorkflow("workflow", function () {
const step1Res = step1()
step3()
return step2(step1Res.step1)
workflow.hooks.hook(() => {
throw new Error("hook failed")
})
workflow()
.run({})
.run()
.then((res) => {
console.log(res.result)
})*/
console.log(res)
})
.catch((e) => {
console.log(e)
})