trimViewRowInfo middleware and api test
This commit is contained in:
parent
5052f2cd68
commit
eaa7d9bf81
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue