1
0
Fork 0
mirror of synced 2024-09-09 22:16:26 +12:00

Validate schema

This commit is contained in:
Adria Navarro 2024-05-31 17:08:50 +02:00
parent 5912c2b129
commit efc9d3399e
2 changed files with 153 additions and 9 deletions

View file

@ -23,9 +23,6 @@ import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
import merge from "lodash/merge"
import { quotas } from "@budibase/pro"
import { roles } from "@budibase/backend-core"
import * as schemaUtils from "../../../utilities/schema"
jest.mock("../../../utilities/schema")
describe.each([
["internal", undefined],
@ -318,15 +315,13 @@ describe.each([
})
it("required fields cannot be marked as readonly", async () => {
const isRequiredSpy = jest.spyOn(schemaUtils, "isRequired")
isRequiredSpy.mockReturnValueOnce(true)
const table = await config.api.table.save(
saveTableRequest({
schema: {
name: {
name: "name",
type: FieldType.STRING,
constraints: { presence: true },
},
description: {
name: "description",
@ -1347,4 +1342,123 @@ describe.each([
})
})
})
describe("updating table schema", () => {
describe("existing columns changed to required", () => {
beforeEach(async () => {
table = await config.api.table.save(
saveTableRequest({
schema: {
id: {
name: "id",
type: FieldType.AUTO,
autocolumn: true,
},
name: {
name: "name",
type: FieldType.STRING,
},
},
})
)
})
it("allows updating when no views constrains the field", async () => {
await config.api.viewV2.create({
name: "view a",
tableId: table._id!,
schema: {
id: { visible: true },
name: { visible: true },
},
})
table = await config.api.table.get(table._id!)
await config.api.table.save(
{
...table,
schema: {
...table.schema,
name: {
name: "name",
type: FieldType.STRING,
constraints: { presence: { allowEmpty: false } },
},
},
},
{ status: 200 }
)
})
it("rejects if field is readonly in any view", async () => {
mocks.licenses.useViewReadonlyColumns()
await config.api.viewV2.create({
name: "view a",
tableId: table._id!,
schema: {
id: { visible: true },
name: {
visible: true,
readonly: true,
},
},
})
table = await config.api.table.get(table._id!)
await config.api.table.save(
{
...table,
schema: {
...table.schema,
name: {
name: "name",
type: FieldType.STRING,
constraints: { presence: true },
},
},
},
{
status: 400,
body: {
status: 400,
message:
'Invalid body - Required field "name" is missing in view "view a"',
},
}
)
})
it("rejects if field is hidden in any view", async () => {
await config.api.viewV2.create({
name: "view a",
tableId: table._id!,
schema: { id: { visible: true } },
})
table = await config.api.table.get(table._id!)
await config.api.table.save(
{
...table,
schema: {
...table.schema,
name: {
name: "name",
type: FieldType.STRING,
constraints: { presence: true },
},
},
},
{
status: 400,
body: {
status: 400,
message:
'Invalid body - Required field "name" is missing in view "view a"',
},
}
)
})
})
})
})

View file

@ -1,14 +1,44 @@
import { auth, permissions } from "@budibase/backend-core"
import { DataSourceOperation } from "../../../constants"
import { WebhookActionType } from "@budibase/types"
import Joi from "joi"
import { Table, WebhookActionType } from "@budibase/types"
import Joi, { CustomValidator } from "joi"
import { ValidSnippetNameRegex } from "@budibase/shared-core"
import { isRequired } from "../../../utilities/schema"
import sdk from "../../../sdk"
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
const APP_NAME_REGEX = /^[\w\s]+$/
const validateViewSchemas: CustomValidator<Table> = (table, helpers) => {
if (table.views && Object.entries(table.views).length) {
const requiredFields = Object.entries(table.schema)
.filter(([_, v]) => isRequired(v.constraints))
.map(([key]) => key)
if (requiredFields.length) {
for (const view of Object.values(table.views)) {
if (!sdk.views.isV2(view)) {
continue
}
const editableViewFields = Object.entries(view.schema || {})
.filter(([_, f]) => f.visible && !f.readonly)
.map(([key]) => key)
const missingField = requiredFields.find(
f => !editableViewFields.includes(f)
)
if (missingField) {
return helpers.message({
custom: `Required field "${missingField}" is missing in view "${view.name}"`,
})
}
}
}
}
return table
}
export function tableValidator() {
// prettier-ignore
return auth.joiValidator.body(Joi.object({
@ -20,7 +50,7 @@ export function tableValidator() {
name: Joi.string().required(),
views: Joi.object(),
rows: Joi.array(),
}).unknown(true))
}).custom(validateViewSchemas).unknown(true))
}
export function nameValidator() {