From 768a9d59da12cfefcd7ccaba05f975e878669504 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 3 Mar 2021 17:05:18 +0000 Subject: [PATCH 01/28] v0.8.4 --- lerna.json | 2 +- packages/builder/package.json | 6 +++--- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 6 +++--- packages/standard-components/package.json | 2 +- packages/string-templates/package.json | 2 +- packages/worker/package.json | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lerna.json b/lerna.json index 367a66a92..e957a5c59 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.8.3", + "version": "0.8.4", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/builder/package.json b/packages/builder/package.json index 6d0bb1508..128838056 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.8.3", + "version": "0.8.4", "license": "AGPL-3.0", "private": true, "scripts": { @@ -64,9 +64,9 @@ }, "dependencies": { "@budibase/bbui": "^1.58.13", - "@budibase/client": "^0.8.3", + "@budibase/client": "^0.8.4", "@budibase/colorpicker": "1.0.1", - "@budibase/string-templates": "^0.8.3", + "@budibase/string-templates": "^0.8.4", "@budibase/svelte-ag-grid": "^1.0.4", "@sentry/browser": "5.19.1", "@svelteschool/svelte-forms": "0.7.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 24ea8808c..a957e8415 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "cli", - "version": "0.7.8", + "version": "0.8.4", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": "src/index.js", diff --git a/packages/client/package.json b/packages/client/package.json index 8eb481c1f..8166dec3d 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.8.3", + "version": "0.8.4", "license": "MPL-2.0", "main": "dist/budibase-client.js", "module": "dist/budibase-client.js", @@ -9,14 +9,14 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/string-templates": "^0.8.3", + "@budibase/string-templates": "^0.8.4", "deep-equal": "^2.0.1", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.8.3", + "@budibase/standard-components": "^0.8.4", "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "fs-extra": "^8.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index 2e8c742cc..0b2849fb1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.8.3", + "version": "0.8.4", "description": "Budibase Web Server", "main": "src/electron.js", "repository": { @@ -66,8 +66,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/client": "^0.8.3", - "@budibase/string-templates": "^0.8.3", + "@budibase/client": "^0.8.4", + "@budibase/string-templates": "^0.8.4", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 1e6cf6b59..f4b8fad2c 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -35,7 +35,7 @@ "keywords": [ "svelte" ], - "version": "0.8.3", + "version": "0.8.4", "license": "MIT", "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd", "dependencies": { diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index b703bbd30..1afe39e4d 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.8.3", + "version": "0.8.4", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.js", "module": "src/index.js", diff --git a/packages/worker/package.json b/packages/worker/package.json index baf4890d9..4bdfbe3c0 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/deployment", "email": "hi@budibase.com", - "version": "0.8.3", + "version": "0.8.4", "description": "Budibase Deployment Server", "main": "src/index.js", "repository": { From 72f0aebb2bd2a01f885a1321d1d82906e2cdf0b4 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 3 Mar 2021 17:46:36 +0000 Subject: [PATCH 02/28] v0.8.5 --- lerna.json | 2 +- packages/builder/package.json | 6 +++--- packages/client/package.json | 8 ++++---- packages/server/package.json | 8 ++++---- packages/standard-components/package.json | 4 ++-- packages/string-templates/package.json | 4 ++-- packages/worker/package.json | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lerna.json b/lerna.json index e957a5c59..3425a979b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.8.4", + "version": "0.8.5", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/builder/package.json b/packages/builder/package.json index 128838056..e19514d2a 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.8.4", + "version": "0.8.5", "license": "AGPL-3.0", "private": true, "scripts": { @@ -64,9 +64,9 @@ }, "dependencies": { "@budibase/bbui": "^1.58.13", - "@budibase/client": "^0.8.4", + "@budibase/client": "^0.8.5", "@budibase/colorpicker": "1.0.1", - "@budibase/string-templates": "^0.8.4", + "@budibase/string-templates": "^0.8.5", "@budibase/svelte-ag-grid": "^1.0.4", "@sentry/browser": "5.19.1", "@svelteschool/svelte-forms": "0.7.0", diff --git a/packages/client/package.json b/packages/client/package.json index 8166dec3d..49ece2ed4 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.8.4", + "version": "0.8.5", "license": "MPL-2.0", "main": "dist/budibase-client.js", "module": "dist/budibase-client.js", @@ -9,14 +9,14 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/string-templates": "^0.8.4", + "@budibase/string-templates": "^0.8.5", "deep-equal": "^2.0.1", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.8.4", + "@budibase/standard-components": "^0.8.5", "@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-node-resolve": "^10.0.0", "fs-extra": "^8.1.0", @@ -30,5 +30,5 @@ "svelte": "^3.30.0", "svelte-jester": "^1.0.6" }, - "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd" + "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" } diff --git a/packages/server/package.json b/packages/server/package.json index 0b2849fb1..3ecaa7690 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.8.4", + "version": "0.8.5", "description": "Budibase Web Server", "main": "src/electron.js", "repository": { @@ -66,8 +66,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/client": "^0.8.4", - "@budibase/string-templates": "^0.8.4", + "@budibase/client": "^0.8.5", + "@budibase/string-templates": "^0.8.5", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -132,5 +132,5 @@ "pouchdb-adapter-memory": "^7.2.1", "supertest": "^4.0.2" }, - "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd" + "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" } diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index f4b8fad2c..8bce8b2f0 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -35,9 +35,9 @@ "keywords": [ "svelte" ], - "version": "0.8.4", + "version": "0.8.5", "license": "MIT", - "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd", + "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504", "dependencies": { "@adobe/spectrum-css-workflow-icons": "^1.1.0", "@budibase/bbui": "^1.58.13", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 1afe39e4d..ec7858da7 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.8.4", + "version": "0.8.5", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.js", "module": "src/index.js", @@ -33,5 +33,5 @@ "rollup-plugin-terser": "^7.0.2", "typescript": "^4.1.3" }, - "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd" + "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" } diff --git a/packages/worker/package.json b/packages/worker/package.json index 4bdfbe3c0..9f12a0ac1 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/deployment", "email": "hi@budibase.com", - "version": "0.8.4", + "version": "0.8.5", "description": "Budibase Deployment Server", "main": "src/index.js", "repository": { @@ -34,5 +34,5 @@ "pouchdb-all-dbs": "^1.0.2", "server-destroy": "^1.0.1" }, - "gitHead": "1a80b09fd093f2599a68f7db72ad639dd50922dd" + "gitHead": "768a9d59da12cfefcd7ccaba05f975e878669504" } From dc586a118581ad1b4bfe3b867fd396cdac0ebf7a Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Fri, 5 Mar 2021 14:13:43 +0000 Subject: [PATCH 03/28] middleware tests --- .../src/middleware/tests/selfhost.spec.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/server/src/middleware/tests/selfhost.spec.js diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js new file mode 100644 index 000000000..0f721bc89 --- /dev/null +++ b/packages/server/src/middleware/tests/selfhost.spec.js @@ -0,0 +1,43 @@ +const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") +const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") +const { basicUser } = require("./utilities/structures") +const setup = require("./utilities") + +describe("Self host middleware", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("fetch", () => { + it("returns a list of users from an instance db", async () => { + await config.createUser("brenda@brenda.com", "brendas_password") + await config.createUser("pam@pam.com", "pam_password") + const res = await request + .get(`/api/users`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.body.length).toBe(2) + expect(res.body.find(u => u.email === "brenda@brenda.com")).toBeDefined() + expect(res.body.find(u => u.email === "pam@pam.com")).toBeDefined() + }) + + it("should apply authorization to endpoint", async () => { + await config.createUser("brenda@brenda.com", "brendas_password") + await checkPermissionsEndpoint({ + config, + request, + method: "GET", + url: `/api/users`, + passRole: BUILTIN_ROLE_IDS.ADMIN, + failRole: BUILTIN_ROLE_IDS.PUBLIC, + }) + }) + }) +}) From 3f9d5ac6e3291f64d35ecad58efb0ab9d1439dbc Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 5 Mar 2021 15:19:33 +0000 Subject: [PATCH 04/28] Fixing an issue discovered where the relationshipType currently specifies the wrong cardinality, for now just flipping the way it is specified in the front end as this will accurately describe what the backend is performing. --- .../backend/DataTable/modals/CreateEditColumn.svelte | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte index 598bcdaad..ad2371f3e 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditColumn.svelte @@ -162,15 +162,15 @@ linkName = truncate(linkTable.name, { length: 15 }) return [ { - name: `Many ${thisName} rows has many ${linkName} rows`, + name: `Many ${thisName} rows → many ${linkName} rows`, value: RelationshipTypes.MANY_TO_MANY, }, { - name: `One ${thisName} row has many ${linkName} rows`, + name: `One ${linkName} row → many ${thisName} rows`, value: RelationshipTypes.ONE_TO_MANY, }, { - name: `Many ${thisName} rows has one ${linkName} row`, + name: `One ${thisName} row → many ${linkName} rows`, value: RelationshipTypes.MANY_TO_ONE, }, ] @@ -270,9 +270,9 @@ {value} bind:group={field.relationshipType}>
- - - + + +
{/each} From a3257d37e673896ce8fd21f5117dcfdd084b7a7c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 8 Mar 2021 14:49:19 +0000 Subject: [PATCH 05/28] Updating routing test cases. --- .../src/api/routes/tests/routing.spec.js | 85 +++++++++++++++++++ .../tests/utilities/TestConfiguration.js | 16 ++++ .../api/routes/tests/utilities/controllers.js | 1 + .../api/routes/tests/utilities/structures.js | 5 ++ 4 files changed, 107 insertions(+) create mode 100644 packages/server/src/api/routes/tests/routing.spec.js diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js new file mode 100644 index 000000000..3b7523f58 --- /dev/null +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -0,0 +1,85 @@ +const setup = require("./utilities") +const { basicScreen } = require("./utilities/structures") +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") + +describe("/routing", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let screen, screen2 + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + screen = await config.createScreen(basicScreen()) + screen2 = basicScreen() + screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER + screen2 = await config.createScreen(screen2) + }) + + describe("fetch", () => { + it("returns the correct routing for basic user", async () => { + const res = await request + .get(`/api/routing/client`) + .set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.BASIC)) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.routes).toBeDefined() + expect(res.body.routes["/"]).toEqual({ + subpaths: { + ["/"]: { + screenId: screen._id, + roleId: screen.routing.roleId + } + } + }) + }) + + it("returns the correct routing for power user", async () => { + const res = await request + .get(`/api/routing/client`) + .set(await config.roleHeaders("basic@test.com", BUILTIN_ROLE_IDS.POWER)) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.routes).toBeDefined() + expect(res.body.routes["/"]).toEqual({ + subpaths: { + ["/"]: { + screenId: screen2._id, + roleId: screen2.routing.roleId + } + } + }) + }) + }) + + describe("fetch all", () => { + it("should fetch all routes for builder", async () => { + const res = await request + .get(`/api/routing`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.routes).toBeDefined() + expect(res.body.routes["/"]).toEqual({ + subpaths: { + ["/"]: { + screens: { + [screen2.routing.roleId]: screen2._id, + [screen.routing.roleId]: screen._id, + } + } + } + }) + }) + + it("make sure it is a builder only endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/routing`, + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js index 5f6b1cc26..a124ca607 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -8,6 +8,7 @@ const { basicAutomation, basicDatasource, basicQuery, + basicScreen, } = require("./structures") const controllers = require("./controllers") const supertest = require("supertest") @@ -83,6 +84,15 @@ class TestConfiguration { return headers } + async roleHeaders(email = EMAIL, roleId) { + try { + await this.createUser(email, PASSWORD, roleId) + } catch (err) { + // allow errors here + } + return this.login(email, PASSWORD) + } + async createApp(appName) { this.app = await this._req({ name: appName }, null, controllers.app.create) this.appId = this.app._id @@ -208,6 +218,11 @@ class TestConfiguration { return this._req(config, null, controllers.query.save) } + async createScreen(config = null) { + config = config || basicScreen() + return this._req(config, null, controllers.screen.save) + } + async createUser( email = EMAIL, password = PASSWORD, @@ -241,6 +256,7 @@ class TestConfiguration { return { Accept: "application/json", Cookie: result.headers["set-cookie"], + "x-budibase-app-id": this.appId, } } } diff --git a/packages/server/src/api/routes/tests/utilities/controllers.js b/packages/server/src/api/routes/tests/utilities/controllers.js index 541495bec..e16aa6964 100644 --- a/packages/server/src/api/routes/tests/utilities/controllers.js +++ b/packages/server/src/api/routes/tests/utilities/controllers.js @@ -9,4 +9,5 @@ module.exports = { automation: require("../../../controllers/automation"), datasource: require("../../../controllers/datasource"), query: require("../../../controllers/query"), + screen: require("../../../controllers/screen"), } diff --git a/packages/server/src/api/routes/tests/utilities/structures.js b/packages/server/src/api/routes/tests/utilities/structures.js index 922228aad..aec482bb2 100644 --- a/packages/server/src/api/routes/tests/utilities/structures.js +++ b/packages/server/src/api/routes/tests/utilities/structures.js @@ -2,6 +2,7 @@ const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") const { BUILTIN_PERMISSION_IDS, } = require("../../../../utilities/security/permissions") +const { createHomeScreen } = require("../../../../constants/screens") exports.basicTable = () => { return { @@ -85,3 +86,7 @@ exports.basicUser = role => { roleId: role, } } + +exports.basicScreen = () => { + return createHomeScreen() +} From c429caf6a47d6becba61d48920a84455ea73d7c4 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 8 Mar 2021 15:46:12 +0000 Subject: [PATCH 06/28] self hosted middleware test --- .../src/middleware/tests/TestConfiguration.js | 247 ++++++++++++++++++ .../server/src/middleware/tests/authorized.js | 0 .../src/middleware/tests/selfhost.spec.js | 70 ++--- 3 files changed, 282 insertions(+), 35 deletions(-) create mode 100644 packages/server/src/middleware/tests/TestConfiguration.js create mode 100644 packages/server/src/middleware/tests/authorized.js diff --git a/packages/server/src/middleware/tests/TestConfiguration.js b/packages/server/src/middleware/tests/TestConfiguration.js new file mode 100644 index 000000000..8092130ce --- /dev/null +++ b/packages/server/src/middleware/tests/TestConfiguration.js @@ -0,0 +1,247 @@ +// const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") +// const jwt = require("jsonwebtoken") +// const env = require("../../../../environment") +// const { +// basicTable, +// basicRow, +// basicRole, +// basicAutomation, +// basicDatasource, +// basicQuery, +// } = require("./structures") +// const controllers = require("./controllers") +// const supertest = require("supertest") + +// const EMAIL = "babs@babs.com" +// const PASSWORD = "babs_password" + +// class TestConfiguration { +// constructor() { +// env.PORT = 4002 +// this.server = require("../../../../app") +// // we need the request for logging in, involves cookies, hard to fake +// this.request = supertest(this.server) +// this.appId = null +// } + +// getRequest() { +// return this.request +// } + +// getAppId() { +// return this.appId +// } + +// async _req(config, params, controlFunc) { +// const request = {} +// // fake cookies, we don't need them +// request.cookies = { set: () => {}, get: () => {} } +// request.config = { jwtSecret: env.JWT_SECRET } +// request.appId = this.appId +// request.user = { appId: this.appId } +// request.request = { +// body: config, +// } +// if (params) { +// request.params = params +// } +// await controlFunc(request) +// return request.body +// } + +// async init(appName = "test_application") { +// return this.createApp(appName) +// } + +// end() { +// this.server.close() +// } + +// defaultHeaders() { +// const builderUser = { +// userId: "BUILDER", +// roleId: BUILTIN_ROLE_IDS.BUILDER, +// } +// const builderToken = jwt.sign(builderUser, env.JWT_SECRET) +// const headers = { +// Accept: "application/json", +// Cookie: [`budibase:builder:local=${builderToken}`], +// } +// if (this.appId) { +// headers["x-budibase-app-id"] = this.appId +// } +// return headers +// } + +// publicHeaders() { +// const headers = { +// Accept: "application/json", +// } +// if (this.appId) { +// headers["x-budibase-app-id"] = this.appId +// } +// return headers +// } + +// async callMiddleware() { +// this.middleware(this.ctx, next) +// return this.app +// } + +// async updateTable(config = null) { +// config = config || basicTable() +// this.table = await this._req(config, null, controllers.table.save) +// return this.table +// } + +// async createTable(config = null) { +// if (config != null && config._id) { +// delete config._id +// } +// return this.updateTable(config) +// } + +// async getTable(tableId = null) { +// tableId = tableId || this.table._id +// return this._req(null, { id: tableId }, controllers.table.find) +// } + +// async createLinkedTable() { +// if (!this.table) { +// throw "Must have created a table first." +// } +// const tableConfig = basicTable() +// tableConfig.primaryDisplay = "name" +// tableConfig.schema.link = { +// type: "link", +// fieldName: "link", +// tableId: this.table._id, +// } +// const linkedTable = await this.createTable(tableConfig) +// this.linkedTable = linkedTable +// return linkedTable +// } + +// async createAttachmentTable() { +// const table = basicTable() +// table.schema.attachment = { +// type: "attachment", +// } +// return this.createTable(table) +// } + +// async createRow(config = null) { +// if (!this.table) { +// throw "Test requires table to be configured." +// } +// config = config || basicRow(this.table._id) +// return this._req(config, { tableId: this.table._id }, controllers.row.save) +// } + +// async createRole(config = null) { +// config = config || basicRole() +// return this._req(config, null, controllers.role.save) +// } + +// async addPermission(roleId, resourceId, level = "read") { +// return this._req( +// null, +// { +// roleId, +// resourceId, +// level, +// }, +// controllers.perms.addPermission +// ) +// } + +// async createView(config) { +// if (!this.table) { +// throw "Test requires table to be configured." +// } +// const view = config || { +// map: "function(doc) { emit(doc[doc.key], doc._id); } ", +// tableId: this.table._id, +// } +// return this._req(view, null, controllers.view.save) +// } + +// async createAutomation(config) { +// config = config || basicAutomation() +// if (config._rev) { +// delete config._rev +// } +// this.automation = ( +// await this._req(config, null, controllers.automation.create) +// ).automation +// return this.automation +// } + +// async getAllAutomations() { +// return this._req(null, null, controllers.automation.fetch) +// } + +// async deleteAutomation(automation = null) { +// automation = automation || this.automation +// if (!automation) { +// return +// } +// return this._req( +// null, +// { id: automation._id, rev: automation._rev }, +// controllers.automation.destroy +// ) +// } + +// async createDatasource(config = null) { +// config = config || basicDatasource() +// this.datasource = await this._req(config, null, controllers.datasource.save) +// return this.datasource +// } + +// async createQuery(config = null) { +// if (!this.datasource && !config) { +// throw "No data source created for query." +// } +// config = config || basicQuery(this.datasource._id) +// return this._req(config, null, controllers.query.save) +// } + +// async createUser( +// email = EMAIL, +// password = PASSWORD, +// roleId = BUILTIN_ROLE_IDS.POWER +// ) { +// return this._req( +// { +// email, +// password, +// roleId, +// }, +// null, +// controllers.user.create +// ) +// } + +// async login(email, password) { +// if (!email || !password) { +// await this.createUser() +// email = EMAIL +// password = PASSWORD +// } +// const result = await this.request +// .post(`/api/authenticate`) +// .set({ +// "x-budibase-app-id": this.appId, +// }) +// .send({ email, password }) + +// // returning necessary request headers +// return { +// Accept: "application/json", +// Cookie: result.headers["set-cookie"], +// } +// } +// } + +// module.exports = TestConfiguration diff --git a/packages/server/src/middleware/tests/authorized.js b/packages/server/src/middleware/tests/authorized.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 0f721bc89..9d66f4446 100644 --- a/packages/server/src/middleware/tests/selfhost.spec.js +++ b/packages/server/src/middleware/tests/selfhost.spec.js @@ -1,43 +1,43 @@ -const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") -const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") -const { basicUser } = require("./utilities/structures") -const setup = require("./utilities") +const selfHostMiddleware = require("../selfhost"); +const env = require("../../environment") +const hosting = require("../../utilities/builder/hosting") +jest.mock("../../environment") +jest.mock("../../utilities/builder/hosting") describe("Self host middleware", () => { - let request = setup.getRequest() - let config = setup.getConfig() + const next = jest.fn() + const throwMock = jest.fn() - afterAll(setup.afterAll) + afterEach(() => { + jest.clearAllMocks() + }) + + it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => { + env.CLOUD = 1 + env.SELF_HOSTED = 1 + + await selfHostMiddleware({}, next) + expect(next).toHaveBeenCalled() + }) - beforeEach(async () => { - await config.init() + it("throws when hostingInfo type is cloud", async () => { + env.CLOUD = 0 + env.SELF_HOSTED = 0 + + hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.CLOUD })) + + await selfHostMiddleware({ throw: throwMock }, next) + expect(throwMock).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.") + expect(next).not.toHaveBeenCalled() }) - describe("fetch", () => { - it("returns a list of users from an instance db", async () => { - await config.createUser("brenda@brenda.com", "brendas_password") - await config.createUser("pam@pam.com", "pam_password") - const res = await request - .get(`/api/users`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - - expect(res.body.length).toBe(2) - expect(res.body.find(u => u.email === "brenda@brenda.com")).toBeDefined() - expect(res.body.find(u => u.email === "pam@pam.com")).toBeDefined() - }) - - it("should apply authorization to endpoint", async () => { - await config.createUser("brenda@brenda.com", "brendas_password") - await checkPermissionsEndpoint({ - config, - request, - method: "GET", - url: `/api/users`, - passRole: BUILTIN_ROLE_IDS.ADMIN, - failRole: BUILTIN_ROLE_IDS.PUBLIC, - }) - }) + it("calls the self hosting middleware to pass through to next() when the hostingInfo type is self", async () => { + env.CLOUD = 0 + env.SELF_HOSTED = 0 + + hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.SELF })) + + await selfHostMiddleware({}, next) + expect(next).toHaveBeenCalled() }) }) From 039de61de3fa3c13bad899ea38b61c43850b5f80 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 8 Mar 2021 15:57:19 +0000 Subject: [PATCH 07/28] Some fixes after testing webhooks as well as adding test cases for all webhook endpoints. --- .../server/src/api/controllers/webhook.js | 10 +- .../tests/utilities/TestConfiguration.js | 9 ++ .../api/routes/tests/utilities/controllers.js | 1 + .../api/routes/tests/utilities/structures.js | 11 ++ .../src/api/routes/tests/webhook.spec.js | 130 ++++++++++++++++++ packages/server/src/middleware/authorized.js | 2 +- 6 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 packages/server/src/api/routes/tests/webhook.spec.js diff --git a/packages/server/src/api/controllers/webhook.js b/packages/server/src/api/controllers/webhook.js index 7a343b1b0..5b76f8619 100644 --- a/packages/server/src/api/controllers/webhook.js +++ b/packages/server/src/api/controllers/webhook.js @@ -43,12 +43,10 @@ exports.save = async ctx => { webhook._id = generateWebhookID() } const response = await db.put(webhook) + webhook._rev = response.rev ctx.body = { message: "Webhook created successfully", - webhook: { - ...webhook, - ...response, - }, + webhook, } } @@ -95,5 +93,7 @@ exports.trigger = async ctx => { }) } ctx.status = 200 - ctx.body = "Webhook trigger fired successfully" + ctx.body = { + message: "Webhook trigger fired successfully", + } } diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js index a124ca607..34b8c4fb1 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -9,6 +9,7 @@ const { basicDatasource, basicQuery, basicScreen, + basicWebhook, } = require("./structures") const controllers = require("./controllers") const supertest = require("supertest") @@ -223,6 +224,14 @@ class TestConfiguration { return this._req(config, null, controllers.screen.save) } + async createWebhook(config = null) { + if (!this.automation) { + throw "Must create an automation before creating webhook." + } + config = config || basicWebhook(this.automation._id) + return (await this._req(config, null, controllers.webhook.save)).webhook + } + async createUser( email = EMAIL, password = PASSWORD, diff --git a/packages/server/src/api/routes/tests/utilities/controllers.js b/packages/server/src/api/routes/tests/utilities/controllers.js index e16aa6964..d6524bb7f 100644 --- a/packages/server/src/api/routes/tests/utilities/controllers.js +++ b/packages/server/src/api/routes/tests/utilities/controllers.js @@ -10,4 +10,5 @@ module.exports = { datasource: require("../../../controllers/datasource"), query: require("../../../controllers/query"), screen: require("../../../controllers/screen"), + webhook: require("../../../controllers/webhook"), } diff --git a/packages/server/src/api/routes/tests/utilities/structures.js b/packages/server/src/api/routes/tests/utilities/structures.js index aec482bb2..500ff7204 100644 --- a/packages/server/src/api/routes/tests/utilities/structures.js +++ b/packages/server/src/api/routes/tests/utilities/structures.js @@ -90,3 +90,14 @@ exports.basicUser = role => { exports.basicScreen = () => { return createHomeScreen() } + +exports.basicWebhook = automationId => { + return { + live: true, + name: "webhook", + action: { + type: "automation", + target: automationId, + }, + } +} diff --git a/packages/server/src/api/routes/tests/webhook.spec.js b/packages/server/src/api/routes/tests/webhook.spec.js new file mode 100644 index 000000000..2bf5445a0 --- /dev/null +++ b/packages/server/src/api/routes/tests/webhook.spec.js @@ -0,0 +1,130 @@ +const setup = require("./utilities") +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { basicWebhook, basicAutomation } = require("./utilities/structures") + +describe("/webhooks", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let webhook + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + const autoConfig = basicAutomation() + autoConfig.definition.trigger = { + schema: { outputs: { properties: {} } }, + inputs: {}, + } + await config.createAutomation(autoConfig) + webhook = await config.createWebhook() + }) + + describe("create", () => { + it("should create a webhook successfully", async () => { + const automation = await config.createAutomation() + const res = await request + .put(`/api/webhooks`) + .send(basicWebhook(automation._id)) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.webhook).toBeDefined() + expect(typeof res.body.webhook._id).toEqual("string") + expect(typeof res.body.webhook._rev).toEqual("string") + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "PUT", + url: `/api/webhooks`, + }) + }) + }) + + describe("fetch", () => { + it("returns the correct routing for basic user", async () => { + const res = await request + .get(`/api/webhooks`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(Array.isArray(res.body)).toEqual(true) + expect(res.body[0]._id).toEqual(webhook._id) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/webhooks`, + }) + }) + }) + + describe("delete", () => { + it("should successfully delete", async () => { + const res = await request + .delete(`/api/webhooks/${webhook._id}/${webhook._rev}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + expect(res.body.ok).toEqual(true) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "DELETE", + url: `/api/webhooks/${webhook._id}/${webhook._rev}`, + }) + }) + }) + + describe("build schema", () => { + it("should allow building a schema", async () => { + const res = await request + .post(`/api/webhooks/schema/${config.getAppId()}/${webhook._id}`) + .send({ + a: 1 + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + // fetch to see if the schema has been updated + const fetch = await request + .get(`/api/webhooks`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(fetch.body[0]).toBeDefined() + expect(fetch.body[0].bodySchema).toEqual({ + properties: { + a: { type: "integer" } + }, + type: "object", + }) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/webhooks/schema/${config.getAppId()}/${webhook._id}`, + }) + }) + }) + + describe("trigger", () => { + it("should allow triggering from public", async () => { + const res = await request + .post(`/api/webhooks/trigger/${config.getAppId()}/${webhook._id}`) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 7eac602f7..1f8b687ba 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -13,7 +13,7 @@ const { AuthTypes } = require("../constants") const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER] -const LOCAL_PASS = new RegExp(["webhooks/trigger", "webhooks/schema"].join("|")) +const LOCAL_PASS = new RegExp(["webhooks/trigger"].join("|")) function hasResource(ctx) { return ctx.resourceId != null From 8a0a0dabdbdda637e62260b86bf9bd6fe8e2be65 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 8 Mar 2021 18:03:26 +0000 Subject: [PATCH 08/28] Adding a query find and planning to tackle mocking out the preview and execute functionality. --- .../server/src/api/routes/tests/query.spec.js | 51 ++++++++++++------- .../tests/utilities/TestConfiguration.js | 2 +- .../src/api/routes/tests/utilities/index.js | 19 +++++++ 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index 765baa442..5867c863d 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -2,14 +2,17 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const { basicQuery } = require("./utilities/structures") const setup = require("./utilities") + describe("/queries", () => { let request = setup.getRequest() let config = setup.getConfig() + let datasource afterAll(setup.afterAll) beforeEach(async () => { await config.init() + datasource = await config.createDatasource() }) describe("create", () => { @@ -35,16 +38,6 @@ describe("/queries", () => { }) describe("fetch", () => { - let datasource - - beforeEach(async () => { - datasource = await config.createDatasource() - }) - - afterEach(() => { - delete datasource._rev - }) - it("returns all the queries from the server", async () => { const query = await config.createQuery() const res = await request @@ -73,17 +66,33 @@ describe("/queries", () => { }) }) - describe("destroy", () => { - let datasource - - beforeEach(async () => { - datasource = await config.createDatasource() + describe("find", () => { + it("should find a query in builder", async () => { + const query = await config.createQuery() + const res = await request + .get(`/api/queries/${query._id}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._id).toEqual(query._id) }) - afterEach(() => { - delete datasource._rev + it("should find a query in cloud", async () => { + await setup.switchToCloudForFunction(async () => { + const query = await config.createQuery() + const res = await request + .get(`/api/queries/${query._id}`) + .set(await config.roleHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.fields).toBeUndefined() + expect(res.body.parameters).toBeUndefined() + expect(res.body.schema).toBeUndefined() + }) }) + }) + describe("destroy", () => { it("deletes a query and returns a success message", async () => { const query = await config.createQuery() @@ -109,4 +118,12 @@ describe("/queries", () => { }) }) }) + + describe("preview", () => { + // TODO: need to mock out an integration with a test one and try this + }) + + describe("execute", () => { + // TODO: need to mock out an integration with a test one and try this + }) }) diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js index 34b8c4fb1..f92321ddf 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -85,7 +85,7 @@ class TestConfiguration { return headers } - async roleHeaders(email = EMAIL, roleId) { + async roleHeaders(email = EMAIL, roleId = BUILTIN_ROLE_IDS.ADMIN) { try { await this.createUser(email, PASSWORD, roleId) } catch (err) { diff --git a/packages/server/src/api/routes/tests/utilities/index.js b/packages/server/src/api/routes/tests/utilities/index.js index 7e9260ce1..3ad415386 100644 --- a/packages/server/src/api/routes/tests/utilities/index.js +++ b/packages/server/src/api/routes/tests/utilities/index.js @@ -1,4 +1,5 @@ const TestConfig = require("./TestConfiguration") +const env = require("../../../../environment") exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms)) @@ -30,3 +31,21 @@ exports.getConfig = () => { } return config } + +exports.switchToCloudForFunction = async func => { + // self hosted stops any attempts to Dynamo + env.CLOUD = true + env.SELF_HOSTED = true + let error + try { + await func() + } catch (err) { + error = err + } + env.CLOUD = false + env.SELF_HOSTED = false + // don't throw error until after reset + if (error) { + throw error + } +} From f1abb9194f8a65d70d4fb51a724bf30183539801 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 8 Mar 2021 18:18:53 +0000 Subject: [PATCH 09/28] Adding API key tests. --- .../src/api/routes/tests/apikeys.spec.js | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/server/src/api/routes/tests/apikeys.spec.js diff --git a/packages/server/src/api/routes/tests/apikeys.spec.js b/packages/server/src/api/routes/tests/apikeys.spec.js new file mode 100644 index 000000000..2a99e9e55 --- /dev/null +++ b/packages/server/src/api/routes/tests/apikeys.spec.js @@ -0,0 +1,59 @@ +const setup = require("./utilities") +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { budibaseAppsDir } = require("../../../utilities/budibaseDir") +const fs = require("fs") +const path = require("path") + +describe("/applications", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("fetch", () => { + it("should allow fetching", async () => { + const res = await request + .get(`/api/keys`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + }) + + it("should check authorization for builder", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/keys`, + }) + }) + }) + + describe("update", () => { + it("should allow updating a value", async () => { + fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "") + const res = await request + .put(`/api/keys/TEST`) + .send({ + value: "test" + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body["TEST"]).toEqual("test") + expect(process.env.TEST_API_KEY).toEqual("test") + }) + + it("should check authorization for builder", async () => { + await checkBuilderEndpoint({ + config, + method: "PUT", + url: `/api/keys/TEST`, + }) + }) + }) +}) \ No newline at end of file From 758e964977c54a418de9609cf7e358442f0769ae Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 9 Mar 2021 11:27:12 +0000 Subject: [PATCH 10/28] tests for authorized middleware --- packages/server/src/middleware/authorized.js | 9 +- .../src/middleware/tests/TestConfiguration.js | 279 ++---------------- .../{authorized.js => authenticated.spec.js} | 0 .../src/middleware/tests/authorized.spec.js | 196 ++++++++++++ .../src/middleware/tests/resourceId.spec.js | 0 .../src/middleware/tests/selfhost.spec.js | 2 +- 6 files changed, 234 insertions(+), 252 deletions(-) rename packages/server/src/middleware/tests/{authorized.js => authenticated.spec.js} (100%) create mode 100644 packages/server/src/middleware/tests/authorized.spec.js create mode 100644 packages/server/src/middleware/tests/resourceId.spec.js diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 7eac602f7..6d646d46f 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -24,6 +24,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) { return next() } + if (env.CLOUD && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { // api key header passed by external webhook if (await isAPIKeyValid(ctx.headers["x-api-key"])) { @@ -37,14 +38,14 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { return next() } - ctx.throw(403, "API key invalid") + return ctx.throw(403, "API key invalid") } // don't expose builder endpoints in the cloud if (env.CLOUD && permType === PermissionTypes.BUILDER) return if (!ctx.user) { - ctx.throw(403, "No user info found") + return ctx.throw(403, "No user info found") } const role = ctx.user.role @@ -52,7 +53,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { ctx.appId, role._id ) - const isAdmin = ADMIN_ROLES.indexOf(role._id) !== -1 + const isAdmin = ADMIN_ROLES.includes(role._id) const isAuthed = ctx.auth.authenticated // this may need to change in the future, right now only admins @@ -61,7 +62,7 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { if (isAdmin && isAuthed) { return next() } else if (permType === PermissionTypes.BUILDER) { - ctx.throw(403, "Not Authorized") + return ctx.throw(403, "Not Authorized") } if ( diff --git a/packages/server/src/middleware/tests/TestConfiguration.js b/packages/server/src/middleware/tests/TestConfiguration.js index 8092130ce..11d925e8f 100644 --- a/packages/server/src/middleware/tests/TestConfiguration.js +++ b/packages/server/src/middleware/tests/TestConfiguration.js @@ -1,247 +1,32 @@ -// const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") -// const jwt = require("jsonwebtoken") -// const env = require("../../../../environment") -// const { -// basicTable, -// basicRow, -// basicRole, -// basicAutomation, -// basicDatasource, -// basicQuery, -// } = require("./structures") -// const controllers = require("./controllers") -// const supertest = require("supertest") - -// const EMAIL = "babs@babs.com" -// const PASSWORD = "babs_password" - -// class TestConfiguration { -// constructor() { -// env.PORT = 4002 -// this.server = require("../../../../app") -// // we need the request for logging in, involves cookies, hard to fake -// this.request = supertest(this.server) -// this.appId = null -// } - -// getRequest() { -// return this.request -// } - -// getAppId() { -// return this.appId -// } - -// async _req(config, params, controlFunc) { -// const request = {} -// // fake cookies, we don't need them -// request.cookies = { set: () => {}, get: () => {} } -// request.config = { jwtSecret: env.JWT_SECRET } -// request.appId = this.appId -// request.user = { appId: this.appId } -// request.request = { -// body: config, -// } -// if (params) { -// request.params = params -// } -// await controlFunc(request) -// return request.body -// } - -// async init(appName = "test_application") { -// return this.createApp(appName) -// } - -// end() { -// this.server.close() -// } - -// defaultHeaders() { -// const builderUser = { -// userId: "BUILDER", -// roleId: BUILTIN_ROLE_IDS.BUILDER, -// } -// const builderToken = jwt.sign(builderUser, env.JWT_SECRET) -// const headers = { -// Accept: "application/json", -// Cookie: [`budibase:builder:local=${builderToken}`], -// } -// if (this.appId) { -// headers["x-budibase-app-id"] = this.appId -// } -// return headers -// } - -// publicHeaders() { -// const headers = { -// Accept: "application/json", -// } -// if (this.appId) { -// headers["x-budibase-app-id"] = this.appId -// } -// return headers -// } - -// async callMiddleware() { -// this.middleware(this.ctx, next) -// return this.app -// } - -// async updateTable(config = null) { -// config = config || basicTable() -// this.table = await this._req(config, null, controllers.table.save) -// return this.table -// } - -// async createTable(config = null) { -// if (config != null && config._id) { -// delete config._id -// } -// return this.updateTable(config) -// } - -// async getTable(tableId = null) { -// tableId = tableId || this.table._id -// return this._req(null, { id: tableId }, controllers.table.find) -// } - -// async createLinkedTable() { -// if (!this.table) { -// throw "Must have created a table first." -// } -// const tableConfig = basicTable() -// tableConfig.primaryDisplay = "name" -// tableConfig.schema.link = { -// type: "link", -// fieldName: "link", -// tableId: this.table._id, -// } -// const linkedTable = await this.createTable(tableConfig) -// this.linkedTable = linkedTable -// return linkedTable -// } - -// async createAttachmentTable() { -// const table = basicTable() -// table.schema.attachment = { -// type: "attachment", -// } -// return this.createTable(table) -// } - -// async createRow(config = null) { -// if (!this.table) { -// throw "Test requires table to be configured." -// } -// config = config || basicRow(this.table._id) -// return this._req(config, { tableId: this.table._id }, controllers.row.save) -// } - -// async createRole(config = null) { -// config = config || basicRole() -// return this._req(config, null, controllers.role.save) -// } - -// async addPermission(roleId, resourceId, level = "read") { -// return this._req( -// null, -// { -// roleId, -// resourceId, -// level, -// }, -// controllers.perms.addPermission -// ) -// } - -// async createView(config) { -// if (!this.table) { -// throw "Test requires table to be configured." -// } -// const view = config || { -// map: "function(doc) { emit(doc[doc.key], doc._id); } ", -// tableId: this.table._id, -// } -// return this._req(view, null, controllers.view.save) -// } - -// async createAutomation(config) { -// config = config || basicAutomation() -// if (config._rev) { -// delete config._rev -// } -// this.automation = ( -// await this._req(config, null, controllers.automation.create) -// ).automation -// return this.automation -// } - -// async getAllAutomations() { -// return this._req(null, null, controllers.automation.fetch) -// } - -// async deleteAutomation(automation = null) { -// automation = automation || this.automation -// if (!automation) { -// return -// } -// return this._req( -// null, -// { id: automation._id, rev: automation._rev }, -// controllers.automation.destroy -// ) -// } - -// async createDatasource(config = null) { -// config = config || basicDatasource() -// this.datasource = await this._req(config, null, controllers.datasource.save) -// return this.datasource -// } - -// async createQuery(config = null) { -// if (!this.datasource && !config) { -// throw "No data source created for query." -// } -// config = config || basicQuery(this.datasource._id) -// return this._req(config, null, controllers.query.save) -// } - -// async createUser( -// email = EMAIL, -// password = PASSWORD, -// roleId = BUILTIN_ROLE_IDS.POWER -// ) { -// return this._req( -// { -// email, -// password, -// roleId, -// }, -// null, -// controllers.user.create -// ) -// } - -// async login(email, password) { -// if (!email || !password) { -// await this.createUser() -// email = EMAIL -// password = PASSWORD -// } -// const result = await this.request -// .post(`/api/authenticate`) -// .set({ -// "x-budibase-app-id": this.appId, -// }) -// .send({ email, password }) - -// // returning necessary request headers -// return { -// Accept: "application/json", -// Cookie: result.headers["set-cookie"], -// } -// } -// } - -// module.exports = TestConfiguration +let env = require("../../environment") + +class TestConfiguration { + constructor(middleware) { + // env = config.env || {} + this.middleware = middleware + this.next = jest.fn() + this.throwMock = jest.fn() + } + + callMiddleware(ctx, next) { + return this.middleware(ctx, next) + } + + clear() { + jest.clearAllMocks() + } + + setEnv(config) { + env = config + } + + async init() { + // return this.createApp(appName) + } + + end() { + // this.server.close() + } +} + +module.exports = TestConfiguration diff --git a/packages/server/src/middleware/tests/authorized.js b/packages/server/src/middleware/tests/authenticated.spec.js similarity index 100% rename from packages/server/src/middleware/tests/authorized.js rename to packages/server/src/middleware/tests/authenticated.spec.js diff --git a/packages/server/src/middleware/tests/authorized.spec.js b/packages/server/src/middleware/tests/authorized.spec.js new file mode 100644 index 000000000..d3e5e52d2 --- /dev/null +++ b/packages/server/src/middleware/tests/authorized.spec.js @@ -0,0 +1,196 @@ +const authorizedMiddleware = require("../authorized") +const env = require("../../environment") +const apiKey = require("../../utilities/security/apikey") +const { AuthTypes } = require("../../constants") +const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions") +const { Test } = require("supertest") +jest.mock("../../environment") +jest.mock("../../utilities/security/apikey") + +class TestConfiguration { + constructor(role) { + this.middleware = authorizedMiddleware(role) + this.next = jest.fn() + this.throw = jest.fn() + this.ctx = { + headers: {}, + request: { + url: "" + }, + auth: {}, + next: this.next, + throw: this.throw + } + } + + executeMiddleware() { + return this.middleware(this.ctx, this.next) + } + + setUser(user) { + this.ctx.user = user + } + + setMiddlewareRequiredPermission(...perms) { + this.middleware = authorizedMiddleware(...perms) + } + + setResourceId(id) { + this.ctx.resourceId = id + } + + setAuthenticated(isAuthed) { + this.ctx.auth = { authenticated: isAuthed } + } + + setRequestUrl(url) { + this.ctx.request.url = url + } + + setCloudEnv(isCloud) { + env.CLOUD = isCloud + } + + setRequestHeaders(headers) { + this.ctx.headers = headers + } + + afterEach() { + jest.clearAllMocks() + } +} + + +describe("Authorization middleware", () => { + const next = jest.fn() + let config + + afterEach(() => { + config.afterEach() + }) + + beforeEach(() => { + config = new TestConfiguration() + }) + + it("passes the middleware for local webhooks", async () => { + config.setRequestUrl("https://something/webhooks/trigger") + await config.executeMiddleware() + expect(config.next).toHaveBeenCalled() + }) + + describe("external web hook call", () => { + let ctx = {} + let middleware + + beforeEach(() => { + config = new TestConfiguration() + config.setCloudEnv(true) + config.setRequestHeaders({ + "x-api-key": "abc123", + "x-instanceid": "instance123", + }) + }) + + it("passes to next() if api key is valid", async () => { + apiKey.isAPIKeyValid.mockResolvedValueOnce(true) + + await config.executeMiddleware() + + expect(config.next).toHaveBeenCalled() + expect(config.ctx.auth).toEqual({ + authenticated: AuthTypes.EXTERNAL, + apiKey: config.ctx.headers["x-api-key"], + }) + expect(config.ctx.user).toEqual({ + appId: config.ctx.headers["x-instanceid"], + }) + }) + + it("throws if api key is invalid", async () => { + apiKey.isAPIKeyValid.mockResolvedValueOnce(false) + + await config.executeMiddleware() + + expect(config.throw).toHaveBeenCalledWith(403, "API key invalid") + }) + }) + + describe("non-webhook call", () => { + let config + + beforeEach(() => { + config = new TestConfiguration() + config.setCloudEnv(true) + config.setAuthenticated(true) + }) + + it("throws when no user data is present in context", async () => { + await config.executeMiddleware() + + expect(config.throw).toHaveBeenCalledWith(403, "No user info found") + }) + + it("passes on to next() middleware if user is an admin", async () => { + config.setUser({ + role: { + _id: "ADMIN", + } + }) + + await config.executeMiddleware() + + expect(config.next).toHaveBeenCalled() + }) + + it("throws if the user has only builder permissions", async () => { + config.setCloudEnv(false) + config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER) + config.setUser({ + role: { + _id: "" + } + }) + await config.executeMiddleware() + + expect(config.throw).toHaveBeenCalledWith(403, "Not Authorized") + }) + + it("passes on to next() middleware if the user has resource permission", async () => { + config.setResourceId(PermissionTypes.QUERY) + config.setUser({ + role: { + _id: "" + } + }) + config.setMiddlewareRequiredPermission(PermissionTypes.QUERY) + + await config.executeMiddleware() + expect(config.next).toHaveBeenCalled() + }) + + it("throws if the user session is not authenticated after permission checks", async () => { + config.setUser({ + role: { + _id: "" + }, + }) + config.setAuthenticated(false) + + await config.executeMiddleware() + expect(config.throw).toHaveBeenCalledWith(403, "Session not authenticated") + }) + + it("throws if the user does not have base permissions to perform the operation", async () => { + config.setUser({ + role: { + _id: "" + }, + }) + config.setMiddlewareRequiredPermission(PermissionTypes.ADMIN, PermissionLevels.BASIC) + + await config.executeMiddleware() + expect(config.throw).toHaveBeenCalledWith(403, "User does not have permission") + }) + }) +}) diff --git a/packages/server/src/middleware/tests/resourceId.spec.js b/packages/server/src/middleware/tests/resourceId.spec.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 9d66f4446..3601df89a 100644 --- a/packages/server/src/middleware/tests/selfhost.spec.js +++ b/packages/server/src/middleware/tests/selfhost.spec.js @@ -1,6 +1,6 @@ const selfHostMiddleware = require("../selfhost"); const env = require("../../environment") -const hosting = require("../../utilities/builder/hosting") +const hosting = require("../../utilities/builder/hosting"); jest.mock("../../environment") jest.mock("../../utilities/builder/hosting") From c073b8639d9f30b1030cb922637baaa7aba3796c Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 9 Mar 2021 11:33:16 +0000 Subject: [PATCH 11/28] refactor selfhost middleware tests to use TestConfiguration --- .../src/middleware/tests/TestConfiguration.js | 32 ---------- .../src/middleware/tests/selfhost.spec.js | 60 ++++++++++++++----- 2 files changed, 46 insertions(+), 46 deletions(-) delete mode 100644 packages/server/src/middleware/tests/TestConfiguration.js diff --git a/packages/server/src/middleware/tests/TestConfiguration.js b/packages/server/src/middleware/tests/TestConfiguration.js deleted file mode 100644 index 11d925e8f..000000000 --- a/packages/server/src/middleware/tests/TestConfiguration.js +++ /dev/null @@ -1,32 +0,0 @@ -let env = require("../../environment") - -class TestConfiguration { - constructor(middleware) { - // env = config.env || {} - this.middleware = middleware - this.next = jest.fn() - this.throwMock = jest.fn() - } - - callMiddleware(ctx, next) { - return this.middleware(ctx, next) - } - - clear() { - jest.clearAllMocks() - } - - setEnv(config) { - env = config - } - - async init() { - // return this.createApp(appName) - } - - end() { - // this.server.close() - } -} - -module.exports = TestConfiguration diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 3601df89a..061da17f9 100644 --- a/packages/server/src/middleware/tests/selfhost.spec.js +++ b/packages/server/src/middleware/tests/selfhost.spec.js @@ -4,40 +4,72 @@ const hosting = require("../../utilities/builder/hosting"); jest.mock("../../environment") jest.mock("../../utilities/builder/hosting") +class TestConfiguration { + constructor() { + this.next = jest.fn() + this.throw = jest.fn() + this.middleware = selfHostMiddleware + + this.ctx = { + next: this.next, + throw: this.throw + } + } + + executeMiddleware() { + return this.middleware(this.ctx, this.next) + } + + setCloudHosted() { + env.CLOUD = 1 + env.SELF_HOSTED = 0 + } + + setSelfHosted() { + env.CLOUD = 0 + env.SELF_HOSTED = 1 + } + + afterEach() { + jest.clearAllMocks() + } +} + describe("Self host middleware", () => { - const next = jest.fn() - const throwMock = jest.fn() + let config + + beforeEach(() => { + config = new TestConfiguration() + }) afterEach(() => { - jest.clearAllMocks() + config.afterEach() }) it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => { env.CLOUD = 1 env.SELF_HOSTED = 1 - await selfHostMiddleware({}, next) - expect(next).toHaveBeenCalled() + await config.executeMiddleware() + expect(config.next).toHaveBeenCalled() }) it("throws when hostingInfo type is cloud", async () => { - env.CLOUD = 0 - env.SELF_HOSTED = 0 + config.setSelfHosted() hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.CLOUD })) - await selfHostMiddleware({ throw: throwMock }, next) - expect(throwMock).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.") - expect(next).not.toHaveBeenCalled() + await config.executeMiddleware() + expect(config.throw).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.") + expect(config.next).not.toHaveBeenCalled() }) it("calls the self hosting middleware to pass through to next() when the hostingInfo type is self", async () => { - env.CLOUD = 0 - env.SELF_HOSTED = 0 + config.setSelfHosted() hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.SELF })) - await selfHostMiddleware({}, next) - expect(next).toHaveBeenCalled() + await config.executeMiddleware() + expect(config.next).toHaveBeenCalled() }) }) From 0c51bb7275c9663a1d76137eaaa789d3bd336b51 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 9 Mar 2021 11:56:32 +0000 Subject: [PATCH 12/28] Updating query test to include mocked preview/execute and adding layout tests. --- packages/server/__mocks__/pg.js | 21 +++++ packages/server/src/api/controllers/layout.js | 2 +- packages/server/src/api/controllers/query.js | 2 - .../src/api/routes/tests/layout.spec.js | 55 ++++++++++++ .../server/src/api/routes/tests/query.spec.js | 85 +++++++++++++++++-- .../tests/utilities/TestConfiguration.js | 6 ++ .../api/routes/tests/utilities/controllers.js | 1 + .../api/routes/tests/utilities/structures.js | 6 ++ 8 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 packages/server/__mocks__/pg.js create mode 100644 packages/server/src/api/routes/tests/layout.spec.js diff --git a/packages/server/__mocks__/pg.js b/packages/server/__mocks__/pg.js new file mode 100644 index 000000000..2bda8afad --- /dev/null +++ b/packages/server/__mocks__/pg.js @@ -0,0 +1,21 @@ +const pg = {} + +// constructor +function Client() {} + +Client.prototype.query = async function() { + return { + rows: [ + { + a: "string", + b: 1, + }, + ], + } +} + +Client.prototype.connect = async function() {} + +pg.Client = Client + +module.exports = pg diff --git a/packages/server/src/api/controllers/layout.js b/packages/server/src/api/controllers/layout.js index de4c647c9..f270e95be 100644 --- a/packages/server/src/api/controllers/layout.js +++ b/packages/server/src/api/controllers/layout.js @@ -38,6 +38,6 @@ exports.destroy = async function(ctx) { } await db.remove(layoutId, layoutRev) - ctx.message = "Layout deleted successfully" + ctx.body = { message: "Layout deleted successfully" } ctx.status = 200 } diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 55c2ad14b..7012219c3 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -108,7 +108,6 @@ exports.preview = async function(ctx) { if (!Integration) { ctx.throw(400, "Integration type does not exist.") - return } const { fields, parameters, queryVerb } = ctx.request.body @@ -138,7 +137,6 @@ exports.execute = async function(ctx) { if (!Integration) { ctx.throw(400, "Integration type does not exist.") - return } const enrichedQuery = await enrichQueryFields( diff --git a/packages/server/src/api/routes/tests/layout.spec.js b/packages/server/src/api/routes/tests/layout.spec.js new file mode 100644 index 000000000..4be1c9e18 --- /dev/null +++ b/packages/server/src/api/routes/tests/layout.spec.js @@ -0,0 +1,55 @@ +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") +const { basicLayout } = require("./utilities/structures") + +describe("/queries", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let layout + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + layout = await config.createLayout() + }) + + describe("save", () => { + it("should be able to create a layout", async () => { + const res = await request + .post(`/api/layouts`) + .send(basicLayout()) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._rev).toBeDefined() + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/layouts`, + }) + }) + }) + + describe("destroy", () => { + it("should be able to delete the layout", async () => { + const res = await request + .delete(`/api/layouts/${layout._id}/${layout._rev}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "DELETE", + url: `/api/layouts/${layout._id}/${layout._rev}`, + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index 5867c863d..aa0e5428c 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -1,20 +1,32 @@ +// mock out postgres for this +jest.mock("pg") + const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const { basicQuery } = require("./utilities/structures") +const { basicQuery, basicDatasource } = require("./utilities/structures") const setup = require("./utilities") - describe("/queries", () => { let request = setup.getRequest() let config = setup.getConfig() - let datasource + let datasource, query afterAll(setup.afterAll) beforeEach(async () => { await config.init() datasource = await config.createDatasource() + query = await config.createQuery() }) + async function createInvalidIntegration() { + const datasource = await config.createDatasource({ + ...basicDatasource(), + source: "INVALID_INTEGRATION", + }) + const query = await config.createQuery() + return { datasource, query } + } + describe("create", () => { it("should create a new query", async () => { const { _id } = await config.createDatasource() @@ -39,7 +51,6 @@ describe("/queries", () => { describe("fetch", () => { it("returns all the queries from the server", async () => { - const query = await config.createQuery() const res = await request .get(`/api/queries`) .set(config.defaultHeaders()) @@ -94,8 +105,6 @@ describe("/queries", () => { describe("destroy", () => { it("deletes a query and returns a success message", async () => { - const query = await config.createQuery() - await request .delete(`/api/queries/${query._id}/${query._rev}`) .set(config.defaultHeaders()) @@ -114,16 +123,74 @@ describe("/queries", () => { await checkBuilderEndpoint({ config, method: "DELETE", - url: `/api/datasources/${datasource._id}/${datasource._rev}`, + url: `/api/queries/${config._id}/${config._rev}`, }) }) }) describe("preview", () => { - // TODO: need to mock out an integration with a test one and try this + it("should be able to preview the query", async () => { + const res = await request + .post(`/api/queries/preview`) + .send({ + datasourceId: datasource._id, + parameters: {}, + fields: {}, + queryVerb: "read", + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + // these responses come from the mock + expect(res.body.schemaFields).toEqual(["a", "b"]) + expect(res.body.rows.length).toEqual(1) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/queries/preview`, + }) + }) + + it("should fail with invalid integration type", async () => { + const { datasource } = await createInvalidIntegration() + await request + .post(`/api/queries/preview`) + .send({ + datasourceId: datasource._id, + parameters: {}, + fields: {}, + queryVerb: "read", + }) + .set(config.defaultHeaders()) + .expect(400) + }) }) describe("execute", () => { - // TODO: need to mock out an integration with a test one and try this + it("should be able to execute the query", async () => { + const res = await request + .post(`/api/queries/${query._id}`) + .send({ + parameters: {}, + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.length).toEqual(1) + }) + + it("should fail with invalid integration type", async () => { + const { query } = await createInvalidIntegration() + await request + .post(`/api/queries/${query._id}`) + .send({ + parameters: {}, + }) + .set(config.defaultHeaders()) + .expect(400) + }) }) }) diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js index f92321ddf..b72f4f4e5 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -9,6 +9,7 @@ const { basicDatasource, basicQuery, basicScreen, + basicLayout, basicWebhook, } = require("./structures") const controllers = require("./controllers") @@ -232,6 +233,11 @@ class TestConfiguration { return (await this._req(config, null, controllers.webhook.save)).webhook } + async createLayout(config = null) { + config = config || basicLayout() + return await this._req(config, null, controllers.layout.save) + } + async createUser( email = EMAIL, password = PASSWORD, diff --git a/packages/server/src/api/routes/tests/utilities/controllers.js b/packages/server/src/api/routes/tests/utilities/controllers.js index d6524bb7f..a4eb9ac9d 100644 --- a/packages/server/src/api/routes/tests/utilities/controllers.js +++ b/packages/server/src/api/routes/tests/utilities/controllers.js @@ -11,4 +11,5 @@ module.exports = { query: require("../../../controllers/query"), screen: require("../../../controllers/screen"), webhook: require("../../../controllers/webhook"), + layout: require("../../../controllers/layout"), } diff --git a/packages/server/src/api/routes/tests/utilities/structures.js b/packages/server/src/api/routes/tests/utilities/structures.js index 500ff7204..ff3a23921 100644 --- a/packages/server/src/api/routes/tests/utilities/structures.js +++ b/packages/server/src/api/routes/tests/utilities/structures.js @@ -3,6 +3,8 @@ const { BUILTIN_PERMISSION_IDS, } = require("../../../../utilities/security/permissions") const { createHomeScreen } = require("../../../../constants/screens") +const { EMPTY_LAYOUT } = require("../../../../constants/layouts") +const { cloneDeep } = require("lodash/fp") exports.basicTable = () => { return { @@ -91,6 +93,10 @@ exports.basicScreen = () => { return createHomeScreen() } +exports.basicLayout = () => { + return cloneDeep(EMPTY_LAYOUT) +} + exports.basicWebhook = automationId => { return { live: true, From 108f4861e36fff71454cc2bc4913c29ebe657439 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 9 Mar 2021 12:39:32 +0000 Subject: [PATCH 13/28] resourceId tests --- packages/server/src/middleware/resourceId.js | 2 + .../src/middleware/tests/resourceId.spec.js | 105 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/packages/server/src/middleware/resourceId.js b/packages/server/src/middleware/resourceId.js index 4351901da..421613111 100644 --- a/packages/server/src/middleware/resourceId.js +++ b/packages/server/src/middleware/resourceId.js @@ -36,6 +36,8 @@ class ResourceIdGetter { } } +module.exports.ResourceIdGetter = ResourceIdGetter + module.exports.paramResource = main => { return new ResourceIdGetter("params").mainResource(main).build() } diff --git a/packages/server/src/middleware/tests/resourceId.spec.js b/packages/server/src/middleware/tests/resourceId.spec.js index e69de29bb..35e6e5af5 100644 --- a/packages/server/src/middleware/tests/resourceId.spec.js +++ b/packages/server/src/middleware/tests/resourceId.spec.js @@ -0,0 +1,105 @@ +const { + paramResource, + paramSubResource, + bodyResource, + bodySubResource, + ResourceIdGetter +} = require("../resourceId") + +class TestConfiguration { + constructor(middleware) { + this.middleware = middleware + this.ctx = { + request: {}, + } + this.next = jest.fn() + } + + setParams(params) { + this.ctx.params = params + } + + setBody(body) { + this.ctx.body = body + } + + executeMiddleware() { + return this.middleware(this.ctx, this.next) + } +} + +describe("resourceId middleware", () => { + it("calls next() when there is no request object to parse", () => { + const config = new TestConfiguration(paramResource("main")) + + config.executeMiddleware() + + expect(config.next).toHaveBeenCalled() + expect(config.ctx.resourceId).toBeUndefined() + }) + + it("generates a resourceId middleware for context query parameters", () => { + const config = new TestConfiguration(paramResource("main")) + config.setParams({ + main: "test" + }) + + config.executeMiddleware() + + expect(config.ctx.resourceId).toEqual("test") + }) + + it("generates a resourceId middleware for context query sub parameters", () => { + const config = new TestConfiguration(paramSubResource("main", "sub")) + config.setParams({ + main: "main", + sub: "test" + }) + + config.executeMiddleware() + + expect(config.ctx.resourceId).toEqual("main") + expect(config.ctx.subResourceId).toEqual("test") + }) + + it("generates a resourceId middleware for context request body", () => { + const config = new TestConfiguration(bodyResource("main")) + config.setBody({ + main: "test" + }) + + config.executeMiddleware() + + expect(config.ctx.resourceId).toEqual("test") + }) + + it("generates a resourceId middleware for context request body sub fields", () => { + const config = new TestConfiguration(bodySubResource("main", "sub")) + config.setBody({ + main: "main", + sub: "test" + }) + + config.executeMiddleware() + + expect(config.ctx.resourceId).toEqual("main") + expect(config.ctx.subResourceId).toEqual("test") + }) + + it("parses resourceIds correctly for custom middlewares", () => { + const middleware = new ResourceIdGetter("body") + .mainResource("custom") + .subResource("customSub") + .build() + config = new TestConfiguration(middleware) + config.setBody({ + custom: "test", + customSub: "subtest" + }) + + config.executeMiddleware() + + expect(config.ctx.resourceId).toEqual("test") + expect(config.ctx.subResourceId).toEqual("subtest") + }) +}) \ No newline at end of file From 38e27b90035c16d59da86385ecb1f442cc5997f2 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 9 Mar 2021 15:13:14 +0000 Subject: [PATCH 14/28] usageQuota tests --- .../src/middleware/tests/usageQuota.spec.js | 129 ++++++++++++++++++ packages/server/src/middleware/usageQuota.js | 1 + 2 files changed, 130 insertions(+) create mode 100644 packages/server/src/middleware/tests/usageQuota.spec.js diff --git a/packages/server/src/middleware/tests/usageQuota.spec.js b/packages/server/src/middleware/tests/usageQuota.spec.js new file mode 100644 index 000000000..c76acb47d --- /dev/null +++ b/packages/server/src/middleware/tests/usageQuota.spec.js @@ -0,0 +1,129 @@ +const usageQuotaMiddleware = require("../usageQuota") +const usageQuota = require("../../utilities/usageQuota") +const CouchDB = require("../../db") +const env = require("../../environment") + +jest.mock("../../db"); +jest.mock("../../utilities/usageQuota") +jest.mock("../../environment") + +class TestConfiguration { + constructor() { + this.throw = jest.fn() + this.next = jest.fn() + this.middleware = usageQuotaMiddleware + this.ctx = { + throw: this.throw, + next: this.next, + user: { + appId: "test" + }, + request: { + body: {} + }, + req: { + method: "POST", + url: "/rows" + } + } + } + + executeMiddleware() { + return this.middleware(this.ctx, this.next) + } + + cloudHosted(bool) { + if (bool) { + env.CLOUD = 1 + this.ctx.auth = { apiKey: "test" } + } else { + env.CLOUD = 0 + } + } + + setMethod(method) { + this.ctx.req.method = method + } + + setUrl(url) { + this.ctx.req.url = url + } + + setBody(body) { + this.ctx.request.body = body + } + + setFiles(files) { + this.ctx.request.files = { file: files } + } +} + +describe("usageQuota middleware", () => { + let config + + beforeEach(() => { + config = new TestConfiguration() + }) + + it("skips the middleware if there is no usage property or method", async () => { + await config.executeMiddleware() + expect(config.next).toHaveBeenCalled() + }) + + it("passes through to next middleware if document already exists", async () => { + config.setBody({ + _id: "test" + }) + + CouchDB.mockImplementationOnce(() => ({ + get: async () => true + })) + + await config.executeMiddleware() + + expect(config.next).toHaveBeenCalled() + expect(config.ctx.preExisting).toBe(true) + }) + + it("throws if request has _id, but the document no longer exists", async () => { + config.setBody({ + _id: "123" + }) + + CouchDB.mockImplementationOnce(() => ({ + get: async () => { + throw new Error() + } + })) + + await config.executeMiddleware() + expect(config.throw).toHaveBeenCalledWith(404, `${config.ctx.request.body._id} does not exist`) + }) + + it("calculates and persists the correct usage quota for the relevant action", async () => { + config.setUrl("/rows") + config.cloudHosted(true) + + await config.executeMiddleware() + + expect(usageQuota.update).toHaveBeenCalledWith("test", "rows", 1) + expect(config.next).toHaveBeenCalled() + }) + + it("calculates the correct file size from a file upload call and adds it to quota", async () => { + config.setUrl("/upload") + config.cloudHosted(true) + config.setFiles([ + { + size: 100 + }, + { + size: 10000 + }, + ]) + await config.executeMiddleware() + + expect(usageQuota.update).toHaveBeenCalledWith("test", "storage", 10100) + expect(config.next).toHaveBeenCalled() + }) +}) \ No newline at end of file diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js index e980afe67..1b809868b 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -43,6 +43,7 @@ module.exports = async (ctx, next) => { return } } + // if running in builder or a self hosted cloud usage quotas should not be executed if (!env.CLOUD || env.SELF_HOSTED) { return next() From daaf352b89b53c0bc998159f33c71719ad148306 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 9 Mar 2021 16:07:44 +0000 Subject: [PATCH 15/28] Adding test cases for component, templates and hosting as well as updating some existing test cases. --- packages/server/__mocks__/node-fetch.js | 17 + packages/server/package.json | 7 +- .../server/src/api/controllers/templates.js | 2 + packages/server/src/api/routes/hosting.js | 8 +- .../src/api/routes/tests/apikeys.spec.js | 2 +- .../src/api/routes/tests/component.spec.js | 49 ++ .../src/api/routes/tests/hosting.spec.js | 130 ++++++ .../src/api/routes/tests/layout.spec.js | 2 +- .../src/api/routes/tests/permissions.spec.js | 15 + .../src/api/routes/tests/templates.spec.js | 49 ++ packages/server/src/utilities/templates.js | 5 +- packages/server/yarn.lock | 430 +++++++++++++++++- 12 files changed, 691 insertions(+), 25 deletions(-) create mode 100644 packages/server/__mocks__/node-fetch.js create mode 100644 packages/server/src/api/routes/tests/component.spec.js create mode 100644 packages/server/src/api/routes/tests/hosting.spec.js create mode 100644 packages/server/src/api/routes/tests/templates.spec.js diff --git a/packages/server/__mocks__/node-fetch.js b/packages/server/__mocks__/node-fetch.js new file mode 100644 index 000000000..1113791ec --- /dev/null +++ b/packages/server/__mocks__/node-fetch.js @@ -0,0 +1,17 @@ +const fetch = jest.requireActual("node-fetch") + +module.exports = async (url, opts) => { + // mocked data based on url + if (url.includes("api/apps")) { + return { + json: async () => { + return { + app1: { + url: "/app1", + }, + } + }, + } + } + return fetch(url, opts) +} diff --git a/packages/server/package.json b/packages/server/package.json index a23c6f6e8..3fe0d68bf 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -53,7 +53,11 @@ "src/**/*.js", "!**/node_modules/**", "!src/db/views/*.js", - "!src/api/routes/tests" + "!src/api/routes/tests/**/*.js", + "!src/api/controllers/deploy/**/*.js", + "!src/api/controllers/static/templates/**/*", + "!src/api/controllers/static/selfhost/**/*", + "!src/*.js" ], "coverageReporters": [ "lcov", @@ -122,6 +126,7 @@ "zlib": "1.0.5" }, "devDependencies": { + "@budibase/standard-components": "^0.8.5", "@jest/test-sequencer": "^24.8.0", "cross-env": "^7.0.3", "electron": "10.1.3", diff --git a/packages/server/src/api/controllers/templates.js b/packages/server/src/api/controllers/templates.js index 94243d3c7..c3cfa2870 100644 --- a/packages/server/src/api/controllers/templates.js +++ b/packages/server/src/api/controllers/templates.js @@ -24,6 +24,8 @@ exports.fetch = async function(ctx) { } } +// can't currently test this, have to ignore from coverage +/* istanbul ignore next */ exports.downloadTemplate = async function(ctx) { const { type, name } = ctx.params diff --git a/packages/server/src/api/routes/hosting.js b/packages/server/src/api/routes/hosting.js index 44429dacb..111968a49 100644 --- a/packages/server/src/api/routes/hosting.js +++ b/packages/server/src/api/routes/hosting.js @@ -11,11 +11,7 @@ router .get("/api/hosting/urls", authorized(BUILDER), controller.fetchUrls) .get("/api/hosting", authorized(BUILDER), controller.fetch) .post("/api/hosting", authorized(BUILDER), controller.save) - .get( - "/api/hosting/apps", - authorized(BUILDER), - selfhost, - controller.getDeployedApps - ) + // this isn't risky, doesn't return anything about apps other than names and URLs + .get("/api/hosting/apps", selfhost, controller.getDeployedApps) module.exports = router diff --git a/packages/server/src/api/routes/tests/apikeys.spec.js b/packages/server/src/api/routes/tests/apikeys.spec.js index 2a99e9e55..a8077a449 100644 --- a/packages/server/src/api/routes/tests/apikeys.spec.js +++ b/packages/server/src/api/routes/tests/apikeys.spec.js @@ -4,7 +4,7 @@ const { budibaseAppsDir } = require("../../../utilities/budibaseDir") const fs = require("fs") const path = require("path") -describe("/applications", () => { +describe("/api/keys", () => { let request = setup.getRequest() let config = setup.getConfig() diff --git a/packages/server/src/api/routes/tests/component.spec.js b/packages/server/src/api/routes/tests/component.spec.js new file mode 100644 index 000000000..926efc51e --- /dev/null +++ b/packages/server/src/api/routes/tests/component.spec.js @@ -0,0 +1,49 @@ +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") +const fs = require("fs") +const { resolve, join } = require("path") +const { budibaseAppsDir } = require("../../../utilities/budibaseDir") + +describe("/component", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + function mock() { + const manifestFile = "manifest.json" + const appId = config.getAppId() + const libraries = ["@budibase/standard-components"] + for (let library of libraries) { + let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules", library, "package") + fs.mkdirSync(appDirectory, { recursive: true }) + const file = require.resolve(library).split("dist/index.js")[0] + manifestFile + fs.copyFileSync(file, join(appDirectory, manifestFile)) + } + } + + describe("fetch definitions", () => { + it("should be able to fetch definitions", async () => { + // have to "mock" the files required + mock() + const res = await request + .get(`/${config.getAppId()}/components/definitions`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body["@budibase/standard-components/container"]).toBeDefined() + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/${config.getAppId()}/components/definitions`, + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/hosting.spec.js b/packages/server/src/api/routes/tests/hosting.spec.js new file mode 100644 index 000000000..2da5b1177 --- /dev/null +++ b/packages/server/src/api/routes/tests/hosting.spec.js @@ -0,0 +1,130 @@ +// mock out node fetch for this +jest.mock("node-fetch") + +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") + +describe("/hosting", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let app + + afterAll(setup.afterAll) + + beforeEach(async () => { + app = await config.init() + }) + + describe("fetchInfo", () => { + it("should be able to fetch hosting information", async () => { + const res = await request + .get(`/api/hosting/info`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toEqual({ types: ["cloud", "self"]}) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/hosting/info`, + }) + }) + }) + + describe("fetchUrls", () => { + it("should be able to fetch current app URLs", async () => { + const res = await request + .get(`/api/hosting/urls`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.app).toEqual(`https://${config.getAppId()}.app.budi.live`) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/hosting/urls`, + }) + }) + }) + + describe("fetch", () => { + it("should be able to fetch the current hosting information", async () => { + const res = await request + .get(`/api/hosting`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._id).toBeDefined() + expect(res.body.hostingUrl).toBeDefined() + expect(res.body.type).toEqual("cloud") + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/hosting`, + }) + }) + }) + + describe("save", () => { + it("should be able to update the hosting information", async () => { + const res = await request + .post(`/api/hosting`) + .send({ + type: "self", + selfHostKey: "budibase", + hostingUrl: "localhost:10000", + useHttps: false, + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.ok).toEqual(true) + // make sure URL updated + const urlRes = await request + .get(`/api/hosting/urls`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(urlRes.body.app).toEqual(`http://localhost:10000/app`) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/hosting`, + }) + }) + }) + + describe("getDeployedApps", () => { + it("should get apps when in builder", async () => { + const res = await request + .get(`/api/hosting/apps`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.app1).toEqual({url: "/app1"}) + }) + + it("should get apps when in cloud", async () => { + await setup.switchToCloudForFunction(async () => { + const res = await request + .get(`/api/hosting/apps`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.app1).toEqual({url: "/app1"}) + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/layout.spec.js b/packages/server/src/api/routes/tests/layout.spec.js index 4be1c9e18..6b21554d7 100644 --- a/packages/server/src/api/routes/tests/layout.spec.js +++ b/packages/server/src/api/routes/tests/layout.spec.js @@ -2,7 +2,7 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") const { basicLayout } = require("./utilities/structures") -describe("/queries", () => { +describe("/layouts", () => { let request = setup.getRequest() let config = setup.getConfig() let layout diff --git a/packages/server/src/api/routes/tests/permissions.spec.js b/packages/server/src/api/routes/tests/permissions.spec.js index 93e6e2913..b24fac57c 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.js +++ b/packages/server/src/api/routes/tests/permissions.spec.js @@ -107,4 +107,19 @@ describe("/permission", () => { expect(res.status).toEqual(403) }) }) + + describe("fetch builtins", () => { + it("should be able to fetch builtin definitions", async () => { + const res = await request + .get(`/api/permission/builtin`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(Array.isArray(res.body)).toEqual(true) + const publicPerm = res.body.find(perm => perm._id === "public") + expect(publicPerm).toBeDefined() + expect(publicPerm.permissions).toBeDefined() + expect(publicPerm.name).toBeDefined() + }) + }) }) diff --git a/packages/server/src/api/routes/tests/templates.spec.js b/packages/server/src/api/routes/tests/templates.spec.js new file mode 100644 index 000000000..f0d26bc7d --- /dev/null +++ b/packages/server/src/api/routes/tests/templates.spec.js @@ -0,0 +1,49 @@ +const setup = require("./utilities") +const { budibaseAppsDir } = require("../../../utilities/budibaseDir") +const fs = require("fs") +const { join } = require("path") + +describe("/templates", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("fetch", () => { + it("should be able to fetch templates", async () => { + const res = await request + .get(`/api/templates`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + // this test is quite light right now, templates aren't heavily utilised yet + expect(Array.isArray(res.body)).toEqual(true) + }) + }) + + describe("export", () => { + it("should be able to export the basic app", async () => { + const res = await request + .post(`/api/templates`) + .send({ + templateName: "test", + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toEqual("Created template: test") + const dir = join( + budibaseAppsDir(), + "templates", + "app", + "test", + "db" + ) + expect(fs.existsSync(dir)).toEqual(true) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/utilities/templates.js b/packages/server/src/utilities/templates.js index 7383e3115..c3d89477d 100644 --- a/packages/server/src/utilities/templates.js +++ b/packages/server/src/utilities/templates.js @@ -31,6 +31,8 @@ exports.getLocalTemplates = function() { return templateObj } +// can't really test this, downloading is just not something we should do in a behavioural test +/* istanbul ignore next */ exports.downloadTemplate = async function(type, name) { const dirName = join(budibaseAppsDir(), "templates", type, name) if (env.LOCAL_TEMPLATES) { @@ -67,8 +69,7 @@ exports.performDump = performDump exports.exportTemplateFromApp = async function({ templateName, appId }) { // Copy frontend files const templatesDir = join( - os.homedir(), - ".budibase", + budibaseAppsDir(), "templates", "app", templateName, diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index e1d6e0363..591205393 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f" integrity sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA== +"@adobe/spectrum-css-workflow-icons@^1.1.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.0.tgz#cda8bbe873ba9317160458858ae979e5393e5550" + integrity sha512-STSQQHvoBM0kf1JrNL3KEt88RklIctaGyGOzwUTnhtTkT1jHLaF4FgxrPDCvr1AT8VOq1nGplKUCeyZ9vdUUmA== + "@azure/ms-rest-azure-env@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz#8505873afd4a1227ec040894a64fdd736b4a101f" @@ -249,12 +254,24 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@budibase/client@^0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.3.tgz#944a745cc82845987cabd48e2ce3a7e58b387865" - integrity sha512-gEOmHlqStsFTtotduRRz9bld2s/066pSwM3CWRuspsz5yycPLMhWKcA3CdfxVlNoR9y7I7IFC9+pfM5STDJAMQ== +"@budibase/bbui@^1.58.13": + version "1.58.13" + resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.58.13.tgz#59df9c73def2d81c75dcbd2266c52c19db88dbd7" + integrity sha512-Zk6CKXdBfKsTVzA1Xs5++shdSSZLfphVpZuKVbjfzkgtuhyH7ruucexuSHEpFsxjW5rEKgKIBoRFzCK5vPvN0w== + dependencies: + markdown-it "^12.0.2" + quill "^1.3.7" + sirv-cli "^0.4.6" + svelte-flatpickr "^2.4.0" + svelte-portal "^1.0.0" + turndown "^7.0.0" + +"@budibase/client@^0.8.5": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.5.tgz#31a6bbf8e7ff2a5ab635e8987357c310dcedf555" + integrity sha512-igiHyFpqbYm2EyCy0aUlBlaPibpFa5DtQow1kFBAjUW2cyZdEt84JV4Mei77NueGo7zHcr6/ByF6ycdyeBgXQw== dependencies: - "@budibase/string-templates" "^0.8.3" + "@budibase/string-templates" "^0.8.5" deep-equal "^2.0.1" regexparam "^1.3.0" shortid "^2.2.15" @@ -292,10 +309,42 @@ to-gfm-code-block "^0.1.1" year "^0.2.1" -"@budibase/string-templates@^0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.3.tgz#f3b1f31ef914b926fb5285bc701e1200568dc92d" - integrity sha512-X4Z9/1TS5PtO5sF1CDoyp8xSJhXFWIhOldTNBzPeCjAaD+c9Q8gOgcwECWugJh2d05RjiVI6gDbeirT8Q2QMig== +"@budibase/standard-components@^0.8.5": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.8.5.tgz#4b94653110e4f20a8cb252b6421b620fd5ac31bc" + integrity sha512-wDEuxiu/DyPQYR2zQSt7TdPlAzdjjePitfKDzdIxm/WM7umXDSvLkA39nRzicEXikti34+waS7H96xGNuednVw== + dependencies: + "@adobe/spectrum-css-workflow-icons" "^1.1.0" + "@budibase/bbui" "^1.58.13" + "@budibase/svelte-ag-grid" "^1.0.4" + "@spectrum-css/actionbutton" "^1.0.0-beta.1" + "@spectrum-css/button" "^3.0.0-beta.6" + "@spectrum-css/checkbox" "^3.0.0-beta.6" + "@spectrum-css/fieldlabel" "^3.0.0-beta.7" + "@spectrum-css/icon" "^3.0.0-beta.2" + "@spectrum-css/inputgroup" "^3.0.0-beta.7" + "@spectrum-css/menu" "^3.0.0-beta.5" + "@spectrum-css/page" "^3.0.0-beta.0" + "@spectrum-css/picker" "^1.0.0-beta.3" + "@spectrum-css/popover" "^3.0.0-beta.6" + "@spectrum-css/stepper" "^3.0.0-beta.7" + "@spectrum-css/textfield" "^3.0.0-beta.6" + "@spectrum-css/vars" "^3.0.0-beta.2" + apexcharts "^3.22.1" + flatpickr "^4.6.6" + loadicons "^1.0.0" + lodash.debounce "^4.0.8" + markdown-it "^12.0.2" + quill "^1.3.7" + remixicon "^2.5.0" + svelte-apexcharts "^1.0.2" + svelte-flatpickr "^3.1.0" + turndown "^7.0.0" + +"@budibase/string-templates@^0.8.5": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.5.tgz#ad30e318f7486d4256b1165099fe2bd8004ef472" + integrity sha512-PcpiiDlYJFIVwtFGIRqZQtRl8wbO6yr0/+1Gca0TwR2WhyUyAs/ojO+jLIj97JWh/hE5zKaZW7d4cMOf+BDI/A== dependencies: "@budibase/handlebars-helpers" "^0.11.3" dayjs "^1.10.4" @@ -303,6 +352,13 @@ handlebars-utils "^1.0.6" lodash "^4.17.20" +"@budibase/svelte-ag-grid@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@budibase/svelte-ag-grid/-/svelte-ag-grid-1.0.4.tgz#41cceec4bde2c4aea8b9da8f610fe36055c7709f" + integrity sha512-JZm6qujxnZpqw7Twbegr6se4sHhyWzN0Cibrk5bVBH32hBgzD6dd33fxwrjHKkWFxjys9wRT+cqYgYVlSt9E3w== + dependencies: + ag-grid-community "^24.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -830,6 +886,11 @@ path-to-regexp "^1.1.1" urijs "^1.19.0" +"@polka/url@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31" + integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw== + "@sendgrid/client@^7.1.1": version "7.4.2" resolved "https://registry.yarnpkg.com/@sendgrid/client/-/client-7.4.2.tgz#204a9fbb5dc05a721a5d8cd8930f57f9f8e612b1" @@ -942,6 +1003,73 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@spectrum-css/actionbutton@^1.0.0-beta.1": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.0.tgz#c2f0939f6d49de0a855f08a9466e3f27105a1747" + integrity sha512-klE5CGJEJXkc4DMLF8W+VPlLZ6SFr4WXI5Tc9NarOtbAc7mqhs2gWA8HpsPT717FWdxRVVt3sSuAydgKC/T0UA== + +"@spectrum-css/button@^3.0.0-beta.6": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0.tgz#eebdd7a05eac9a40f297f802aca3efeb95931e83" + integrity sha512-CUGkuHOhqgfIRYTEceybcW1YsUN61F9BgDhqymhVd1yJFsuh1xkwnmv3IIodukgS+1e3L0JY6ifU86IWX/Dx5w== + +"@spectrum-css/checkbox@^3.0.0-beta.6": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/checkbox/-/checkbox-3.0.0.tgz#6ed15f433bed31a63818d154960aca044ce62182" + integrity sha512-FpxxftMzuWT8qq3XB4oBQgWglXuCCEGBfgX82EI9VtrJmw9j0Lm/nThMLX353p9awM4GfT3l2LNOneHbNetaRQ== + +"@spectrum-css/fieldlabel@^3.0.0-beta.7": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.0.tgz#01be5e5a7b024516574820962b9a656f7b2e20d4" + integrity sha512-dEOvDEigL9E60kQ9fT6MLyRzPKrPXAKulqDYOYpZaK2bsKrbIvsKb7NcuQynPAOE26FiuqQsp2khv5VqF4KzrA== + +"@spectrum-css/icon@^3.0.0-beta.2": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0.tgz#a822c901ca049f487420053dfeaaf71c0850c848" + integrity sha512-0VVx34WECxe+acSZsB+zk8T+AG8YimlCfUothuqLzcUgY6MnBESHJKOEuKKihxnihEm6EJiMc2NYA7+09kPv/A== + +"@spectrum-css/inputgroup@^3.0.0-beta.7": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/inputgroup/-/inputgroup-3.0.0.tgz#c2790c2c0a4c435ca3fff9ba04f64dd2b252f980" + integrity sha512-dlF8LmMwTa5G6Rl4zUiNCmRv7p2v+88jINnSwZHucgKZL0/HJZBRxjF1neeSfRFrc8R6cemoVXDHRDtZFaVtXQ== + +"@spectrum-css/menu@^3.0.0-beta.5": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.0.tgz#78153ea60a36c87e9d815ce51dc7f84d6b9b9abd" + integrity sha512-E6L6s1/cwh6hn4yhUHegiJ+Su03Bpa7qP5a6nEccpYePZxPAAN2FjZBWdMOPlGtv1e70vudAsoejli9nVthC2w== + +"@spectrum-css/page@^3.0.0-beta.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0.tgz#159e79fd376e2def1a7a25a8b8a8fcfa94bd79d1" + integrity sha512-4rNpGq99cfNSq/IOQNCiXio5gF/EEfjcSmihHBJlh7/VOB9zE84kMNW1Gux4cGEmdP14U1Zo1ZwnPIVs5ZuPgg== + dependencies: + "@spectrum-css/vars" "^3.0.0" + +"@spectrum-css/picker@^1.0.0-beta.3": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.0.tgz#5758a128da081becd425b8d9433b24541f12b4b3" + integrity sha512-aSoin2SVYl5W2R3nFp+V/Er6rAJUnwygO4E3g/tfDuImq8p5U3FKZj4sggSqfuD2U1PIwNSwX0D1RdxuGXsnUQ== + +"@spectrum-css/popover@^3.0.0-beta.6": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.0.tgz#ec1ab86a66cc59bd522d3de2b7febe41e2a9fe46" + integrity sha512-Lr2FZSJbDbDMp3bOlLtvDjOw6AwzRu3g0BbQ7NGK1l5MB06AhnqJX+TPB2iEDTfPdNyaDc5SCp55lBHP3RzHuw== + +"@spectrum-css/stepper@^3.0.0-beta.7": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.0.tgz#ab5af818c86f2bc5050d0caee8b0a1c75201bfaf" + integrity sha512-Gwvb4YLEBy/YtnFQ4aySnlve+pBrgPIm5LSq5IkeyjAKy7ZalQm9IIEkrVERHO1b+vbRZ6DW/aj2zYgzKgGMrA== + +"@spectrum-css/textfield@^3.0.0-beta.6": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0.tgz#2f2d341b8d2c6f74e074b7e8df4a28307561cbbf" + integrity sha512-ooXiSc5TZuZCFr3wl1JB60nS9FBBkGgqsml7kAS/7bOwRTCUPH7cY80SoaabRL8Z9Clml+K1Pa7I/r+Wphb53g== + +"@spectrum-css/vars@^3.0.0", "@spectrum-css/vars@^3.0.0-beta.2": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.0.tgz#c3ef4c2f07bd4f0d2734e730233ca81cb18106e7" + integrity sha512-fNXU6qmcCbSiUoWGe/m9A8/THRHbpzwZ+iN8o/27tWIzcQIyZBZgjmV/kIMdF1dHpu5CuWik7mGV1Ex8tlzATg== + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -1226,6 +1354,11 @@ adal-node@^0.1.28: xmldom ">= 0.1.x" xpath.js "~1.1.0" +ag-grid-community@^24.0.0: + version "24.1.0" + resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-24.1.0.tgz#1e3cab51211822e08d56f03a491b7c0deaa398e6" + integrity sha512-pWnWphuDcejZ8ahf6C734EpCx3XQ6dHEZWMWTlCdHNT0mZBLJ4YKCGACX+ttAEtSX2MGM3G13JncvuratUlYag== + agent-base@6: version "6.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" @@ -1563,6 +1696,18 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apexcharts@^3.19.2, apexcharts@^3.22.1: + version "3.25.0" + resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.25.0.tgz#f3f0f9f344f997230f5c7f2918408aa072627496" + integrity sha512-uM7OF+jLL4ba79noYcrMwMgJW8DI+Ff28CCQoGq23g25z8nGSQEoU+u12YWlECA9gBA5tbmdaQhMxjlK+M6B9Q== + dependencies: + svg.draggable.js "^2.2.2" + svg.easing.js "^2.0.0" + svg.filter.js "^2.0.2" + svg.pathmorphing.js "^0.1.3" + svg.resize.js "^1.4.3" + svg.select.js "^3.0.1" + app-builder-bin@3.5.10: version "3.5.10" resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.10.tgz#4a7f9999fccc0c435b6284ae1366bc76a17c4a7d" @@ -1621,6 +1766,11 @@ argparse@^1.0.10, argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + args@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761" @@ -2356,6 +2506,11 @@ clone-response@1.0.2, clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + co-body@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" @@ -2489,6 +2644,11 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" +console-clear@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7" + integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ== + content-disposition@^0.5.2, content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -2752,6 +2912,18 @@ decompress@^4.2.1: pify "^2.3.0" strip-dirs "^2.0.0" +deep-equal@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + deep-equal@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" @@ -2944,6 +3116,11 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" +domino@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe" + integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ== + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -3165,6 +3342,11 @@ ent@^2.2.0: resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" @@ -3450,6 +3632,11 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3551,7 +3738,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -3621,6 +3808,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" + integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -3800,6 +3992,11 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flatpickr@^4.5.2, flatpickr@^4.6.6: + version "4.6.9" + resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" + integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== + flatstr@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" @@ -3988,6 +4185,11 @@ get-object@^0.2.0: is-number "^2.0.2" isobject "^0.2.0" +get-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -4859,7 +5061,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-regex@^1.1.1, is-regex@^1.1.2: +is-regex@^1.0.4, is-regex@^1.1.1, is-regex@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== @@ -5693,7 +5895,7 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kleur@^3.0.3: +kleur@^3.0.0, kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== @@ -6002,6 +6204,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +linkify-it@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8" + integrity sha512-gDBO4aHNZS6coiZCKVhSNh43F9ioIL4JwRjLZPkoLIY4yZFwg264Y5lu2x6rb1Js42Gh6Yqm2f6L2AJcnkzinQ== + dependencies: + uc.micro "^1.0.1" + load-bmfont@^1.3.1, load-bmfont@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9" @@ -6026,6 +6235,16 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" +loadicons@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/loadicons/-/loadicons-1.0.0.tgz#79fd9b08ef2933988c94068cbd246ef3f21cbd04" + integrity sha512-KSywiudfuOK5sTdhNMM8hwRpMxZ5TbQlU4ZijMxUFwRW7jpxUmb9YJoLIzDn7+xuxeLzCZWBmLJS2JDjDWCpsw== + +local-access@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.1.0.tgz#e007c76ba2ca83d5877ba1a125fc8dfe23ba4798" + integrity sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw== + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -6280,6 +6499,17 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +markdown-it@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.0.4.tgz#eec8247d296327eac3ba9746bdeec9cfcc751e33" + integrity sha512-34RwOXZT8kyuOJy25oJNJoulO8L0bTHYWXcdZBYZqFnjIy3NgjeoM3FmPXIOFQ26/lSHYMr8oc62B6adxXcb3Q== + dependencies: + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -6287,6 +6517,11 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +mdurl@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -6396,6 +6631,11 @@ mime@^1.3.4, mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.3.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + mime@^2.4.6: version "2.4.6" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" @@ -6473,6 +6713,11 @@ mri@1.1.4: resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== +mri@^1.1.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.6.tgz#49952e1044db21dbf90f6cd92bc9c9a777d415a6" + integrity sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -6755,7 +7000,7 @@ object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== -object-is@^1.1.4: +object-is@^1.0.1, object-is@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== @@ -6973,6 +7218,11 @@ pako@^1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== +parchment@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5" + integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -7646,6 +7896,27 @@ quick-format-unescaped@^4.0.1: resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz#437a5ea1a0b61deb7605f8ab6a8fd3858dbeb701" integrity sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A== +quill-delta@^3.6.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032" + integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg== + dependencies: + deep-equal "^1.0.1" + extend "^3.0.2" + fast-diff "1.1.2" + +quill@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" + integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== + dependencies: + clone "^2.1.1" + deep-equal "^1.0.1" + eventemitter3 "^2.0.3" + extend "^3.0.2" + parchment "^1.1.4" + quill-delta "^3.6.2" + raw-body@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -7822,7 +8093,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.3.0: +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== @@ -7869,6 +8140,11 @@ remarkable@^1.6.2, remarkable@^1.7.1: argparse "^1.0.10" autolinker "~0.28.0" +remixicon@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41" + integrity sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww== + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -8055,6 +8331,13 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" +sade@^1.4.0: + version "1.7.4" + resolved "https://registry.yarnpkg.com/sade/-/sade-1.7.4.tgz#ea681e0c65d248d2095c90578c03ca0bb1b54691" + integrity sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA== + dependencies: + mri "^1.1.0" + safe-buffer@*, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -8287,6 +8570,27 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== +sirv-cli@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-0.4.6.tgz#c28ab20deb3b34637f5a60863dc350f055abca04" + integrity sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww== + dependencies: + console-clear "^1.1.0" + get-port "^3.2.0" + kleur "^3.0.0" + local-access "^1.0.1" + sade "^1.4.0" + sirv "^0.4.6" + tinydate "^1.0.0" + +sirv@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-0.4.6.tgz#185e44eb93d24009dd183b7494285c5180b81f22" + integrity sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ== + dependencies: + "@polka/url" "^0.5.0" + mime "^2.3.1" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -8749,6 +9053,32 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +svelte-apexcharts@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/svelte-apexcharts/-/svelte-apexcharts-1.0.2.tgz#4e000f8b8f7c901c05658c845457dfc8314d54c1" + integrity sha512-6qlx4rE+XsonZ0FZudfwqOQ34Pq+3wpxgAD75zgEmGoYhYBJcwmikTuTf3o8ZBsZue9U/pAwhNy3ed1Bkq1gmA== + dependencies: + apexcharts "^3.19.2" + +svelte-flatpickr@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-2.4.0.tgz#190871fc3305956c8c8fd3601cd036b8ac71ef49" + integrity sha512-UUC5Te+b0qi4POg7VDwfGh0m5W3Hf64OwkfOTj6FEe/dYZN4cBzpQ82EuuQl0CTbbBAsMkcjJcixV1d2V6EHCQ== + dependencies: + flatpickr "^4.5.2" + +svelte-flatpickr@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/svelte-flatpickr/-/svelte-flatpickr-3.1.0.tgz#ad83588430dbd55196a1a258b8ba27e7f9c1ee37" + integrity sha512-zKyV+ukeVuJ8CW0Ing3T19VSekc4bPkou/5Riutt1yATrLvSsanNqcgqi7Q5IePvIoOF9GJ5OtHvn1qK9Wx9BQ== + dependencies: + flatpickr "^4.5.2" + +svelte-portal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/svelte-portal/-/svelte-portal-1.0.0.tgz#36a47c5578b1a4d9b4dc60fa32a904640ec4cdd3" + integrity sha512-nHf+DS/jZ6jjnZSleBMSaZua9JlG5rZv9lOGKgJuaZStfevtjIlUJrkLc3vbV8QdBvPPVmvcjTlazAzfKu0v3Q== + svelte-spa-router@^3.0.5: version "3.1.0" resolved "https://registry.yarnpkg.com/svelte-spa-router/-/svelte-spa-router-3.1.0.tgz#a929f0def7e12c41f32bc356f91685aeadcd75bf" @@ -8761,6 +9091,61 @@ svelte@3.30.0: resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.30.0.tgz#cbde341e96bf34f4ac73c8f14f8a014e03bfb7d6" integrity sha512-z+hdIACb9TROGvJBQWcItMtlr4s0DBUgJss6qWrtFkOoIInkG+iAMo/FJZQFyDBQZc+dul2+TzYSi/tpTT5/Ag== +svg.draggable.js@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba" + integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw== + dependencies: + svg.js "^2.0.1" + +svg.easing.js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12" + integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI= + dependencies: + svg.js ">=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203" + integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM= + dependencies: + svg.js "^2.2.5" + +svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -8940,6 +9325,11 @@ tinycolor2@^1.4.1: resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tinydate@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tinydate/-/tinydate-1.3.0.tgz#e6ca8e5a22b51bb4ea1c3a2a4fd1352dbd4c57fb" + integrity sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9083,6 +9473,13 @@ tunnel@0.0.6, tunnel@^0.0.6: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== +turndown@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.0.0.tgz#19b2a6a2d1d700387a1e07665414e4af4fec5225" + integrity sha512-G1FfxfR0mUNMeGjszLYl3kxtopC4O9DRRiMlMDDVHvU1jaBkGFg4qxIyjIk2aiKLHyDyZvZyu4qBO2guuYBy3Q== + dependencies: + domino "^2.1.6" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9142,6 +9539,11 @@ typeof-article@^0.1.1: dependencies: kind-of "^3.1.0" +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + uglify-js@^3.1.4: version "3.13.0" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.0.tgz#66ed69f7241f33f13531d3d51d5bcebf00df7f69" From 61110be9d0a184266989bfa837f9b5468190296f Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 9 Mar 2021 16:28:41 +0000 Subject: [PATCH 16/28] Adding test cases for backup and integration. --- .../src/api/routes/tests/backup.spec.js | 32 ++++++++++++ .../src/api/routes/tests/integration.spec.js | 52 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 packages/server/src/api/routes/tests/backup.spec.js create mode 100644 packages/server/src/api/routes/tests/integration.spec.js diff --git a/packages/server/src/api/routes/tests/backup.spec.js b/packages/server/src/api/routes/tests/backup.spec.js new file mode 100644 index 000000000..d60399029 --- /dev/null +++ b/packages/server/src/api/routes/tests/backup.spec.js @@ -0,0 +1,32 @@ +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") + +describe("/backups", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("exportAppDump", () => { + it("should be able to export app", async () => { + const res = await request + .get(`/api/backups/export?appId=${config.getAppId()}`) + .set(config.defaultHeaders()) + .expect(200) + expect(res.text).toBeDefined() + expect(res.text.includes(`"db_name":"${config.getAppId()}"`)).toEqual(true) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/backups/export?appId=${config.getAppId()}`, + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/integration.spec.js b/packages/server/src/api/routes/tests/integration.spec.js new file mode 100644 index 000000000..528d0d341 --- /dev/null +++ b/packages/server/src/api/routes/tests/integration.spec.js @@ -0,0 +1,52 @@ +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") + +describe("/integrations", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("fetch", () => { + it("should be able to get all integration definitions", async () => { + const res = await request + .get(`/api/integrations`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.POSTGRES).toBeDefined() + expect(res.body.POSTGRES.friendlyName).toEqual("PostgreSQL") + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/integrations`, + }) + }) + }) + + describe("find", () => { + it("should be able to get postgres definition", async () => { + const res = await request + .get(`/api/integrations/POSTGRES`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.friendlyName).toEqual("PostgreSQL") + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/integrations/POSTGRES`, + }) + }) + }) +}) \ No newline at end of file From 67c4a5ef6c7bad7073c1f1cff3c54fa4d5070695 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 9 Mar 2021 17:04:24 +0000 Subject: [PATCH 17/28] authenticated tests --- .../middleware/tests/authenticated.spec.js | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/packages/server/src/middleware/tests/authenticated.spec.js b/packages/server/src/middleware/tests/authenticated.spec.js index e69de29bb..799fbaf41 100644 --- a/packages/server/src/middleware/tests/authenticated.spec.js +++ b/packages/server/src/middleware/tests/authenticated.spec.js @@ -0,0 +1,98 @@ +const { AuthTypes } = require("../../constants") +const authenticatedMiddleware = require("../authenticated") + +class TestConfiguration { + constructor(middleware) { + this.middleware = authenticatedMiddleware + this.ctx = { + auth: {}, + request: {}, + cookies: { + set: jest.fn(), + get: jest.fn() + }, + headers: {}, + params: {}, + path: "", + request: { + headers: {} + } + } + this.next = jest.fn() + } + + setHeaders(headers) { + this.ctx.headers = headers + } + + executeMiddleware() { + return this.middleware(this.ctx, this.next) + } +} + +describe("Authenticated middleware", () => { + let config + + beforeEach(() => { + config = new TestConfiguration() + }) + + it("calls next() when on the builder path", async () => { + config.ctx.path = "/_builder" + + await config.executeMiddleware() + + expect(config.next).toHaveBeenCalled() + }) + + it("sets a new cookie when the current cookie does not match the app id from context", async () => { + const appId = "app_123" + config.ctx.cookies.get.mockImplementationOnce(() => "cookieAppId") + config.setHeaders({ + "x-budibase-app-id": appId + }) + + await config.executeMiddleware() + + expect(config.ctx.cookies.set).toHaveBeenCalledWith( + "budibase:currentapp:local", + appId, + expect.any(Object) + ) + + }) + + fit("sets a BUILDER auth type when the x-budibase-type header is not 'client'", async () => { + config.ctx.cookies.get.mockImplementationOnce(() => `budibase:builder:local`) + + await config.executeMiddleware() + + expect(config.ctx.auth.authenticated).toEqual(AuthTypes.BUILDER) + }) + + it("assigns an APP auth type when the user is not in the builder", async () => { + config.setHeaders({ + "x-budibase-type": "client" + }) + config.ctx.cookies.get.mockImplementationOnce(() => `budibase:builder:local`) + + await config.executeMiddleware() + + expect(config.ctx.auth.authenticated).toEqual(AuthTypes.APP) + }) + + it("marks the user as unauthenticated when a token cannot be determined from the users cookie", async () => { + config.executeMiddleware() + expect() + }) + + it("verifies the users JWT token and sets the user information in context when successful", async () => { + config.executeMiddleware() + expect() + }) + + it("clears the cookie when there is an error authenticating in the builder", async () => { + config.executeMiddleware() + expect() + }) +}) \ No newline at end of file From 3f88ed391f8236220c6d692227160a7f6acbb1fd Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 9 Mar 2021 17:09:18 +0000 Subject: [PATCH 18/28] Upping user test cases to cover all of controller. --- packages/server/src/api/controllers/user.js | 10 ++- .../server/src/api/routes/tests/user.spec.js | 76 +++++++++++++++++-- packages/server/src/api/routes/user.js | 2 +- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index c100f43d8..3dd28284b 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -52,7 +52,7 @@ exports.create = async function(ctx) { const response = await db.post(user) ctx.status = 200 ctx.message = "User created successfully." - ctx.userId = response._id + ctx.userId = response.id ctx.body = { _rev: response.rev, email, @@ -70,6 +70,9 @@ exports.update = async function(ctx) { const db = new CouchDB(ctx.user.appId) const user = ctx.request.body let dbUser + if (user.email && !user._id) { + user._id = generateUserID(user.email) + } // get user incase password removed if (user._id) { dbUser = await db.get(user._id) @@ -87,14 +90,15 @@ exports.update = async function(ctx) { user._rev = response.rev ctx.status = 200 - ctx.message = `User ${ctx.request.body.email} updated successfully.` ctx.body = response } exports.destroy = async function(ctx) { const database = new CouchDB(ctx.user.appId) await database.destroy(generateUserID(ctx.params.email)) - ctx.message = `User ${ctx.params.email} deleted.` + ctx.body = { + message: `User ${ctx.params.email} deleted.`, + } ctx.status = 200 } diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 6ec607a09..5e7ec9e9d 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -42,15 +42,19 @@ describe("/users", () => { }) describe("create", () => { - it("returns a success message when a user is successfully created", async () => { - const body = basicUser(BUILTIN_ROLE_IDS.POWER) - body.email = "bill@budibase.com" - const res = await request + async function create(user, status = 200) { + return request .post(`/api/users`) .set(config.defaultHeaders()) - .send(body) - .expect(200) + .send(user) + .expect(status) .expect("Content-Type", /json/) + } + + it("returns a success message when a user is successfully created", async () => { + const body = basicUser(BUILTIN_ROLE_IDS.POWER) + body.email = "bill@budibase.com" + const res = await create(body) expect(res.res.statusMessage).toEqual("User created successfully.") expect(res.body._id).toBeUndefined() @@ -68,5 +72,65 @@ describe("/users", () => { failRole: BUILTIN_ROLE_IDS.PUBLIC, }) }) + + it("should error if no email provided", async () => { + const user = basicUser(BUILTIN_ROLE_IDS.POWER) + delete user.email + await create(user, 400) + }) + + it("should error if no role provided", async () => { + const user = basicUser(null) + await create(user, 400) + }) + + it("should throw error if user exists already", async () => { + await config.createUser("test@test.com") + const user = basicUser(BUILTIN_ROLE_IDS.POWER) + user.email = "test@test.com" + await create(user, 400) + }) + }) + + describe("update", () => { + it("should be able to update the user", async () => { + const user = await config.createUser() + user.roleId = BUILTIN_ROLE_IDS.BASIC + const res = await request + .put(`/api/users`) + .set(config.defaultHeaders()) + .send(user) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body.ok).toEqual(true) + }) + }) + + describe("destroy", () => { + it("should be able to delete the user", async () => { + const email = "test@test.com" + await config.createUser(email) + const res = await request + .delete(`/api/users/${email}`) + .set(config.defaultHeaders()) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body.message).toBeDefined() + }) + }) + + describe("find", () => { + it("should be able to find the user", async () => { + const email = "test@test.com" + await config.createUser(email) + const res = await request + .get(`/api/users/${email}`) + .set(config.defaultHeaders()) + .expect(200) + .expect("Content-Type", /json/) + expect(res.body.email).toEqual(email) + expect(res.body.roleId).toEqual(BUILTIN_ROLE_IDS.POWER) + expect(res.body.tableId).toBeDefined() + }) }) }) diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index 1ad1d2363..cdaab0cc5 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -21,7 +21,7 @@ router controller.find ) .put( - "/api/users/", + "/api/users", authorized(PermissionTypes.USER, PermissionLevels.WRITE), controller.update ) From 6263300a6857b2ea73f797e66fbbaa0b07c02d6e Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Tue, 9 Mar 2021 17:31:52 +0000 Subject: [PATCH 19/28] finish authenticated tests --- .../server/src/middleware/authenticated.js | 2 + .../__snapshots__/authenticated.spec.js.snap | 28 ++++++++++ .../middleware/tests/authenticated.spec.js | 56 ++++++++++++++----- 3 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 packages/server/src/middleware/tests/__snapshots__/authenticated.spec.js.snap diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index 659baa8f6..32ed3f63d 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -31,6 +31,7 @@ module.exports = async (ctx, next) => { token = ctx.cookies.get(getCookieName()) authType = AuthTypes.BUILDER } + if (!token && appId) { token = ctx.cookies.get(getCookieName(appId)) authType = AuthTypes.APP @@ -58,6 +59,7 @@ module.exports = async (ctx, next) => { role: await getRole(appId, jwtPayload.roleId), } } catch (err) { + console.log(err) if (authType === AuthTypes.BUILDER) { clearCookie(ctx) ctx.status = 200 diff --git a/packages/server/src/middleware/tests/__snapshots__/authenticated.spec.js.snap b/packages/server/src/middleware/tests/__snapshots__/authenticated.spec.js.snap new file mode 100644 index 000000000..1583ecb51 --- /dev/null +++ b/packages/server/src/middleware/tests/__snapshots__/authenticated.spec.js.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Authenticated middleware sets the correct APP auth type information when the user is not in the builder 1`] = ` +Object { + "apiKey": "1234", + "appId": "budibase:app:local", + "role": Role { + "_id": "ADMIN", + "inherits": "POWER", + "name": "Admin", + "permissionId": "admin", + }, + "roleId": "ADMIN", +} +`; + +exports[`Authenticated middleware sets the correct BUILDER auth type information when the x-budibase-type header is not 'client' 1`] = ` +Object { + "apiKey": "1234", + "appId": "budibase:builder:local", + "role": Role { + "_id": "BUILDER", + "name": "Builder", + "permissionId": "admin", + }, + "roleId": "BUILDER", +} +`; diff --git a/packages/server/src/middleware/tests/authenticated.spec.js b/packages/server/src/middleware/tests/authenticated.spec.js index 799fbaf41..bb124d2f4 100644 --- a/packages/server/src/middleware/tests/authenticated.spec.js +++ b/packages/server/src/middleware/tests/authenticated.spec.js @@ -1,10 +1,13 @@ const { AuthTypes } = require("../../constants") const authenticatedMiddleware = require("../authenticated") +const jwt = require("jsonwebtoken") +jest.mock("jsonwebtoken") class TestConfiguration { constructor(middleware) { this.middleware = authenticatedMiddleware this.ctx = { + config: {}, auth: {}, request: {}, cookies: { @@ -16,7 +19,8 @@ class TestConfiguration { path: "", request: { headers: {} - } + }, + throw: jest.fn() } this.next = jest.fn() } @@ -28,6 +32,10 @@ class TestConfiguration { executeMiddleware() { return this.middleware(this.ctx, this.next) } + + afterEach() { + jest.resetAllMocks() + } } describe("Authenticated middleware", () => { @@ -37,6 +45,10 @@ describe("Authenticated middleware", () => { config = new TestConfiguration() }) + afterEach(() => { + config.afterEach() + }) + it("calls next() when on the builder path", async () => { config.ctx.path = "/_builder" @@ -47,10 +59,10 @@ describe("Authenticated middleware", () => { it("sets a new cookie when the current cookie does not match the app id from context", async () => { const appId = "app_123" - config.ctx.cookies.get.mockImplementationOnce(() => "cookieAppId") config.setHeaders({ "x-budibase-app-id": appId }) + config.ctx.cookies.get.mockImplementation(() => "cookieAppId") await config.executeMiddleware() @@ -62,37 +74,53 @@ describe("Authenticated middleware", () => { }) - fit("sets a BUILDER auth type when the x-budibase-type header is not 'client'", async () => { - config.ctx.cookies.get.mockImplementationOnce(() => `budibase:builder:local`) + it("sets the correct BUILDER auth type information when the x-budibase-type header is not 'client'", async () => { + config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local") + jwt.verify.mockImplementationOnce(() => ({ + apiKey: "1234", + roleId: "BUILDER" + })) await config.executeMiddleware() expect(config.ctx.auth.authenticated).toEqual(AuthTypes.BUILDER) + expect(config.ctx.user).toMatchSnapshot() }) - it("assigns an APP auth type when the user is not in the builder", async () => { + it("sets the correct APP auth type information when the user is not in the builder", async () => { config.setHeaders({ "x-budibase-type": "client" }) - config.ctx.cookies.get.mockImplementationOnce(() => `budibase:builder:local`) + config.ctx.cookies.get.mockImplementation(() => `budibase:app:local`) + jwt.verify.mockImplementationOnce(() => ({ + apiKey: "1234", + roleId: "ADMIN" + })) await config.executeMiddleware() expect(config.ctx.auth.authenticated).toEqual(AuthTypes.APP) + expect(config.ctx.user).toMatchSnapshot() }) it("marks the user as unauthenticated when a token cannot be determined from the users cookie", async () => { config.executeMiddleware() - expect() - }) - - it("verifies the users JWT token and sets the user information in context when successful", async () => { - config.executeMiddleware() - expect() + expect(config.ctx.auth.authenticated).toBe(false) + expect(config.ctx.user.role).toEqual({ + _id: "PUBLIC", + name: "Public", + permissionId: "public" + }) }) it("clears the cookie when there is an error authenticating in the builder", async () => { - config.executeMiddleware() - expect() + config.ctx.cookies.get.mockImplementation(() => "budibase:builder:local") + jwt.verify.mockImplementationOnce(() => { + throw new Error() + }) + + await config.executeMiddleware() + + expect(config.ctx.cookies.set).toBeCalledWith("budibase:builder:local") }) }) \ No newline at end of file From bc7cc089b852bb5a614b76064250a4cebdd79228 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 10 Mar 2021 10:39:58 +0000 Subject: [PATCH 20/28] allowing airtable query to send down the number of records you want from your table --- packages/server/src/api/controllers/query.js | 4 +++- packages/server/src/integrations/airtable.js | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 55c2ad14b..951fedd3d 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -61,9 +61,11 @@ async function enrichQueryFields(fields, parameters) { if (typeof fields[key] === "object") { // enrich nested fields object enrichedQuery[key] = await enrichQueryFields(fields[key], parameters) - } else { + } else if (typeof fields[key] === "string") { // enrich string value as normal enrichedQuery[key] = await processString(fields[key], parameters) + } else { + enrichedQuery[key] = fields[key] } } diff --git a/packages/server/src/integrations/airtable.js b/packages/server/src/integrations/airtable.js index 2c8371228..37e552a7b 100644 --- a/packages/server/src/integrations/airtable.js +++ b/packages/server/src/integrations/airtable.js @@ -40,6 +40,10 @@ const SCHEMA = { type: FIELD_TYPES.STRING, required: true, }, + numRecords: { + type: FIELD_TYPES.NUMBER, + default: 10, + }, }, }, update: { From 63f081930ffe3740585043b828a7e7840f561aad Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Mar 2021 11:47:39 +0000 Subject: [PATCH 21/28] Upping automation coverage by adding webhook testing and increasing screen coverage. --- packages/builder/src/analytics.js | 2 +- .../server/src/api/controllers/analytics.js | 4 +- .../server/src/api/controllers/automation.js | 5 ++ packages/server/src/api/controllers/screen.js | 4 +- .../src/api/routes/tests/automation.spec.js | 42 ++++++++++ .../server/src/api/routes/tests/misc.spec.js | 38 +++++++++ .../src/api/routes/tests/screen.spec.js | 77 +++++++++++++++++++ .../server/src/utilities/security/roles.js | 3 +- 8 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 packages/server/src/api/routes/tests/misc.spec.js create mode 100644 packages/server/src/api/routes/tests/screen.spec.js diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index df91c16e0..e6a647a08 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -16,7 +16,7 @@ async function activate() { // this was an issue as NODE_ENV = 'cypress' on the server, // but 'production' on the client const response = await api.get("/api/analytics") - analyticsEnabled = (await response.json()) === true + analyticsEnabled = (await response.json()).enabled === true } if (!analyticsEnabled) return if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN }) diff --git a/packages/server/src/api/controllers/analytics.js b/packages/server/src/api/controllers/analytics.js index 566b4970e..76e5eb11f 100644 --- a/packages/server/src/api/controllers/analytics.js +++ b/packages/server/src/api/controllers/analytics.js @@ -1,5 +1,7 @@ const env = require("../../environment") exports.isEnabled = async function(ctx) { - ctx.body = JSON.stringify(env.ENABLE_ANALYTICS === "true") + ctx.body = { + enabled: env.ENABLE_ANALYTICS === "true", + } } diff --git a/packages/server/src/api/controllers/automation.js b/packages/server/src/api/controllers/automation.js index 5fa618654..df17371f9 100644 --- a/packages/server/src/api/controllers/automation.js +++ b/packages/server/src/api/controllers/automation.js @@ -98,6 +98,11 @@ exports.create = async function(ctx) { let automation = ctx.request.body automation.appId = ctx.user.appId + // call through to update if already exists + if (automation._id && automation._rev) { + return exports.update(ctx) + } + automation._id = generateAutomationID() automation.type = "automation" diff --git a/packages/server/src/api/controllers/screen.js b/packages/server/src/api/controllers/screen.js index f56dae153..8f9baa817 100644 --- a/packages/server/src/api/controllers/screen.js +++ b/packages/server/src/api/controllers/screen.js @@ -41,6 +41,8 @@ exports.save = async ctx => { exports.destroy = async ctx => { const db = new CouchDB(ctx.user.appId) await db.remove(ctx.params.screenId, ctx.params.screenRev) - ctx.message = "Screen deleted successfully" + ctx.body = { + message: "Screen deleted successfully", + } ctx.status = 200 } diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 0648bfefa..588de641b 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -109,6 +109,35 @@ describe("/automations", () => { automation = res.body.automation }) + it("should be able to create an automation with a webhook trigger", async () => { + const autoConfig = basicAutomation() + autoConfig.definition.trigger = TRIGGER_DEFINITIONS["WEBHOOK"] + autoConfig.definition.trigger.id = "webhook_trigger_id" + const res = await request + .post(`/api/automations`) + .set(config.defaultHeaders()) + .send(autoConfig) + .expect('Content-Type', /json/) + .expect(200) + const originalAuto = res.body.automation + expect(originalAuto._id).toBeDefined() + expect(originalAuto._rev).toBeDefined() + // try removing the webhook trigger + const newConfig = originalAuto + newConfig.definition.trigger = TRIGGER_DEFINITIONS["ROW_SAVED"] + newConfig.definition.trigger.id = "row_saved_id" + const newRes = await request + .post(`/api/automations`) + .set(config.defaultHeaders()) + .send(newConfig) + .expect('Content-Type', /json/) + .expect(200) + const newAuto = newRes.body.automation + expect(newAuto._id).toEqual(originalAuto._id) + expect(newAuto._rev).toBeDefined() + expect(newAuto._rev).not.toEqual(originalAuto._rev) + }) + it("should apply authorization to endpoint", async () => { await checkBuilderEndpoint({ config, @@ -119,6 +148,19 @@ describe("/automations", () => { }) }) + describe("find", () => { + it("should be able to find the automation", async () => { + const automation = await config.createAutomation() + const res = await request + .get(`/api/automations/${automation._id}`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body._id).toEqual(automation._id) + expect(res.body._rev).toEqual(automation._rev) + }) + }) + describe("trigger", () => { it("trigger the automation successfully", async () => { let table = await config.createTable() diff --git a/packages/server/src/api/routes/tests/misc.spec.js b/packages/server/src/api/routes/tests/misc.spec.js new file mode 100644 index 000000000..3d3b6047e --- /dev/null +++ b/packages/server/src/api/routes/tests/misc.spec.js @@ -0,0 +1,38 @@ +const setup = require("./utilities") + +describe("/analytics", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("isEnabled", () => { + it("check if analytics enabled", async () => { + const res = await request + .get(`/api/analytics`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(typeof res.body.enabled).toEqual("boolean") + }) + }) +}) + +describe("/health", () => { + it("should confirm healthy", async () => { + let config = setup.getConfig() + await config.getRequest().get("/health").expect(200) + }) +}) + +describe("/version", () => { + it("should confirm version", async () => { + const config = setup.getConfig() + const res = await config.getRequest().get("/version").expect(200) + expect(res.text.split(".").length).toEqual(3) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/screen.spec.js b/packages/server/src/api/routes/tests/screen.spec.js new file mode 100644 index 000000000..0c2799eca --- /dev/null +++ b/packages/server/src/api/routes/tests/screen.spec.js @@ -0,0 +1,77 @@ +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") +const { basicScreen } = require("./utilities/structures") + +describe("/screens", () => { + let request = setup.getRequest() + let config = setup.getConfig() + let screen + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + screen = await config.createScreen() + }) + + describe("fetch", () => { + it("should be able to create a layout", async () => { + const res = await request + .get(`/api/screens`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.length).toEqual(1) + expect(res.body[0]._id).toEqual(screen._id) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/screens`, + }) + }) + }) + + describe("save", () => { + it("should be able to save a screen", async () => { + const screenCfg = basicScreen() + const res = await request + .post(`/api/screens`) + .send(screenCfg) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._rev).toBeDefined() + expect(res.body.name).toEqual(screenCfg.name) + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/screens`, + }) + }) + }) + + describe("destroy", () => { + it("should be able to delete the screen", async () => { + const res = await request + .delete(`/api/screens/${screen._id}/${screen._rev}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.message).toBeDefined() + }) + + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "DELETE", + url: `/api/screens/${screen._id}/${screen._rev}`, + }) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/utilities/security/roles.js b/packages/server/src/utilities/security/roles.js index 79fd72007..b0cd84359 100644 --- a/packages/server/src/utilities/security/roles.js +++ b/packages/server/src/utilities/security/roles.js @@ -205,7 +205,8 @@ class AccessController { tryingRoleId == null || tryingRoleId === "" || tryingRoleId === userRoleId || - tryingRoleId === BUILTIN_IDS.BUILDER + tryingRoleId === BUILTIN_IDS.BUILDER || + userRoleId === BUILTIN_IDS.BUILDER ) { return true } From 2b7f4457873a54a1e9ff1946c5e7a4bda0fdf384 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Mar 2021 11:56:52 +0000 Subject: [PATCH 22/28] Fixing issue with datasource find endpoint, removing un-used code and updating data source test. --- .../server/src/api/controllers/datasource.js | 34 ++------ packages/server/src/api/routes/datasource.js | 2 +- .../src/api/routes/tests/datasource.spec.js | 81 +++++++++---------- 3 files changed, 43 insertions(+), 74 deletions(-) diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index 15cea72da..d73a316ad 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -26,35 +26,12 @@ exports.save = async function(ctx) { ...ctx.request.body, } - try { - const response = await db.post(datasource) - datasource._rev = response.rev - - ctx.status = 200 - ctx.message = "Datasource saved successfully." - ctx.body = datasource - } catch (err) { - ctx.throw(err.status, err) - } -} - -exports.update = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - const user = ctx.request.body - const dbUser = await db.get(ctx.request.body._id) - if (user.password) { - user.password = await bcrypt.hash(user.password) - } else { - delete user.password - } - const newData = { ...dbUser, ...user } - - const response = await db.put(newData) - user._rev = response.rev + const response = await db.post(datasource) + datasource._rev = response.rev ctx.status = 200 - ctx.message = `User ${ctx.request.body.email} updated successfully.` - ctx.body = response + ctx.message = "Datasource saved successfully." + ctx.body = datasource } exports.destroy = async function(ctx) { @@ -73,6 +50,5 @@ exports.destroy = async function(ctx) { exports.find = async function(ctx) { const database = new CouchDB(ctx.user.appId) - const datasource = await database.get(ctx.params.datasourceId) - ctx.body = datasource + ctx.body = await database.get(ctx.params.datasourceId) } diff --git a/packages/server/src/api/routes/datasource.js b/packages/server/src/api/routes/datasource.js index 5746ce183..ee2210704 100644 --- a/packages/server/src/api/routes/datasource.js +++ b/packages/server/src/api/routes/datasource.js @@ -12,7 +12,7 @@ const router = Router() router .get("/api/datasources", authorized(BUILDER), datasourceController.fetch) .get( - "/api/datasources/:id", + "/api/datasources/:datasourceId", authorized(PermissionTypes.TABLE, PermissionLevels.READ), datasourceController.find ) diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.js index 7602b027e..ee1a1c47f 100644 --- a/packages/server/src/api/routes/tests/datasource.spec.js +++ b/packages/server/src/api/routes/tests/datasource.spec.js @@ -1,15 +1,17 @@ -let { basicDatasource } = require("./utilities/structures") -let { checkBuilderEndpoint } = require("./utilities/TestFunctions") +let {basicDatasource} = require("./utilities/structures") +let {checkBuilderEndpoint} = require("./utilities/TestFunctions") let setup = require("./utilities") describe("/datasources", () => { let request = setup.getRequest() let config = setup.getConfig() + let datasource afterAll(setup.afterAll) beforeEach(async () => { await config.init() + datasource = await config.createDatasource() }) describe("create", () => { @@ -21,22 +23,12 @@ describe("/datasources", () => { .expect('Content-Type', /json/) .expect(200) - expect(res.res.statusMessage).toEqual("Datasource saved successfully."); - expect(res.body.name).toEqual("Test"); - }) - }); + expect(res.res.statusMessage).toEqual("Datasource saved successfully.") + expect(res.body.name).toEqual("Test") + }) + }) describe("fetch", () => { - let datasource - - beforeEach(async () => { - datasource = await config.createDatasource() - }); - - afterEach(() => { - delete datasource._rev - }); - it("returns all the datasources from the server", async () => { const res = await request .get(`/api/datasources`) @@ -44,36 +36,37 @@ describe("/datasources", () => { .expect('Content-Type', /json/) .expect(200) - const datasources = res.body - expect(datasources).toEqual([ - { - "_id": datasources[0]._id, - "_rev": datasources[0]._rev, - ...basicDatasource() - } - ]); + const datasources = res.body + expect(datasources).toEqual([ + { + "_id": datasources[0]._id, + "_rev": datasources[0]._rev, + ...basicDatasource() + } + ]) }) it("should apply authorization to endpoint", async () => { - await checkBuilderEndpoint({ - config, - method: "GET", - url: `/api/datasources`, - }) + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/datasources`, }) - }); - - describe("destroy", () => { - let datasource - - beforeEach(async () => { - datasource = await config.createDatasource() - }); + }) + }) - afterEach(() => { - delete datasource._rev - }); + describe("find", () => { + it("should be able to find a datasource", async () => { + const res = await request + .get(`/api/datasources/${datasource._id}`) + .set(config.defaultHeaders()) + .expect(200) + expect(res.body._rev).toBeDefined() + expect(res.body._id).toEqual(datasource._id) + }) + }) + describe("destroy", () => { it("deletes queries for the datasource after deletion and returns a success message", async () => { await config.createQuery() @@ -87,8 +80,8 @@ describe("/datasources", () => { .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - - expect(res.body).toEqual([]) + + expect(res.body).toEqual([]) }) it("should apply authorization to endpoint", async () => { @@ -99,5 +92,5 @@ describe("/datasources", () => { }) }) - }); -}); + }) +}) From 08d15f9d0336c9a0ccccb649b37ed00f000b38d6 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Mar 2021 12:20:07 +0000 Subject: [PATCH 23/28] Adding auth tests. --- packages/server/src/api/controllers/auth.js | 2 + .../server/src/api/routes/tests/auth.spec.js | 106 ++++++++++++++++++ .../tests/utilities/TestConfiguration.js | 20 +++- 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/api/routes/tests/auth.spec.js diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index 1cc6db318..e5c0f9a02 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -46,6 +46,7 @@ exports.authenticate = async ctx => { version: app.version, } // if in cloud add the user api key, unless self hosted + /* istanbul ignore next */ if (env.CLOUD && !env.SELF_HOSTED) { const { apiKey } = await getAPIKey(ctx.user.appId) payload.apiKey = apiKey @@ -70,6 +71,7 @@ exports.authenticate = async ctx => { exports.fetchSelf = async ctx => { const { userId, appId } = ctx.user + /* istanbul ignore next */ if (!userId || !appId) { ctx.body = {} return diff --git a/packages/server/src/api/routes/tests/auth.spec.js b/packages/server/src/api/routes/tests/auth.spec.js new file mode 100644 index 000000000..0eb0b6d85 --- /dev/null +++ b/packages/server/src/api/routes/tests/auth.spec.js @@ -0,0 +1,106 @@ +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") + +describe("/authenticate", () => { + let request = setup.getRequest() + let config = setup.getConfig() + + afterAll(setup.afterAll) + + beforeEach(async () => { + await config.init() + }) + + describe("authenticate", () => { + it("should be able to create a layout", async () => { + await config.createUser("test@test.com", "p4ssw0rd") + const res = await request + .post(`/api/authenticate`) + .send({ + email: "test@test.com", + password: "p4ssw0rd", + }) + .set(config.publicHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.token).toBeDefined() + expect(res.body.email).toEqual("test@test.com") + expect(res.body.password).toBeUndefined() + }) + + it("should error if no app specified", async () => { + await request + .post(`/api/authenticate`) + .expect(400) + }) + + it("should error if no email specified", async () => { + await request + .post(`/api/authenticate`) + .send({ + password: "test", + }) + .set(config.publicHeaders()) + .expect(400) + }) + + it("should error if no password specified", async () => { + await request + .post(`/api/authenticate`) + .send({ + email: "test", + }) + .set(config.publicHeaders()) + .expect(400) + }) + + it("should error if invalid user specified", async () => { + await request + .post(`/api/authenticate`) + .send({ + email: "test", + password: "test", + }) + .set(config.publicHeaders()) + .expect(401) + }) + + it("should throw same error if wrong password specified", async () => { + await config.createUser("test@test.com", "password") + await request + .post(`/api/authenticate`) + .send({ + email: "test@test.com", + password: "test", + }) + .set(config.publicHeaders()) + .expect(401) + }) + + it("should throw an error for inactive users", async () => { + await config.createUser("test@test.com", "password") + await config.makeUserInactive("test@test.com") + await request + .post(`/api/authenticate`) + .send({ + email: "test@test.com", + password: "password", + }) + .set(config.publicHeaders()) + .expect(401) + }) + }) + + describe("fetch self", () => { + it("should be able to delete the layout", async () => { + await config.createUser("test@test.com", "p4ssw0rd") + const headers = await config.login("test@test.com", "p4ssw0rd") + const res = await request + .get(`/api/self`) + .set(headers) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.email).toEqual("test@test.com") + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js index b72f4f4e5..31989894e 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -241,7 +241,7 @@ class TestConfiguration { async createUser( email = EMAIL, password = PASSWORD, - roleId = BUILTIN_ROLE_IDS.POWER + roleId = BUILTIN_ROLE_IDS.POWER, ) { return this._req( { @@ -254,6 +254,24 @@ class TestConfiguration { ) } + async makeUserInactive(email) { + const user = await this._req( + null, + { + email, + }, + controllers.user.find + ) + return this._req( + { + ...user, + status: "inactive", + }, + null, + controllers.user.update + ) + } + async login(email, password) { if (!email || !password) { await this.createUser() From e78f73a76d53bd048169134cb2eb0d88a96dd043 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Mar 2021 12:56:30 +0000 Subject: [PATCH 24/28] Updating application tests to run a lot deeper, which required updating some other tests to account for creation of empty screens and layouts. --- .../server/src/api/controllers/application.js | 6 ++- .../src/api/routes/tests/application.spec.js | 39 +++++++++++++++++++ .../src/api/routes/tests/automation.spec.js | 2 +- .../src/api/routes/tests/routing.spec.js | 29 +++++++------- .../src/api/routes/tests/screen.spec.js | 4 +- .../tests/utilities/TestConfiguration.js | 2 +- 6 files changed, 61 insertions(+), 21 deletions(-) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index e899a2dca..cba3e6455 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -104,9 +104,10 @@ async function createInstance(template) { await createRoutingView(appId) // replicate the template data to the instance DB + // this is currently very hard to test, downloading and importing template files + /* istanbul ignore next */ if (template) { let dbDumpReadStream - if (template.fileImportPath) { dbDumpReadStream = fs.createReadStream(template.fileImportPath) } else { @@ -181,8 +182,9 @@ exports.create = async function(ctx) { const instanceDb = new CouchDB(appId) await instanceDb.put(newApplication) + const newAppFolder = await createEmptyAppPackage(ctx, newApplication) + /* istanbul ignore next */ if (env.NODE_ENV !== "jest") { - const newAppFolder = await createEmptyAppPackage(ctx, newApplication) await downloadExtractComponentLibraries(newAppFolder) } diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js index c146cc63f..12fcf6f8e 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.js @@ -58,4 +58,43 @@ describe("/applications", () => { }) }) + describe("fetchAppDefinition", () => { + it("should be able to get an apps definition", async () => { + const res = await request + .get(`/api/applications/${config.getAppId()}/definition`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + // should have empty packages + expect(res.body.screens.length).toEqual(2) + expect(res.body.layouts.length).toEqual(2) + }) + }) + + describe("fetchAppPackage", () => { + it("should be able to fetch the app package", async () => { + const res = await request + .get(`/api/applications/${config.getAppId()}/appPackage`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.application).toBeDefined() + expect(res.body.screens.length).toEqual(2) + expect(res.body.layouts.length).toEqual(2) + }) + }) + + describe("update", () => { + it("should be able to fetch the app package", async () => { + const res = await request + .put(`/api/applications/${config.getAppId()}`) + .send({ + name: "TEST_APP" + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.rev).toBeDefined() + }) + }) }) diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 588de641b..9d1121950 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -73,7 +73,7 @@ describe("/automations", () => { .expect('Content-Type', /json/) .expect(200) - expect(Object.keys(res.body.action).length).toEqual(Object.keys(ACTION_DEFINITIONS).length) + expect(Object.keys(res.body.action).length).toBeGreaterThanOrEqual(Object.keys(ACTION_DEFINITIONS).length) expect(Object.keys(res.body.trigger).length).toEqual(Object.keys(TRIGGER_DEFINITIONS).length) expect(Object.keys(res.body.logic).length).toEqual(Object.keys(LOGIC_DEFINITIONS).length) }) diff --git a/packages/server/src/api/routes/tests/routing.spec.js b/packages/server/src/api/routes/tests/routing.spec.js index 3b7523f58..70d1632bf 100644 --- a/packages/server/src/api/routes/tests/routing.spec.js +++ b/packages/server/src/api/routes/tests/routing.spec.js @@ -3,6 +3,8 @@ const { basicScreen } = require("./utilities/structures") const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") +const route = "/test" + describe("/routing", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -12,9 +14,12 @@ describe("/routing", () => { beforeEach(async () => { await config.init() - screen = await config.createScreen(basicScreen()) + screen = basicScreen() + screen.routing.route = route + screen = await config.createScreen(screen) screen2 = basicScreen() screen2.routing.roleId = BUILTIN_ROLE_IDS.POWER + screen2.routing.route = route screen2 = await config.createScreen(screen2) }) @@ -26,9 +31,9 @@ describe("/routing", () => { .expect("Content-Type", /json/) .expect(200) expect(res.body.routes).toBeDefined() - expect(res.body.routes["/"]).toEqual({ + expect(res.body.routes[route]).toEqual({ subpaths: { - ["/"]: { + [route]: { screenId: screen._id, roleId: screen.routing.roleId } @@ -43,9 +48,9 @@ describe("/routing", () => { .expect("Content-Type", /json/) .expect(200) expect(res.body.routes).toBeDefined() - expect(res.body.routes["/"]).toEqual({ + expect(res.body.routes[route]).toEqual({ subpaths: { - ["/"]: { + [route]: { screenId: screen2._id, roleId: screen2.routing.roleId } @@ -62,16 +67,10 @@ describe("/routing", () => { .expect("Content-Type", /json/) .expect(200) expect(res.body.routes).toBeDefined() - expect(res.body.routes["/"]).toEqual({ - subpaths: { - ["/"]: { - screens: { - [screen2.routing.roleId]: screen2._id, - [screen.routing.roleId]: screen._id, - } - } - } - }) + expect(res.body.routes[route].subpaths[route]).toBeDefined() + const subpath = res.body.routes[route].subpaths[route] + expect(subpath.screens[screen2.routing.roleId]).toEqual(screen2._id) + expect(subpath.screens[screen.routing.roleId]).toEqual(screen._id) }) it("make sure it is a builder only endpoint", async () => { diff --git a/packages/server/src/api/routes/tests/screen.spec.js b/packages/server/src/api/routes/tests/screen.spec.js index 0c2799eca..ae30afd29 100644 --- a/packages/server/src/api/routes/tests/screen.spec.js +++ b/packages/server/src/api/routes/tests/screen.spec.js @@ -21,8 +21,8 @@ describe("/screens", () => { .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) - expect(res.body.length).toEqual(1) - expect(res.body[0]._id).toEqual(screen._id) + expect(res.body.length).toEqual(3) + expect(res.body.some(s => s._id === screen._id)).toEqual(true) }) it("should apply authorization to endpoint", async () => { diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js index 31989894e..5e50bd033 100644 --- a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -241,7 +241,7 @@ class TestConfiguration { async createUser( email = EMAIL, password = PASSWORD, - roleId = BUILTIN_ROLE_IDS.POWER, + roleId = BUILTIN_ROLE_IDS.POWER ) { return this._req( { From d9217b1148782ee85e918bb164515ae52443c836 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Mar 2021 17:55:42 +0000 Subject: [PATCH 25/28] Updating row tests, reducing console logging during tests for speed and clarity, testing some misc endpoints and updating search functionality to use a starts with operator when working with strings on rows. --- packages/server/package.json | 2 +- .../server/src/api/controllers/apikeys.js | 13 +- packages/server/src/api/controllers/row.js | 25 ++- packages/server/src/api/index.js | 6 +- packages/server/src/api/routes/static.js | 1 + .../src/api/routes/tests/apikeys.spec.js | 2 +- .../server/src/api/routes/tests/cloud.spec.js | 16 ++ .../server/src/api/routes/tests/row.spec.js | 193 +++++++++++++++--- packages/server/src/app.js | 6 +- .../middleware/tests/authenticated.spec.js | 1 - 10 files changed, 204 insertions(+), 61 deletions(-) create mode 100644 packages/server/src/api/routes/tests/cloud.spec.js diff --git a/packages/server/package.json b/packages/server/package.json index 3fe0d68bf..99763109f 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 routes --runInBand --coverage", + "test:integration": "jest --runInBand --coverage", "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/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 1cd69c54d..96754f17c 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -3,20 +3,13 @@ const { join } = require("../../utilities/centralPath") const readline = require("readline") const { budibaseAppsDir } = require("../../utilities/budibaseDir") const env = require("../../environment") -const selfhost = require("../../selfhost") const ENV_FILE_PATH = "/.env" exports.fetch = async function(ctx) { ctx.status = 200 - if (env.SELF_HOSTED) { - ctx.body = { - selfhost: await selfhost.getSelfHostAPIKey(), - } - } else { - ctx.body = { - budibase: env.BUDIBASE_API_KEY, - userId: env.USERID_API_KEY, - } + ctx.body = { + budibase: env.BUDIBASE_API_KEY, + userId: env.USERID_API_KEY, } } diff --git a/packages/server/src/api/controllers/row.js b/packages/server/src/api/controllers/row.js index 5d783b7d1..bf985fe55 100644 --- a/packages/server/src/api/controllers/row.js +++ b/packages/server/src/api/controllers/row.js @@ -224,6 +224,7 @@ exports.fetchView = async function(ctx) { try { table = await db.get(viewInfo.meta.tableId) } catch (err) { + /* istanbul ignore next */ table = { schema: {}, } @@ -255,16 +256,24 @@ exports.fetchView = async function(ctx) { exports.search = async function(ctx) { const appId = ctx.user.appId - const db = new CouchDB(appId) - const { query, pagination: { pageSize = 10, page }, } = ctx.request.body - query.tableId = ctx.params.tableId + // make all strings a starts with operation rather than pure equality + for (const [key, queryVal] of Object.entries(query)) { + if (typeof queryVal === "string") { + query[key] = { + $gt: queryVal, + $lt: `${queryVal}\uffff`, + } + } + } + // pure equality for table + query.tableId = ctx.params.tableId const response = await db.find({ selector: query, limit: pageSize, @@ -324,7 +333,6 @@ exports.destroy = async function(ctx) { const row = await db.get(ctx.params.rowId) if (row.tableId !== ctx.params.tableId) { ctx.throw(400, "Supplied tableId doesn't match the row's tableId") - return } await linkRows.updateLinks({ appId, @@ -376,15 +384,6 @@ exports.fetchEnrichedRow = async function(ctx) { const db = new CouchDB(appId) const tableId = ctx.params.tableId const rowId = ctx.params.rowId - if (appId == null || tableId == null || rowId == null) { - ctx.status = 400 - ctx.body = { - status: 400, - error: - "Cannot handle request, URI params have not been successfully prepared.", - } - return - } // need table to work out where links go in row let [table, row] = await Promise.all([ db.get(tableId), diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 7b063cb52..7628fa207 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -41,13 +41,15 @@ router.use(async (ctx, next) => { try { await next() } catch (err) { - ctx.log.error(err) ctx.status = err.status || err.statusCode || 500 ctx.body = { message: err.message, status: ctx.status, } - console.trace(err) + if (env.NODE_ENV !== "jest") { + ctx.log.error(err) + console.trace(err) + } } }) diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 2b0c9b36f..c812c4d3b 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -8,6 +8,7 @@ const usage = require("../../middleware/usageQuota") const router = Router() +/* istanbul ignore next */ router.param("file", async (file, ctx, next) => { ctx.file = file && file.includes(".") ? file : "index.html" diff --git a/packages/server/src/api/routes/tests/apikeys.spec.js b/packages/server/src/api/routes/tests/apikeys.spec.js index a8077a449..dbee57c8b 100644 --- a/packages/server/src/api/routes/tests/apikeys.spec.js +++ b/packages/server/src/api/routes/tests/apikeys.spec.js @@ -35,7 +35,7 @@ describe("/api/keys", () => { describe("update", () => { it("should allow updating a value", async () => { - fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "") + fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "TEST_API_KEY=thing") const res = await request .put(`/api/keys/TEST`) .send({ diff --git a/packages/server/src/api/routes/tests/cloud.spec.js b/packages/server/src/api/routes/tests/cloud.spec.js new file mode 100644 index 000000000..3cb65ed81 --- /dev/null +++ b/packages/server/src/api/routes/tests/cloud.spec.js @@ -0,0 +1,16 @@ +const setup = require("./utilities") + +describe("test things in the Cloud/Self hosted", () => { + describe("test self hosted static page", () => { + it("should be able to load the static page", async () => { + await setup.switchToCloudForFunction(async () => { + let request = setup.getRequest() + let config = setup.getConfig() + await config.init() + const res = await request.get(`/`).expect(200) + expect(res.text.includes("Budibase self hosting️")).toEqual(true) + setup.afterAll() + }) + }) + }) +}) diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index cc50520b7..1442e4eb7 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -17,15 +17,15 @@ describe("/rows", () => { row = basicRow(table._id) }) - const loadRow = async id => + const loadRow = async (id, status = 200) => await request .get(`/api/${table._id}/rows/${id}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) - .expect(200) + .expect(status) - describe("save, load, update, delete", () => { + describe("save, load, update", () => { it("returns a success message when the row is created", async () => { const res = await request .post(`/api/${row.tableId}/rows`) @@ -217,38 +217,152 @@ describe("/rows", () => { expect(savedRow.body.description).toEqual(existing.description) expect(savedRow.body.name).toEqual("Updated Name") - + }) + + it("should throw an error when given improper types", async () => { + const existing = await config.createRow() + await request + .patch(`/api/${table._id}/rows/${existing._id}`) + .send({ + _id: existing._id, + _rev: existing._rev, + tableId: table._id, + name: 1, + }) + .set(config.defaultHeaders()) + .expect(400) + }) + }) + + describe("destroy", () => { + it("should be able to delete a row", async () => { + const createdRow = await config.createRow(row) + const res = await request + .delete(`/api/${table._id}/rows/${createdRow._id}/${createdRow._rev}`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.ok).toEqual(true) + }) + + it("shouldn't allow deleting a row in a table which is different to the one the row was created on", async () => { + const createdRow = await config.createRow(row) + await request + .delete(`/api/wrong_table/rows/${createdRow._id}/${createdRow._rev}`) + .set(config.defaultHeaders()) + .expect(400) }) }) describe("validate", () => { it("should return no errors on valid row", async () => { - const result = await request + const res = await request .post(`/api/${table._id}/rows/validate`) .send({ name: "ivan" }) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - expect(result.body.valid).toBe(true) - expect(Object.keys(result.body.errors)).toEqual([]) + expect(res.body.valid).toBe(true) + expect(Object.keys(res.body.errors)).toEqual([]) }) it("should errors on invalid row", async () => { - const result = await request + const res = await request .post(`/api/${table._id}/rows/validate`) .send({ name: 1 }) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - expect(result.body.valid).toBe(false) - expect(Object.keys(result.body.errors)).toEqual(["name"]) + expect(res.body.valid).toBe(false) + expect(Object.keys(res.body.errors)).toEqual(["name"]) }) }) - describe("enrich row unit test", () => { + describe("bulkDelete", () => { + it("should be able to delete a bulk set of rows", async () => { + const row1 = await config.createRow() + const row2 = await config.createRow() + const res = await request + .post(`/api/${table._id}/rows`) + .send({ + type: "delete", + rows: [ + row1, + row2, + ] + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.length).toEqual(2) + await loadRow(row1._id, 404) + }) + }) + + describe("search", () => { + it("should run a search on the table", async () => { + const row = await config.createRow() + // add another row that shouldn't be found + await config.createRow({ + ...basicRow(), + name: "Other Contact", + }) + const res = await request + .post(`/api/${table._id}/rows/search`) + .send({ + query: { + name: "Test", + }, + pagination: { pageSize: 25, page: 0 } + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.length).toEqual(1) + expect(res.body[0]._id).toEqual(row._id) + }) + }) + + describe("fetchView", () => { + it("should be able to fetch tables contents via 'view'", async () => { + const row = await config.createRow() + const res = await request + .get(`/api/views/all_${table._id}`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.length).toEqual(1) + expect(res.body[0]._id).toEqual(row._id) + }) + + it("should throw an error if view doesn't exist", async () => { + await request + .get(`/api/views/derp`) + .set(config.defaultHeaders()) + .expect(400) + }) + + it("should be able to run on a view", async () => { + const view = await config.createView() + const row = await config.createRow() + const res = await request + .get(`/api/views/${view._id}`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.length).toEqual(1) + expect(res.body[0]._id).toEqual(row._id) + }) + }) + + describe("user testing", () => { + + }) + + describe("fetchEnrichedRows", () => { it("should allow enriching some linked rows", async () => { const table = await config.createLinkedTable() const firstRow = await config.createRow({ @@ -262,30 +376,45 @@ describe("/rows", () => { link: [{_id: firstRow._id}], tableId: table._id, }) - const enriched = await outputProcessing(config.getAppId(), table, [secondRow]) - expect(enriched[0].link.length).toBe(1) - expect(enriched[0].link[0]._id).toBe(firstRow._id) - expect(enriched[0].link[0].primaryDisplay).toBe("Test Contact") + + // test basic enrichment + const resBasic = await request + .get(`/api/${table._id}/rows/${secondRow._id}`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(resBasic.body.link[0]._id).toBe(firstRow._id) + expect(resBasic.body.link[0].primaryDisplay).toBe("Test Contact") + + // test full enrichment + const resEnriched = await request + .get(`/api/${table._id}/${secondRow._id}/enrich`) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(resEnriched.body.link.length).toBe(1) + expect(resEnriched.body.link[0]._id).toBe(firstRow._id) + expect(resEnriched.body.link[0].name).toBe("Test Contact") + expect(resEnriched.body.link[0].description).toBe("original description") }) }) - it("should allow enriching attachment rows", async () => { - const table = await config.createAttachmentTable() - const row = await config.createRow({ - name: "test", - description: "test", - attachment: [{ - url: "/test/thing", - }], - tableId: table._id, + describe("attachments", () => { + it("should allow enriching attachment rows", async () => { + const table = await config.createAttachmentTable() + const row = await config.createRow({ + name: "test", + description: "test", + attachment: [{ + url: "/test/thing", + }], + tableId: table._id, + }) + // the environment needs configured for this + await setup.switchToCloudForFunction(async () => { + const enriched = await outputProcessing(config.getAppId(), table, [row]) + expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`) + }) }) - // the environment needs configured for this - env.CLOUD = 1 - env.SELF_HOSTED = 1 - const enriched = await outputProcessing(config.getAppId(), table, [row]) - expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`) - // remove env config - env.CLOUD = undefined - env.SELF_HOSTED = undefined }) }) \ No newline at end of file diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 3779890c9..15e996cfe 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -56,7 +56,11 @@ if (electron.app && electron.app.isPackaged) { const server = http.createServer(app.callback()) destroyable(server) -server.on("close", () => console.log("Server Closed")) +server.on("close", () => { + if (env.NODE_ENV !== "jest") { + console.log("Server Closed") + } +}) module.exports = server.listen(env.PORT || 0, async () => { console.log(`Budibase running on ${JSON.stringify(server.address())}`) diff --git a/packages/server/src/middleware/tests/authenticated.spec.js b/packages/server/src/middleware/tests/authenticated.spec.js index bb124d2f4..fe7e59252 100644 --- a/packages/server/src/middleware/tests/authenticated.spec.js +++ b/packages/server/src/middleware/tests/authenticated.spec.js @@ -9,7 +9,6 @@ class TestConfiguration { this.ctx = { config: {}, auth: {}, - request: {}, cookies: { set: jest.fn(), get: jest.fn() From 3947044014e9d87d455c8a3c18d27adabcadab3e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 10 Mar 2021 17:56:16 +0000 Subject: [PATCH 26/28] Formatting. --- .../SetupPanel/AutomationBindingPanel.svelte | 358 +++++++++--------- .../SetupPanel/AutomationBlockSetup.svelte | 6 +- .../automation/SetupPanel/RowSelector.svelte | 4 +- .../design/PropertiesPanel/DesignView.svelte | 28 +- .../EventsEditor/EventEditor.svelte | 30 +- .../PropertiesPanel/SettingsView.svelte | 2 +- .../settings/ThemeEditorDropdown.svelte | 2 +- .../client/src/components/Component.svelte | 2 +- .../standard-components/src/Container.svelte | 52 ++- .../src/grid/Component.svelte | 4 +- 10 files changed, 264 insertions(+), 224 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBindingPanel.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBindingPanel.svelte index a05d5b999..bda233f9f 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBindingPanel.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBindingPanel.svelte @@ -1,190 +1,186 @@
-
- Available bindings - - - - {#each categories as [categoryName, bindings]} - {categoryName} - - {#each bindableProperties.filter(binding => - binding.label.match(searchRgx) - ) as binding} -
addToText(binding)}> - {binding.label} - {binding.type} -
-
- {binding.description || ''} -
-
- {/each} - {/each} - Helpers - - {#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper} -
addToText(helper)}> - {helper.label} -
-
- {@html helper.description || ''} -
-
{helper.example || ''}
-
- {/each} -
-
-