From ece948e4ef876133f413cec633a43d44abfe4d72 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 15 Mar 2021 14:11:13 +0000 Subject: [PATCH] Finishing off automation test cases, above 90% coverage for automations codebase. --- packages/server/package.json | 2 +- packages/server/src/app.js | 2 +- packages/server/src/automations/actions.js | 5 +- packages/server/src/automations/index.js | 33 ++-- .../src/automations/tests/automation.spec.js | 143 +++++++++++++++++- packages/server/src/automations/triggers.js | 5 + packages/server/src/tests/utilities/index.js | 11 ++ 7 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 packages/server/src/tests/utilities/index.js diff --git a/packages/server/package.json b/packages/server/package.json index 703c1cd2b9..a025d274f1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -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", diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 15e996cfe6..6167bdecdf 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -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() diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 37126c7ed4..ee57f5a109 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -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] diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index a983495fb5..9aba399133 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -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}` + ) + } }) } diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index 8f2dcc3475..f2463b2306 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -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") }) }) \ No newline at end of file diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 73ce9edeed..7e50e5ee74 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -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 diff --git a/packages/server/src/tests/utilities/index.js b/packages/server/src/tests/utilities/index.js new file mode 100644 index 0000000000..aa8039ce2f --- /dev/null +++ b/packages/server/src/tests/utilities/index.js @@ -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) +}