Add more automation tests (#14369)

* Add more automation tests

* remove comment

* update assertion

* unused type

* split out query creation

* remove map
This commit is contained in:
Peter Clement 2024-08-13 14:16:55 +01:00 committed by GitHub
parent 7c3593c929
commit 37f749019d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 266 additions and 39 deletions

View File

@ -1,55 +1,36 @@
import { Datasource, Query } from "@budibase/types" import { Datasource, Query } from "@budibase/types"
import * as setup from "./utilities" import * as setup from "./utilities"
import { import { DatabaseName } from "../../integrations/tests/utils"
DatabaseName,
getDatasource,
knexClient,
} from "../../integrations/tests/utils"
import { Knex } from "knex" import { Knex } from "knex"
import { generator } from "@budibase/backend-core/tests"
describe.each( describe.each([
[ DatabaseName.POSTGRES,
DatabaseName.POSTGRES, DatabaseName.MYSQL,
DatabaseName.MYSQL, DatabaseName.SQL_SERVER,
DatabaseName.SQL_SERVER, DatabaseName.MARIADB,
DatabaseName.MARIADB, DatabaseName.ORACLE,
DatabaseName.ORACLE, ])("execute query action (%s)", name => {
].map(name => [name, getDatasource(name)])
)("execute query action (%s)", (_, dsProvider) => {
let tableName: string let tableName: string
let client: Knex let client: Knex
let datasource: Datasource let datasource: Datasource
let query: Query let query: Query
let config = setup.getConfig() const config = setup.getConfig()
beforeAll(async () => { beforeAll(async () => {
await config.init() await config.init()
const ds = await dsProvider const testSetup = await setup.setupTestDatasource(config, name)
datasource = await config.api.datasource.create(ds) datasource = testSetup.datasource
client = await knexClient(ds) client = testSetup.client
}) })
beforeEach(async () => { beforeEach(async () => {
tableName = generator.guid() tableName = await setup.createTestTable(client, {
await client.schema.createTable(tableName, table => { a: { type: "string" },
table.string("a") b: { type: "number" },
table.integer("b")
})
await client(tableName).insert({ a: "string", b: 1 })
query = await config.api.query.save({
name: "test query",
datasourceId: datasource._id!,
parameters: [],
fields: {
sql: client(tableName).select("*").toSQL().toNative().sql,
},
transformer: "",
schema: {},
readable: true,
queryVerb: "read",
}) })
await setup.insertTestData(client, tableName, [{ a: "string", b: 1 }])
query = await setup.saveTestQuery(config, client, tableName, datasource)
}) })
afterEach(async () => { afterEach(async () => {

View File

@ -1,7 +1,14 @@
import * as automation from "../../index" import * as automation from "../../index"
import * as setup from "../utilities" import * as setup from "../utilities"
import { Table, LoopStepType } from "@budibase/types" import {
Table,
LoopStepType,
CreateRowStepOutputs,
ServerLogStepOutputs,
FieldType,
} from "@budibase/types"
import { createAutomationBuilder } from "../utilities/AutomationBuilder" import { createAutomationBuilder } from "../utilities/AutomationBuilder"
import { DatabaseName } from "../../../integrations/tests/utils"
describe("Automation Scenarios", () => { describe("Automation Scenarios", () => {
let config = setup.getConfig(), let config = setup.getConfig(),
@ -63,6 +70,72 @@ describe("Automation Scenarios", () => {
}) })
}) })
}) })
it("should run an automation where a loop is successfully run twice", async () => {
const builder = createAutomationBuilder({
name: "Test Trigger with Loop and Create Row",
})
const results = await builder
.rowSaved(
{ tableId: table._id! },
{
row: {
name: "Trigger Row",
description: "This row triggers the automation",
},
id: "1234",
revision: "1",
}
)
.loop({
option: LoopStepType.ARRAY,
binding: [1, 2, 3],
})
.createRow({
row: {
name: "Item {{ loop.currentItem }}",
description: "Created from loop",
tableId: table._id,
},
})
.loop({
option: LoopStepType.STRING,
binding: "Message 1,Message 2,Message 3",
})
.serverLog({ text: "{{loop.currentItem}}" })
.run()
expect(results.trigger).toBeDefined()
expect(results.steps).toHaveLength(2)
expect(results.steps[0].outputs.iterations).toBe(3)
expect(results.steps[0].outputs.items).toHaveLength(3)
results.steps[0].outputs.items.forEach(
(output: CreateRowStepOutputs, index: number) => {
expect(output).toMatchObject({
success: true,
row: {
name: `Item ${index + 1}`,
description: "Created from loop",
},
})
}
)
expect(results.steps[1].outputs.iterations).toBe(3)
expect(results.steps[1].outputs.items).toHaveLength(3)
results.steps[1].outputs.items.forEach(
(output: ServerLogStepOutputs, index: number) => {
expect(output).toMatchObject({
success: true,
})
expect(output.message).toContain(`Message ${index + 1}`)
}
)
})
}) })
describe("Row Automations", () => { describe("Row Automations", () => {
@ -157,4 +230,94 @@ describe("Automation Scenarios", () => {
expect(results.steps[1].outputs.success).toBeTruthy() expect(results.steps[1].outputs.success).toBeTruthy()
expect(results.steps[2].outputs.rows).toHaveLength(1) expect(results.steps[2].outputs.rows).toHaveLength(1)
}) })
it("should query an external database for some data then insert than into an internal table", async () => {
const { datasource, client } = await setup.setupTestDatasource(
config,
DatabaseName.MYSQL
)
const newTable = await config.createTable({
name: "table",
type: "table",
schema: {
name: {
name: "name",
type: FieldType.STRING,
constraints: {
presence: true,
},
},
age: {
name: "age",
type: FieldType.NUMBER,
constraints: {
presence: true,
},
},
},
})
const tableName = await setup.createTestTable(client, {
name: { type: "string" },
age: { type: "number" },
})
const rows = [
{ name: "Joe", age: 20 },
{ name: "Bob", age: 25 },
{ name: "Paul", age: 30 },
]
await setup.insertTestData(client, tableName, rows)
const query = await setup.saveTestQuery(
config,
client,
tableName,
datasource
)
const builder = createAutomationBuilder({
name: "Test external query and save",
})
const results = await builder
.appAction({
fields: {},
})
.executeQuery({
query: {
queryId: query._id!,
},
})
.loop({
option: LoopStepType.ARRAY,
binding: "{{ steps.1.response }}",
})
.createRow({
row: {
name: "{{ loop.currentItem.name }}",
age: "{{ loop.currentItem.age }}",
tableId: newTable._id!,
},
})
.queryRows({
tableId: newTable._id!,
})
.run()
expect(results.steps).toHaveLength(3)
expect(results.steps[1].outputs.iterations).toBe(3)
expect(results.steps[1].outputs.items).toHaveLength(3)
expect(results.steps[2].outputs.rows).toHaveLength(3)
rows.forEach(expectedRow => {
expect(results.steps[2].outputs.rows).toEqual(
expect.arrayContaining([expect.objectContaining(expectedRow)])
)
})
})
}) })

