diff --git a/packages/server/src/api/controllers/table/index.js b/packages/server/src/api/controllers/table/index.js index 93f4ec9f94..4cb1d16146 100644 --- a/packages/server/src/api/controllers/table/index.js +++ b/packages/server/src/api/controllers/table/index.js @@ -65,12 +65,14 @@ exports.save = async function(ctx) { // Don't rename if the name is the same let { _rename } = tableToSave + /* istanbul ignore next */ if (_rename && _rename.old === _rename.updated) { _rename = null delete tableToSave._rename } // rename row fields when table column is renamed + /* istanbul ignore next */ if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) { ctx.throw(400, "Cannot rename a linked column.") } else if (_rename && tableToSave.primaryDisplay === _rename.old) { @@ -159,7 +161,7 @@ exports.destroy = async function(ctx) { ctx.eventEmitter && ctx.eventEmitter.emitTable(`table:delete`, appId, tableToDelete) ctx.status = 200 - ctx.message = `Table ${ctx.params.tableId} deleted.` + ctx.body = { message: `Table ${ctx.params.tableId} deleted.` } } exports.validateCSVSchema = async function(ctx) { diff --git a/packages/server/src/api/controllers/table/utils.js b/packages/server/src/api/controllers/table/utils.js index 73e6e60551..66b3651ccf 100644 --- a/packages/server/src/api/controllers/table/utils.js +++ b/packages/server/src/api/controllers/table/utils.js @@ -90,7 +90,8 @@ exports.handleDataImport = async (user, table, dataImport) => { return table } -exports.handleSearchIndexes = async (db, table) => { +exports.handleSearchIndexes = async (appId, table) => { + const db = new CouchDB(appId) // create relevant search indexes if (table.indexes && table.indexes.length > 0) { const currentIndexes = await db.getIndexes() @@ -150,6 +151,9 @@ class TableSaveFunctions { constructor({ db, ctx, oldTable, dataImport }) { this.db = db this.ctx = ctx + if (this.ctx && this.ctx.user) { + this.appId = this.ctx.user.appId + } this.oldTable = oldTable this.dataImport = dataImport // any rows that need updated @@ -178,7 +182,7 @@ class TableSaveFunctions { // after saving async after(table) { - table = await exports.handleSearchIndexes(this.db, table) + table = await exports.handleSearchIndexes(this.appId, table) table = await exports.handleDataImport( this.ctx.user, table, diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index 05dc299754..f482f3f2a6 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -29,11 +29,13 @@ const controller = { save: async ctx => { const db = new CouchDB(ctx.user.appId) const { originalName, ...viewToSave } = ctx.request.body - const designDoc = await db.get("_design/database") - const view = viewTemplate(viewToSave) + if (!viewToSave.name) { + ctx.throw(400, "Cannot create view without a name") + } + designDoc.views = { ...designDoc.views, [viewToSave.name]: view, @@ -60,17 +62,16 @@ const controller = { await db.put(table) - ctx.body = table.views[viewToSave.name] - ctx.message = `View ${viewToSave.name} saved successfully.` + ctx.body = { + ...table.views[viewToSave.name], + name: viewToSave.name, + } }, destroy: async ctx => { const db = new CouchDB(ctx.user.appId) const designDoc = await db.get("_design/database") - const viewName = decodeURI(ctx.params.viewName) - const view = designDoc.views[viewName] - delete designDoc.views[viewName] await db.put(designDoc) @@ -80,16 +81,17 @@ const controller = { await db.put(table) ctx.body = view - ctx.message = `View ${ctx.params.viewName} saved successfully.` }, exportView: async ctx => { const db = new CouchDB(ctx.user.appId) const designDoc = await db.get("_design/database") - const viewName = decodeURI(ctx.query.view) const view = designDoc.views[viewName] const format = ctx.query.format + if (!format) { + ctx.throw(400, "Format must be specified, either csv or json") + } if (view) { ctx.params.viewName = viewName @@ -102,6 +104,7 @@ const controller = { } } else { // table all_ view + /* istanbul ignore next */ ctx.params.viewName = viewName } diff --git a/packages/server/src/api/routes/tests/misc.spec.js b/packages/server/src/api/routes/tests/misc.spec.js index 3d3b6047e2..2957e42d90 100644 --- a/packages/server/src/api/routes/tests/misc.spec.js +++ b/packages/server/src/api/routes/tests/misc.spec.js @@ -1,6 +1,7 @@ const setup = require("./utilities") +const tableUtils = require("../../controllers/table/utils") -describe("/analytics", () => { +describe("run misc tests", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -10,29 +11,44 @@ describe("/analytics", () => { 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("/analytics", () => { + 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 () => { + await request.get("/health").expect(200) }) }) -}) -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 res = await request.get("/version").expect(200) + expect(res.text.split(".").length).toEqual(3) + }) }) -}) -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) + describe("test table utilities", () => { + it("should be able to import a CSV", async () => { + const table = await config.createTable() + const dataImport = { + csvString: "a,b,c,d\n1,2,3,4" + } + await tableUtils.handleDataImport({ + appId: config.getAppId(), + userId: "test", + }, table, dataImport) + const rows = await config.getRows() + expect(rows[0].a).toEqual("1") + expect(rows[0].b).toEqual("2") + expect(rows[0].c).toEqual("3") + }) }) }) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index f597db0cc0..652a17366d 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -348,7 +348,7 @@ describe("/rows", () => { const view = await config.createView() const row = await config.createRow() const res = await request - .get(`/api/views/${view._id}`) + .get(`/api/views/${view.name}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) diff --git a/packages/server/src/api/routes/tests/table.spec.js b/packages/server/src/api/routes/tests/table.spec.js index 1a2df624f1..df28eed0c2 100644 --- a/packages/server/src/api/routes/tests/table.spec.js +++ b/packages/server/src/api/routes/tests/table.spec.js @@ -1,5 +1,6 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") +const { checkBuilderEndpoint, getDB } = require("./utilities/TestFunctions") const setup = require("./utilities") +const { basicTable } = setup.structures describe("/tables", () => { let request = setup.getRequest() @@ -12,25 +13,22 @@ describe("/tables", () => { }) describe("create", () => { - it("returns a success message when the table is successfully created", done => { - request + it("returns a success message when the table is successfully created", async () => { + const res = await request .post(`/api/tables`) - .send({ + .send({ name: "TestTable", key: "name", schema: { - name: { type: "string" } + name: {type: "string"} } }) .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") + }) it("renames all the row fields for a table when a schema key is renamed", async () => { const testTable = await config.createTable() @@ -46,7 +44,7 @@ describe("/tables", () => { const updatedTable = await request .post(`/api/tables`) - .send({ + .send({ _id: testTable._id, _rev: testTable._rev, name: "TestTable", @@ -56,41 +54,40 @@ describe("/tables", () => { updated: "updatedName" }, schema: { - updatedName: { type: "string" } + updatedName: {type: "string"} } }) .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(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) - const res = await request - .get(`/api/${testTable._id}/rows/${testRow.body._id}`) - .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 checkBuilderEndpoint({ - config, - method: "POST", - url: `/api/tables`, - body: { - name: "TestTable", - key: "name", - schema: { - name: { type: "string" } - } + it("should apply authorization to endpoint", async () => { + await checkBuilderEndpoint({ + config, + method: "POST", + url: `/api/tables`, + body: { + name: "TestTable", + key: "name", + schema: { + name: {type: "string"} } - }) + } }) }) + }) describe("fetch", () => { let testTable @@ -103,28 +100,91 @@ describe("/tables", () => { delete testTable._rev }) - it("returns all the tables for that instance in the response body", done => { - request + it("returns all the tables for that instance in the response body", async () => { + const res = await request .get(`/api/tables`) .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") }) it("should apply authorization to endpoint", async () => { - await checkBuilderEndpoint({ - config, - method: "GET", - url: `/api/tables`, - }) + await checkBuilderEndpoint({ + config, + method: "GET", + url: `/api/tables`, }) }) + }) + + describe("indexing", () => { + it("should be able to create a table with indexes", async () => { + const db = getDB(config) + const indexCount = (await db.getIndexes()).total_rows + const table = basicTable() + table.indexes = ["name"] + const res = await request + .post(`/api/tables`) + .send(table) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body._id).toBeDefined() + expect(res.body._rev).toBeDefined() + expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1) + // update index to see what happens + table.indexes = ["name", "description"] + await request + .post(`/api/tables`) + .send({ + ...table, + _id: res.body._id, + _rev: res.body._rev, + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + // shouldn't have created a new index + expect((await db.getIndexes()).total_rows).toEqual(indexCount + 1) + }) + }) + + describe("updating user table", () => { + it("should add roleId and email field when adjusting user table schema", async () => { + const res = await request + .post(`/api/tables`) + .send({ + ...basicTable(), + _id: "ta_users", + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.schema.email).toBeDefined() + expect(res.body.schema.roleId).toBeDefined() + }) + }) + + describe("validate csv", () => { + it("should be able to validate a CSV layout", async () => { + const res = await request + .post(`/api/tables/csv/validate`) + .send({ + csvString: "a,b,c,d\n1,2,3,4" + }) + .set(config.defaultHeaders()) + .expect('Content-Type', /json/) + .expect(200) + expect(res.body.schema).toBeDefined() + expect(res.body.schema.a).toEqual({ + type: "string", + success: true, + }) + }) + }) describe("destroy", () => { let testTable @@ -137,19 +197,16 @@ describe("/tables", () => { delete testTable._rev }) - it("returns a success response when a table is deleted.", async done => { - request + it("returns a success response when a table is deleted.", async () => { + const res = await request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - .end(async (_, res) => { - expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`) - done() - }) - }) + expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`) + }) - it("deletes linked references to the table after deletion", async done => { + it("deletes linked references to the table after deletion", async () => { const linkedTable = await config.createTable({ name: "LinkedTable", type: "table", @@ -171,18 +228,15 @@ describe("/tables", () => { }, }) - request + const res = await request .delete(`/api/tables/${testTable._id}/${testTable._rev}`) .set(config.defaultHeaders()) .expect('Content-Type', /json/) .expect(200) - .end(async (_, res) => { - expect(res.res.statusMessage).toEqual(`Table ${testTable._id} deleted.`) - const dependentTable = await config.getTable(linkedTable._id) - expect(dependentTable.schema.TestTable).not.toBeDefined() - done() - }) - }) + expect(res.body.message).toEqual(`Table ${testTable._id} deleted.`) + const dependentTable = await config.getTable(linkedTable._id) + expect(dependentTable.schema.TestTable).not.toBeDefined() + }) it("should apply authorization to endpoint", async () => { await checkBuilderEndpoint({ @@ -191,6 +245,5 @@ describe("/tables", () => { url: `/api/tables/${testTable._id}/${testTable._rev}`, }) }) - }) }) diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 534119d279..313b9e63a8 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -1,5 +1,6 @@ const rowController = require("../../../controllers/row") const appController = require("../../../controllers/application") +const CouchDB = require("../../../../db") function Request(appId, params) { this.user = { appId } @@ -77,3 +78,7 @@ exports.checkPermissionsEndpoint = async ({ .set(failHeader) .expect(403) } + +exports.getDB = config => { + return new CouchDB(config.getAppId()) +} diff --git a/packages/server/src/api/routes/tests/view.spec.js b/packages/server/src/api/routes/tests/view.spec.js index a80b09d3a0..3bfbacccbe 100644 --- a/packages/server/src/api/routes/tests/view.spec.js +++ b/packages/server/src/api/routes/tests/view.spec.js @@ -29,9 +29,7 @@ describe("/views", () => { .expect("Content-Type", /json/) .expect(200) - expect(res.res.statusMessage).toEqual( - "View TestView saved successfully." - ) + expect(res.body.tableId).toBe(table._id) }) it("updates the table row with the new view metadata", async () => { @@ -46,10 +44,8 @@ describe("/views", () => { .set(config.defaultHeaders()) .expect("Content-Type", /json/) .expect(200) + expect(res.body.tableId).toBe(table._id) - expect(res.res.statusMessage).toEqual( - "View TestView saved successfully." - ) const updatedTable = await config.getTable(table._id) expect(updatedTable.views).toEqual({ TestView: { @@ -173,4 +169,49 @@ describe("/views", () => { expect(res.body).toMatchSnapshot() }) }) + + describe("destroy", () => { + it("should be able to delete a view", async () => { + const table = await config.createTable() + const view = await config.createView() + const res = await request + .delete(`/api/views/${view.name}`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body.map).toBeDefined() + expect(res.body.meta.tableId).toEqual(table._id) + }) + }) + + describe("exportView", () => { + it("should be able to delete a view", async () => { + await config.createTable() + await config.createRow() + const view = await config.createView() + let res = await request + .get(`/api/views/export?view=${view.name}&format=json`) + .set(config.defaultHeaders()) + .expect(200) + let error + try { + const obj = JSON.parse(res.text) + expect(obj.length).toBe(1) + } catch (err) { + error = err + } + expect(error).toBeUndefined() + res = await request + .get(`/api/views/export?view=${view.name}&format=csv`) + .set(config.defaultHeaders()) + .expect(200) + // this shouldn't be JSON + try { + JSON.parse(res.text) + } catch (err) { + error = err + } + expect(error).toBeDefined() + }) + }) }) diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index f2463b2306..f4d3b4c865 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -3,11 +3,11 @@ const usageQuota = require("../../utilities/usageQuota") const thread = require("../thread") const triggers = require("../triggers") const { basicAutomation, basicTable } = require("../../tests/utilities/structures") -const TestConfig = require("../../tests/utilities/TestConfiguration") const { wait } = require("../../utilities") const env = require("../../environment") const { makePartial } = require("../../tests/utilities") const { cleanInputValues } = require("../automationUtils") +const setup = require("./utilities") let workerJob @@ -31,13 +31,14 @@ jest.mock("worker-farm", () => { }) describe("Run through some parts of the automations system", () => { - let config = new TestConfig(false) + let config = setup.getConfig() beforeEach(async () => { await automation.init() await config.init() }) + afterAll(setup.afterAll) it("should be able to init in builder", async () => { await triggers.externalTrigger(basicAutomation(), { a: 1 }) diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index b5edb1e877..f6dea33a40 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -30,6 +30,7 @@ const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS) allDbs(Pouch) // replicate your local levelDB pouch to a running HTTP compliant couch or pouchdb server. +/* istanbul ignore next */ // eslint-disable-next-line no-unused-vars function replicateLocal() { Pouch.allDbs().then(dbs => { diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index 9651bfb670..b36b45186a 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -171,6 +171,13 @@ class TestConfiguration { return this._req(null, { tableId, rowId }, controllers.row.find) } + async getRows(tableId) { + if (!tableId && this.table) { + tableId = this.table._id + } + return this._req(null, { tableId }, controllers.row.fetchTableRows) + } + async createRole(config = null) { config = config || basicRole() return this._req(config, null, controllers.role.save) @@ -195,6 +202,7 @@ class TestConfiguration { const view = config || { map: "function(doc) { emit(doc[doc.key], doc._id); } ", tableId: this.table._id, + name: "ViewTest", } return this._req(view, null, controllers.view.save) }