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/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index 8fc3efc8a9..15cea72da7 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -8,14 +8,13 @@ const { exports.fetch = async function(ctx) { const database = new CouchDB(ctx.user.appId) - const datasources = ( + ctx.body = ( await database.allDocs( getDatasourceParams(null, { include_docs: true, }) ) ).rows.map(row => row.doc) - ctx.body = datasources } exports.save = async function(ctx) { diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js index 6068d783cb..c146cc63fb 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.js @@ -1,25 +1,15 @@ -const { - createApplication, - builderEndpointShouldBlockNormalUsers, - supertest, - clearApplications, - defaultHeaders, -} = require("./couchTestUtils") +const { clearAllApps, checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") describe("/applications", () => { - let request - let server + let request = setup.getRequest() + let config = setup.getConfig() - beforeAll(async () => { - ({ request, server } = await supertest()) - }); + afterAll(setup.afterAll) beforeEach(async () => { - await clearApplications(request) - }) - - afterAll(() => { - server.close() + await clearAllApps() + await config.init() }) describe("create", () => { @@ -27,7 +17,7 @@ describe("/applications", () => { const res = await request .post("/api/applications") .send({ name: "My App" }) - .set(defaultHeaders()) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) expect(res.res.statusMessage).toEqual("Application My App created successfully") @@ -35,41 +25,35 @@ describe("/applications", () => { }) it("should apply authorization to endpoint", async () => { - const otherApplication = await createApplication(request) - const appId = otherApplication.instance._id - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, 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") - .set(defaultHeaders()) + .set(config.defaultHeaders()) .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({ - request, + await checkBuilderEndpoint({ + config, method: "GET", url: `/api/applications`, - appId: appId, }) }) }) diff --git a/packages/server/src/api/routes/tests/automation.spec.js b/packages/server/src/api/routes/tests/automation.spec.js index 128d3530e9..0648bfefa5 100644 --- a/packages/server/src/api/routes/tests/automation.spec.js +++ b/packages/server/src/api/routes/tests/automation.spec.js @@ -1,74 +1,33 @@ const { - createApplication, - createTable, - getAllFromTable, - defaultHeaders, - supertest, - insertDocument, - destroyDocument, - builderEndpointShouldBlockNormalUsers -} = require("./couchTestUtils") -let { generateAutomationID } = require("../../../db/utils") - -const { delay } = require("./testUtils") + checkBuilderEndpoint, + getAllTableRows, + clearAllAutomations, +} = require("./utilities/TestFunctions") +const { basicAutomation } = require("./utilities/structures") +const setup = require("./utilities") const MAX_RETRIES = 4 -const AUTOMATION_ID = generateAutomationID() -const TEST_AUTOMATION = { - _id: AUTOMATION_ID, - name: "My Automation", - screenId: "kasdkfldsafkl", - live: true, - uiTree: { - - }, - definition: { - trigger: {}, - steps: [ - ], - }, - type: "automation", -} let ACTION_DEFINITIONS = {} let TRIGGER_DEFINITIONS = {} let LOGIC_DEFINITIONS = {} describe("/automations", () => { - let request - let server - let app - let appId + let request = setup.getRequest() + let config = setup.getConfig() let automation - let automationId - beforeAll(async () => { - ({ request, server } = await supertest()) - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id - if (automation) await destroyDocument(automation.id) + await config.init() }) - afterAll(() => { - server.close() - }) - - const createAutomation = async () => { - automation = await insertDocument(appId, { - type: "automation", - ...TEST_AUTOMATION - }) - automation = { ...automation, ...TEST_AUTOMATION } - } - - const triggerWorkflow = async (automationId) => { + const triggerWorkflow = async automation => { return await request - .post(`/api/automations/${automationId}/trigger`) + .post(`/api/automations/${automation._id}/trigger`) .send({ name: "Test", description: "TEST" }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) } @@ -77,7 +36,7 @@ describe("/automations", () => { it("returns a list of definitions for actions", async () => { const res = await request .get(`/api/automations/action/list`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -88,7 +47,7 @@ describe("/automations", () => { it("returns a list of definitions for triggers", async () => { const res = await request .get(`/api/automations/trigger/list`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -99,7 +58,7 @@ describe("/automations", () => { it("returns a list of definitions for actions", async () => { const res = await request .get(`/api/automations/logic/list`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -110,7 +69,7 @@ describe("/automations", () => { it("returns all of the definitions in one", async () => { const res = await request .get(`/api/automations/definitions/list`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -121,6 +80,7 @@ describe("/automations", () => { }) describe("create", () => { + const autoConfig = basicAutomation() it("should setup the automation fully", () => { let trigger = TRIGGER_DEFINITIONS["ROW_SAVED"] trigger.id = "wadiawdo34" @@ -131,52 +91,51 @@ describe("/automations", () => { } createAction.id = "awde444wk" - TEST_AUTOMATION.definition.steps.push(createAction) - TEST_AUTOMATION.definition.trigger = trigger + autoConfig.definition.steps.push(createAction) + autoConfig.definition.trigger = trigger }) it("returns a success message when the automation is successfully created", async () => { const res = await request .post(`/api/automations`) - .set(defaultHeaders(appId)) - .send(TEST_AUTOMATION) + .set(config.defaultHeaders()) + .send(autoConfig) .expect('Content-Type', /json/) .expect(200) expect(res.body.message).toEqual("Automation created successfully") expect(res.body.automation.name).toEqual("My Automation") expect(res.body.automation._id).not.toEqual(null) - automationId = res.body.automation._id + automation = res.body.automation }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "POST", url: `/api/automations`, - appId: appId, - body: TEST_AUTOMATION + body: autoConfig }) }) }) describe("trigger", () => { it("trigger the automation successfully", async () => { - let table = await createTable(request, appId) - TEST_AUTOMATION.definition.trigger.inputs.tableId = table._id - TEST_AUTOMATION.definition.steps[0].inputs.row.tableId = table._id - await createAutomation() - await delay(500) - const res = await triggerWorkflow(automation._id) + let table = await config.createTable() + automation.definition.trigger.inputs.tableId = table._id + automation.definition.steps[0].inputs.row.tableId = table._id + automation = await config.createAutomation(automation) + await setup.delay(500) + const res = await triggerWorkflow(automation) // this looks a bit mad but we don't actually have a way to wait for a response from the automation to // know that it has finished all of its actions - this is currently the best way // also when this runs in CI it is very temper-mental so for now trying to make run stable by repeating until it works // TODO: update when workflow logs are a thing for (let tries = 0; tries < MAX_RETRIES; tries++) { expect(res.body.message).toEqual(`Automation ${automation._id} has been triggered.`) - expect(res.body.automation.name).toEqual(TEST_AUTOMATION.name) - await delay(500) - let elements = await getAllFromTable(request, appId, table._id) + expect(res.body.automation.name).toEqual(automation.name) + await setup.delay(500) + let elements = await getAllTableRows(config) // don't test it unless there are values to test if (elements.length > 1) { expect(elements.length).toEqual(5) @@ -191,65 +150,63 @@ describe("/automations", () => { describe("update", () => { it("updates a automations data", async () => { - await createAutomation() - automation._id = automation.id - automation._rev = automation.rev + automation = await config.createAutomation(automation) automation.name = "Updated Name" automation.type = "automation" const res = await request .put(`/api/automations`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .send(automation) .expect('Content-Type', /json/) .expect(200) - expect(res.body.message).toEqual(`Automation ${AUTOMATION_ID} updated successfully.`) + expect(res.body.message).toEqual(`Automation ${automation._id} updated successfully.`) expect(res.body.automation.name).toEqual("Updated Name") }) }) describe("fetch", () => { it("return all the automations for an instance", async () => { - await createAutomation() + await clearAllAutomations(config) + const autoConfig = basicAutomation() + automation = await config.createAutomation(autoConfig) const res = await request .get(`/api/automations`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - expect(res.body[0]).toEqual(expect.objectContaining(TEST_AUTOMATION)) + expect(res.body[0]).toEqual(expect.objectContaining(autoConfig)) }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "GET", url: `/api/automations`, - appId: appId, }) }) }) describe("destroy", () => { it("deletes a automation by its ID", async () => { - await createAutomation() + const automation = await config.createAutomation() const res = await request .delete(`/api/automations/${automation.id}/${automation.rev}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - expect(res.body.id).toEqual(TEST_AUTOMATION._id) + expect(res.body.id).toEqual(automation._id) }) it("should apply authorization to endpoint", async () => { - await createAutomation() - await builderEndpointShouldBlockNormalUsers({ - request, + const automation = await config.createAutomation() + await checkBuilderEndpoint({ + config, method: "DELETE", url: `/api/automations/${automation.id}/${automation._rev}`, - appId: appId, }) }) }) diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js deleted file mode 100644 index 90d15612f0..0000000000 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ /dev/null @@ -1,326 +0,0 @@ -const CouchDB = require("../../../db") -const supertest = require("supertest") -const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") -const packageJson = require("../../../../package") -const jwt = require("jsonwebtoken") -const env = require("../../../environment") -const { - BUILTIN_PERMISSION_IDS, -} = require("../../../utilities/security/permissions") - -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 -} - -exports.BASE_TABLE = { - name: "TestTable", - type: "table", - key: "name", - schema: { - name: { - type: "string", - constraints: { - type: "string", - }, - }, - description: { - type: "string", - constraints: { - type: "string", - }, - }, - }, -} - -exports.createTable = async (request, appId, table, removeId = true) => { - if (removeId && table != null && table._id) { - delete table._id - } - table = table || exports.BASE_TABLE - - const res = await request - .post(`/api/tables`) - .set(exports.defaultHeaders(appId)) - .send(table) - return res.body -} - -exports.makeBasicRow = tableId => { - return { - name: "Test Contact", - description: "original description", - status: "new", - tableId: tableId, - } -} - -exports.createRow = async (request, appId, tableId, row = null) => { - row = row || exports.makeBasicRow(tableId) - const res = await request - .post(`/api/${tableId}/rows`) - .send(row) - .set(exports.defaultHeaders(appId)) - .expect("Content-Type", /json/) - .expect(200) - return res.body -} - -exports.createRole = async (request, appId) => { - const roleBody = { - name: "NewRole", - inherits: BUILTIN_ROLE_IDS.BASIC, - permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY, - } - const res = await request - .post(`/api/roles`) - .send(roleBody) - .set(exports.defaultHeaders(appId)) - .expect("Content-Type", /json/) - .expect(200) - return res.body -} - -exports.addPermission = async ( - request, - appId, - role, - resource, - level = "read" -) => { - const res = await request - .post(`/api/permission/${role}/${resource}/${level}`) - .set(exports.defaultHeaders(appId)) - .expect("Content-Type", /json/) - .expect(200) - return res.body -} - -exports.createLinkedTable = async (request, appId) => { - // get the ID to link to - const table = await exports.createTable(request, appId) - table.primaryDisplay = "name" - table.schema.link = { - type: "link", - fieldName: "link", - tableId: table._id, - } - return exports.createTable(request, appId, table, false) -} - -exports.createAttachmentTable = async (request, appId) => { - const table = await exports.createTable(request, appId) - table.schema.attachment = { - type: "attachment", - } - return exports.createTable(request, appId, table, false) -} - -exports.getAllFromTable = async (request, appId, tableId) => { - const res = await request - .get(`/api/${tableId}/rows`) - .set(exports.defaultHeaders(appId)) - return res.body -} - -exports.createView = async (request, appId, tableId, view) => { - view = view || { - map: "function(doc) { emit(doc[doc.key], doc._id); } ", - tableId: tableId, - } - - const res = await request - .post(`/api/views`) - .set(exports.defaultHeaders(appId)) - .send(view) - return res.body -} - -exports.createApplication = async (request, name = "test_application") => { - const res = await request - .post("/api/applications") - .send({ - name, - }) - .set(exports.defaultHeaders()) - return res.body -} - -exports.clearApplications = async request => { - const res = await request - .get("/api/applications") - .set(exports.defaultHeaders()) - for (let app of res.body) { - const appId = app._id - await request - .delete(`/api/applications/${appId}`) - .set(exports.defaultHeaders(appId)) - } -} - -exports.createUser = async ( - request, - appId, - email = "babs@babs.com", - password = "babs_password" -) => { - const res = await request - .post(`/api/users`) - .set(exports.defaultHeaders(appId)) - .send({ - name: "Bill", - email, - password, - roleId: BUILTIN_ROLE_IDS.POWER, - }) - return res.body -} - -const createUserWithRole = async (request, appId, roleId, email) => { - const password = `password_${email}` - await request - .post(`/api/users`) - .set(exports.defaultHeaders(appId)) - .send({ - email, - password, - roleId, - }) - - const anonUser = { - userId: "ANON", - roleId: BUILTIN_ROLE_IDS.PUBLIC, - appId: appId, - version: packageJson.version, - } - - const anonToken = jwt.sign(anonUser, env.JWT_SECRET) - - const loginResult = await request - .post(`/api/authenticate`) - .set({ - Cookie: `budibase:${appId}:local=${anonToken}`, - "x-budibase-app-id": appId, - }) - .send({ email, password }) - - // returning necessary request headers - return { - Accept: "application/json", - Cookie: loginResult.headers["set-cookie"], - } -} - -exports.testPermissionsForEndpoint = async ({ - request, - method, - url, - body, - appId, - passRole, - failRole, -}) => { - const passHeader = await createUserWithRole( - request, - appId, - passRole, - "passUser@budibase.com" - ) - - await createRequest(request, method, url, body) - .set(passHeader) - .expect(200) - - const failHeader = await createUserWithRole( - request, - appId, - failRole, - "failUser@budibase.com" - ) - - await createRequest(request, method, url, body) - .set(failHeader) - .expect(403) -} - -exports.builderEndpointShouldBlockNormalUsers = async ({ - request, - method, - url, - body, - appId, -}) => { - const headers = await createUserWithRole( - request, - appId, - BUILTIN_ROLE_IDS.BASIC, - "basicUser@budibase.com" - ) - - await createRequest(request, method, url, body) - .set(headers) - .expect(403) -} - -const 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.insertDocument = async (databaseId, document) => { - const { id, ...documentFields } = document - return await new CouchDB(databaseId).put({ _id: id, ...documentFields }) -} - -exports.destroyDocument = async (databaseId, documentId) => { - return await new CouchDB(databaseId).destroy(documentId) -} - -exports.getDocument = async (databaseId, documentId) => { - return await new CouchDB(databaseId).get(documentId) -} diff --git a/packages/server/src/api/routes/tests/datasource.spec.js b/packages/server/src/api/routes/tests/datasource.spec.js index 768d54fbcd..7602b027e1 100644 --- a/packages/server/src/api/routes/tests/datasource.spec.js +++ b/packages/server/src/api/routes/tests/datasource.spec.js @@ -1,67 +1,23 @@ -const { - supertest, - createApplication, - defaultHeaders, - builderEndpointShouldBlockNormalUsers, - getDocument, - insertDocument -} = require("./couchTestUtils") -let { generateDatasourceID, generateQueryID } = require("../../../db/utils") - -const DATASOURCE_ID = generateDatasourceID() -const TEST_DATASOURCE = { - _id: DATASOURCE_ID, - type: "datasource", - name: "Test", - source: "POSTGRES", - config: {}, - type: "datasource", -} - -const TEST_QUERY = { - _id: generateQueryID(DATASOURCE_ID), - datasourceId: DATASOURCE_ID, - name:"New Query", - parameters:[], - fields:{}, - schema:{}, - queryVerb:"read", -} +let { basicDatasource } = require("./utilities/structures") +let { checkBuilderEndpoint } = require("./utilities/TestFunctions") +let setup = require("./utilities") describe("/datasources", () => { - let request - let server - let app - let appId - let datasource + let request = setup.getRequest() + let config = setup.getConfig() - beforeAll(async () => { - ({ request, server } = await supertest()) - }); - - afterAll(() => { - server.close() - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id - }); - - async function createDatasource() { - return await insertDocument(appId, TEST_DATASOURCE) - } - - async function createQuery() { - return await insertDocument(appId, TEST_QUERY) - } + await config.init() + }) describe("create", () => { it("should create a new datasource", async () => { const res = await request .post(`/api/datasources`) - .send(TEST_DATASOURCE) - .set(defaultHeaders(appId)) + .send(basicDatasource()) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -74,7 +30,7 @@ describe("/datasources", () => { let datasource beforeEach(async () => { - datasource = await createDatasource() + datasource = await config.createDatasource() }); afterEach(() => { @@ -84,34 +40,34 @@ describe("/datasources", () => { it("returns all the datasources from the server", async () => { const res = await request .get(`/api/datasources`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - const datasources = res.body; + const datasources = res.body expect(datasources).toEqual([ { + "_id": datasources[0]._id, "_rev": datasources[0]._rev, - ...TEST_DATASOURCE + ...basicDatasource() } ]); }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "GET", url: `/api/datasources`, - appId: appId, }) }) }); describe("destroy", () => { - let datasource; + let datasource beforeEach(async () => { - datasource = await createDatasource() + datasource = await config.createDatasource() }); afterEach(() => { @@ -119,16 +75,16 @@ describe("/datasources", () => { }); it("deletes queries for the datasource after deletion and returns a success message", async () => { - await createQuery(datasource.id) + await config.createQuery() await request - .delete(`/api/datasources/${datasource.id}/${datasource.rev}`) - .set(defaultHeaders(appId)) + .delete(`/api/datasources/${datasource._id}/${datasource._rev}`) + .set(config.defaultHeaders()) .expect(200) const res = await request .get(`/api/datasources`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -136,11 +92,10 @@ describe("/datasources", () => { }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "DELETE", url: `/api/datasources/${datasource._id}/${datasource._rev}`, - appId: appId, }) }) diff --git a/packages/server/src/api/routes/tests/permissions.spec.js b/packages/server/src/api/routes/tests/permissions.spec.js index bb1f072efc..93e6e29131 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.js +++ b/packages/server/src/api/routes/tests/permissions.spec.js @@ -1,46 +1,30 @@ -const { - createApplication, - createTable, - createRow, - supertest, - defaultHeaders, - addPermission, - publicHeaders, - makeBasicRow, -} = require("./couchTestUtils") const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") +const setup = require("./utilities") +const { basicRow } = require("./utilities/structures") const HIGHER_ROLE_ID = BUILTIN_ROLE_IDS.BASIC const STD_ROLE_ID = BUILTIN_ROLE_IDS.PUBLIC describe("/permission", () => { - let server - let request - let appId + let request = setup.getRequest() + let config = setup.getConfig() let table let perms let row - beforeAll(async () => { - ;({ request, server } = await supertest()) - }) - - afterAll(() => { - server.close() - }) + afterAll(setup.afterAll) beforeEach(async () => { - let app = await createApplication(request) - appId = app.instance._id - table = await createTable(request, appId) - perms = await addPermission(request, appId, STD_ROLE_ID, table._id) - row = await createRow(request, appId, table._id) + await config.init() + table = await config.createTable() + row = await config.createRow() + perms = await config.addPermission(STD_ROLE_ID, table._id) }) async function getTablePermissions() { return request .get(`/api/permission/${table._id}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) } @@ -49,7 +33,7 @@ describe("/permission", () => { it("should be able to get levels", async () => { const res = await request .get(`/api/permission/levels`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect(res.body).toBeDefined() @@ -68,7 +52,7 @@ describe("/permission", () => { it("should get the resource permissions", async () => { const res = await request .get(`/api/permission/${table._id}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect(res.body["read"]).toEqual(STD_ROLE_ID) @@ -76,13 +60,13 @@ describe("/permission", () => { }) it("should get resource permissions with multiple roles", async () => { - perms = await addPermission(request, appId, HIGHER_ROLE_ID, table._id, "write") + perms = await config.addPermission(HIGHER_ROLE_ID, table._id, "write") const res = await getTablePermissions() expect(res.body["read"]).toEqual(STD_ROLE_ID) expect(res.body["write"]).toEqual(HIGHER_ROLE_ID) const allRes = await request .get(`/api/permission`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect(allRes.body[table._id]["write"]).toEqual(HIGHER_ROLE_ID) @@ -94,7 +78,7 @@ describe("/permission", () => { it("should be able to remove the permission", async () => { const res = await request .delete(`/api/permission/${STD_ROLE_ID}/${table._id}/read`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect(res.body[0]._id).toEqual(STD_ROLE_ID) @@ -107,7 +91,7 @@ describe("/permission", () => { it("should be able to read the row", async () => { const res = await request .get(`/api/${table._id}/rows`) - .set(publicHeaders(appId)) + .set(config.publicHeaders()) .expect("Content-Type", /json/) .expect(200) expect(res.body[0]._id).toEqual(row._id) @@ -116,8 +100,8 @@ describe("/permission", () => { it("shouldn't allow writing from a public user", async () => { const res = await request .post(`/api/${table._id}/rows`) - .send(makeBasicRow(table._id)) - .set(publicHeaders(appId)) + .send(basicRow(table._id)) + .set(config.publicHeaders()) .expect("Content-Type", /json/) .expect(403) expect(res.status).toEqual(403) diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index e97887846e..765baa4426 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -1,77 +1,35 @@ -const { - supertest, - createApplication, - defaultHeaders, - builderEndpointShouldBlockNormalUsers, - getDocument, - insertDocument, -} = require("./couchTestUtils") -let { generateDatasourceID, generateQueryID } = require("../../../db/utils") - -const DATASOURCE_ID = generateDatasourceID() -const TEST_DATASOURCE = { - _id: DATASOURCE_ID, - type: "datasource", - name: "Test", - source: "POSTGRES", - config: {}, - type: "datasource", -} - -const TEST_QUERY = { - _id: generateQueryID(DATASOURCE_ID), - datasourceId: DATASOURCE_ID, - name: "New Query", - parameters: [], - fields: {}, - schema: {}, - queryVerb: "read", -} +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { basicQuery } = require("./utilities/structures") +const setup = require("./utilities") describe("/queries", () => { - let request - let server - let app - let appId - let datasource - let query + let request = setup.getRequest() + let config = setup.getConfig() - beforeAll(async () => { - ;({ request, server } = await supertest()) - }) - - afterAll(() => { - server.close() - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id + await config.init() }) - async function createDatasource() { - return await insertDocument(appId, TEST_DATASOURCE) - } - - async function createQuery() { - return await insertDocument(appId, TEST_QUERY) - } - describe("create", () => { it("should create a new query", async () => { + const { _id } = await config.createDatasource() + const query = basicQuery(_id) const res = await request .post(`/api/queries`) - .send(TEST_QUERY) - .set(defaultHeaders(appId)) + .send(query) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) expect(res.res.statusMessage).toEqual( - `Query ${TEST_QUERY.name} saved successfully.` + `Query ${query.name} saved successfully.` ) expect(res.body).toEqual({ _rev: res.body._rev, - ...TEST_QUERY, + _id: res.body._id, + ...query, }) }) }) @@ -80,7 +38,7 @@ describe("/queries", () => { let datasource beforeEach(async () => { - datasource = await createDatasource() + datasource = await config.createDatasource() }) afterEach(() => { @@ -88,29 +46,29 @@ describe("/queries", () => { }) it("returns all the queries from the server", async () => { - const query = await createQuery() + const query = await config.createQuery() const res = await request .get(`/api/queries`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) const queries = res.body expect(queries).toEqual([ { - _rev: query.rev, - ...TEST_QUERY, + _rev: query._rev, + _id: query._id, + ...basicQuery(datasource._id), readable: true, }, ]) }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "GET", url: `/api/datasources`, - appId: appId, }) }) }) @@ -119,7 +77,7 @@ describe("/queries", () => { let datasource beforeEach(async () => { - datasource = await createDatasource() + datasource = await config.createDatasource() }) afterEach(() => { @@ -127,16 +85,16 @@ describe("/queries", () => { }) it("deletes a query and returns a success message", async () => { - const query = await createQuery() + const query = await config.createQuery() await request - .delete(`/api/queries/${query.id}/${query.rev}`) - .set(defaultHeaders(appId)) + .delete(`/api/queries/${query._id}/${query._rev}`) + .set(config.defaultHeaders()) .expect(200) const res = await request .get(`/api/queries`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -144,11 +102,10 @@ describe("/queries", () => { }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "DELETE", url: `/api/datasources/${datasource._id}/${datasource._rev}`, - appId: appId, }) }) }) diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js index b86b285dbc..9c08bf289a 100644 --- a/packages/server/src/api/routes/tests/role.spec.js +++ b/packages/server/src/api/routes/tests/role.spec.js @@ -1,43 +1,26 @@ -const { - createApplication, - supertest, - defaultHeaders, -} = require("./couchTestUtils") const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") const { BUILTIN_PERMISSION_IDS, } = require("../../../utilities/security/permissions") - -const roleBody = { - name: "NewRole", - inherits: BUILTIN_ROLE_IDS.BASIC, - permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY, -} +const { basicRole } = require("./utilities/structures") +const setup = require("./utilities") describe("/roles", () => { - let server - let request - let appId + let request = setup.getRequest() + let config = setup.getConfig() - beforeAll(async () => { - ;({ request, server } = await supertest()) - }) - - afterAll(() => { - server.close() - }) + afterAll(setup.afterAll) beforeEach(async () => { - let app = await createApplication(request) - appId = app.instance._id + await config.init() }) describe("create", () => { it("returns a success message when role is successfully created", async () => { const res = await request .post(`/api/roles`) - .send(roleBody) - .set(defaultHeaders(appId)) + .send(basicRole()) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -53,8 +36,8 @@ describe("/roles", () => { it("should list custom roles, plus 2 default roles", async () => { const createRes = await request .post(`/api/roles`) - .send(roleBody) - .set(defaultHeaders(appId)) + .send(basicRole()) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -62,7 +45,7 @@ describe("/roles", () => { const res = await request .get(`/api/roles`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -92,7 +75,7 @@ describe("/roles", () => { const createRes = await request .post(`/api/roles`) .send({ name: "user", permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -100,12 +83,12 @@ describe("/roles", () => { await request .delete(`/api/roles/${customRole._id}/${customRole._rev}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect(200) await request .get(`/api/roles/${customRole._id}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect(404) }) }) diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 777a99b091..cc50520b77 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -1,66 +1,45 @@ -const { - createApplication, - createTable, - supertest, - defaultHeaders, - createLinkedTable, - createAttachmentTable, - makeBasicRow, -} = require("./couchTestUtils"); const { outputProcessing } = require("../../../utilities/rowProcessor") const env = require("../../../environment") +const { basicRow } = require("./utilities/structures") +const setup = require("./utilities") describe("/rows", () => { - let request - let server - let appId + let request = setup.getRequest() + let config = setup.getConfig() let table let row - let app - beforeAll(async () => { - ({ request, server } = await supertest()) - - }); - - afterAll(() => { - server.close() - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id - table = await createTable(request, appId) - row = makeBasicRow(table._id) + await config.init() + table = await config.createTable() + row = basicRow(table._id) }) - const createRow = async r => - await request - .post(`/api/${r ? r.tableId : row.tableId}/rows`) - .send(r || row) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - const loadRow = async id => await request .get(`/api/${table._id}/rows/${id}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) describe("save, load, update, delete", () => { it("returns a success message when the row is created", async () => { - const res = await createRow() + const res = await request + .post(`/api/${row.tableId}/rows`) + .send(row) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`) expect(res.body.name).toEqual("Test Contact") expect(res.body._rev).toBeDefined() }) it("updates a row successfully", async () => { - const rec = await createRow() - const existing = rec.body + const existing = await config.createRow() const res = await request .post(`/api/${table._id}/rows`) @@ -70,7 +49,7 @@ describe("/rows", () => { tableId: table._id, name: "Updated Name", }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -79,12 +58,11 @@ describe("/rows", () => { }) it("should load a row", async () => { - const rec = await createRow() - const existing = rec.body + const existing = await config.createRow() const res = await request .get(`/api/${table._id}/rows/${existing._id}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -102,12 +80,12 @@ describe("/rows", () => { name: "Second Contact", status: "new" } - await createRow() - await createRow(newRow) + await config.createRow() + await config.createRow(newRow) const res = await request .get(`/api/${table._id}/rows`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -117,10 +95,10 @@ describe("/rows", () => { }) it("load should return 404 when row does not exist", async () => { - await createRow() + await config.createRow() await request .get(`/api/${table._id}/rows/not-a-valid-id`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(404) }) @@ -132,7 +110,7 @@ describe("/rows", () => { const number = {type:"number", constraints: { type: "number", presence: false }} const datetime = {type:"datetime", constraints: { type: "string", presence: false, datetime: {earliest:"", latest: ""} }} - table = await createTable(request, appId, { + table = await config.createTable({ name: "TestTable2", type: "table", key: "name", @@ -187,7 +165,7 @@ describe("/rows", () => { attachmentEmpty : "", } - const id = (await createRow(row)).body._id + const id = (await config.createRow(row))._id const saved = (await loadRow(id)).body @@ -217,8 +195,7 @@ describe("/rows", () => { describe("patch", () => { it("should update only the fields that are supplied", async () => { - const rec = await createRow() - const existing = rec.body + const existing = await config.createRow() const res = await request .patch(`/api/${table._id}/rows/${existing._id}`) @@ -228,7 +205,7 @@ describe("/rows", () => { tableId: table._id, name: "Updated Name", }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -249,7 +226,7 @@ describe("/rows", () => { const result = await request .post(`/api/${table._id}/rows/validate`) .send({ name: "ivan" }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -261,7 +238,7 @@ describe("/rows", () => { const result = await request .post(`/api/${table._id}/rows/validate`) .send({ name: 1 }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -273,19 +250,19 @@ describe("/rows", () => { describe("enrich row unit test", () => { it("should allow enriching some linked rows", async () => { - const table = await createLinkedTable(request, appId) - const firstRow = (await createRow({ + const table = await config.createLinkedTable() + const firstRow = await config.createRow({ name: "Test Contact", description: "original description", tableId: table._id - })).body - const secondRow = (await createRow({ + }) + const secondRow = await config.createRow({ name: "Test 2", description: "og desc", link: [{_id: firstRow._id}], tableId: table._id, - })).body - const enriched = await outputProcessing(appId, table, [secondRow]) + }) + 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") @@ -293,20 +270,20 @@ describe("/rows", () => { }) it("should allow enriching attachment rows", async () => { - const table = await createAttachmentTable(request, appId) - const row = (await createRow({ + const table = await config.createAttachmentTable() + const row = await config.createRow({ name: "test", description: "test", attachment: [{ url: "/test/thing", }], tableId: table._id, - })).body + }) // the environment needs configured for this env.CLOUD = 1 env.SELF_HOSTED = 1 - const enriched = await outputProcessing(appId, table, [row]) - expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${appId}/test/thing`) + 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 diff --git a/packages/server/src/api/routes/tests/table.spec.js b/packages/server/src/api/routes/tests/table.spec.js index 67d53a489b..1a2df624f1 100644 --- a/packages/server/src/api/routes/tests/table.spec.js +++ b/packages/server/src/api/routes/tests/table.spec.js @@ -1,30 +1,15 @@ -const { - createTable, - supertest, - createApplication, - defaultHeaders, - builderEndpointShouldBlockNormalUsers, - getDocument -} = require("./couchTestUtils") +const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const setup = require("./utilities") describe("/tables", () => { - let request - let server - let app - let appId + let request = setup.getRequest() + let config = setup.getConfig() - beforeAll(async () => { - ({ request, server } = await supertest()) - }); - - afterAll(() => { - server.close() - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id - }); + await config.init() + }) describe("create", () => { it("returns a success message when the table is successfully created", done => { @@ -37,25 +22,25 @@ describe("/tables", () => { name: { type: "string" } } }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) .end(async (err, res) => { - expect(res.res.statusMessage).toEqual("Table TestTable saved successfully."); - expect(res.body.name).toEqual("TestTable"); - done(); - }); + expect(res.res.statusMessage).toEqual("Table TestTable saved successfully.") + expect(res.body.name).toEqual("TestTable") + done() + }) }) it("renames all the row fields for a table when a schema key is renamed", async () => { - const testTable = await createTable(request, appId); + const testTable = await config.createTable() const testRow = await request .post(`/api/${testTable._id}/rows`) .send({ name: "test" }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) @@ -74,29 +59,28 @@ describe("/tables", () => { updatedName: { type: "string" } } }) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully."); - expect(updatedTable.body.name).toEqual("TestTable"); + expect(updatedTable.res.statusMessage).toEqual("Table TestTable saved successfully.") + expect(updatedTable.body.name).toEqual("TestTable") const res = await request .get(`/api/${testTable._id}/rows/${testRow.body._id}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - expect(res.body.updatedName).toEqual("test"); - expect(res.body.name).toBeUndefined(); - }); + expect(res.body.updatedName).toEqual("test") + expect(res.body.name).toBeUndefined() + }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "POST", url: `/api/tables`, - appId: appId, body: { name: "TestTable", key: "name", @@ -106,68 +90,67 @@ describe("/tables", () => { } }) }) - }); + }) describe("fetch", () => { let testTable beforeEach(async () => { - testTable = await createTable(request, appId, testTable) - }); + testTable = await config.createTable(testTable) + }) afterEach(() => { delete testTable._rev - }); + }) it("returns all the tables for that instance in the response body", done => { request .get(`/api/tables`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) .end(async (_, res) => { - const fetchedTable = res.body[0]; - expect(fetchedTable.name).toEqual(testTable.name); - expect(fetchedTable.type).toEqual("table"); - done(); - }); + const fetchedTable = res.body[0] + expect(fetchedTable.name).toEqual(testTable.name) + expect(fetchedTable.type).toEqual("table") + done() + }) }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "GET", url: `/api/tables`, - appId: appId, }) }) - }); + }) describe("destroy", () => { - let testTable; + let testTable beforeEach(async () => { - testTable = await createTable(request, appId, testTable) - }); + testTable = await config.createTable(testTable) + }) afterEach(() => { delete testTable._rev - }); + }) it("returns a success response when a table is deleted.", async done => { request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) .end(async (_, res) => { - expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`); - done(); - }); + expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`) + done() + }) }) it("deletes linked references to the table after deletion", async done => { - const linkedTable = await createTable(request, appId, { + const linkedTable = await config.createTable({ name: "LinkedTable", type: "table", key: "name", @@ -190,25 +173,24 @@ describe("/tables", () => { request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) .end(async (_, res) => { - expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`); - const dependentTable = await getDocument(appId, linkedTable._id) - expect(dependentTable.schema.TestTable).not.toBeDefined(); - done(); - }); + expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`) + const dependentTable = await config.getTable(linkedTable._id) + expect(dependentTable.schema.TestTable).not.toBeDefined() + done() + }) }) it("should apply authorization to endpoint", async () => { - await builderEndpointShouldBlockNormalUsers({ - request, + await checkBuilderEndpoint({ + config, method: "DELETE", url: `/api/tables/${testTable._id}/${testTable._rev}`, - appId: appId, }) }) - }); -}); + }) +}) diff --git a/packages/server/src/api/routes/tests/testUtils.js b/packages/server/src/api/routes/tests/testUtils.js deleted file mode 100644 index 0e66b47c3d..0000000000 --- a/packages/server/src/api/routes/tests/testUtils.js +++ /dev/null @@ -1 +0,0 @@ -module.exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms)) diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index 771b547ef8..6ec607a093 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -1,46 +1,25 @@ -const { - createApplication, - supertest, - defaultHeaders, - createUser, - testPermissionsForEndpoint, -} = require("./couchTestUtils") const { BUILTIN_ROLE_IDS } = require("../../../utilities/security/roles") -const { cloneDeep } = require("lodash/fp") - -const baseBody = { - email: "bill@bill.com", - password: "yeeooo", - roleId: BUILTIN_ROLE_IDS.POWER, -} +const { checkPermissionsEndpoint } = require("./utilities/TestFunctions") +const { basicUser } = require("./utilities/structures") +const setup = require("./utilities") describe("/users", () => { - let request - let server - let app - let appId + let request = setup.getRequest() + let config = setup.getConfig() - beforeAll(async () => { - ;({ request, server } = await supertest(server)) - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id - }) - - afterAll(() => { - server.close() - server.destroy() + await config.init() }) describe("fetch", () => { it("returns a list of users from an instance db", async () => { - await createUser(request, appId, "brenda@brenda.com", "brendas_password") - await createUser(request, appId, "pam@pam.com", "pam_password") + await config.createUser("brenda@brenda.com", "brendas_password") + await config.createUser("pam@pam.com", "pam_password") const res = await request .get(`/api/users`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) @@ -50,12 +29,12 @@ describe("/users", () => { }) it("should apply authorization to endpoint", async () => { - await createUser(request, appId, "brenda@brenda.com", "brendas_password") - await testPermissionsForEndpoint({ + await config.createUser("brenda@brenda.com", "brendas_password") + await checkPermissionsEndpoint({ + config, request, method: "GET", url: `/api/users`, - appId: appId, passRole: BUILTIN_ROLE_IDS.ADMIN, failRole: BUILTIN_ROLE_IDS.PUBLIC, }) @@ -64,11 +43,11 @@ describe("/users", () => { describe("create", () => { it("returns a success message when a user is successfully created", async () => { - const body = cloneDeep(baseBody) + const body = basicUser(BUILTIN_ROLE_IDS.POWER) body.email = "bill@budibase.com" const res = await request .post(`/api/users`) - .set(defaultHeaders(appId)) + .set(config.defaultHeaders()) .send(body) .expect(200) .expect("Content-Type", /json/) @@ -78,14 +57,13 @@ describe("/users", () => { }) it("should apply authorization to endpoint", async () => { - const body = cloneDeep(baseBody) + const body = basicUser(BUILTIN_ROLE_IDS.POWER) body.email = "brandNewUser@user.com" - await testPermissionsForEndpoint({ - request, + await checkPermissionsEndpoint({ + config, method: "POST", body, url: `/api/users`, - appId: appId, passRole: BUILTIN_ROLE_IDS.ADMIN, failRole: BUILTIN_ROLE_IDS.PUBLIC, }) 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..5f6b1cc267 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/TestConfiguration.js @@ -0,0 +1,248 @@ +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 createApp(appName) { + this.app = await this._req({ name: appName }, null, controllers.app.create) + this.appId = this.app._id + 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/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js new file mode 100644 index 0000000000..534119d279 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -0,0 +1,79 @@ +const rowController = require("../../../controllers/row") +const appController = require("../../../controllers/application") + +function Request(appId, params) { + this.user = { appId } + this.params = params +} + +exports.getAllTableRows = async config => { + const req = new Request(config.appId, { tableId: config.table._id }) + 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.clearAllAutomations = async config => { + const automations = await config.getAllAutomations() + for (let auto of automations) { + await config.deleteAutomation(auto) + } +} + +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, method, url, body }) => { + const headers = await config.login() + await exports + .createRequest(config.request, method, url, body) + .set(headers) + .expect(403) +} + +exports.checkPermissionsEndpoint = async ({ + config, + method, + url, + body, + passRole, + failRole, +}) => { + const password = "PASSWORD" + await config.createUser("passUser@budibase.com", password, passRole) + const passHeader = await config.login("passUser@budibase.com", password) + + await exports + .createRequest(config.request, method, url, body) + .set(passHeader) + .expect(200) + + await config.createUser("failUser@budibase.com", password, failRole) + const failHeader = await config.login("failUser@budibase.com", password) + + await exports + .createRequest(config.request, method, url, body) + .set(failHeader) + .expect(403) +} diff --git a/packages/server/src/api/routes/tests/utilities/controllers.js b/packages/server/src/api/routes/tests/utilities/controllers.js new file mode 100644 index 0000000000..541495bec8 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/controllers.js @@ -0,0 +1,12 @@ +module.exports = { + table: require("../../../controllers/table"), + row: require("../../../controllers/row"), + role: require("../../../controllers/role"), + perms: require("../../../controllers/permission"), + view: require("../../../controllers/view"), + app: require("../../../controllers/application"), + user: require("../../../controllers/user"), + automation: require("../../../controllers/automation"), + datasource: require("../../../controllers/datasource"), + query: require("../../../controllers/query"), +} 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..7e9260ce18 --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/index.js @@ -0,0 +1,32 @@ +const TestConfig = require("./TestConfiguration") + +exports.delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + +let request, config + +exports.beforeAll = () => { + config = new TestConfig() + request = config.getRequest() +} + +exports.afterAll = () => { + if (config) { + config.end() + } + request = null + config = null +} + +exports.getRequest = () => { + if (!request) { + exports.beforeAll() + } + return request +} + +exports.getConfig = () => { + if (!config) { + exports.beforeAll() + } + return config +} 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..922228aadf --- /dev/null +++ b/packages/server/src/api/routes/tests/utilities/structures.js @@ -0,0 +1,87 @@ +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.basicAutomation = () => { + return { + name: "My Automation", + screenId: "kasdkfldsafkl", + live: true, + uiTree: {}, + definition: { + trigger: { + inputs: {}, + }, + steps: [], + }, + type: "automation", + } +} + +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, + } +} + +exports.basicDatasource = () => { + return { + type: "datasource", + name: "Test", + source: "POSTGRES", + config: {}, + } +} + +exports.basicQuery = datasourceId => { + return { + datasourceId: datasourceId, + name: "New Query", + parameters: [], + fields: {}, + schema: {}, + queryVerb: "read", + } +} + +exports.basicUser = role => { + return { + email: "bill@bill.com", + password: "yeeooo", + roleId: role, + } +} diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js index f1ee5d8218..a80b09d3a0 100644 --- a/packages/server/src/api/routes/tests/view.spec.js +++ b/packages/server/src/api/routes/tests/view.spec.js @@ -1,65 +1,56 @@ -const { - createApplication, - createTable, - supertest, - defaultHeaders, - getDocument -} = require("./couchTestUtils") +const setup = require("./utilities") describe("/views", () => { - let request - let server - let app - let appId + let request = setup.getRequest() + let config = setup.getConfig() let table - const createView = async (config = { - name: "TestView", - field: "Price", - calculation: "stats", - tableId: table._id - }) => - await request - .post(`/api/views`) - .send(config) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - - const createRow = async row => request - .post(`/api/${table._id}/rows`) - .send(row) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) - .expect(200) - - beforeAll(async () => { - ({ request, server } = await supertest()) - }) + afterAll(setup.afterAll) beforeEach(async () => { - app = await createApplication(request) - appId = app.instance._id - }) - - afterAll(() => { - server.close() + await config.init() }) describe("create", () => { beforeEach(async () => { - table = await createTable(request, appId); + table = await config.createTable() }) it("returns a success message when the view is successfully created", async () => { - const res = await createView() - expect(res.res.statusMessage).toEqual("View TestView saved successfully."); + const res = await request + .post(`/api/views`) + .send({ + name: "TestView", + field: "Price", + calculation: "stats", + tableId: table._id, + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.res.statusMessage).toEqual( + "View TestView saved successfully." + ) }) it("updates the table row with the new view metadata", async () => { - const res = await createView() - expect(res.res.statusMessage).toEqual("View TestView saved successfully."); - const updatedTable = await getDocument(appId, table._id) + const res = await request + .post(`/api/views`) + .send({ + name: "TestView", + field: "Price", + calculation: "stats", + tableId: table._id, + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + + expect(res.res.statusMessage).toEqual( + "View TestView saved successfully." + ) + const updatedTable = await config.getTable(table._id) expect(updatedTable.views).toEqual({ TestView: { field: "Price", @@ -88,88 +79,98 @@ describe("/views", () => { field: { type: "string", }, - } - } - }); + }, + }, + }) }) - }); + }) describe("fetch", () => { beforeEach(async () => { - table = await createTable(request, appId); - }); + table = await config.createTable() + }) it("returns only custom views", async () => { - await createView() + await config.createView({ + name: "TestView", + field: "Price", + calculation: "stats", + tableId: table._id, + }) const res = await request .get(`/api/views`) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) .expect(200) expect(res.body.length).toBe(1) expect(res.body.find(({ name }) => name === "TestView")).toBeDefined() }) - }); + }) describe("query", () => { beforeEach(async () => { - table = await createTable(request, appId); - }); + table = await config.createTable() + }) it("returns data for the created view", async () => { - await createView() - await createRow({ + await config.createView({ + name: "TestView", + field: "Price", + calculation: "stats", tableId: table._id, - Price: 1000 }) - await createRow({ + await config.createRow({ tableId: table._id, - Price: 2000 + Price: 1000, }) - await createRow({ + await config.createRow({ tableId: table._id, - Price: 4000 + Price: 2000, + }) + await config.createRow({ + tableId: table._id, + Price: 4000, }) const res = await request .get(`/api/views/TestView?calculation=stats`) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) .expect(200) expect(res.body.length).toBe(1) expect(res.body).toMatchSnapshot() }) it("returns data for the created view using a group by", async () => { - await createView({ + await config.createView({ calculation: "stats", name: "TestView", field: "Price", groupBy: "Category", - tableId: table._id + tableId: table._id, }) - await createRow({ + await config.createRow({ tableId: table._id, Price: 1000, - Category: "One" + Category: "One", }) - await createRow({ + await config.createRow({ tableId: table._id, Price: 2000, - Category: "One" + Category: "One", }) - await createRow({ + await config.createRow({ tableId: table._id, Price: 4000, - Category: "Two" + Category: "Two", }) const res = await request .get(`/api/views/TestView?calculation=stats&group=Category`) - .set(defaultHeaders(appId)) - .expect('Content-Type', /json/) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) .expect(200) - + expect(res.body.length).toBe(2) expect(res.body).toMatchSnapshot() }) - }); -}); \ No newline at end of file + }) +}) diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index b76177865b..eddd597459 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -49,6 +49,12 @@ const TYPE_TRANSFORM_MAP = { "": null, [undefined]: undefined, [null]: null, + parse: date => { + if (date instanceof Date) { + return date.toISOString() + } + return date + }, }, [FieldTypes.ATTACHMENT]: { "": [],