View File

@ -28,6 +28,7 @@ import {
SmtpEmailStepInputs, SmtpEmailStepInputs,
ExecuteQueryStepInputs, ExecuteQueryStepInputs,
QueryRowsStepInputs, QueryRowsStepInputs,
ServerLogStepInputs,
} from "@budibase/types" } from "@budibase/types"
import {} from "../../steps/loop" import {} from "../../steps/loop"
import TestConfiguration from "../../../tests/utilities/TestConfiguration" import TestConfiguration from "../../../tests/utilities/TestConfiguration"
@ -119,6 +120,10 @@ class AutomationBuilder {
return this.step(BUILTIN_ACTION_DEFINITIONS.LOOP, inputs) return this.step(BUILTIN_ACTION_DEFINITIONS.LOOP, inputs)
} }
serverLog(input: ServerLogStepInputs): this {
return this.step(BUILTIN_ACTION_DEFINITIONS.SERVER_LOG, input)
}
private trigger<T extends { [key: string]: any }>( private trigger<T extends { [key: string]: any }>(
triggerSchema: AutomationTriggerSchema, triggerSchema: AutomationTriggerSchema,
inputs?: T, inputs?: T,

View File

@ -3,7 +3,14 @@ import { context } from "@budibase/backend-core"
import { BUILTIN_ACTION_DEFINITIONS, getAction } from "../../actions" import { BUILTIN_ACTION_DEFINITIONS, getAction } from "../../actions"
import emitter from "../../../events/index" import emitter from "../../../events/index"
import env from "../../../environment" import env from "../../../environment"
import { AutomationActionStepId } from "@budibase/types" import { AutomationActionStepId, Datasource } from "@budibase/types"
import { Knex } from "knex"
import { generator } from "@budibase/backend-core/tests"
import {
getDatasource,
knexClient,
DatabaseName,
} from "../../../integrations/tests/utils"
let config: TestConfig let config: TestConfig
@ -57,5 +64,58 @@ export async function runStep(stepId: string, inputs: any, stepContext?: any) {
} }
} }
export async function createTestTable(client: Knex, schema: any) {
const tableName = generator.guid()
await client.schema.createTable(tableName, table => {
for (const fieldName in schema) {
const field = schema[fieldName]
if (field.type === "string") {
table.string(fieldName)
} else if (field.type === "number") {
table.integer(fieldName)
}
}
})
return tableName
}
export async function insertTestData(
client: Knex,
tableName: string,
rows: any[]
) {
await client(tableName).insert(rows)
}
export async function saveTestQuery(
config: TestConfig,
client: Knex,
tableName: string,
datasource: Datasource
) {
return await config.api.query.save({
name: "test query",
datasourceId: datasource._id!,
parameters: [],
fields: {
sql: client(tableName).select("*").toSQL().toNative().sql,
},
transformer: "",
schema: {},
readable: true,
queryVerb: "read",
})
}
export async function setupTestDatasource(
config: TestConfig,
dbName: DatabaseName
) {
const db = await getDatasource(dbName)
const datasource = await config.api.datasource.create(db)
const client = await knexClient(db)
return { datasource, client }
}
export const apiKey = "test" export const apiKey = "test"
export const actions = BUILTIN_ACTION_DEFINITIONS export const actions = BUILTIN_ACTION_DEFINITIONS

View File

@ -98,6 +98,24 @@ export type ActionImplementations<T extends Hosting> = {
} }
: {}) : {})
export type AutomationStepOutputs =
| CollectStepOutputs
| CreateRowStepOutputs
| DelayStepOutputs
| DeleteRowStepOutputs
| ExecuteQueryStepOutputs
| ExecuteScriptStepOutputs
| FilterStepOutputs
| QueryRowsStepOutputs
| BaseAutomationOutputs
| BashStepOutputs
| ExternalAppStepOutputs
| OpenAIStepOutputs
| ServerLogStepOutputs
| TriggerAutomationStepOutputs
| UpdateRowStepOutputs
| ZapierStepOutputs
export type BaseAutomationOutputs = { export type BaseAutomationOutputs = {
success?: boolean success?: boolean
response?: { response?: {
@ -199,7 +217,7 @@ export type LoopStepInputs = {
} }
export type LoopStepOutputs = { export type LoopStepOutputs = {
items: string items: AutomationStepOutputs[]
success: boolean success: boolean
iterations: number iterations: number
} }