From fbbb3d12a1c74e4cb69a6cf81ff2602cf4b09600 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 16 Mar 2021 13:24:44 +0000 Subject: [PATCH 1/4] starting into relationship testing. --- .../src/db/linkedRows/LinkController.js | 26 ++++---- .../src/db/tests/linkController.spec.js | 59 +++++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 packages/server/src/db/tests/linkController.spec.js diff --git a/packages/server/src/db/linkedRows/LinkController.js b/packages/server/src/db/linkedRows/LinkController.js index 433bf57ad44..300645638bf 100644 --- a/packages/server/src/db/linkedRows/LinkController.js +++ b/packages/server/src/db/linkedRows/LinkController.js @@ -133,12 +133,12 @@ class LinkController { } /** - * Returns whether the two schemas are equal (in the important parts, not a pure equality check) + * Returns whether the two link schemas are equal (in the important parts, not a pure equality check) */ - areSchemasEqual(schema1, schema2) { + areLinkSchemasEqual(linkSchema1, linkSchema2) { const compareFields = ["name", "type", "tableId", "fieldName", "autocolumn"] for (let field of compareFields) { - if (schema1[field] !== schema2[field]) { + if (linkSchema1[field] !== linkSchema2[field]) { return false } } @@ -146,24 +146,24 @@ class LinkController { } /** - * Given two the field of this table, and the field of the linked table, this makes sure + * Given the link field of this table, and the link field of the linked table, this makes sure * the state of relationship type is accurate on both. */ - handleRelationshipType(field, linkedField) { + handleRelationshipType(linkerField, linkedField) { if ( - !field.relationshipType || - field.relationshipType === RelationshipTypes.MANY_TO_MANY + !linkerField.relationshipType || + linkerField.relationshipType === RelationshipTypes.MANY_TO_MANY ) { linkedField.relationshipType = RelationshipTypes.MANY_TO_MANY // make sure by default all are many to many (if not specified) - field.relationshipType = RelationshipTypes.MANY_TO_MANY - } else if (field.relationshipType === RelationshipTypes.MANY_TO_ONE) { + linkerField.relationshipType = RelationshipTypes.MANY_TO_MANY + } else if (linkerField.relationshipType === RelationshipTypes.MANY_TO_ONE) { // Ensure that the other side of the relationship is locked to one record linkedField.relationshipType = RelationshipTypes.ONE_TO_MANY - } else if (field.relationshipType === RelationshipTypes.ONE_TO_MANY) { + } else if (linkerField.relationshipType === RelationshipTypes.ONE_TO_MANY) { linkedField.relationshipType = RelationshipTypes.MANY_TO_ONE } - return { field, linkedField } + return { linkerField, linkedField } } // all operations here will assume that the table @@ -347,7 +347,7 @@ class LinkController { }) // update table schema after checking relationship types - schema[fieldName] = fields.field + schema[fieldName] = fields.linkerField const linkedField = fields.linkedField if (field.autocolumn) { @@ -358,7 +358,7 @@ class LinkController { const existingSchema = linkedTable.schema[field.fieldName] if ( existingSchema != null && - !this.areSchemasEqual(existingSchema, linkedField) + !this.areLinkSchemasEqual(existingSchema, linkedField) ) { throw new Error("Cannot overwrite existing column.") } diff --git a/packages/server/src/db/tests/linkController.spec.js b/packages/server/src/db/tests/linkController.spec.js new file mode 100644 index 00000000000..79b22bd3402 --- /dev/null +++ b/packages/server/src/db/tests/linkController.spec.js @@ -0,0 +1,59 @@ +const TestConfig = require("../../tests/utilities/TestConfiguration") +const { basicTable } = require("../../tests/utilities/structures") +const LinkController = require("../linkedRows/LinkController") +const { RelationshipTypes } = require("../../constants") + +describe("test the link controller", () => { + let config = new TestConfig(false) + let table1, table2 + + beforeEach(async () => { + await config.init() + const { _id } = await config.createTable() + table2 = await config.createLinkedTable() + // update table after creating link + table1 = await config.getTable(_id) + }) + + afterAll(config.end) + + function createLinkController(table, row = null, oldTable = null) { + const linkConfig = { + appId: config.getAppId(), + tableId: table._id, + table, + } + if (row) { + linkConfig.row = row + } + if (oldTable) { + linkConfig.oldTable = oldTable + } + return new LinkController(linkConfig) + } + + it("should be able to confirm if two table schemas are equal", () => { + const controller = createLinkController(table1) + let equal = controller.areLinkSchemasEqual(table2.schema.link, table2.schema.link) + expect(equal).toEqual(true) + equal = controller.areLinkSchemasEqual(table1.schema.link, table2.schema.link) + expect(equal).toEqual(false) + }) + + it("should be able to check the relationship types across two fields", () => { + const controller = createLinkController(table1) + // empty case + let output = controller.handleRelationshipType({}, {}) + expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY) + expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY) + output = controller.handleRelationshipType({ relationshipType: RelationshipTypes.MANY_TO_MANY }, {}) + expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY) + expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.MANY_TO_MANY) + output = controller.handleRelationshipType({ relationshipType: RelationshipTypes.MANY_TO_ONE }, {}) + expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.ONE_TO_MANY) + expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.MANY_TO_ONE) + output = controller.handleRelationshipType({ relationshipType: RelationshipTypes.ONE_TO_MANY }, {}) + expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_ONE) + expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.ONE_TO_MANY) + }) +}) From d6b23b3a79e0b6a7bd34f696b2c100ecfee68a8b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 16 Mar 2021 18:13:00 +0000 Subject: [PATCH 2/4] Adding test cases which bring the link controller coverage to 100%. --- .../src/db/linkedRows/LinkController.js | 11 +- .../src/db/tests/linkController.spec.js | 163 +++++++++++++++++- .../src/tests/utilities/TestConfiguration.js | 21 ++- .../server/src/tests/utilities/structures.js | 8 + 4 files changed, 193 insertions(+), 10 deletions(-) diff --git a/packages/server/src/db/linkedRows/LinkController.js b/packages/server/src/db/linkedRows/LinkController.js index 300645638bf..53ce8e45ad7 100644 --- a/packages/server/src/db/linkedRows/LinkController.js +++ b/packages/server/src/db/linkedRows/LinkController.js @@ -136,7 +136,14 @@ class LinkController { * Returns whether the two link schemas are equal (in the important parts, not a pure equality check) */ areLinkSchemasEqual(linkSchema1, linkSchema2) { - const compareFields = ["name", "type", "tableId", "fieldName", "autocolumn"] + const compareFields = [ + "name", + "type", + "tableId", + "fieldName", + "autocolumn", + "relationshipType", + ] for (let field of compareFields) { if (linkSchema1[field] !== linkSchema2[field]) { return false @@ -336,6 +343,7 @@ class LinkController { try { linkedTable = await this._db.get(field.tableId) } catch (err) { + /* istanbul ignore next */ continue } const fields = this.handleRelationshipType(field, { @@ -416,6 +424,7 @@ class LinkController { await this._db.put(linkedTable) } } catch (err) { + /* istanbul ignore next */ Sentry.captureException(err) } } diff --git a/packages/server/src/db/tests/linkController.spec.js b/packages/server/src/db/tests/linkController.spec.js index 79b22bd3402..d45bd99ea26 100644 --- a/packages/server/src/db/tests/linkController.spec.js +++ b/packages/server/src/db/tests/linkController.spec.js @@ -1,7 +1,8 @@ const TestConfig = require("../../tests/utilities/TestConfiguration") -const { basicTable } = require("../../tests/utilities/structures") +const { basicRow, basicLinkedRow, basicTable } = require("../../tests/utilities/structures") const LinkController = require("../linkedRows/LinkController") const { RelationshipTypes } = require("../../constants") +const { cloneDeep } = require("lodash/fp") describe("test the link controller", () => { let config = new TestConfig(false) @@ -10,7 +11,7 @@ describe("test the link controller", () => { beforeEach(async () => { await config.init() const { _id } = await config.createTable() - table2 = await config.createLinkedTable() + table2 = await config.createLinkedTable(RelationshipTypes.MANY_TO_MANY, ["link", "link2"]) // update table after creating link table1 = await config.getTable(_id) }) @@ -32,6 +33,12 @@ describe("test the link controller", () => { return new LinkController(linkConfig) } + async function createLinkedRow(linkField = "link", t1 = table1, t2 = table2) { + const row = await config.createRow(basicRow(t2._id)) + const { _id } = await config.createRow(basicLinkedRow(t1._id, row._id, linkField)) + return config.getRow(t1._id, _id) + } + it("should be able to confirm if two table schemas are equal", () => { const controller = createLinkController(table1) let equal = controller.areLinkSchemasEqual(table2.schema.link, table2.schema.link) @@ -56,4 +63,156 @@ describe("test the link controller", () => { expect(output.linkedField.relationshipType).toEqual(RelationshipTypes.MANY_TO_ONE) expect(output.linkerField.relationshipType).toEqual(RelationshipTypes.ONE_TO_MANY) }) + + it("should be able to delete a row", async () => { + const row = await createLinkedRow() + const controller = createLinkController(table1, row) + // get initial count + const beforeLinks = await controller.getRowLinkDocs(row._id) + await controller.rowDeleted() + let afterLinks = await controller.getRowLinkDocs(row._id) + expect(beforeLinks.length).toEqual(1) + expect(afterLinks.length).toEqual(0) + }) + + it("shouldn't throw an error when deleting a row with no links", async () => { + const row = await config.createRow(basicRow(table1._id)) + const controller = createLinkController(table1, row) + let error + try { + await controller.rowDeleted() + } catch (err) { + error = err + } + expect(error).toBeUndefined() + }) + + it("should throw an error when validating a table which is invalid", () => { + const controller = createLinkController(table1) + const copyTable = { + ...table1 + } + copyTable.schema.otherTableLink = { + type: "link", + fieldName: "link", + tableId: table2._id, + } + let error + try { + controller.validateTable(copyTable) + } catch (err) { + error = err + } + expect(error).toBeDefined() + expect(error.message).toEqual("Cannot re-use the linked column name for a linked table.") + }) + + it("should be able to remove a link when saving/updating the row", async () => { + const row = await createLinkedRow() + // remove the link from the row + row.link = [] + const controller = createLinkController(table1, row) + await controller.rowSaved() + let links = await controller.getRowLinkDocs(row._id) + expect(links.length).toEqual(0) + }) + + it("should be able to delete a table and have links deleted", async () => { + await createLinkedRow() + const controller = createLinkController(table1) + let before = await controller.getTableLinkDocs() + await controller.tableDeleted() + let after = await controller.getTableLinkDocs() + expect(before.length).toEqual(1) + expect(after.length).toEqual(0) + }) + + it("should be able to remove a linked field from a table", async () => { + await createLinkedRow() + await createLinkedRow("link2") + const controller = createLinkController(table1, null, table1) + let before = await controller.getTableLinkDocs() + await controller.removeFieldFromTable("link") + let after = await controller.getTableLinkDocs() + expect(before.length).toEqual(2) + // shouldn't delete the other field + expect(after.length).toEqual(1) + }) + + it("should throw an error when overwriting a link column", async () => { + const update = cloneDeep(table1) + update.schema.link.relationshipType = RelationshipTypes.MANY_TO_ONE + let error + try { + const controller = createLinkController(update) + await controller.tableSaved() + } catch (err) { + error = err + } + expect(error).toBeDefined() + }) + + it("should be able to remove a field view table update", async () => { + await createLinkedRow() + await createLinkedRow() + const newTable = cloneDeep(table1) + delete newTable.schema.link + const controller = createLinkController(newTable, null, table1) + await controller.tableUpdated() + const links = await controller.getTableLinkDocs() + expect(links.length).toEqual(0) + }) + + it("shouldn't allow one to many having many relationships against it", async () => { + const firstTable = await config.createTable() + const { _id } = await config.createLinkedTable(RelationshipTypes.MANY_TO_ONE, ["link"]) + const linkTable = await config.getTable(_id) + // an initial row to link around + const row = await createLinkedRow("link", linkTable, firstTable) + let error + try { + // create another row to initiate the error + await config.createRow(basicLinkedRow(row.tableId, row.link[0])) + } catch (err) { + error = err + } + expect(error).toBeDefined() + }) + + it("should not error if a link being created doesn't exist", async () => { + let error + try { + await config.createRow(basicLinkedRow(table1._id, "invalid")) + } catch (err) { + error = err + } + expect(error).toBeUndefined() + }) + + it("make sure auto column goes onto other row too", async () => { + const table = await config.createTable() + const tableCfg = basicTable() + tableCfg.schema.link = { + type: "link", + fieldName: "link", + tableId: table._id, + name: "link", + autocolumn: true, + } + await config.createTable(tableCfg) + const afterTable = await config.getTable(table._id) + expect(afterTable.schema.link.autocolumn).toBe(true) + }) + + it("should be able to link to self", async () => { + const table = await config.createTable() + table.schema.link = { + type: "link", + fieldName: "link", + tableId: table._id, + name: "link", + autocolumn: true, + } + await config.updateTable(table) + }) }) diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index b36b45186af..d51274cd555 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -135,16 +135,22 @@ class TestConfiguration { return this._req(null, { id: tableId }, controllers.table.find) } - async createLinkedTable() { + async createLinkedTable(relationshipType, links = ["link"]) { 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, + for (let link of links) { + tableConfig.schema[link] = { + type: "link", + fieldName: link, + tableId: this.table._id, + name: link, + } + if (relationshipType) { + tableConfig.schema[link].relationshipType = relationshipType + } } const linkedTable = await this.createTable(tableConfig) this.linkedTable = linkedTable @@ -163,8 +169,9 @@ class TestConfiguration { 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) + const tableId = (config && config.tableId) || this.table._id + config = config || basicRow(tableId) + return this._req(config, { tableId }, controllers.row.save) } async getRow(tableId, rowId) { diff --git a/packages/server/src/tests/utilities/structures.js b/packages/server/src/tests/utilities/structures.js index e6489f09031..5e27ff4a3b4 100644 --- a/packages/server/src/tests/utilities/structures.js +++ b/packages/server/src/tests/utilities/structures.js @@ -53,6 +53,14 @@ exports.basicRow = tableId => { } } +exports.basicLinkedRow = (tableId, linkedRowId, linkField = "link") => { + // this is based on the basic linked tables you get from the test configuration + return { + ...exports.basicRow(tableId), + [linkField]: [linkedRowId], + } +} + exports.basicRole = () => { return { name: "NewRole", From b1e443f5814b76a6671a373d196a5000b7103487 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 16 Mar 2021 23:25:18 +0000 Subject: [PATCH 3/4] Upping relationship coverage overall to 98% - looking over utilities which are barely ever used. --- .../server/src/db/linkedRows/linkUtils.js | 1 + .../server/src/db/tests/linkTests.spec.js | 74 +++++++++++++++++++ .../src/tests/utilities/TestConfiguration.js | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/db/tests/linkTests.spec.js diff --git a/packages/server/src/db/linkedRows/linkUtils.js b/packages/server/src/db/linkedRows/linkUtils.js index 7193e59465c..9200a05b98b 100644 --- a/packages/server/src/db/linkedRows/linkUtils.js +++ b/packages/server/src/db/linkedRows/linkUtils.js @@ -75,6 +75,7 @@ exports.getLinkDocuments = async function({ await exports.createLinkView(appId) return exports.getLinkDocuments(arguments[0]) } else { + /* istanbul ignore next */ Sentry.captureException(err) } } diff --git a/packages/server/src/db/tests/linkTests.spec.js b/packages/server/src/db/tests/linkTests.spec.js new file mode 100644 index 00000000000..3fed6938b71 --- /dev/null +++ b/packages/server/src/db/tests/linkTests.spec.js @@ -0,0 +1,74 @@ +const TestConfig = require("../../tests/utilities/TestConfiguration") +const { basicTable, basicLinkedRow } = require("../../tests/utilities/structures") +const linkUtils = require("../linkedRows/linkUtils") +const links = require("../linkedRows") +const CouchDB = require("../index") + +describe("test link functionality", () => { + const config = new TestConfig(false) + + describe("getLinkedTable", () => { + let db, table + beforeEach(async () => { + await config.init() + db = new CouchDB(config.getAppId()) + table = await config.createTable() + }) + + it("should be able to retrieve a linked table from a list", async () => { + const retrieved = await linkUtils.getLinkedTable(db, table._id, [table]) + expect(retrieved._id).toBe(table._id) + }) + + it("should be able to retrieve a table from DB and update list", async () => { + const tables = [] + const retrieved = await linkUtils.getLinkedTable(db, table._id, tables) + expect(retrieved._id).toBe(table._id) + expect(tables[0]).toBeDefined() + }) + }) + + describe("getRelatedTableForField", () => { + let link = basicTable() + link.schema.link = { + fieldName: "otherLink", + tableId: "tableID", + type: "link", + } + + it("should get the field from the table directly", () => { + expect(linkUtils.getRelatedTableForField(link, "link")).toBe("tableID") + }) + + it("should get the field from the link", () => { + expect(linkUtils.getRelatedTableForField(link, "otherLink")).toBe("tableID") + }) + }) + + describe("getLinkDocuments", () => { + it("should create the link view when it doesn't exist", async () => { + // create the DB and a very basic app design DB + const db = new CouchDB("test") + await db.put({ _id: "_design/database", views: {} }) + const output = await linkUtils.getLinkDocuments({ + appId: "test", + tableId: "test", + rowId: "test", + includeDocs: false, + }) + expect(Array.isArray(output)).toBe(true) + }) + }) + + describe("attachLinkIDs", () => { + it("should be able to attach linkIDs", async () => { + await config.init() + await config.createTable() + const table = await config.createLinkedTable() + const row = await config.createRow() + const linkRow = await config.createRow(basicLinkedRow(table._id, row._id)) + const attached = await links.attachLinkIDs(config.getAppId(), [linkRow]) + expect(attached[0].link[0]).toBe(row._id) + }) + }) +}) \ No newline at end of file diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index d51274cd555..a12d5965345 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -135,7 +135,7 @@ class TestConfiguration { return this._req(null, { id: tableId }, controllers.table.find) } - async createLinkedTable(relationshipType, links = ["link"]) { + async createLinkedTable(relationshipType = null, links = ["link"]) { if (!this.table) { throw "Must have created a table first." } From de4a413b988981bc861734840c512bb2b2f57410 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 16 Mar 2021 23:30:20 +0000 Subject: [PATCH 4/4] Adding db test directory to list of ignored from coverage. --- packages/server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/package.json b/packages/server/package.json index 6f52f1ac36c..6746b01c890 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -59,6 +59,7 @@ "!src/db/dynamoClient.js", "!src/utilities/usageQuota.js", "!src/api/routes/tests/**/*", + "!src/db/tests/**/*", "!src/tests/**/*", "!src/automations/tests/**/*", "!src/utilities/fileProcessor.js",