trimViewRowInfo middleware and api test

This commit is contained in:
Adria Navarro 2023-07-31 12:03:03 +02:00
parent 5052f2cd68
commit eaa7d9bf81
8 changed files with 157 additions and 112 deletions

View File

@ -93,7 +93,6 @@ export async function patch(ctx: UserCtx<PatchRowRequest, PatchRowResponse>) {
}
export async function save(ctx: UserCtx) {
const db = context.getAppDB()
let inputs = ctx.request.body
inputs.tableId = ctx.params.tableId
@ -177,7 +176,6 @@ export async function destroy(ctx: UserCtx) {
}
export async function bulkDestroy(ctx: UserCtx) {
const db = context.getAppDB()
const tableId = ctx.params.tableId
const table = await sdk.tables.getTable(tableId)
let { rows } = ctx.request.body
@ -206,6 +204,7 @@ export async function bulkDestroy(ctx: UserCtx) {
})
)
} else {
const db = context.getAppDB()
await db.bulkDocs(processedRows.map(row => ({ ...row, _deleted: true })))
}
// remove any attachments that were on the rows from object storage

View File

@ -5,6 +5,7 @@ import { paramResource, paramSubResource } from "../../middleware/resourceId"
import { permissions } from "@budibase/backend-core"
import { internalSearchValidator } from "./utils/validators"
import noViewData from "../../middleware/noViewData"
import trimViewRowInfo from "../../middleware/trimViewRowInfo"
const { PermissionType, PermissionLevel } = permissions
const router: Router = new Router()
@ -301,6 +302,7 @@ router
"/api/v2/views/:viewId/rows",
paramResource("viewId"),
authorized(PermissionType.VIEW, PermissionLevel.WRITE),
trimViewRowInfo(),
rowController.save
)

View File

@ -1039,4 +1039,80 @@ describe("/rows", () => {
expect(response.body.rows).toHaveLength(0)
})
})
describe("view 2.0", () => {
function userTable(): Table {
return {
name: "user",
type: "user",
schema: {
name: {
type: FieldType.STRING,
name: "name",
},
surname: {
type: FieldType.STRING,
name: "name",
},
age: {
type: FieldType.NUMBER,
name: "age",
},
address: {
type: FieldType.STRING,
name: "address",
},
jobTitle: {
type: FieldType.STRING,
name: "jobTitle",
},
},
}
}
const randomRowData = () => ({
name: generator.first(),
surname: generator.last(),
age: generator.age(),
address: generator.address(),
jobTitle: generator.word(),
})
describe("create", () => {
it("should persist a new row with only the provided view fields", async () => {
const table = await config.createTable(userTable())
const view = await config.api.viewV2.create({
tableId: table._id!,
columns: {
name: { visible: true },
surname: { visible: true },
address: { visible: true },
},
})
const data = randomRowData()
const newRow = await config.api.viewV2.row.create(view.id, {
tableId: config.table!._id,
_viewId: view.id,
...data,
})
const row = await config.api.row.get(table._id!, newRow._id!)
expect(row.body).toEqual({
name: data.name,
surname: data.surname,
address: data.address,
tableId: config.table!._id,
type: "row",
_id: expect.any(String),
_rev: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
})
expect(row.body._viewId).toBeUndefined()
expect(row.body.age).toBeUndefined()
expect(row.body.jobTitle).toBeUndefined()
})
})
})
})

View File

@ -0,0 +1,47 @@
import { Ctx, Row } from "@budibase/types"
import * as utils from "../db/utils"
import sdk from "../sdk"
import { db } from "@budibase/backend-core"
export default () => async (ctx: Ctx<Row>, next: any) => {
const { body } = ctx.request
const { _viewId: viewId } = body
if (!viewId) {
ctx.throw(400, "_viewId is required")
}
const { tableId } = utils.extractViewInfoFromID(viewId)
const { _viewId, ...trimmedView } = await trimViewFields(
viewId,
tableId,
body
)
ctx.request.body = trimmedView
ctx.params.tableId = body.tableId
return next()
}
export async function trimViewFields<T extends Row>(
viewId: string,
tableId: string,
data: T
): Promise<T> {
const view = await sdk.views.get(viewId)
if (!view?.columns || !Object.keys(view.columns).length) {
return data
}
const table = await sdk.tables.getTable(tableId)
const { schema } = sdk.views.enrichSchema(view!, table.schema)
const result: Record<string, any> = {}
for (const key of [
...Object.keys(schema),
...db.CONSTANT_EXTERNAL_ROW_COLS,
...db.CONSTANT_INTERNAL_ROW_COLS,
]) {
result[key] = data[key] !== null ? data[key] : undefined
}
return result as T
}

