diff --git a/packages/server/package.json b/packages/server/package.json index 2e8c742ccd..fe80923f00 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -52,7 +52,8 @@ "collectCoverageFrom": [ "src/**/*.js", "!**/node_modules/**", - "!src/db/views/*.js" + "!src/db/views/*.js", + "!src/api/routes/tests" ], "coverageReporters": [ "lcov", diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js index 6068d783cb..c5e9daabbd 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.js @@ -1,21 +1,23 @@ const { - createApplication, - builderEndpointShouldBlockNormalUsers, supertest, - clearApplications, defaultHeaders, -} = require("./couchTestUtils") +} = require("./utilities") +const TestConfig = require("./utilities/TestConfiguration") +const { clearAllApps, checkBuilderEndpoint } = require("./utilities/TestFunctions") describe("/applications", () => { let request let server + let config beforeAll(async () => { ({ request, server } = await supertest()) }); beforeEach(async () => { - await clearApplications(request) + await clearAllApps() + config = new TestConfig(request) + await config.init() }) afterAll(() => { @@ -35,23 +37,20 @@ describe("/applications", () => { }) it("should apply authorization to endpoint", async () => { - const otherApplication = await createApplication(request) - const appId = otherApplication.instance._id - await builderEndpointShouldBlockNormalUsers({ + await checkBuilderEndpoint({ + config, request, method: "POST", url: `/api/applications`, - appId: appId, body: { name: "My App" } }) }) - }) describe("fetch", () => { it("lists all applications", async () => { - await createApplication(request, "app1") - await createApplication(request, "app2") + await config.createApp(request, "app1") + await config.createApp(request, "app2") const res = await request .get("/api/applications") @@ -59,17 +58,16 @@ describe("/applications", () => { .expect('Content-Type', /json/) .expect(200) - expect(res.body.length).toBe(2) + // two created apps + the inited app + expect(res.body.length).toBe(3) }) it("should apply authorization to endpoint", async () => { - const otherApplication = await createApplication(request) - const appId = otherApplication.instance._id - await builderEndpointShouldBlockNormalUsers({ + await checkBuilderEndpoint({ + config, request, method: "GET", url: `/api/applications`, - appId: appId, }) }) }) diff --git a/packages/server/src/api/routes/tests/utilities/TestConfiguration.js b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js new file mode 100644 index 0000000000..be9192b7f2 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -0,0 +1,159 @@ +const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") +const env = require("../../../../environment") +const { basicTable, basicRow, basicRole } = require("./structures") +const tableController = require("../../../controllers/table") +const rowController = require("../../../controllers/row") +const roleController = require("../../../controllers/role") +const permsController = require("../../../controllers/permission") +const viewController = require("../../../controllers/view") +const appController = require("../../../controllers/application") +const userController = require("../../../controllers/user") + +const EMAIL = "babs@babs.com" +const PASSWORD = "babs_password" + +class TestConfiguration { + constructor(request) { + // we need the request for logging in, involves cookies, hard to fake + this.request = request + this.appId = null + this.table = null + this.linkedTable = null + } + + 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) + } + + async createApp(appName) { + this.app = await this._req({ name: appName }, null, appController.create) + this.appId = this.app._id + return this.app + } + + async updateTable(config = null) { + config = config || basicTable() + this.table = await this._req(config, null, tableController.save) + return this.table + } + + async createTable(config = null) { + if (config != null && config._id) { + delete config._id + } + return this.updateTable(config) + } + + async createLinkedTables() { + const table = await this.createTable() + table.primaryDisplay = "name" + table.schema.link = { + type: "link", + fieldName: "link", + tableId: table._id, + } + const linkedTable = await this.createTable(table) + this.table = table + 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 }, rowController.save) + } + + async createRole(config = null) { + config = config || basicRole() + return this._req(config, null, roleController.save) + } + + async addPermission(roleId, resourceId, level = "read") { + return this._req( + null, + { + roleId, + resourceId, + level, + }, + permsController.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, viewController.save) + } + + async createUser( + email = EMAIL, + password = PASSWORD, + roleId = BUILTIN_ROLE_IDS.POWER + ) { + return this._req( + { + email, + password, + roleId, + }, + null, + userController.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/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js new file mode 100644 index 0000000000..dcb944b0a1 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -0,0 +1,75 @@ +const rowController = require("../../../controllers/row") +const appController = require("../../../controllers/application") +const CouchDB = require("../../../../db") + +function Request(appId, params) { + this.user = { appId } + this.params = params +} + +exports.getAllTableRows = async (appId, tableId) => { + const req = new Request(appId, { tableId }) + await rowController.fetchTableRows(req) + return req.body +} + +exports.clearAllApps = async () => { + const req = {} + await appController.fetch(req) + const apps = req.body + if (!apps || apps.length <= 0) { + return + } + for (let app of apps) { + const appId = app._id + await appController.delete(new Request(null, { appId })) + } +} + +exports.createRequest = (request, method, url, body) => { + let req + + if (method === "POST") req = request.post(url).send(body) + else if (method === "GET") req = request.get(url) + else if (method === "DELETE") req = request.delete(url) + else if (method === "PATCH") req = request.patch(url).send(body) + else if (method === "PUT") req = request.put(url).send(body) + + return req +} + +exports.checkBuilderEndpoint = async ({ + config, + request, + method, + url, + body, +}) => { + const headers = await config.login() + await exports + .createRequest(request, method, url, body) + .set(headers) + .expect(403) +} + +/** + * Raw DB insert utility. + */ +exports.insertDocument = async (databaseId, document) => { + const { id, ...documentFields } = document + return await new CouchDB(databaseId).put({ _id: id, ...documentFields }) +} + +/** + * Raw DB delete utility. + */ +exports.destroyDocument = async (databaseId, documentId) => { + return await new CouchDB(databaseId).destroy(documentId) +} + +/** + * Raw DB get utility. + */ +exports.getDocument = async (databaseId, documentId) => { + return await new CouchDB(databaseId).get(documentId) +} diff --git a/packages/server/src/api/routes/tests/utilities/index.js b/packages/server/src/api/routes/tests/utilities/index.js new file mode 100644 index 0000000000..9c58364aaa --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/index.js @@ -0,0 +1,47 @@ +const supertest = require("supertest") +const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") +const jwt = require("jsonwebtoken") +const env = require("../../../../environment") + +const TEST_CLIENT_ID = "test-client-id" + +exports.TEST_CLIENT_ID = TEST_CLIENT_ID +exports.supertest = async () => { + let request + let server + env.PORT = 4002 + server = require("../../../../app") + + request = supertest(server) + return { request, server } +} + +exports.defaultHeaders = appId => { + 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 (appId) { + headers["x-budibase-app-id"] = appId + } + + return headers +} + +exports.publicHeaders = appId => { + const headers = { + Accept: "application/json", + } + if (appId) { + headers["x-budibase-app-id"] = appId + } + + return headers +} diff --git a/packages/server/src/api/routes/tests/utilities/structures.js b/packages/server/src/api/routes/tests/utilities/structures.js new file mode 100644 index 0000000000..cf98a2eb79 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/structures.js @@ -0,0 +1,43 @@ +const { BUILTIN_ROLE_IDS } = require("../../../../utilities/security/roles") +const { + BUILTIN_PERMISSION_IDS, +} = require("../../../../utilities/security/permissions") + +exports.basicTable = () => { + return { + name: "TestTable", + type: "table", + key: "name", + schema: { + name: { + type: "string", + constraints: { + type: "string", + }, + }, + description: { + type: "string", + constraints: { + type: "string", + }, + }, + }, + } +} + +exports.basicRow = tableId => { + return { + name: "Test Contact", + description: "original description", + status: "new", + tableId: tableId, + } +} + +exports.basicRole = () => { + return { + name: "NewRole", + inherits: BUILTIN_ROLE_IDS.BASIC, + permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY, + } +}