Finishing off automation test cases, above 90% coverage for automations codebase.

This commit is contained in:
mike12345567 2021-03-15 14:11:13 +00:00
parent 799168c6b8
commit 3406138f34
7 changed files with 177 additions and 24 deletions

View File

@ -33,7 +33,7 @@
},
"scripts": {
"test": "jest --testPathIgnorePatterns=routes && npm run test:integration",
"test:integration": "jest --runInBand --coverage",
"test:integration": "jest --coverage --detectOpenHandles",
"test:watch": "jest --watch",
"run:docker": "node src/index",
"dev:builder": "cross-env PORT=4001 nodemon src/index.js",

View File

@ -66,7 +66,7 @@ module.exports = server.listen(env.PORT || 0, async () => {
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
env._set("PORT", server.address().port)
eventEmitter.emitPort(env.PORT)
automations.init()
await automations.init()
// only init the self hosting DB info in the Pouch, not needed in self hosting prod
if (!env.CLOUD) {
await selfhost.init()

View File

@ -37,12 +37,12 @@ let AUTOMATION_BUCKET = env.AUTOMATION_BUCKET
let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY
let MANIFEST = null
/* instanbul ignore next */
/* istanbul ignore next */
function buildBundleName(pkgName, version) {
return `${pkgName}@${version}.min.js`
}
/* instanbul ignore next */
/* istanbul ignore next */
async function downloadPackage(name, version, bundleName) {
await download(
`${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`,
@ -51,6 +51,7 @@ async function downloadPackage(name, version, bundleName) {
return require(join(AUTOMATION_DIRECTORY, bundleName))
}
/* istanbul ignore next */
module.exports.getAction = async function(actionName) {
if (BUILTIN_ACTIONS[actionName] != null) {
return BUILTIN_ACTIONS[actionName]

View File

@ -30,23 +30,22 @@ async function updateQuota(automation) {
/**
* This module is built purely to kick off the worker farm and manage the inputs/outputs
*/
module.exports.init = function() {
actions.init().then(() => {
triggers.automationQueue.process(async job => {
try {
if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) {
job.data.automation.apiKey = await updateQuota(job.data.automation)
}
if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") {
await runWorker(job)
} else {
await singleThread(job)
}
} catch (err) {
console.error(
`${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}`
)
module.exports.init = async function() {
await actions.init()
triggers.automationQueue.process(async job => {
try {
if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) {
job.data.automation.apiKey = await updateQuota(job.data.automation)
}
})
if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") {
await runWorker(job)
} else {
await singleThread(job)
}
} catch (err) {
console.error(
`${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}`
)
}
})
}

View File

@ -1,14 +1,151 @@
const automation = require("../index")
const usageQuota = require("../../utilities/usageQuota")
const thread = require("../thread")
const triggers = require("../triggers")
const { basicAutomation, basicTable } = require("../../tests/utilities/structures")
const TestConfig = require("../../tests/utilities/TestConfiguration")
const { wait } = require("../../utilities")
const env = require("../../environment")
const { makePartial } = require("../../tests/utilities")
const { cleanInputValues } = require("../automationUtils")
let workerJob
jest.mock("../../utilities/usageQuota")
usageQuota.getAPIKey.mockReturnValue({ apiKey: "test" })
jest.mock("../thread")
jest.spyOn(global.console, "error")
jest.mock("worker-farm", () => {
return () => {
const value = jest
.fn()
.mockReturnValueOnce(undefined)
.mockReturnValueOnce("Error")
return (input, callback) => {
workerJob = input
if (callback) {
callback(value())
}
}
}
})
describe("Run through some parts of the automations system", () => {
let config = new TestConfig(false)
beforeEach(async () => {
await automation.init()
await config.init()
})
describe("Check the primary input functions to automations", () => {
it("should be able to init in builder", async () => {
await triggers.externalTrigger(basicAutomation(), { a: 1 })
await wait(100)
expect(workerJob).toBeUndefined()
expect(thread).toHaveBeenCalled()
})
it("should be able to init in cloud", async () => {
env.CLOUD = true
env.BUDIBASE_ENVIRONMENT = "PRODUCTION"
await triggers.externalTrigger(basicAutomation(), { a: 1 })
await wait(100)
// haven't added a mock implementation so getAPIKey of usageQuota just returns undefined
expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1)
expect(workerJob).toBeDefined()
env.BUDIBASE_ENVIRONMENT = "JEST"
env.CLOUD = false
})
it("try error scenario", async () => {
env.CLOUD = true
env.BUDIBASE_ENVIRONMENT = "PRODUCTION"
// the second call will throw an error
await triggers.externalTrigger(basicAutomation(), { a: 1 })
await wait(100)
expect(console.error).toHaveBeenCalled()
env.BUDIBASE_ENVIRONMENT = "JEST"
env.CLOUD = false
})
it("should be able to check triggering row filling", async () => {
const automation = basicAutomation()
let table = basicTable()
table.schema.boolean = {
type: "boolean",
constraints: {
type: "boolean",
},
}
table.schema.number = {
type: "number",
constraints: {
type: "number",
},
}
table.schema.datetime = {
type: "datetime",
constraints: {
type: "datetime",
},
}
table = await config.createTable(table)
automation.definition.trigger.inputs.tableId = table._id
const params = await triggers.fillRowOutput(automation, { appId: config.getAppId() })
expect(params.row).toBeDefined()
const date = new Date(params.row.datetime)
expect(typeof params.row.name).toBe("string")
expect(typeof params.row.boolean).toBe("boolean")
expect(typeof params.row.number).toBe("number")
expect(date.getFullYear()).toBe(1970)
})
it("should check coercion", async () => {
const table = await config.createTable()
const automation = basicAutomation()
automation.definition.trigger.inputs.tableId = table._id
automation.definition.trigger.stepId = "APP"
automation.definition.trigger.inputs.fields = { a: "number" }
await triggers.externalTrigger(automation, {
appId: config.getAppId(),
fields: {
a: "1"
}
})
await wait(100)
expect(thread).toHaveBeenCalledWith(makePartial({
data: {
event: {
fields: {
a: 1
}
}
}
}))
})
it("should be able to clean inputs with the utilities", () => {
// can't clean without a schema
let output = cleanInputValues({a: "1"})
expect(output.a).toBe("1")
output = cleanInputValues({a: "1", b: "true", c: "false", d: 1, e: "help"}, {
properties: {
a: {
type: "number",
},
b: {
type: "boolean",
},
c: {
type: "boolean",
}
}
})
expect(output.a).toBe(1)
expect(output.b).toBe(true)
expect(output.c).toBe(false)
expect(output.d).toBe(1)
expect(output.e).toBe("help")
})
})

View File

@ -225,6 +225,7 @@ async function queueRelevantRowAutomations(event, eventType) {
}
emitter.on("row:save", async function(event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
@ -232,6 +233,7 @@ emitter.on("row:save", async function(event) {
})
emitter.on("row:update", async function(event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
@ -239,6 +241,7 @@ emitter.on("row:update", async function(event) {
})
emitter.on("row:delete", async function(event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) {
return
}
@ -272,6 +275,7 @@ async function fillRowOutput(automation, params) {
}
params.row = row
} catch (err) {
/* istanbul ignore next */
throw "Failed to find table for trigger"
}
return params
@ -297,6 +301,7 @@ module.exports.externalTrigger = async function(automation, params) {
automationQueue.add({ automation, event: params })
}
module.exports.fillRowOutput = fillRowOutput
module.exports.automationQueue = automationQueue
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS

View File

@ -0,0 +1,11 @@
exports.makePartial = obj => {
const newObj = {}
for (let key of Object.keys(obj)) {
if (typeof obj[key] === "object") {
newObj[key] = exports.makePartial(obj[key])
} else {
newObj[key] = obj[key]
}
}
return expect.objectContaining(newObj)
}