View File

@ -1,90 +0,0 @@
import { generator } from "@budibase/backend-core/tests"
import { FieldType, Table } from "@budibase/types"
jest.mock("../../../../sdk", () => ({
views: {
...jest.requireActual("../../../../sdk/app/views"),
get: jest.fn(),
},
}))
import sdk from "../../../../sdk"
import { trimViewFields } from "../utils"
const mockGetView = sdk.views.get as jest.MockedFunction<typeof sdk.views.get>
describe("utils", () => {
const table: Table = {
name: generator.word(),
type: "table",
schema: {
name: {
name: "name",
type: FieldType.STRING,
},
age: {
name: "age",
type: FieldType.NUMBER,
},
address: {
name: "address",
type: FieldType.STRING,
},
},
}
beforeEach(() => {
jest.resetAllMocks()
})
describe("trimViewFields", () => {
it("when no columns are defined, same data is returned", async () => {
mockGetView.mockResolvedValue({
version: 2,
id: generator.guid(),
name: generator.guid(),
tableId: generator.guid(),
})
const viewId = generator.guid()
const data = {
_id: generator.guid(),
name: generator.name(),
age: generator.age(),
address: generator.address(),
}
const result = await trimViewFields(viewId, table, data)
expect(result).toBe(data)
})
it("when columns are defined, trim data is returned", async () => {
mockGetView.mockResolvedValue({
version: 2,
id: generator.guid(),
name: generator.guid(),
tableId: generator.guid(),
columns: {
name: { visible: true },
address: { visible: true },
age: { visible: false },
},
})
const viewId = generator.guid()
const data = {
_id: generator.guid(),
name: generator.name(),
age: generator.age(),
address: generator.address(),
}
const result = await trimViewFields(viewId, table, data)
expect(result).toEqual({
name: data.name,
address: data.address,
})
})
})
})

View File

@ -135,22 +135,3 @@ export async function validate({
}
return { valid: Object.keys(errors).length === 0, errors }
}
export async function trimViewFields<T extends Row>(
viewId: string,
table: Table,
data: T
): Promise<T> {
const view = await sdk.views.get(viewId)
if (!view?.columns || !Object.keys(view.columns).length) {
return data
}
const { schema } = sdk.views.enrichSchema(view!, table.schema)
const result: Record<string, any> = {}
for (const key of Object.keys(schema)) {
result[key] = data[key] !== null ? data[key] : undefined
}
return result as T
}

View File

@ -7,6 +7,14 @@ export class RowAPI extends TestAPI {
super(config)
}
get = async (tableId: string, rowId: string) => {
return await this.request
.get(`/api/${tableId}/rows/${rowId}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
}
patch = async (
tableId: string,
row: PatchRowRequest,

View File

@ -1,4 +1,10 @@
import { CreateViewRequest, SortOrder, SortType, ViewV2 } from "@budibase/types"
import {
CreateViewRequest,
Row,
SortOrder,
SortType,
ViewV2,
} from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import { generator } from "@budibase/backend-core/tests"
@ -93,4 +99,20 @@ export class ViewV2API extends TestAPI {
.expect("Content-Type", /json/)
.expect(expectStatus)
}
row = {
create: async (
viewId: string,
row: Row,
{ expectStatus } = { expectStatus: 200 }
): Promise<Row> => {
const result = await this.request
.post(`/api/v2/views/${viewId}/rows`)
.send(row)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
return result.body as Row
},
}
}