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:
Adrien de Peretti
2023-08-09 14:33:04 +00:00
committed by GitHub
parent 86c314eb2a
commit ac866ebb51
21 changed files with 391 additions and 193 deletions
@@ -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)
}