Skip to content

Commit

Permalink
Merge pull request #1296 from Budibase/tests/relationships
Browse files Browse the repository at this point in the history
Server relationship tests
  • Loading branch information
Michael Drury committed Mar 18, 2021
2 parents 06ede44 + de4a413 commit 9f8d212
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/server/package.json
Expand Up @@ -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",
Expand Down
37 changes: 23 additions & 14 deletions packages/server/src/db/linkedRows/LinkController.js
Expand Up @@ -133,37 +133,44 @@ 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) {
const compareFields = ["name", "type", "tableId", "fieldName", "autocolumn"]
areLinkSchemasEqual(linkSchema1, linkSchema2) {
const compareFields = [
"name",
"type",
"tableId",
"fieldName",
"autocolumn",
"relationshipType",
]
for (let field of compareFields) {
if (schema1[field] !== schema2[field]) {
if (linkSchema1[field] !== linkSchema2[field]) {
return false
}
}
return true
}

/**
* 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
Expand Down Expand Up @@ -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, {
Expand All @@ -347,7 +355,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) {
Expand All @@ -358,7 +366,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.")
}
Expand Down Expand Up @@ -416,6 +424,7 @@ class LinkController {
await this._db.put(linkedTable)
}
} catch (err) {
/* istanbul ignore next */
Sentry.captureException(err)
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/db/linkedRows/linkUtils.js
Expand Up @@ -75,6 +75,7 @@ exports.getLinkDocuments = async function({
await exports.createLinkView(appId)
return exports.getLinkDocuments(arguments[0])
} else {
/* istanbul ignore next */
Sentry.captureException(err)
}
}
Expand Down
218 changes: 218 additions & 0 deletions packages/server/src/db/tests/linkController.spec.js
@@ -0,0 +1,218 @@
const TestConfig = require("../../tests/utilities/TestConfiguration")
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)
let table1, table2

beforeEach(async () => {
await config.init()
const { _id } = await config.createTable()
table2 = await config.createLinkedTable(RelationshipTypes.MANY_TO_MANY, ["link", "link2"])
// 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)
}

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)
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)
})

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)
})
})

0 comments on commit 9f8d212

Please sign in to comment.