Create table test builder

This commit is contained in:
Adria Navarro 2024-12-18 18:34:40 +01:00
parent 460b5fd7dd
commit 9396292c4a
1 changed files with 293 additions and 388 deletions

View File

@ -13,6 +13,8 @@ import { generator } from "@budibase/backend-core/tests"
import { generateViewID } from "../../../../../db/utils" import { generateViewID } from "../../../../../db/utils"
import sdk from "../../../../../sdk" import sdk from "../../../../../sdk"
import { cloneDeep } from "lodash"
import { utils } from "@budibase/shared-core"
jest.mock("../../../../../sdk/app/views", () => ({ jest.mock("../../../../../sdk/app/views", () => ({
...jest.requireActual("../../../../../sdk/app/views"), ...jest.requireActual("../../../../../sdk/app/views"),
@ -23,17 +25,19 @@ const getTableMock = sdk.views.getTable as jest.MockedFunction<
> >
describe("buildSqlFieldList", () => { describe("buildSqlFieldList", () => {
let table: Table & { _id: string } let allTables: Record<string, Table>
beforeEach(() => { class TableConfig {
jest.clearAllMocks() private _table: Table & { _id: string }
table = {
constructor(name: string) {
this._table = {
...structures.tableForDatasource({ ...structures.tableForDatasource({
type: "datasource", type: "datasource",
source: SourceName.POSTGRES, source: SourceName.POSTGRES,
}), }),
name: "table", name,
_id: sql.utils.buildExternalTableId("ds_id", "table"), _id: sql.utils.buildExternalTableId("ds_id", name),
schema: { schema: {
name: { name: {
name: "name", name: "name",
@ -49,10 +53,133 @@ describe("buildSqlFieldList", () => {
}, },
}, },
} }
allTables[name] = this._table
}
withHiddenField(field: string) {
this._table.schema[field].visible = false
return this
}
withField(
name: string,
type:
| FieldType.STRING
| FieldType.NUMBER
| FieldType.FORMULA
| FieldType.AI,
options?: { visible: boolean }
) {
switch (type) {
case FieldType.NUMBER:
case FieldType.STRING:
this._table.schema[name] = {
name,
type,
...options,
}
break
case FieldType.FORMULA:
this._table.schema[name] = {
name,
type,
formula: "any",
...options,
}
break
case FieldType.AI:
this._table.schema[name] = {
name,
type,
operation: AIOperationEnum.PROMPT,
...options,
}
break
default:
utils.unreachable(type)
}
return this
}
withRelation(name: string, toTableId: string) {
this._table.schema[name] = {
name,
type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: toTableId,
}
return this
}
withPrimary(field: string) {
this._table.primary = [field]
return this
}
withDisplay(field: string) {
this._table.primaryDisplay = field
return this
}
create() {
return cloneDeep(this._table)
}
}
class ViewConfig {
private _table: Table
private _view: ViewV2
constructor(table: Table) {
this._table = table
this._view = {
version: 2,
id: generateViewID(table._id!),
name: generator.word(),
tableId: table._id!,
}
}
withVisible(field: string) {
this._view.schema ??= {}
this._view.schema[field] ??= {}
this._view.schema[field].visible = true
return this
}
withHidden(field: string) {
this._view.schema ??= {}
this._view.schema[field] ??= {}
this._view.schema[field].visible = false
return this
}
withRelationshipColumns(
field: string,
columns: Record<string, { visible: boolean }>
) {
this._view.schema ??= {}
this._view.schema[field] ??= {}
this._view.schema[field].columns = columns
return this
}
create() {
getTableMock.mockResolvedValueOnce(this._table)
return cloneDeep(this._view)
}
}
beforeEach(() => {
jest.clearAllMocks()
allTables = {}
}) })
describe("table", () => { describe("table", () => {
it("extracts fields from table schema", async () => { it("extracts fields from table schema", async () => {
const table = new TableConfig("table").create()
const result = await buildSqlFieldList(table, {}) const result = await buildSqlFieldList(table, {})
expect(result).toEqual([ expect(result).toEqual([
"table.name", "table.name",
@ -62,29 +189,19 @@ describe("buildSqlFieldList", () => {
}) })
it("excludes hidden fields", async () => { it("excludes hidden fields", async () => {
table.schema.description.visible = false const table = new TableConfig("table")
.withHiddenField("description")
.create()
const result = await buildSqlFieldList(table, {}) const result = await buildSqlFieldList(table, {})
expect(result).toEqual(["table.name", "table.amount"]) expect(result).toEqual(["table.name", "table.amount"])
}) })
it("excludes non-sql fields fields", async () => { it("excludes non-sql fields fields", async () => {
table.schema.formula = { const table = new TableConfig("table")
name: "formula", .withField("formula", FieldType.FORMULA)
type: FieldType.FORMULA, .withField("ai", FieldType.AI)
formula: "any", .withRelation("link", "otherTableId")
} .create()
table.schema.ai = {
name: "ai",
type: FieldType.AI,
operation: AIOperationEnum.PROMPT,
}
table.schema.link = {
name: "link",
type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: "otherTableId",
}
const result = await buildSqlFieldList(table, {}) const result = await buildSqlFieldList(table, {})
expect(result).toEqual([ expect(result).toEqual([
@ -95,12 +212,10 @@ describe("buildSqlFieldList", () => {
}) })
it("includes hidden fields if there is a formula column", async () => { it("includes hidden fields if there is a formula column", async () => {
table.schema.description.visible = false const table = new TableConfig("table")
table.schema.formula = { .withHiddenField("description")
name: "formula", .withField("formula", FieldType.FORMULA)
type: FieldType.FORMULA, .create()
formula: "any",
}
const result = await buildSqlFieldList(table, {}) const result = await buildSqlFieldList(table, {})
expect(result).toEqual([ expect(result).toEqual([
@ -111,31 +226,15 @@ describe("buildSqlFieldList", () => {
}) })
it("includes relationships fields when flagged", async () => { it("includes relationships fields when flagged", async () => {
const otherTable: Table = { const otherTable = new TableConfig("linkedTable")
...table, .withField("id", FieldType.NUMBER)
name: "linkedTable", .withPrimary("id")
primary: ["id"], .withDisplay("name")
primaryDisplay: "name", .create()
schema: {
...table.schema,
id: {
name: "id",
type: FieldType.NUMBER,
},
},
}
table.schema.link = { const table = new TableConfig("table")
name: "link", .withRelation("link", otherTable._id)
type: FieldType.LINK, .create()
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
const allTables: Record<string, Table> = {
otherTableId: otherTable,
}
const result = await buildSqlFieldList(table, allTables, { const result = await buildSqlFieldList(table, allTables, {
relationships: true, relationships: true,
@ -150,56 +249,42 @@ describe("buildSqlFieldList", () => {
}) })
it("includes all relationship fields if there is a formula column", async () => { it("includes all relationship fields if there is a formula column", async () => {
const otherTable: Table = { const otherTable = new TableConfig("linkedTable")
...table, .withField("hidden", FieldType.STRING, { visible: false })
name: "linkedTable", .create()
schema: {
...table.schema,
id: {
name: "id",
type: FieldType.NUMBER,
},
hidden: {
name: "other",
type: FieldType.STRING,
visible: false,
},
formula: {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
},
ai: {
name: "ai",
type: FieldType.AI,
operation: AIOperationEnum.PROMPT,
},
link: {
name: "link",
type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: "otherTableId",
},
},
}
table.schema.link = { const table = new TableConfig("table")
name: "link", .withRelation("link", otherTable._id)
type: FieldType.LINK, .withField("formula", FieldType.FORMULA)
relationshipType: RelationshipType.ONE_TO_MANY, .create()
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
table.schema.formula = {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
}
const allTables: Record<string, Table> = { const result = await buildSqlFieldList(table, allTables, {
otherTableId: otherTable, relationships: true,
} })
expect(result).toEqual([
"table.name",
"table.description",
"table.amount",
"linkedTable.name",
"linkedTable.description",
"linkedTable.amount",
"linkedTable.hidden",
])
})
it("never includes non-sql columns from relationships", async () => {
const otherTable = new TableConfig("linkedTable")
.withField("id", FieldType.NUMBER)
.withField("hidden", FieldType.STRING, { visible: false })
.withField("formula", FieldType.FORMULA)
.withField("ai", FieldType.AI)
.withRelation("link", "otherTableId")
.create()
const table = new TableConfig("table")
.withRelation("link", otherTable._id)
.withField("formula", FieldType.FORMULA)
.create()
const result = await buildSqlFieldList(table, allTables, { const result = await buildSqlFieldList(table, allTables, {
relationships: true, relationships: true,
@ -215,107 +300,28 @@ describe("buildSqlFieldList", () => {
"linkedTable.hidden", "linkedTable.hidden",
]) ])
}) })
it("never includes non-sql columns from relationships", async () => {
const otherTable: Table = {
...table,
name: "linkedTable",
schema: {
id: {
name: "id",
type: FieldType.NUMBER,
},
hidden: {
name: "other",
type: FieldType.STRING,
visible: false,
},
formula: {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
},
ai: {
name: "ai",
type: FieldType.AI,
operation: AIOperationEnum.PROMPT,
},
link: {
name: "link",
type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: "otherTableId",
},
},
}
table.schema.link = {
name: "link",
type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
table.schema.formula = {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
}
const allTables: Record<string, Table> = {
otherTableId: otherTable,
}
const result = await buildSqlFieldList(table, allTables, {
relationships: true,
})
expect(result).toEqual([
"table.name",
"table.description",
"table.amount",
"linkedTable.id",
"linkedTable.hidden",
])
})
}) })
describe("view", () => { describe("view", () => {
let view: ViewV2
beforeEach(() => {
getTableMock.mockResolvedValueOnce(table)
view = {
version: 2,
id: generateViewID(table._id),
name: generator.word(),
tableId: table._id,
}
})
it("extracts fields from table schema", async () => { it("extracts fields from table schema", async () => {
view.schema = { const view = new ViewConfig(new TableConfig("table").create())
name: { visible: false }, .withVisible("amount")
amount: { visible: true }, .withHidden("name")
} .create()
const result = await buildSqlFieldList(view, {}) const result = await buildSqlFieldList(view, {})
expect(result).toEqual(["table.amount"]) expect(result).toEqual(["table.amount"])
}) })
it("includes all fields if there is a formula column", async () => { it("includes all fields if there is a formula column", async () => {
table.schema.formula = { const table = new TableConfig("table")
name: "formula", .withField("formula", FieldType.FORMULA)
type: FieldType.FORMULA, .create()
formula: "any", const view = new ViewConfig(table)
} .withHidden("name")
.withVisible("amount")
view.schema = { .withVisible("formula")
name: { visible: false }, .create()
amount: { visible: true },
formula: { visible: true },
}
const result = await buildSqlFieldList(view, {}) const result = await buildSqlFieldList(view, {})
expect(result).toEqual([ expect(result).toEqual([
@ -326,53 +332,35 @@ describe("buildSqlFieldList", () => {
}) })
it("does not includes all fields if the formula column is not included", async () => { it("does not includes all fields if the formula column is not included", async () => {
table.schema.formula = { const table = new TableConfig("table")
name: "formula", .withField("formula", FieldType.FORMULA)
type: FieldType.FORMULA, .create()
formula: "any", const view = new ViewConfig(table)
} .withHidden("name")
.withVisible("amount")
view.schema = { .withHidden("formula")
name: { visible: false }, .create()
amount: { visible: true },
formula: { visible: false },
}
const result = await buildSqlFieldList(view, {}) const result = await buildSqlFieldList(view, {})
expect(result).toEqual(["table.amount"]) expect(result).toEqual(["table.amount"])
}) })
it("includes relationships fields when flagged", async () => { it("includes relationships fields when flagged", async () => {
const otherTable: Table = { const otherTable = new TableConfig("linkedTable")
...table, .withField("id", FieldType.NUMBER)
name: "linkedTable", .withPrimary("id")
primary: ["id"], .withDisplay("name")
primaryDisplay: "name", .create()
schema: {
...table.schema,
id: {
name: "id",
type: FieldType.NUMBER,
},
},
}
table.schema.link = { const table = new TableConfig("table")
name: "link", .withRelation("link", otherTable._id)
type: FieldType.LINK, .withField("formula", FieldType.FORMULA)
relationshipType: RelationshipType.ONE_TO_MANY, .create()
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
view.schema = { const view = new ViewConfig(table)
name: { visible: true }, .withVisible("name")
link: { visible: true }, .withHidden("amount")
} .create()
const allTables: Record<string, Table> = {
otherTableId: otherTable,
}
const result = await buildSqlFieldList(view, allTables, { const result = await buildSqlFieldList(view, allTables, {
relationships: true, relationships: true,
@ -385,47 +373,25 @@ describe("buildSqlFieldList", () => {
}) })
it("includes relationships columns", async () => { it("includes relationships columns", async () => {
const otherTable: Table = { const otherTable = new TableConfig("linkedTable")
...table, .withField("id", FieldType.NUMBER)
name: "linkedTable", .withField("formula", FieldType.FORMULA)
primary: ["id"], .withPrimary("id")
schema: { .create()
...table.schema,
id: {
name: "id",
type: FieldType.NUMBER,
},
formula: {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
},
},
}
table.schema.link = { const table = new TableConfig("table")
name: "link", .withRelation("link", otherTable._id)
type: FieldType.LINK, .create()
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
view.schema = { const view = new ViewConfig(table)
name: { visible: true }, .withVisible("name")
link: { .withVisible("link")
visible: true, .withRelationshipColumns("link", {
columns: {
name: { visible: false }, name: { visible: false },
amount: { visible: true }, amount: { visible: true },
formula: { visible: false }, formula: { visible: false },
}, })
}, .create()
}
const allTables: Record<string, Table> = {
otherTableId: otherTable,
}
const result = await buildSqlFieldList(view, allTables, { const result = await buildSqlFieldList(view, allTables, {
relationships: true, relationships: true,
@ -438,47 +404,25 @@ describe("buildSqlFieldList", () => {
}) })
it("does not include relationships columns for hidden links", async () => { it("does not include relationships columns for hidden links", async () => {
const otherTable: Table = { const otherTable = new TableConfig("linkedTable")
...table, .withField("id", FieldType.NUMBER)
name: "linkedTable", .withField("formula", FieldType.FORMULA)
primary: ["id"], .withPrimary("id")
schema: { .create()
...table.schema,
id: {
name: "id",
type: FieldType.NUMBER,
},
formula: {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
},
},
}
table.schema.link = { const table = new TableConfig("table")
name: "link", .withRelation("link", otherTable._id)
type: FieldType.LINK, .create()
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
view.schema = { const view = new ViewConfig(table)
name: { visible: true }, .withVisible("name")
link: { .withHidden("link")
visible: false, .withRelationshipColumns("link", {
columns: {
name: { visible: false }, name: { visible: false },
amount: { visible: true }, amount: { visible: true },
formula: { visible: false }, formula: { visible: false },
}, })
}, .create()
}
const allTables: Record<string, Table> = {
otherTableId: otherTable,
}
const result = await buildSqlFieldList(view, allTables, { const result = await buildSqlFieldList(view, allTables, {
relationships: true, relationships: true,
@ -487,69 +431,30 @@ describe("buildSqlFieldList", () => {
}) })
it("includes all relationship fields if there is a formula column", async () => { it("includes all relationship fields if there is a formula column", async () => {
const otherTable: Table = { const otherTable = new TableConfig("linkedTable")
...table, .withField("id", FieldType.NUMBER)
name: "linkedTable", .withField("hidden", FieldType.STRING, { visible: false })
schema: { .withField("formula", FieldType.FORMULA)
...table.schema, .withField("ai", FieldType.AI)
id: { .withRelation("link", "otherTableId")
name: "id", .withPrimary("id")
type: FieldType.NUMBER, .create()
},
hidden: {
name: "other",
type: FieldType.STRING,
visible: false,
},
formula: {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
},
ai: {
name: "ai",
type: FieldType.AI,
operation: AIOperationEnum.PROMPT,
},
link: {
name: "link",
type: FieldType.LINK,
relationshipType: RelationshipType.ONE_TO_MANY,
fieldName: "link",
tableId: "otherTableId",
},
},
}
table.schema.link = { const table = new TableConfig("table")
name: "link", .withRelation("link", otherTable._id)
type: FieldType.LINK, .withField("formula", FieldType.FORMULA)
relationshipType: RelationshipType.ONE_TO_MANY, .create()
fieldName: "link",
tableId: sql.utils.buildExternalTableId("ds_id", "otherTableId"),
}
table.schema.formula = {
name: "formula",
type: FieldType.FORMULA,
formula: "any",
}
view.schema = { const view = new ViewConfig(table)
name: { visible: true }, .withVisible("name")
formula: { visible: true }, .withVisible("formula")
link: { .withHidden("link")
visible: false, .withRelationshipColumns("link", {
columns: {
name: { visible: false }, name: { visible: false },
amount: { visible: true }, amount: { visible: true },
formula: { visible: false }, formula: { visible: false },
}, })
}, .create()
}
const allTables: Record<string, Table> = {
otherTableId: otherTable,
}
const result = await buildSqlFieldList(view, allTables, { const result = await buildSqlFieldList(view, allTables, {
relationships: true, relationships: true,