add validators and tests for automation branching
This commit is contained in:
parent
56641e06c3
commit
839292b84d
|
@ -25,6 +25,8 @@ let {
|
||||||
collectAutomation,
|
collectAutomation,
|
||||||
filterAutomation,
|
filterAutomation,
|
||||||
updateRowAutomationWithFilters,
|
updateRowAutomationWithFilters,
|
||||||
|
branchAutomationIncorrectPosition,
|
||||||
|
branchAutomation,
|
||||||
} = setup.structures
|
} = setup.structures
|
||||||
|
|
||||||
describe("/automations", () => {
|
describe("/automations", () => {
|
||||||
|
@ -121,6 +123,78 @@ describe("/automations", () => {
|
||||||
expect(events.automation.stepCreated).toHaveBeenCalledTimes(2)
|
expect(events.automation.stepCreated).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("Should ensure you can't have a branch as not a last step", async () => {
|
||||||
|
const automation = branchAutomationIncorrectPosition()
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.send(automation)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.body.message).toContain("must contain at least 1 items")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should check validation on an automation that has a branch step with no children", async () => {
|
||||||
|
const automation = branchAutomationIncorrectPosition()
|
||||||
|
automation.definition.steps[0].inputs.branches = [
|
||||||
|
{ name: "test", condition: { equal: { "steps.1.success": "true" } } },
|
||||||
|
]
|
||||||
|
automation.definition.steps[0].inputs.children = {}
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.send(automation)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.body.message).toContain(
|
||||||
|
"Branch steps are only allowed as the last step"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should check validation on a branch step with empty conditions", async () => {
|
||||||
|
const automation = branchAutomation()
|
||||||
|
|
||||||
|
automation.definition.steps[1].inputs.branches = [
|
||||||
|
{ name: "test", condition: {} },
|
||||||
|
]
|
||||||
|
automation.definition.steps[1].inputs.children = {}
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.send(automation)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.body.message).toContain("must have at least 1 key")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should check validation on an branch that has a condition that is not valid", async () => {
|
||||||
|
const automation = branchAutomation()
|
||||||
|
|
||||||
|
automation.definition.steps[1].inputs.branches = [
|
||||||
|
{
|
||||||
|
name: "test",
|
||||||
|
condition: {
|
||||||
|
INCORRECT: { "steps.1.success": true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
automation.definition.steps[1].inputs.children = {}
|
||||||
|
|
||||||
|
const res = await request
|
||||||
|
.post(`/api/automations`)
|
||||||
|
.set(config.defaultHeaders())
|
||||||
|
.send(automation)
|
||||||
|
.expect("Content-Type", /json/)
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.body.message).toContain('INCORRECT" is not allowed')
|
||||||
|
})
|
||||||
|
|
||||||
it("should apply authorization to endpoint", async () => {
|
it("should apply authorization to endpoint", async () => {
|
||||||
const automation = newAutomation()
|
const automation = newAutomation()
|
||||||
await checkBuilderEndpoint({
|
await checkBuilderEndpoint({
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { auth, permissions } from "@budibase/backend-core"
|
import { auth, permissions } from "@budibase/backend-core"
|
||||||
import { DataSourceOperation } from "../../../constants"
|
import { DataSourceOperation } from "../../../constants"
|
||||||
import {
|
import {
|
||||||
|
AutomationActionStepId,
|
||||||
|
AutomationStep,
|
||||||
|
AutomationStepType,
|
||||||
EmptyFilterOption,
|
EmptyFilterOption,
|
||||||
SearchFilters,
|
SearchFilters,
|
||||||
Table,
|
Table,
|
||||||
|
@ -88,7 +91,7 @@ export function datasourceValidator() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterObject() {
|
function filterObject(unknown = true) {
|
||||||
const conditionalFilteringObject = () =>
|
const conditionalFilteringObject = () =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
conditions: Joi.array().items(Joi.link("#schema")).required(),
|
conditions: Joi.array().items(Joi.link("#schema")).required(),
|
||||||
|
@ -115,7 +118,7 @@ function filterObject() {
|
||||||
fuzzyOr: Joi.forbidden(),
|
fuzzyOr: Joi.forbidden(),
|
||||||
documentType: Joi.forbidden(),
|
documentType: Joi.forbidden(),
|
||||||
}
|
}
|
||||||
return Joi.object(filtersValidators).unknown(true).id("schema")
|
return Joi.object(filtersValidators).unknown(unknown).id("schema")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function internalSearchValidator() {
|
export function internalSearchValidator() {
|
||||||
|
@ -259,6 +262,11 @@ export function screenValidator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateStepSchema(allowStepTypes: string[]) {
|
function generateStepSchema(allowStepTypes: string[]) {
|
||||||
|
const branchSchema = Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
condition: filterObject(false).required().min(1),
|
||||||
|
})
|
||||||
|
|
||||||
return Joi.object({
|
return Joi.object({
|
||||||
stepId: Joi.string().required(),
|
stepId: Joi.string().required(),
|
||||||
id: Joi.string().required(),
|
id: Joi.string().required(),
|
||||||
|
@ -267,6 +275,17 @@ function generateStepSchema(allowStepTypes: string[]) {
|
||||||
tagline: Joi.string().required(),
|
tagline: Joi.string().required(),
|
||||||
icon: Joi.string().required(),
|
icon: Joi.string().required(),
|
||||||
params: Joi.object(),
|
params: Joi.object(),
|
||||||
|
inputs: Joi.when("stepId", {
|
||||||
|
is: AutomationActionStepId.BRANCH,
|
||||||
|
then: Joi.object({
|
||||||
|
branches: Joi.array().items(branchSchema).min(1).required(),
|
||||||
|
children: Joi.object()
|
||||||
|
.pattern(Joi.string(), Joi.array().items(Joi.link("#step")))
|
||||||
|
.required(),
|
||||||
|
}).required(),
|
||||||
|
otherwise: Joi.object(),
|
||||||
|
}),
|
||||||
|
|
||||||
args: Joi.object(),
|
args: Joi.object(),
|
||||||
type: Joi.string()
|
type: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
|
@ -274,6 +293,17 @@ function generateStepSchema(allowStepTypes: string[]) {
|
||||||
}).unknown(true)
|
}).unknown(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateStepsArray = (
|
||||||
|
steps: AutomationStep[],
|
||||||
|
helpers: Joi.CustomHelpers
|
||||||
|
) => {
|
||||||
|
for (let i = 0; i < steps.length - 1; i++) {
|
||||||
|
if (steps[i].stepId === AutomationActionStepId.BRANCH) {
|
||||||
|
return helpers.error("branchStepPosition")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function automationValidator(existing = false) {
|
export function automationValidator(existing = false) {
|
||||||
return auth.joiValidator.body(
|
return auth.joiValidator.body(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
|
@ -284,9 +314,20 @@ export function automationValidator(existing = false) {
|
||||||
definition: Joi.object({
|
definition: Joi.object({
|
||||||
steps: Joi.array()
|
steps: Joi.array()
|
||||||
.required()
|
.required()
|
||||||
.items(generateStepSchema(["ACTION", "LOGIC"])),
|
.items(
|
||||||
trigger: generateStepSchema(["TRIGGER"]).allow(null),
|
generateStepSchema([
|
||||||
|
AutomationStepType.ACTION,
|
||||||
|
AutomationStepType.LOGIC,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.custom(validateStepsArray)
|
||||||
|
.messages({
|
||||||
|
branchStepPosition:
|
||||||
|
"Branch steps are only allowed as the last step",
|
||||||
|
}),
|
||||||
|
trigger: generateStepSchema([AutomationStepType.TRIGGER]).allow(null),
|
||||||
})
|
})
|
||||||
|
|
||||||
.required()
|
.required()
|
||||||
.unknown(true),
|
.unknown(true),
|
||||||
}).unknown(true)
|
}).unknown(true)
|
||||||
|
|
|
@ -292,6 +292,132 @@ export function serverLogAutomation(appId?: string): Automation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function branchAutomationIncorrectPosition(appId?: string): Automation {
|
||||||
|
return {
|
||||||
|
name: "My Automation",
|
||||||
|
screenId: "kasdkfldsafkl",
|
||||||
|
live: true,
|
||||||
|
uiTree: {},
|
||||||
|
definition: {
|
||||||
|
trigger: {
|
||||||
|
stepId: AutomationTriggerStepId.APP,
|
||||||
|
name: "test",
|
||||||
|
tagline: "test",
|
||||||
|
icon: "test",
|
||||||
|
description: "test",
|
||||||
|
type: AutomationStepType.TRIGGER,
|
||||||
|
id: "test",
|
||||||
|
inputs: { fields: {} },
|
||||||
|
schema: {
|
||||||
|
inputs: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
stepId: AutomationActionStepId.BRANCH,
|
||||||
|
name: "Branch",
|
||||||
|
tagline: "Console log a value in the backend",
|
||||||
|
icon: "Monitoring",
|
||||||
|
description: "Logs the given text to the server (using console.log)",
|
||||||
|
inputs: {
|
||||||
|
branches: [],
|
||||||
|
},
|
||||||
|
schema: { inputs: { properties: {} }, outputs: { properties: {} } },
|
||||||
|
id: "y8lkZbeSe",
|
||||||
|
type: AutomationStepType.LOGIC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stepId: AutomationActionStepId.SERVER_LOG,
|
||||||
|
name: "Backend log",
|
||||||
|
tagline: "Console log a value in the backend",
|
||||||
|
icon: "Monitoring",
|
||||||
|
description: "Logs the given text to the server (using console.log)",
|
||||||
|
internal: true,
|
||||||
|
features: {
|
||||||
|
LOOPING: true,
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
text: "log statement",
|
||||||
|
},
|
||||||
|
schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
|
||||||
|
id: "y8lkZbeSe",
|
||||||
|
type: AutomationStepType.ACTION,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: "automation",
|
||||||
|
appId: appId!,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function branchAutomation(appId?: string): Automation {
|
||||||
|
return {
|
||||||
|
name: "My Automation",
|
||||||
|
screenId: "kasdkfldsafkl",
|
||||||
|
live: true,
|
||||||
|
uiTree: {},
|
||||||
|
definition: {
|
||||||
|
trigger: {
|
||||||
|
stepId: AutomationTriggerStepId.APP,
|
||||||
|
name: "test",
|
||||||
|
tagline: "test",
|
||||||
|
icon: "test",
|
||||||
|
description: "test",
|
||||||
|
type: AutomationStepType.TRIGGER,
|
||||||
|
id: "test",
|
||||||
|
inputs: { fields: {} },
|
||||||
|
schema: {
|
||||||
|
inputs: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
stepId: AutomationActionStepId.SERVER_LOG,
|
||||||
|
name: "Backend log",
|
||||||
|
tagline: "Console log a value in the backend",
|
||||||
|
icon: "Monitoring",
|
||||||
|
description: "Logs the given text to the server (using console.log)",
|
||||||
|
internal: true,
|
||||||
|
features: {
|
||||||
|
LOOPING: true,
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
text: "log statement",
|
||||||
|
},
|
||||||
|
schema: BUILTIN_ACTION_DEFINITIONS.SERVER_LOG.schema,
|
||||||
|
id: "y8lkZbeSe",
|
||||||
|
type: AutomationStepType.ACTION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stepId: AutomationActionStepId.BRANCH,
|
||||||
|
name: "Branch",
|
||||||
|
tagline: "Console log a value in the backend",
|
||||||
|
icon: "Monitoring",
|
||||||
|
description: "Logs the given text to the server (using console.log)",
|
||||||
|
inputs: {
|
||||||
|
branches: [],
|
||||||
|
},
|
||||||
|
schema: { inputs: { properties: {} }, outputs: { properties: {} } },
|
||||||
|
id: "y8lkZbeSe",
|
||||||
|
type: AutomationStepType.LOGIC,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: "automation",
|
||||||
|
appId: appId!,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loopAutomation(
|
export function loopAutomation(
|
||||||
tableId: string,
|
tableId: string,
|
||||||
loopOpts?: LoopInput
|
loopOpts?: LoopInput
|
||||||
|
|
Loading…
Reference in New Issue