From a9ab12b9999358b50ca32a8faab2e47148a1cfd5 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 367a66a926..e957a5c59c 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 6d0bb15083..1288380565 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 24ea8808c1..a957e8415d 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 8eb481c1fe..8166dec3da 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 2e8c742ccd..0b2849fb16 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 1e6cf6b595..f4b8fad2c2 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 b703bbd300..1afe39e4dd 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 baf4890d94..4bdfbe3c0d 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 7f384159672117a2ec237790c201cfef8e24b4ba 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 e957a5c59c..3425a979b8 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 1288380565..e19514d2ac 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 8166dec3da..49ece2ed43 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 0b2849fb16..3ecaa7690b 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 f4b8fad2c2..8bce8b2f0b 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 1afe39e4dd..ec7858da74 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 4bdfbe3c0d..9f12a0ac1a 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 1a9290b3959239a10c90fe4e0d8f714d3d5f456d 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 0000000000..0f721bc890 --- /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 9609c59368944abc28feab7fe8bf875d905f7278 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 598bcdaad2..ad2371f3ea 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 701a479b2ac26281bdf9a0df48aa44329a80ae93 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 0000000000..3b7523f586 --- /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 5f6b1cc267..a124ca607e 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 541495bec8..e16aa6964c 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 922228aadf..aec482bb24 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 1b1ed8a82324d9a90300199fc8df9458814adb3f 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 | 68 ++--- 3 files changed, 281 insertions(+), 34 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 0000000000..8092130ce3 --- /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 0000000000..e69de29bb2 diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 0f721bc890..9d66f44463 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) - - beforeEach(async () => { - await config.init() + afterEach(() => { + jest.clearAllMocks() }) - 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) + it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => { + env.CLOUD = 1 + env.SELF_HOSTED = 1 - 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() - }) + await selfHostMiddleware({}, next) + expect(next).toHaveBeenCalled() + }) - 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("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() + }) + + 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 0652133a30e23a35ee5f91dae6c9839448456867 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 7a343b1b07..5b76f86190 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 a124ca607e..34b8c4fb10 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 e16aa6964c..d6524bb7f0 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 aec482bb24..500ff72044 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 0000000000..2bf5445a09 --- /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 7eac602f78..1f8b687ba8 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 c5cb40c1cf032c85bfe5ff7ca727401c84cd5691 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 | 57 ++++++++++++------- .../tests/utilities/TestConfiguration.js | 2 +- .../src/api/routes/tests/utilities/index.js | 19 +++++++ 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index 765baa4426..5867c863dc 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("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) + }) + + 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", () => { - let datasource - - beforeEach(async () => { - datasource = await config.createDatasource() - }) - - afterEach(() => { - delete datasource._rev - }) - 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 34b8c4fb10..f92321ddfb 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 7e9260ce18..3ad415386c 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 2bf227ab583779bc692bfcd735a3bd545b7d2a82 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 0000000000..2a99e9e555 --- /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 05efe0506109c4c01f60723ae5f8c8670f55a6c7 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 | 265 ++---------------- .../{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, 227 insertions(+), 245 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 7eac602f78..6d646d46fd 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 8092130ce3..11d925e8f0 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") +let env = require("../../environment") -// const EMAIL = "babs@babs.com" -// const PASSWORD = "babs_password" +class TestConfiguration { + constructor(middleware) { + // env = config.env || {} + this.middleware = middleware + this.next = jest.fn() + this.throwMock = jest.fn() + } -// 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 -// } + callMiddleware(ctx, next) { + return this.middleware(ctx, next) + } -// getRequest() { -// return this.request -// } + clear() { + jest.clearAllMocks() + } -// getAppId() { -// return this.appId -// } + setEnv(config) { + env = config + } -// 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() { + // return this.createApp(appName) + } -// async init(appName = "test_application") { -// return this.createApp(appName) -// } + end() { + // this.server.close() + } +} -// 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 +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 0000000000..d3e5e52d2d --- /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 0000000000..e69de29bb2 diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 9d66f44463..3601df89a2 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 c4b3a7c88430bbceaac5721774c3796b29c9df6c 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 11d925e8f0..0000000000 --- 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 3601df89a2..061da17f9c 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 6231c25ed556d1b529bb4036a1a03d77adb00609 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 | 87 ++++++++++++++++--- .../tests/utilities/TestConfiguration.js | 6 ++ .../api/routes/tests/utilities/controllers.js | 1 + .../api/routes/tests/utilities/structures.js | 6 ++ 8 files changed, 167 insertions(+), 13 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 0000000000..2bda8afad0 --- /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 de4c647c9f..f270e95bec 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 55c2ad14b0..7012219c39 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 0000000000..4be1c9e18e --- /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 5867c863dc..aa0e5428c5 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 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const { basicQuery } = require("./utilities/structures") -const setup = require("./utilities") +// mock out postgres for this +jest.mock("pg") +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +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 f92321ddfb..b72f4f4e5f 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 d6524bb7f0..a4eb9ac9de 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 500ff72044..ff3a239211 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 c755df9f913170b5b36f61bc5acdb69cfa4713bb 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 4351901dad..4216131119 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 e69de29bb2..35e6e5af50 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 5dcc4f23c6b6f6adc4ff59de6a4dd62cbbf7498e 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 0000000000..c76acb47d2 --- /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 e980afe678..1b809868be 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 33fa31aae9b68449f47fb62020e18e91844db014 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 0000000000..1113791ec2 --- /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 a23c6f6e89..3fe0d68bf3 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 94243d3c75..c3cfa28706 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 44429dacbf..111968a497 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 2a99e9e555..a8077a4492 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 0000000000..926efc51e3 --- /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 0000000000..2da5b11778 --- /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 4be1c9e18e..6b21554d71 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 93e6e29131..b24fac57c0 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 0000000000..f0d26bc7db --- /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 7383e3115d..c3d89477df 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 e1d6e03634..591205393a 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: - "@budibase/string-templates" "^0.8.3" + 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.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 d9151cca0ad4122642f87ee4feccc14537fc1ad5 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 0000000000..d603990294 --- /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 0000000000..528d0d3417 --- /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 4d39cf5405280e039874e2f98cbfe4c75d7ac5b2 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 e69de29bb2..799fbaf41b 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 929db83e99018c7bd14246fbe58ea708eeb51047 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 c100f43d88..3dd28284be 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 6ec607a093..5e7ec9e9d4 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", () => { + async function create(user, status = 200) { + return request + .post(`/api/users`) + .set(config.defaultHeaders()) + .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 request - .post(`/api/users`) - .set(config.defaultHeaders()) - .send(body) - .expect(200) - .expect("Content-Type", /json/) + 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 1ad1d2363e..cdaab0cc5b 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 ca5ca7add91ed58836a8ce46497634ef785a7392 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 659baa8f6c..32ed3f63d0 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 0000000000..1583ecb51f --- /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 799fbaf41b..bb124d2f4a 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 4b5a1d61775f50d0e7d40f430ff2b1a6c20bcf2d 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 55c2ad14b0..951fedd3d0 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 2c83712289..37e552a7b8 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 dd16c84ecd6feb13b5c3489abf5eea1a218d9456 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 df91c16e00..e6a647a08b 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 566b4970e9..76e5eb11f1 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 5fa618654b..df17371f92 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 f56dae153e..8f9baa8172 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 0648bfefa5..588de641b7 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 0000000000..3d3b6047e2 --- /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 0000000000..0c2799eca2 --- /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 79fd720078..b0cd843591 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 28329d7f6b1f7b532665685af78e51827ac80c7e 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 15cea72da7..d73a316adb 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 5746ce1839..ee2210704f 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 7602b027e1..ee1a1c47f5 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("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", () => { - let datasource - - beforeEach(async () => { - datasource = await config.createDatasource() - }); - - afterEach(() => { - delete datasource._rev - }); - 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 a48f1c72f24f51f8220a5cc9f50d9944498fe805 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 1cc6db3185..e5c0f9a029 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 0000000000..0eb0b6d851 --- /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 b72f4f4e5f..31989894ee 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 a6bde49ad3a7cda31c0bbec8e125b76ebca99365 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 e899a2dca9..cba3e6455a 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 c146cc63fb..12fcf6f8ea 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 588de641b7..9d11219506 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 3b7523f586..70d1632bf3 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 0c2799eca2..ae30afd29c 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 31989894ee..5e50bd033c 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 163d24a7671f777e7303ed308a18867709932f28 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 3fe0d68bf3..99763109f3 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 1cd69c54df..96754f17cc 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 5d783b7d18..bf985fe55d 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 7b063cb522..7628fa2077 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 2b0c9b36fc..c812c4d3b1 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 a8077a4492..dbee57c8b0 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 0000000000..3cb65ed819 --- /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 cc50520b77..1442e4eb75 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 3779890c9d..15e996cfe6 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 bb124d2f4a..fe7e592528 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 68735f1b4f21a1971c6c1c7068cdba9d00deb7d1 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 | 356 +++++++++--------- .../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, 263 insertions(+), 223 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBindingPanel.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBindingPanel.svelte index a05d5b9996..bda233f9f2 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} -
-
-