If else node tests (#16916)
This commit is contained in:
+632
@@ -0,0 +1,632 @@
|
||||
import request from 'supertest';
|
||||
import {
|
||||
destroyWorkflowRun,
|
||||
runWorkflowVersion,
|
||||
waitForWorkflowCompletion,
|
||||
} from 'test/integration/graphql/suites/workflow/utils/workflow-run-test.util';
|
||||
import { StepLogicalOperator, ViewFilterOperand } from 'twenty-shared/types';
|
||||
import { type StepIfElseBranch } from 'twenty-shared/workflow';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type WorkflowIfElseAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
const client = request(`http://localhost:${APP_PORT}`);
|
||||
|
||||
describe('If/Else Workflow (e2e)', () => {
|
||||
let createdWorkflowId: string | null = null;
|
||||
let createdWorkflowVersionId: string | null = null;
|
||||
let ifElseStepId: string | null = null;
|
||||
let ifBranchEmptyNodeId: string | null = null;
|
||||
let elseBranchEmptyNodeId: string | null = null;
|
||||
let elseIfBranchEmptyNodeId: string | null = null;
|
||||
let elseIfBranchId: string | null = null;
|
||||
|
||||
beforeAll(async () => {
|
||||
const createWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation CreateWorkflow {
|
||||
createWorkflow(data: {
|
||||
name: "If/Else Test Workflow"
|
||||
}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(createWorkflowResponse.body.errors).toBeUndefined();
|
||||
createdWorkflowId = createWorkflowResponse.body.data.createWorkflow.id;
|
||||
|
||||
const getWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflow($id: UUID!) {
|
||||
workflow(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
versions {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowId },
|
||||
});
|
||||
|
||||
expect(getWorkflowResponse.body.errors).toBeUndefined();
|
||||
expect(
|
||||
getWorkflowResponse.body.data.workflow.versions.edges.length,
|
||||
).toBeGreaterThan(0);
|
||||
createdWorkflowVersionId =
|
||||
getWorkflowResponse.body.data.workflow.versions.edges[0].node.id;
|
||||
|
||||
const manualTrigger = {
|
||||
name: 'Manual Trigger',
|
||||
type: 'MANUAL',
|
||||
settings: {
|
||||
outputSchema: {
|
||||
number: {
|
||||
isLeaf: true,
|
||||
type: 'number',
|
||||
value: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
nextStepIds: [],
|
||||
position: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
const updateWorkflowVersionResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation UpdateWorkflowVersion($id: UUID!, $data: WorkflowVersionUpdateInput!) {
|
||||
updateWorkflowVersion(id: $id, data: $data) {
|
||||
id
|
||||
trigger
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: createdWorkflowVersionId,
|
||||
data: {
|
||||
trigger: manualTrigger,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateWorkflowVersionResponse.body.errors).toBeUndefined();
|
||||
|
||||
const createIfElseStepResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) {
|
||||
createWorkflowVersionStep(input: $input) {
|
||||
stepsDiff
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: createdWorkflowVersionId,
|
||||
stepType: 'IF_ELSE',
|
||||
parentStepId: 'trigger',
|
||||
position: { x: 200, y: 0 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(createIfElseStepResponse.body.errors).toBeUndefined();
|
||||
|
||||
const getWorkflowVersionResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflowVersion($id: UUID!) {
|
||||
workflowVersion(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
steps
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
expect(getWorkflowVersionResponse.body.errors).toBeUndefined();
|
||||
const steps = getWorkflowVersionResponse.body.data.workflowVersion.steps;
|
||||
const ifElseStep = steps.find(
|
||||
(step: { type: string }) => step.type === 'IF_ELSE',
|
||||
);
|
||||
|
||||
expect(ifElseStep).toBeDefined();
|
||||
ifElseStepId = ifElseStep.id;
|
||||
|
||||
const branches = ifElseStep.settings.input.branches;
|
||||
const ifBranch = branches.find(
|
||||
(branch: StepIfElseBranch) => branch.filterGroupId,
|
||||
);
|
||||
const elseBranch = branches.find(
|
||||
(branch: StepIfElseBranch) => !branch.filterGroupId,
|
||||
);
|
||||
|
||||
expect(ifBranch).toBeDefined();
|
||||
expect(elseBranch).toBeDefined();
|
||||
expect(ifBranch.nextStepIds.length).toBeGreaterThan(0);
|
||||
expect(elseBranch.nextStepIds.length).toBeGreaterThan(0);
|
||||
|
||||
ifBranchEmptyNodeId = ifBranch.nextStepIds[0];
|
||||
elseBranchEmptyNodeId = elseBranch.nextStepIds[0];
|
||||
|
||||
expect(ifElseStep.settings.input.stepFilterGroups.length).toBeGreaterThan(
|
||||
0,
|
||||
);
|
||||
expect(ifElseStep.settings.input.stepFilters.length).toBeGreaterThan(0);
|
||||
expect(ifElseStep.settings.input.branches.length).toBe(2);
|
||||
|
||||
const ifFilterGroupId = ifBranch.filterGroupId;
|
||||
|
||||
expect(ifFilterGroupId).toBeDefined();
|
||||
|
||||
const filterGroup = ifElseStep.settings.input.stepFilterGroups.find(
|
||||
(g: { id: string }) => g.id === ifFilterGroupId,
|
||||
);
|
||||
|
||||
expect(filterGroup).toBeDefined();
|
||||
|
||||
const updateIfElseStepResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation UpdateWorkflowVersionStep($input: UpdateWorkflowVersionStepInput!) {
|
||||
updateWorkflowVersionStep(input: $input) {
|
||||
id
|
||||
type
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: createdWorkflowVersionId,
|
||||
step: {
|
||||
...ifElseStep,
|
||||
settings: {
|
||||
...ifElseStep.settings,
|
||||
input: {
|
||||
...ifElseStep.settings.input,
|
||||
stepFilters: [
|
||||
{
|
||||
id: ifElseStep.settings.input.stepFilters[0].id,
|
||||
type: 'NUMBER',
|
||||
stepOutputKey: '{{trigger.number}}',
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: '10',
|
||||
stepFilterGroupId: ifFilterGroupId,
|
||||
positionInStepFilterGroup: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateIfElseStepResponse.body.errors).toBeUndefined();
|
||||
|
||||
const createEmptyNodeResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation CreateWorkflowVersionStep($input: CreateWorkflowVersionStepInput!) {
|
||||
createWorkflowVersionStep(input: $input) {
|
||||
stepsDiff
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: createdWorkflowVersionId,
|
||||
stepType: 'EMPTY',
|
||||
parentStepId: undefined,
|
||||
position: { x: 300, y: 100 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(createEmptyNodeResponse.body.errors).toBeUndefined();
|
||||
|
||||
const getUpdatedWorkflowVersionResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflowVersion($id: UUID!) {
|
||||
workflowVersion(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
steps
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
const updatedSteps =
|
||||
getUpdatedWorkflowVersionResponse.body.data.workflowVersion.steps;
|
||||
const newEmptyNodes = updatedSteps.filter(
|
||||
(step: { type: string }) => step.type === 'EMPTY',
|
||||
);
|
||||
const newElseIfEmptyNode = newEmptyNodes.find(
|
||||
(node: { id: string }) =>
|
||||
node.id !== ifBranchEmptyNodeId && node.id !== elseBranchEmptyNodeId,
|
||||
);
|
||||
|
||||
expect(newElseIfEmptyNode).toBeDefined();
|
||||
elseIfBranchEmptyNodeId = newElseIfEmptyNode.id;
|
||||
|
||||
const elseIfFilterGroupId = v4();
|
||||
const elseIfFilterId = v4();
|
||||
|
||||
elseIfBranchId = v4();
|
||||
|
||||
const getCurrentIfElseStepResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflowVersion($id: UUID!) {
|
||||
workflowVersion(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
steps
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
const currentSteps =
|
||||
getCurrentIfElseStepResponse.body.data.workflowVersion.steps;
|
||||
const currentIfElseStep = currentSteps.find(
|
||||
(step: { id: string }) => step.id === ifElseStepId,
|
||||
);
|
||||
|
||||
const updatedBranches = [...currentIfElseStep.settings.input.branches];
|
||||
|
||||
updatedBranches.splice(
|
||||
currentIfElseStep.settings.input.branches.length - 1,
|
||||
0,
|
||||
{
|
||||
id: elseIfBranchId,
|
||||
filterGroupId: elseIfFilterGroupId,
|
||||
nextStepIds: [elseIfBranchEmptyNodeId],
|
||||
},
|
||||
);
|
||||
|
||||
const updatedStepFilterGroups = [
|
||||
...currentIfElseStep.settings.input.stepFilterGroups,
|
||||
{
|
||||
id: elseIfFilterGroupId,
|
||||
logicalOperator: StepLogicalOperator.AND,
|
||||
positionInStepFilterGroup: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const updatedStepFilters = [
|
||||
...currentIfElseStep.settings.input.stepFilters,
|
||||
{
|
||||
id: elseIfFilterId,
|
||||
type: 'NUMBER',
|
||||
stepOutputKey: '{{trigger.number}}',
|
||||
operand: ViewFilterOperand.IS,
|
||||
value: '20',
|
||||
stepFilterGroupId: elseIfFilterGroupId,
|
||||
positionInStepFilterGroup: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const updateIfElseStepWithElseIfResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation UpdateWorkflowVersionStep($input: UpdateWorkflowVersionStepInput!) {
|
||||
updateWorkflowVersionStep(input: $input) {
|
||||
id
|
||||
type
|
||||
name
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: createdWorkflowVersionId,
|
||||
step: {
|
||||
...currentIfElseStep,
|
||||
settings: {
|
||||
...currentIfElseStep.settings,
|
||||
input: {
|
||||
...currentIfElseStep.settings.input,
|
||||
branches: updatedBranches,
|
||||
stepFilterGroups: updatedStepFilterGroups,
|
||||
stepFilters: updatedStepFilters,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateIfElseStepWithElseIfResponse.body.errors).toBeUndefined();
|
||||
|
||||
const activateResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation ActivateWorkflowVersion($workflowVersionId: UUID!) {
|
||||
activateWorkflowVersion(workflowVersionId: $workflowVersionId)
|
||||
}
|
||||
`,
|
||||
variables: { workflowVersionId: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
expect(activateResponse.body.errors).toBeUndefined();
|
||||
expect(activateResponse.body.data.activateWorkflowVersion).toBe(true);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (createdWorkflowId) {
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation DestroyWorkflow($id: ID!) {
|
||||
destroyWorkflow(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowId },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const identifyBranches = (branches: StepIfElseBranch[]) => {
|
||||
const ifBranch = branches[0];
|
||||
const elseBranch = branches.find((branch) => !branch.filterGroupId);
|
||||
const elseIfBranches = branches.filter(
|
||||
(branch, index) =>
|
||||
index > 0 &&
|
||||
index < branches.length - 1 &&
|
||||
branch.filterGroupId !== undefined,
|
||||
);
|
||||
|
||||
return { ifBranch, elseBranch, elseIfBranches };
|
||||
};
|
||||
|
||||
describe('Workflow structure', () => {
|
||||
it('should verify If/Else workflow exists and is active', async () => {
|
||||
const response = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query FindWorkflow($id: UUID!) {
|
||||
workflow(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
name
|
||||
lastPublishedVersionId
|
||||
statuses
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowId },
|
||||
});
|
||||
|
||||
expect(response.body.errors).toBeUndefined();
|
||||
expect(response.body.data.workflow.id).toBe(createdWorkflowId);
|
||||
expect(response.body.data.workflow.name).toBe('If/Else Test Workflow');
|
||||
expect(response.body.data.workflow.lastPublishedVersionId).toBe(
|
||||
createdWorkflowVersionId,
|
||||
);
|
||||
expect(response.body.data.workflow.statuses).toContain('ACTIVE');
|
||||
});
|
||||
|
||||
it('should verify If/Else workflow version has correct structure', async () => {
|
||||
const response = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query FindWorkflowVersion($id: UUID!) {
|
||||
workflowVersion(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
name
|
||||
status
|
||||
trigger
|
||||
steps
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
expect(response.body.errors).toBeUndefined();
|
||||
|
||||
const workflowVersion = response.body.data.workflowVersion;
|
||||
|
||||
expect(workflowVersion.status).toBe('ACTIVE');
|
||||
|
||||
const trigger = workflowVersion.trigger;
|
||||
|
||||
expect(trigger.type).toBe('MANUAL');
|
||||
expect(trigger.nextStepIds).toContain(ifElseStepId);
|
||||
|
||||
const steps = workflowVersion.steps;
|
||||
|
||||
const ifElseStep = steps.find(
|
||||
(step: { id: string }) => step.id === ifElseStepId,
|
||||
);
|
||||
|
||||
expect(ifElseStep).toBeDefined();
|
||||
expect(ifElseStep.type).toBe('IF_ELSE');
|
||||
expect(ifElseStep.name).toBe('If/Else');
|
||||
expect(ifElseStep.settings.input.branches.length).toBe(3);
|
||||
|
||||
const { ifBranch, elseBranch, elseIfBranches } = identifyBranches(
|
||||
ifElseStep.settings.input.branches,
|
||||
);
|
||||
|
||||
expect(ifBranch?.filterGroupId).toBeDefined();
|
||||
expect(elseBranch?.filterGroupId).toBeUndefined();
|
||||
expect(ifBranch?.nextStepIds).toContain(ifBranchEmptyNodeId);
|
||||
expect(elseBranch?.nextStepIds).toContain(elseBranchEmptyNodeId);
|
||||
|
||||
expect(elseIfBranches.length).toBe(1);
|
||||
expect(elseIfBranches[0].filterGroupId).toBeDefined();
|
||||
expect(elseIfBranches[0].nextStepIds).toContain(elseIfBranchEmptyNodeId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('If/Else branching execution', () => {
|
||||
const getIfElseStepWithBranches = async (): Promise<
|
||||
Pick<WorkflowIfElseAction, 'id' | 'settings'>
|
||||
> => {
|
||||
const getWorkflowVersionResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query GetWorkflowVersion($id: UUID!) {
|
||||
workflowVersion(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
steps
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowVersionId },
|
||||
});
|
||||
|
||||
expect(getWorkflowVersionResponse.body.errors).toBeUndefined();
|
||||
const steps = getWorkflowVersionResponse.body.data.workflowVersion.steps;
|
||||
const ifElseStep = steps.find(
|
||||
(step: { id: string }) => step.id === ifElseStepId,
|
||||
) as WorkflowIfElseAction | undefined;
|
||||
|
||||
expect(ifElseStep).toBeDefined();
|
||||
|
||||
return ifElseStep!;
|
||||
};
|
||||
|
||||
const verifyBranchExecution = async ({
|
||||
payload,
|
||||
expectedBranchType,
|
||||
}: {
|
||||
payload: { number: number };
|
||||
expectedBranchType: 'if' | 'else' | 'else-if';
|
||||
}) => {
|
||||
const workflowRunId = await runWorkflowVersion({
|
||||
workflowVersionId: createdWorkflowVersionId!,
|
||||
payload,
|
||||
});
|
||||
|
||||
const workflowRun = await waitForWorkflowCompletion(workflowRunId);
|
||||
|
||||
expect(workflowRun?.status).toBe('COMPLETED');
|
||||
expect(workflowRun?.state?.stepInfos?.trigger?.status).toBe('SUCCESS');
|
||||
expect(workflowRun?.state?.stepInfos?.[ifElseStepId!]?.status).toBe(
|
||||
'SUCCESS',
|
||||
);
|
||||
|
||||
const ifElseStepResult = workflowRun?.state?.stepInfos?.[ifElseStepId!]
|
||||
?.result as { matchingBranchId?: string } | undefined;
|
||||
|
||||
expect(ifElseStepResult?.matchingBranchId).toBeDefined();
|
||||
|
||||
const ifElseStep = await getIfElseStepWithBranches();
|
||||
|
||||
const matchedBranch = ifElseStep.settings.input.branches.find(
|
||||
(branch) => branch.id === ifElseStepResult?.matchingBranchId,
|
||||
);
|
||||
|
||||
if (!matchedBranch) {
|
||||
const branchIds = ifElseStep.settings.input.branches.map((b) => b.id);
|
||||
|
||||
throw new Error(
|
||||
`Branch with ID ${ifElseStepResult?.matchingBranchId} not found. Available branch IDs: ${branchIds.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { ifBranch, elseBranch, elseIfBranches } = identifyBranches(
|
||||
ifElseStep.settings.input.branches,
|
||||
);
|
||||
|
||||
let expectedBranch: StepIfElseBranch | undefined;
|
||||
let expectedEmptyNodeId: string | null = null;
|
||||
let otherEmptyNodeIds: (string | null)[] = [];
|
||||
|
||||
if (expectedBranchType === 'if') {
|
||||
expectedBranch = ifBranch;
|
||||
expectedEmptyNodeId = ifBranchEmptyNodeId;
|
||||
otherEmptyNodeIds = [elseBranchEmptyNodeId, elseIfBranchEmptyNodeId];
|
||||
} else if (expectedBranchType === 'else') {
|
||||
expectedBranch = elseBranch;
|
||||
expectedEmptyNodeId = elseBranchEmptyNodeId;
|
||||
otherEmptyNodeIds = [ifBranchEmptyNodeId, elseIfBranchEmptyNodeId];
|
||||
} else if (expectedBranchType === 'else-if') {
|
||||
expectedBranch = elseIfBranches.find(
|
||||
(branch) => branch.id === elseIfBranchId,
|
||||
);
|
||||
expectedEmptyNodeId = elseIfBranchEmptyNodeId;
|
||||
otherEmptyNodeIds = [ifBranchEmptyNodeId, elseBranchEmptyNodeId];
|
||||
}
|
||||
|
||||
expect(matchedBranch.id).toBe(expectedBranch?.id);
|
||||
expect(matchedBranch.nextStepIds).toContain(expectedEmptyNodeId);
|
||||
otherEmptyNodeIds.forEach((id) => {
|
||||
if (id) {
|
||||
expect(matchedBranch.nextStepIds).not.toContain(id);
|
||||
}
|
||||
});
|
||||
|
||||
await destroyWorkflowRun(workflowRunId);
|
||||
};
|
||||
|
||||
it('should execute IF branch when condition is true', async () => {
|
||||
await verifyBranchExecution({
|
||||
payload: { number: 10 },
|
||||
expectedBranchType: 'if',
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute ELSE branch when condition is false', async () => {
|
||||
await verifyBranchExecution({
|
||||
payload: { number: 5 },
|
||||
expectedBranchType: 'else',
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute ELSE-IF branch when condition is true', async () => {
|
||||
await verifyBranchExecution({
|
||||
payload: { number: 20 },
|
||||
expectedBranchType: 'else-if',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+17
-169
@@ -1,5 +1,9 @@
|
||||
import request from 'supertest';
|
||||
import { WORKFLOW_RUN_GQL_FIELDS } from 'test/integration/constants/workflow-gql-fields.constants';
|
||||
import {
|
||||
destroyWorkflowRun,
|
||||
getWorkflowRun,
|
||||
runWorkflowVersion,
|
||||
} from 'test/integration/graphql/suites/workflow/utils/workflow-run-test.util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const client = request(`http://localhost:${APP_PORT}`);
|
||||
@@ -9,86 +13,12 @@ const QUICK_LEAD_WORKFLOW_ID = '8b213cac-a68b-4ffe-817a-3ec994e9932d';
|
||||
const QUICK_LEAD_WORKFLOW_VERSION_ID = 'ac67974f-c524-4288-9d88-af8515400b68';
|
||||
const FORM_STEP_ID = '6e089bc9-aabd-435f-865f-f31c01c8f4a7';
|
||||
|
||||
type WorkflowRunStatusType =
|
||||
| 'NOT_STARTED'
|
||||
| 'RUNNING'
|
||||
| 'COMPLETED'
|
||||
| 'FAILED'
|
||||
| 'ENQUEUED'
|
||||
| 'STOPPING'
|
||||
| 'STOPPED';
|
||||
|
||||
type WorkflowRunState = {
|
||||
stepInfos?: Record<
|
||||
string,
|
||||
{
|
||||
status: string;
|
||||
result?: Record<string, unknown>;
|
||||
}
|
||||
>;
|
||||
flow?: {
|
||||
trigger?: {
|
||||
type: string;
|
||||
nextStepIds: string[];
|
||||
};
|
||||
steps?: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
type WorkflowRunResponse = {
|
||||
id: string;
|
||||
status: WorkflowRunStatusType;
|
||||
state: WorkflowRunState;
|
||||
workflowVersionId: string;
|
||||
};
|
||||
|
||||
const getWorkflowRun = async (
|
||||
workflowRunId: string,
|
||||
): Promise<WorkflowRunResponse | null> => {
|
||||
const response = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query FindWorkflowRun($id: UUID!) {
|
||||
workflowRun(filter: { id: { eq: $id } }) {
|
||||
${WORKFLOW_RUN_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: workflowRunId },
|
||||
});
|
||||
|
||||
if (response.body.errors || !response.body.data?.workflowRun) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.body.data.workflowRun;
|
||||
};
|
||||
|
||||
describe('Quick Lead Workflow (e2e)', () => {
|
||||
let createdWorkflowRunId: string | null = null;
|
||||
|
||||
afterAll(async () => {
|
||||
// Clean up workflow run
|
||||
if (createdWorkflowRunId) {
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation DestroyWorkflowRun($id: ID!) {
|
||||
destroyWorkflowRun(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: createdWorkflowRunId },
|
||||
});
|
||||
await destroyWorkflowRun(createdWorkflowRunId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -189,32 +119,9 @@ describe('Quick Lead Workflow (e2e)', () => {
|
||||
});
|
||||
|
||||
it('should trigger Quick Lead workflow and create workflow run', async () => {
|
||||
const runWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) {
|
||||
runWorkflowVersion(input: $input) {
|
||||
workflowRunId
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(runWorkflowResponse.status).toBe(200);
|
||||
expect(runWorkflowResponse.body.errors).toBeUndefined();
|
||||
expect(
|
||||
runWorkflowResponse.body.data.runWorkflowVersion.workflowRunId,
|
||||
).toBeDefined();
|
||||
|
||||
const workflowRunId =
|
||||
runWorkflowResponse.body.data.runWorkflowVersion.workflowRunId;
|
||||
const workflowRunId = await runWorkflowVersion({
|
||||
workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID,
|
||||
});
|
||||
|
||||
createdWorkflowRunId = workflowRunId;
|
||||
|
||||
@@ -251,26 +158,9 @@ describe('Quick Lead Workflow (e2e)', () => {
|
||||
});
|
||||
|
||||
it('should be able to stop a running workflow run', async () => {
|
||||
const runWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) {
|
||||
runWorkflowVersion(input: $input) {
|
||||
workflowRunId
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const workflowRunId =
|
||||
runWorkflowResponse.body.data.runWorkflowVersion.workflowRunId;
|
||||
const workflowRunId = await runWorkflowVersion({
|
||||
workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID,
|
||||
});
|
||||
|
||||
const stopResponse = await client
|
||||
.post('/graphql')
|
||||
@@ -293,19 +183,7 @@ describe('Quick Lead Workflow (e2e)', () => {
|
||||
const workflowRun = await getWorkflowRun(workflowRunId);
|
||||
|
||||
expect(workflowRun?.status).toBe('STOPPED');
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation DestroyWorkflowRun($id: ID!) {
|
||||
destroyWorkflowRun(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: workflowRunId },
|
||||
});
|
||||
await destroyWorkflowRun(workflowRunId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -349,44 +227,14 @@ describe('Quick Lead Workflow (e2e)', () => {
|
||||
}
|
||||
|
||||
if (testWorkflowRunId) {
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation DestroyWorkflowRun($id: ID!) {
|
||||
destroyWorkflowRun(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: testWorkflowRunId },
|
||||
});
|
||||
await destroyWorkflowRun(testWorkflowRunId);
|
||||
}
|
||||
});
|
||||
|
||||
it('should complete full workflow: trigger → submit form → create Company and Person', async () => {
|
||||
const runWorkflowResponse = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) {
|
||||
runWorkflowVersion(input: $input) {
|
||||
workflowRunId
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(runWorkflowResponse.body.errors).toBeUndefined();
|
||||
testWorkflowRunId =
|
||||
runWorkflowResponse.body.data.runWorkflowVersion.workflowRunId;
|
||||
testWorkflowRunId = await runWorkflowVersion({
|
||||
workflowVersionId: QUICK_LEAD_WORKFLOW_VERSION_ID,
|
||||
});
|
||||
|
||||
expect(testWorkflowRunId).toBeDefined();
|
||||
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
import request from 'supertest';
|
||||
import { WORKFLOW_RUN_GQL_FIELDS } from 'test/integration/constants/workflow-gql-fields.constants';
|
||||
|
||||
const client = request(`http://localhost:${APP_PORT}`);
|
||||
|
||||
export type WorkflowRunStatusType =
|
||||
| 'NOT_STARTED'
|
||||
| 'RUNNING'
|
||||
| 'COMPLETED'
|
||||
| 'FAILED'
|
||||
| 'ENQUEUED'
|
||||
| 'STOPPING'
|
||||
| 'STOPPED';
|
||||
|
||||
export type WorkflowRunState = {
|
||||
stepInfos?: Record<
|
||||
string,
|
||||
{
|
||||
status: string;
|
||||
result?: Record<string, unknown>;
|
||||
}
|
||||
>;
|
||||
flow?: {
|
||||
trigger?: {
|
||||
type: string;
|
||||
nextStepIds: string[];
|
||||
};
|
||||
steps?: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
name: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type WorkflowRunResponse = {
|
||||
id: string;
|
||||
status: WorkflowRunStatusType;
|
||||
state: WorkflowRunState;
|
||||
workflowVersionId: string;
|
||||
};
|
||||
|
||||
export const getWorkflowRun = async (
|
||||
workflowRunId: string,
|
||||
): Promise<WorkflowRunResponse | null> => {
|
||||
const response = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
query FindWorkflowRun($id: UUID!) {
|
||||
workflowRun(filter: { id: { eq: $id } }) {
|
||||
${WORKFLOW_RUN_GQL_FIELDS}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: workflowRunId },
|
||||
});
|
||||
|
||||
if (response.body.errors || !response.body.data?.workflowRun) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.body.data.workflowRun;
|
||||
};
|
||||
|
||||
export const runWorkflowVersion = async ({
|
||||
workflowVersionId,
|
||||
payload,
|
||||
}: {
|
||||
workflowVersionId: string;
|
||||
payload?: object;
|
||||
}): Promise<string> => {
|
||||
const response = await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation RunWorkflowVersion($input: RunWorkflowVersionInput!) {
|
||||
runWorkflowVersion(input: $input) {
|
||||
workflowRunId
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
workflowVersionId,
|
||||
payload,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (response.body.errors || !response.body.data?.runWorkflowVersion) {
|
||||
throw new Error(
|
||||
`Failed to run workflow version: ${JSON.stringify(response.body.errors)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return response.body.data.runWorkflowVersion.workflowRunId;
|
||||
};
|
||||
|
||||
export const destroyWorkflowRun = async (
|
||||
workflowRunId: string,
|
||||
): Promise<void> => {
|
||||
await client
|
||||
.post('/graphql')
|
||||
.set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`)
|
||||
.send({
|
||||
query: `
|
||||
mutation DestroyWorkflowRun($id: ID!) {
|
||||
destroyWorkflowRun(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { id: workflowRunId },
|
||||
});
|
||||
};
|
||||
|
||||
export const waitForWorkflowCompletion = async (
|
||||
workflowRunId: string,
|
||||
maxAttempts = 30,
|
||||
intervalMs = 500,
|
||||
): Promise<WorkflowRunResponse | null> => {
|
||||
let workflowRun = await getWorkflowRun(workflowRunId);
|
||||
let attempts = 0;
|
||||
|
||||
while (
|
||||
workflowRun?.status === 'RUNNING' &&
|
||||
attempts < maxAttempts &&
|
||||
workflowRun !== null
|
||||
) {
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
workflowRun = await getWorkflowRun(workflowRunId);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
return workflowRun;
|
||||
};
|
||||
Reference in New Issue
Block a user