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 b63599d024
commit ece948e4ef
7 changed files with 177 additions and 24 deletions

View File

@ -33,7 +33,7 @@
}, },
"scripts": { "scripts": {
"test": "jest --testPathIgnorePatterns=routes && npm run test:integration", "test": "jest --testPathIgnorePatterns=routes && npm run test:integration",
"test:integration": "jest --runInBand --coverage", "test:integration": "jest --coverage --detectOpenHandles",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"run:docker": "node src/index", "run:docker": "node src/index",
"dev:builder": "cross-env PORT=4001 nodemon src/index.js", "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())}`) console.log(`Budibase running on ${JSON.stringify(server.address())}`)
env._set("PORT", server.address().port) env._set("PORT", server.address().port)
eventEmitter.emitPort(env.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 // only init the self hosting DB info in the Pouch, not needed in self hosting prod
if (!env.CLOUD) { if (!env.CLOUD) {
await selfhost.init() await selfhost.init()

View File

@ -37,12 +37,12 @@ let AUTOMATION_BUCKET = env.AUTOMATION_BUCKET
let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY
let MANIFEST = null let MANIFEST = null
/* instanbul ignore next */ /* istanbul ignore next */
function buildBundleName(pkgName, version) { function buildBundleName(pkgName, version) {
return `${pkgName}@${version}.min.js` return `${pkgName}@${version}.min.js`
} }
/* instanbul ignore next */ /* istanbul ignore next */
async function downloadPackage(name, version, bundleName) { async function downloadPackage(name, version, bundleName) {
await download( await download(
`${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`, `${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`,
@ -51,6 +51,7 @@ async function downloadPackage(name, version, bundleName) {
return require(join(AUTOMATION_DIRECTORY, bundleName)) return require(join(AUTOMATION_DIRECTORY, bundleName))
} }
/* istanbul ignore next */
module.exports.getAction = async function(actionName) { module.exports.getAction = async function(actionName) {
if (BUILTIN_ACTIONS[actionName] != null) { if (BUILTIN_ACTIONS[actionName] != null) {
return BUILTIN_ACTIONS[actionName] 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 * This module is built purely to kick off the worker farm and manage the inputs/outputs
*/ */
module.exports.init = function() { module.exports.init = async function() {
actions.init().then(() => { await actions.init()
triggers.automationQueue.process(async job => { triggers.automationQueue.process(async job => {
try { try {
if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) { if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) {
job.data.automation.apiKey = await updateQuota(job.data.automation) 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}`
)
} }
}) 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 automation = require("../index")
const usageQuota = require("../../utilities/usageQuota") 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") 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 () => { 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 () => { 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) { emitter.on("row:save", async function(event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) { if (!event || !event.row || !event.row.tableId) {
return return
} }
@ -232,6 +233,7 @@ emitter.on("row:save", async function(event) {
}) })
emitter.on("row:update", async function(event) { emitter.on("row:update", async function(event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) { if (!event || !event.row || !event.row.tableId) {
return return
} }
@ -239,6 +241,7 @@ emitter.on("row:update", async function(event) {
}) })
emitter.on("row:delete", async function(event) { emitter.on("row:delete", async function(event) {
/* istanbul ignore next */
if (!event || !event.row || !event.row.tableId) { if (!event || !event.row || !event.row.tableId) {
return return
} }
@ -272,6 +275,7 @@ async function fillRowOutput(automation, params) {
} }
params.row = row params.row = row
} catch (err) { } catch (err) {
/* istanbul ignore next */
throw "Failed to find table for trigger" throw "Failed to find table for trigger"
} }
return params return params
@ -297,6 +301,7 @@ module.exports.externalTrigger = async function(automation, params) {
automationQueue.add({ automation, event: params }) automationQueue.add({ automation, event: params })
} }
module.exports.fillRowOutput = fillRowOutput
module.exports.automationQueue = automationQueue module.exports.automationQueue = automationQueue
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS 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)
}