diff --git a/packages/server/__mocks__/@sendgrid/mail.js b/packages/server/__mocks__/@sendgrid/mail.js new file mode 100644 index 0000000000..e162237ff4 --- /dev/null +++ b/packages/server/__mocks__/@sendgrid/mail.js @@ -0,0 +1,18 @@ +class Email { + constructor() { + this.apiKey = null + } + + setApiKey(apiKey) { + this.apiKey = apiKey + } + + async send(msg) { + if (msg.to === "invalid@test.com") { + throw "Invalid" + } + return msg + } +} + +module.exports = new Email() diff --git a/packages/server/__mocks__/node-fetch.js b/packages/server/__mocks__/node-fetch.js index 1113791ec2..3cc412b1c6 100644 --- a/packages/server/__mocks__/node-fetch.js +++ b/packages/server/__mocks__/node-fetch.js @@ -1,17 +1,35 @@ const fetch = jest.requireActual("node-fetch") module.exports = async (url, opts) => { - // mocked data based on url - if (url.includes("api/apps")) { + function json(body, status = 200) { return { + status, json: async () => { - return { - app1: { - url: "/app1", - }, - } + return body }, } } + + // mocked data based on url + if (url.includes("api/apps")) { + return json({ + app1: { + url: "/app1", + }, + }) + } else if (url.includes("test.com")) { + return json({ + body: opts.body, + url, + method: opts.method, + }) + } else if (url.includes("invalid.com")) { + return json( + { + invalid: true, + }, + 404 + ) + } return fetch(url, opts) } diff --git a/packages/server/package.json b/packages/server/package.json index 990a69115b..703c1cd2b9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -53,11 +53,14 @@ "src/**/*.js", "!**/node_modules/**", "!src/db/views/*.js", - "!src/api/routes/tests/**/*.js", "!src/api/controllers/deploy/**/*.js", "!src/*.js", "!src/api/controllers/static/**/*", - "!src/db/dynamoClient.js" + "!src/db/dynamoClient.js", + "!src/utilities/usageQuota.js", + "!src/api/routes/tests/**/*", + "!src/tests/**/*", + "!src/automations/tests/**/*" ], "coverageReporters": [ "lcov", diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index 8edee38dee..ea4d60a04e 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -51,9 +51,13 @@ module.exports.definition = { } module.exports.run = async function({ inputs, appId, apiKey, emitter }) { - // TODO: better logging of when actions are missed due to missing parameters if (inputs.id == null || inputs.revision == null) { - return + return { + success: false, + response: { + message: "Invalid inputs", + }, + } } let ctx = { params: { diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js index 817ec424b2..ab8c747c58 100644 --- a/packages/server/src/automations/steps/outgoingWebhook.js +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -87,6 +87,7 @@ module.exports.run = async function({ inputs }) { success: response.status === 200, } } catch (err) { + /* istanbul ignore next */ return { success: false, response: err, diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index 3b83f961f5..a545662cf8 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -55,14 +55,14 @@ module.exports.definition = { module.exports.run = async function({ inputs, appId, emitter }) { if (inputs.rowId == null || inputs.row == null) { - return + return { + success: false, + response: { + message: "Invalid inputs", + }, + } } - inputs.row = await automationUtils.cleanUpRowById( - appId, - inputs.rowId, - inputs.row - ) // clear any falsy properties so that they aren't updated for (let propKey of Object.keys(inputs.row)) { if (!inputs.row[propKey] || inputs.row[propKey] === "") { @@ -73,7 +73,7 @@ module.exports.run = async function({ inputs, appId, emitter }) { // have to clean up the row, remove the table from it const ctx = { params: { - id: inputs.rowId, + rowId: inputs.rowId, }, request: { body: inputs.row, @@ -83,6 +83,11 @@ module.exports.run = async function({ inputs, appId, emitter }) { } try { + inputs.row = await automationUtils.cleanUpRowById( + appId, + inputs.rowId, + inputs.row + ) await rowController.patch(ctx) return { row: ctx.body, diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js new file mode 100644 index 0000000000..8f2dcc3475 --- /dev/null +++ b/packages/server/src/automations/tests/automation.spec.js @@ -0,0 +1,14 @@ +const automation = require("../index") +const usageQuota = require("../../utilities/usageQuota") + +jest.mock("../../utilities/usageQuota") + +describe("Check the primary input functions to automations", () => { + it("should be able to init in builder", async () => { + + }) + + it("should be able to init in cloud", async () => { + + }) +}) \ No newline at end of file diff --git a/packages/server/src/automations/tests/delay.spec.js b/packages/server/src/automations/tests/delay.spec.js index 8e9a725d01..99046e8171 100644 --- a/packages/server/src/automations/tests/delay.spec.js +++ b/packages/server/src/automations/tests/delay.spec.js @@ -1,6 +1,6 @@ const setup = require("./utilities") -describe("test the delay action", () => { +describe("test the delay logic", () => { it("should be able to run the delay", async () => { const time = 100 const before = Date.now() diff --git a/packages/server/src/automations/tests/deleteRow.spec.js b/packages/server/src/automations/tests/deleteRow.spec.js new file mode 100644 index 0000000000..0d5ff47ed8 --- /dev/null +++ b/packages/server/src/automations/tests/deleteRow.spec.js @@ -0,0 +1,58 @@ +const usageQuota = require("../../utilities/usageQuota") +const env = require("../../environment") +const setup = require("./utilities") + +jest.mock("../../utilities/usageQuota") + +describe("test the delete row action", () => { + let table, row, inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + table = await config.createTable() + row = await config.createRow() + inputs = { + tableId: table._id, + id: row._id, + revision: row._rev, + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) + expect(res.success).toEqual(true) + expect(res.response).toBeDefined() + expect(res.row._id).toEqual(row._id) + let error + try { + await config.getRow(table._id, res.id) + } catch (err) { + error = err + } + expect(error).toBeDefined() + }) + + it("check usage quota attempts", async () => { + env.CLOUD = true + await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1) + env.CLOUD = false + }) + + it("should check invalid inputs return an error", async () => { + const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, {}) + expect(res.success).toEqual(false) + }) + + it("should return an error when table doesn't exist", async () => { + const res = await setup.runStep(setup.actions.DELETE_ROW.stepId, { + tableId: "invalid", + id: "invalid", + revision: "invalid", + }) + expect(res.success).toEqual(false) + }) +}) diff --git a/packages/server/src/automations/tests/filter.spec.js b/packages/server/src/automations/tests/filter.spec.js index 8ddd94f7e4..05361f43ed 100644 --- a/packages/server/src/automations/tests/filter.spec.js +++ b/packages/server/src/automations/tests/filter.spec.js @@ -1,7 +1,7 @@ const setup = require("./utilities") const { LogicConditions } = require("../steps/filter") -describe("test the delay action", () => { +describe("test the filter logic", () => { async function checkFilter(field, condition, value, pass = true) { let res = await setup.runStep(setup.logic.FILTER.stepId, { field, condition, value } diff --git a/packages/server/src/automations/tests/outgoingWebhook.spec.js b/packages/server/src/automations/tests/outgoingWebhook.spec.js new file mode 100644 index 0000000000..f1d8d25ba8 --- /dev/null +++ b/packages/server/src/automations/tests/outgoingWebhook.spec.js @@ -0,0 +1,39 @@ +const setup = require("./utilities") +const fetch = require("node-fetch") + +jest.mock("node-fetch") + +describe("test the outgoing webhook action", () => { + let inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + requestMethod: "POST", + url: "www.test.com", + requestBody: JSON.stringify({ + a: 1, + }), + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.OUTGOING_WEBHOOK.stepId, inputs) + expect(res.success).toEqual(true) + expect(res.response.url).toEqual("http://www.test.com") + expect(res.response.method).toEqual("POST") + expect(res.response.body.a).toEqual(1) + }) + + it("should return an error if something goes wrong in fetch", async () => { + const res = await setup.runStep(setup.actions.OUTGOING_WEBHOOK.stepId, { + requestMethod: "GET", + url: "www.invalid.com" + }) + expect(res.success).toEqual(false) + }) + +}) diff --git a/packages/server/src/automations/tests/sendEmail.spec.js b/packages/server/src/automations/tests/sendEmail.spec.js new file mode 100644 index 0000000000..5f3aabfff8 --- /dev/null +++ b/packages/server/src/automations/tests/sendEmail.spec.js @@ -0,0 +1,36 @@ +const setup = require("./utilities") + +jest.mock("@sendgrid/mail") + +describe("test the send email action", () => { + let inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + inputs = { + to: "me@test.com", + from: "budibase@test.com", + subject: "Testing", + text: "Email contents", + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, inputs) + expect(res.success).toEqual(true) + // the mocked module throws back the input + expect(res.response.to).toEqual("me@test.com") + }) + + it("should return an error if input an invalid email address", async () => { + const res = await setup.runStep(setup.actions.SEND_EMAIL.stepId, { + ...inputs, + to: "invalid@test.com", + }) + expect(res.success).toEqual(false) + }) + +}) diff --git a/packages/server/src/automations/tests/updateRow.spec.js b/packages/server/src/automations/tests/updateRow.spec.js new file mode 100644 index 0000000000..79c998459b --- /dev/null +++ b/packages/server/src/automations/tests/updateRow.spec.js @@ -0,0 +1,45 @@ +const env = require("../../environment") +const setup = require("./utilities") + +describe("test the update row action", () => { + let table, row, inputs + let config = setup.getConfig() + + beforeEach(async () => { + await config.init() + table = await config.createTable() + row = await config.createRow() + inputs = { + rowId: row._id, + row: { + ...row, + name: "Updated name", + // put a falsy option in to be removed + description: "", + } + } + }) + + afterAll(setup.afterAll) + + it("should be able to run the action", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs) + expect(res.success).toEqual(true) + const updatedRow = await config.getRow(table._id, res.id) + expect(updatedRow.name).toEqual("Updated name") + expect(updatedRow.description).not.toEqual("") + }) + + it("should check invalid inputs return an error", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {}) + expect(res.success).toEqual(false) + }) + + it("should return an error when table doesn't exist", async () => { + const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { + row: { _id: "invalid" }, + rowId: "invalid", + }) + expect(res.success).toEqual(false) + }) +}) diff --git a/packages/server/src/utilities/routing/index.js b/packages/server/src/utilities/routing/index.js index f4af585dc6..541733dcc4 100644 --- a/packages/server/src/utilities/routing/index.js +++ b/packages/server/src/utilities/routing/index.js @@ -12,6 +12,7 @@ exports.getRoutingInfo = async appId => { return allRouting.rows.map(row => row.value) } catch (err) { // check if the view doesn't exist, it should for all new instances + /* istanbul ignore next */ if (err != null && err.name === "not_found") { await createRoutingView(appId) return exports.getRoutingInfo(appId)