test(): Test the create product workflow compensation (#4716)
**What** Integration tests to validate the workflow compensation. Also, fix the transaction state when the workflow is compensating and some steps does not have any compensation
This commit is contained in:
@@ -787,6 +787,88 @@ describe("Transaction Orchestrator", () => {
|
||||
expect(resumedTransaction.getState()).toBe(TransactionState.REVERTED)
|
||||
})
|
||||
|
||||
it("Should hold the status REVERTED if the steps failed and the compensation succeed and has some no compensations step set", async () => {
|
||||
const mocks = {
|
||||
one: jest.fn().mockImplementation((payload) => {
|
||||
return
|
||||
}),
|
||||
compensateOne: jest.fn().mockImplementation((payload) => {
|
||||
return
|
||||
}),
|
||||
two: jest.fn().mockImplementation((payload) => {
|
||||
return
|
||||
}),
|
||||
compensateTwo: jest.fn().mockImplementation((payload) => {
|
||||
return
|
||||
}),
|
||||
three: jest.fn().mockImplementation((payload) => {
|
||||
throw new Error("Third step error")
|
||||
}),
|
||||
}
|
||||
|
||||
async function handler(
|
||||
actionId: string,
|
||||
functionHandlerType: TransactionHandlerType,
|
||||
payload: TransactionPayload
|
||||
) {
|
||||
const command = {
|
||||
firstMethod: {
|
||||
[TransactionHandlerType.INVOKE]: () => {
|
||||
mocks.one(payload)
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: () => {
|
||||
mocks.compensateOne(payload)
|
||||
},
|
||||
},
|
||||
secondMethod: {
|
||||
[TransactionHandlerType.INVOKE]: () => {
|
||||
mocks.two(payload)
|
||||
},
|
||||
[TransactionHandlerType.COMPENSATE]: () => {
|
||||
mocks.compensateTwo(payload)
|
||||
},
|
||||
},
|
||||
thirdMethod: {
|
||||
[TransactionHandlerType.INVOKE]: () => {
|
||||
mocks.three(payload)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return command[actionId][functionHandlerType](payload)
|
||||
}
|
||||
|
||||
const flow: TransactionStepsDefinition = {
|
||||
next: {
|
||||
action: "firstMethod",
|
||||
next: {
|
||||
action: "secondMethod",
|
||||
next: {
|
||||
action: "thirdMethod",
|
||||
noCompensation: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const strategy = new TransactionOrchestrator("transaction-name", flow)
|
||||
|
||||
const transaction = await strategy.beginTransaction(
|
||||
"transaction_id_123",
|
||||
handler
|
||||
)
|
||||
|
||||
await strategy.resume(transaction)
|
||||
|
||||
expect(mocks.one).toBeCalledTimes(1)
|
||||
expect(mocks.compensateOne).toBeCalledTimes(1)
|
||||
expect(mocks.two).toBeCalledTimes(1)
|
||||
expect(mocks.compensateTwo).toBeCalledTimes(1)
|
||||
expect(mocks.three).toBeCalledTimes(1)
|
||||
|
||||
expect(transaction.getState()).toBe(TransactionState.REVERTED)
|
||||
})
|
||||
|
||||
it("Should revert a transaction when .cancelTransaction() is called", async () => {
|
||||
const mocks = {
|
||||
one: jest.fn().mockImplementation((payload) => {
|
||||
|
||||
@@ -147,8 +147,7 @@ describe("WorkflowManager", () => {
|
||||
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(0)
|
||||
expect(handlers.get("bar").compensate).toHaveBeenCalledTimes(0)
|
||||
|
||||
// Failed because the async is flagged as noCompensation
|
||||
expect(continuation.getState()).toBe(TransactionState.FAILED)
|
||||
expect(continuation.getState()).toBe(TransactionState.REVERTED)
|
||||
})
|
||||
|
||||
it("should update an existing global flow with a new step and a new handler", async () => {
|
||||
|
||||
@@ -146,8 +146,7 @@ describe("WorkflowManager", () => {
|
||||
expect(handlers.get("bar").invoke).toHaveBeenCalledTimes(0)
|
||||
expect(handlers.get("bar").compensate).toHaveBeenCalledTimes(0)
|
||||
|
||||
// Failed because the async is flagged as noCompensation
|
||||
expect(continuation.getState()).toBe(TransactionState.FAILED)
|
||||
expect(continuation.getState()).toBe(TransactionState.REVERTED)
|
||||
})
|
||||
|
||||
it("should update a flow with a new step and a new handler", async () => {
|
||||
|
||||
@@ -74,9 +74,9 @@ export class DistributedTransaction {
|
||||
public modelId: string
|
||||
public transactionId: string
|
||||
|
||||
private errors: TransactionStepError[] = []
|
||||
private readonly errors: TransactionStepError[] = []
|
||||
|
||||
private context: TransactionContext = new TransactionContext()
|
||||
private readonly context: TransactionContext = new TransactionContext()
|
||||
|
||||
constructor(
|
||||
private flow: TransactionFlow,
|
||||
|
||||
@@ -7,8 +7,8 @@ import { TransactionStep, TransactionStepHandler } from "./transaction-step"
|
||||
import {
|
||||
TransactionHandlerType,
|
||||
TransactionState,
|
||||
TransactionStepStatus,
|
||||
TransactionStepsDefinition,
|
||||
TransactionStepStatus,
|
||||
} from "./types"
|
||||
|
||||
import { EventEmitter } from "events"
|
||||
@@ -233,9 +233,8 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
const stepDef = flow.steps[step]
|
||||
const curState = stepDef.getStates()
|
||||
if (
|
||||
(curState.state === TransactionState.DONE ||
|
||||
curState.status === TransactionStepStatus.PERMANENT_FAILURE) &&
|
||||
!stepDef.definition.noCompensation
|
||||
curState.state === TransactionState.DONE ||
|
||||
curState.status === TransactionStepStatus.PERMANENT_FAILURE
|
||||
) {
|
||||
stepDef.beginCompensation()
|
||||
stepDef.changeState(TransactionState.NOT_STARTED)
|
||||
@@ -334,6 +333,11 @@ export class TransactionOrchestrator extends EventEmitter {
|
||||
if (curState.state === TransactionState.NOT_STARTED) {
|
||||
if (step.isCompensating()) {
|
||||
step.changeState(TransactionState.COMPENSATING)
|
||||
|
||||
if (step.definition.noCompensation) {
|
||||
step.changeState(TransactionState.REVERTED)
|
||||
continue
|
||||
}
|
||||
} else if (flow.state === TransactionState.INVOKING) {
|
||||
step.changeState(TransactionState.INVOKING)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user