Merge pull request #13157 from Budibase/test-api-unification

Unifying how we send requests to the server in packages/server tests.
This commit is contained in:
Sam Rose 2024-03-04 13:45:03 +00:00 committed by GitHub
commit 7bbda49c69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 914 additions and 974 deletions

View File

@ -1,5 +1,6 @@
import { APIError } from "@budibase/types" import { APIError } from "@budibase/types"
import * as errors from "../errors" import * as errors from "../errors"
import environment from "../environment"
export async function errorHandling(ctx: any, next: any) { export async function errorHandling(ctx: any, next: any) {
try { try {
@ -14,15 +15,19 @@ export async function errorHandling(ctx: any, next: any) {
console.error(err) console.error(err)
} }
const error = errors.getPublicError(err) let error: APIError = {
const body: APIError = {
message: err.message, message: err.message,
status: status, status: status,
validationErrors: err.validation, validationErrors: err.validation,
error, error: errors.getPublicError(err),
} }
ctx.body = body if (environment.isTest() && ctx.headers["x-budibase-include-stacktrace"]) {
// @ts-ignore
error.stack = err.stack
}
ctx.body = error
} }
} }

View File

@ -7,6 +7,10 @@ import {
GetResourcePermsResponse, GetResourcePermsResponse,
ResourcePermissionInfo, ResourcePermissionInfo,
GetDependantResourcesResponse, GetDependantResourcesResponse,
AddPermissionResponse,
AddPermissionRequest,
RemovePermissionRequest,
RemovePermissionResponse,
} from "@budibase/types" } from "@budibase/types"
import { getRoleParams } from "../../db/utils" import { getRoleParams } from "../../db/utils"
import { import {
@ -16,9 +20,9 @@ import {
import { removeFromArray } from "../../utilities" import { removeFromArray } from "../../utilities"
import sdk from "../../sdk" import sdk from "../../sdk"
const PermissionUpdateType = { const enum PermissionUpdateType {
REMOVE: "remove", REMOVE = "remove",
ADD: "add", ADD = "add",
} }
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
@ -39,7 +43,7 @@ async function updatePermissionOnRole(
resourceId, resourceId,
level, level,
}: { roleId: string; resourceId: string; level: PermissionLevel }, }: { roleId: string; resourceId: string; level: PermissionLevel },
updateType: string updateType: PermissionUpdateType
) { ) {
const allowedAction = await sdk.permissions.resourceActionAllowed({ const allowedAction = await sdk.permissions.resourceActionAllowed({
resourceId, resourceId,
@ -107,11 +111,15 @@ async function updatePermissionOnRole(
} }
const response = await db.bulkDocs(docUpdates) const response = await db.bulkDocs(docUpdates)
return response.map((resp: any) => { return response.map(resp => {
const version = docUpdates.find(role => role._id === resp.id)?.version const version = docUpdates.find(role => role._id === resp.id)?.version
resp._id = roles.getExternalRoleID(resp.id, version) const _id = roles.getExternalRoleID(resp.id, version)
delete resp.id return {
return resp _id,
rev: resp.rev,
error: resp.error,
reason: resp.reason,
}
}) })
} }
@ -189,13 +197,14 @@ export async function getDependantResources(
} }
} }
export async function addPermission(ctx: UserCtx) { export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
ctx.body = await updatePermissionOnRole(ctx.params, PermissionUpdateType.ADD) const params: AddPermissionRequest = ctx.params
ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.ADD)
} }
export async function removePermission(ctx: UserCtx) { export async function removePermission(
ctx.body = await updatePermissionOnRole( ctx: UserCtx<void, RemovePermissionResponse>
ctx.params, ) {
PermissionUpdateType.REMOVE const params: RemovePermissionRequest = ctx.params
) ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.REMOVE)
} }

View File

@ -17,10 +17,12 @@ import {
QueryPreview, QueryPreview,
QuerySchema, QuerySchema,
FieldType, FieldType,
type ExecuteQueryRequest, ExecuteQueryRequest,
type ExecuteQueryResponse, ExecuteQueryResponse,
type Row, Row,
QueryParameter, QueryParameter,
PreviewQueryRequest,
PreviewQueryResponse,
} from "@budibase/types" } from "@budibase/types"
import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core" import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core"
@ -134,14 +136,16 @@ function enrichParameters(
return requestParameters return requestParameters
} }
export async function preview(ctx: UserCtx) { export async function preview(
ctx: UserCtx<PreviewQueryRequest, PreviewQueryResponse>
) {
const { datasource, envVars } = await sdk.datasources.getWithEnvVars( const { datasource, envVars } = await sdk.datasources.getWithEnvVars(
ctx.request.body.datasourceId ctx.request.body.datasourceId
) )
const query: QueryPreview = ctx.request.body
// preview may not have a queryId as it hasn't been saved, but if it does // preview may not have a queryId as it hasn't been saved, but if it does
// this stops dynamic variables from calling the same query // this stops dynamic variables from calling the same query
const { fields, parameters, queryVerb, transformer, queryId, schema } = query const { fields, parameters, queryVerb, transformer, queryId, schema } =
ctx.request.body
let existingSchema = schema let existingSchema = schema
if (queryId && !existingSchema) { if (queryId && !existingSchema) {
@ -266,9 +270,7 @@ export async function preview(ctx: UserCtx) {
}, },
} }
const { rows, keys, info, extra } = (await Runner.run( const { rows, keys, info, extra } = await Runner.run<QueryResponse>(inputs)
inputs
)) as QueryResponse
const { previewSchema, nestedSchemaFields } = getSchemaFields(rows, keys) const { previewSchema, nestedSchemaFields } = getSchemaFields(rows, keys)
// if existing schema, update to include any previous schema keys // if existing schema, update to include any previous schema keys
@ -281,7 +283,7 @@ export async function preview(ctx: UserCtx) {
} }
// remove configuration before sending event // remove configuration before sending event
delete datasource.config delete datasource.config
await events.query.previewed(datasource, query) await events.query.previewed(datasource, ctx.request.body)
ctx.body = { ctx.body = {
rows, rows,
nestedSchemaFields, nestedSchemaFields,
@ -295,7 +297,10 @@ export async function preview(ctx: UserCtx) {
} }
async function execute( async function execute(
ctx: UserCtx<ExecuteQueryRequest, ExecuteQueryResponse | Row[]>, ctx: UserCtx<
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
>,
opts: any = { rowsOnly: false, isAutomation: false } opts: any = { rowsOnly: false, isAutomation: false }
) { ) {
const db = context.getAppDB() const db = context.getAppDB()
@ -350,18 +355,23 @@ async function execute(
} }
} }
export async function executeV1(ctx: UserCtx) { export async function executeV1(
ctx: UserCtx<ExecuteQueryRequest, Record<string, any>[]>
) {
return execute(ctx, { rowsOnly: true, isAutomation: false }) return execute(ctx, { rowsOnly: true, isAutomation: false })
} }
export async function executeV2( export async function executeV2(
ctx: UserCtx, ctx: UserCtx<
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
>,
{ isAutomation }: { isAutomation?: boolean } = {} { isAutomation }: { isAutomation?: boolean } = {}
) { ) {
return execute(ctx, { rowsOnly: false, isAutomation }) return execute(ctx, { rowsOnly: false, isAutomation })
} }
const removeDynamicVariables = async (queryId: any) => { const removeDynamicVariables = async (queryId: string) => {
const db = context.getAppDB() const db = context.getAppDB()
const query = await db.get<Query>(queryId) const query = await db.get<Query>(queryId)
const datasource = await sdk.datasources.get(query.datasourceId) const datasource = await sdk.datasources.get(query.datasourceId)
@ -384,7 +394,7 @@ const removeDynamicVariables = async (queryId: any) => {
export async function destroy(ctx: UserCtx) { export async function destroy(ctx: UserCtx) {
const db = context.getAppDB() const db = context.getAppDB()
const queryId = ctx.params.queryId const queryId = ctx.params.queryId as string
await removeDynamicVariables(queryId) await removeDynamicVariables(queryId)
const query = await db.get<Query>(queryId) const query = await db.get<Query>(queryId)
const datasource = await sdk.datasources.get(query.datasourceId) const datasource = await sdk.datasources.get(query.datasourceId)

View File

@ -211,7 +211,7 @@ export async function validate(ctx: Ctx<Row, ValidateResponse>) {
} }
} }
export async function fetchEnrichedRow(ctx: any) { export async function fetchEnrichedRow(ctx: UserCtx<void, Row>) {
const tableId = utils.getTableId(ctx) const tableId = utils.getTableId(ctx)
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx) ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
} }

View File

@ -184,7 +184,7 @@ describe("/applications", () => {
it("app should not sync if production", async () => { it("app should not sync if production", async () => {
const { message } = await config.api.application.sync( const { message } = await config.api.application.sync(
app.appId.replace("_dev", ""), app.appId.replace("_dev", ""),
{ statusCode: 400 } { status: 400 }
) )
expect(message).toEqual( expect(message).toEqual(

View File

@ -29,7 +29,7 @@ describe("/api/applications/:appId/sync", () => {
let resp = (await config.api.attachment.process( let resp = (await config.api.attachment.process(
"ohno.exe", "ohno.exe",
Buffer.from([0]), Buffer.from([0]),
{ expectStatus: 400 } { status: 400 }
)) as unknown as APIError )) as unknown as APIError
expect(resp.message).toContain("invalid extension") expect(resp.message).toContain("invalid extension")
}) })
@ -40,7 +40,7 @@ describe("/api/applications/:appId/sync", () => {
let resp = (await config.api.attachment.process( let resp = (await config.api.attachment.process(
"OHNO.EXE", "OHNO.EXE",
Buffer.from([0]), Buffer.from([0]),
{ expectStatus: 400 } { status: 400 }
)) as unknown as APIError )) as unknown as APIError
expect(resp.message).toContain("invalid extension") expect(resp.message).toContain("invalid extension")
}) })
@ -51,7 +51,7 @@ describe("/api/applications/:appId/sync", () => {
undefined as any, undefined as any,
undefined as any, undefined as any,
{ {
expectStatus: 400, status: 400,
} }
)) as unknown as APIError )) as unknown as APIError
expect(resp.message).toContain("No file provided") expect(resp.message).toContain("No file provided")

View File

@ -19,11 +19,8 @@ describe("/backups", () => {
describe("/api/backups/export", () => { describe("/api/backups/export", () => {
it("should be able to export app", async () => { it("should be able to export app", async () => {
const { body, headers } = await config.api.backup.exportBasicBackup( const body = await config.api.backup.exportBasicBackup(config.getAppId()!)
config.getAppId()!
)
expect(body instanceof Buffer).toBe(true) expect(body instanceof Buffer).toBe(true)
expect(headers["content-type"]).toEqual("application/gzip")
expect(events.app.exported).toBeCalledTimes(1) expect(events.app.exported).toBeCalledTimes(1)
}) })
@ -38,15 +35,13 @@ describe("/backups", () => {
it("should infer the app name from the app", async () => { it("should infer the app name from the app", async () => {
tk.freeze(mocks.date.MOCK_DATE) tk.freeze(mocks.date.MOCK_DATE)
const { headers } = await config.api.backup.exportBasicBackup( await config.api.backup.exportBasicBackup(config.getAppId()!, {
config.getAppId()! headers: {
) "content-disposition": `attachment; filename="${
config.getApp().name
expect(headers["content-disposition"]).toEqual( }-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`,
`attachment; filename="${ },
config.getApp().name })
}-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
)
}) })
}) })

View File

@ -45,7 +45,7 @@ describe("/permission", () => {
table = (await config.createTable()) as typeof table table = (await config.createTable()) as typeof table
row = await config.createRow() row = await config.createRow()
view = await config.api.viewV2.create({ tableId: table._id }) view = await config.api.viewV2.create({ tableId: table._id })
perms = await config.api.permission.set({ perms = await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
@ -88,13 +88,13 @@ describe("/permission", () => {
}) })
it("should get resource permissions with multiple roles", async () => { it("should get resource permissions with multiple roles", async () => {
perms = await config.api.permission.set({ perms = await config.api.permission.add({
roleId: HIGHER_ROLE_ID, roleId: HIGHER_ROLE_ID,
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.WRITE, level: PermissionLevel.WRITE,
}) })
const res = await config.api.permission.get(table._id) const res = await config.api.permission.get(table._id)
expect(res.body).toEqual({ expect(res).toEqual({
permissions: { permissions: {
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID }, read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID }, write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID },
@ -117,16 +117,19 @@ describe("/permission", () => {
level: PermissionLevel.READ, level: PermissionLevel.READ,
}) })
const response = await config.api.permission.set( await config.api.permission.add(
{ {
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.EXECUTE, level: PermissionLevel.EXECUTE,
}, },
{ expectStatus: 403 } {
) status: 403,
expect(response.message).toEqual( body: {
"You are not allowed to 'read' the resource type 'datasource'" message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
) )
}) })
}) })
@ -138,9 +141,9 @@ describe("/permission", () => {
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
}) })
expect(res.body[0]._id).toEqual(STD_ROLE_ID) expect(res[0]._id).toEqual(STD_ROLE_ID)
const permsRes = await config.api.permission.get(table._id) const permsRes = await config.api.permission.get(table._id)
expect(permsRes.body[STD_ROLE_ID]).toBeUndefined() expect(permsRes.permissions[STD_ROLE_ID]).toBeUndefined()
}) })
it("throw forbidden if the action is not allowed for the resource", async () => { it("throw forbidden if the action is not allowed for the resource", async () => {
@ -156,10 +159,13 @@ describe("/permission", () => {
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.EXECUTE, level: PermissionLevel.EXECUTE,
}, },
{ expectStatus: 403 } {
) status: 403,
expect(response.body.message).toEqual( body: {
"You are not allowed to 'read' the resource type 'datasource'" message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
) )
}) })
}) })
@ -181,10 +187,8 @@ describe("/permission", () => {
// replicate changes before checking permissions // replicate changes before checking permissions
await config.publish() await config.publish()
const res = await config.api.viewV2.search(view.id, undefined, { const res = await config.api.viewV2.publicSearch(view.id)
usePublicUser: true, expect(res.rows[0]._id).toEqual(row._id)
})
expect(res.body.rows[0]._id).toEqual(row._id)
}) })
it("should not be able to access the view data when the table is not public and there are no view permissions overrides", async () => { it("should not be able to access the view data when the table is not public and there are no view permissions overrides", async () => {
@ -196,14 +200,11 @@ describe("/permission", () => {
// replicate changes before checking permissions // replicate changes before checking permissions
await config.publish() await config.publish()
await config.api.viewV2.search(view.id, undefined, { await config.api.viewV2.publicSearch(view.id, undefined, { status: 403 })
expectStatus: 403,
usePublicUser: true,
})
}) })
it("should ignore the view permissions if the flag is not on", async () => { it("should ignore the view permissions if the flag is not on", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: view.id, resourceId: view.id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
@ -216,15 +217,14 @@ describe("/permission", () => {
// replicate changes before checking permissions // replicate changes before checking permissions
await config.publish() await config.publish()
await config.api.viewV2.search(view.id, undefined, { await config.api.viewV2.publicSearch(view.id, undefined, {
expectStatus: 403, status: 403,
usePublicUser: true,
}) })
}) })
it("should use the view permissions if the flag is on", async () => { it("should use the view permissions if the flag is on", async () => {
mocks.licenses.useViewPermissions() mocks.licenses.useViewPermissions()
await config.api.permission.set({ await config.api.permission.add({
roleId: STD_ROLE_ID, roleId: STD_ROLE_ID,
resourceId: view.id, resourceId: view.id,
level: PermissionLevel.READ, level: PermissionLevel.READ,
@ -237,10 +237,8 @@ describe("/permission", () => {
// replicate changes before checking permissions // replicate changes before checking permissions
await config.publish() await config.publish()
const res = await config.api.viewV2.search(view.id, undefined, { const res = await config.api.viewV2.publicSearch(view.id)
usePublicUser: true, expect(res.rows[0]._id).toEqual(row._id)
})
expect(res.body.rows[0]._id).toEqual(row._id)
}) })
it("shouldn't allow writing from a public user", async () => { it("shouldn't allow writing from a public user", async () => {
@ -277,7 +275,7 @@ describe("/permission", () => {
const res = await config.api.permission.get(legacyView.name) const res = await config.api.permission.get(legacyView.name)
expect(res.body).toEqual({ expect(res).toEqual({
permissions: { permissions: {
read: { read: {
permissionType: "BASE", permissionType: "BASE",

View File

@ -397,15 +397,16 @@ describe("/queries", () => {
}) })
it("should fail with invalid integration type", async () => { it("should fail with invalid integration type", async () => {
const response = await config.api.datasource.create( const datasource: Datasource = {
{ ...basicDatasource().datasource,
...basicDatasource().datasource, source: "INVALID_INTEGRATION" as SourceName,
source: "INVALID_INTEGRATION" as SourceName, }
await config.api.datasource.create(datasource, {
status: 500,
body: {
message: "No datasource implementation found.",
}, },
{ expectStatus: 500, rawResponse: true } })
)
expect(response.body.message).toBe("No datasource implementation found.")
}) })
}) })

View File

@ -93,7 +93,7 @@ describe("/roles", () => {
it("should be able to get the role with a permission added", async () => { it("should be able to get the role with a permission added", async () => {
const table = await config.createTable() const table = await config.createTable()
await config.api.permission.set({ await config.api.permission.add({
roleId: BUILTIN_ROLE_IDS.POWER, roleId: BUILTIN_ROLE_IDS.POWER,
resourceId: table._id, resourceId: table._id,
level: PermissionLevel.READ, level: PermissionLevel.READ,

View File

@ -7,6 +7,7 @@ import { context, InternalTable, roles, tenancy } from "@budibase/backend-core"
import { quotas } from "@budibase/pro" import { quotas } from "@budibase/pro"
import { import {
AutoFieldSubType, AutoFieldSubType,
DeleteRow,
FieldSchema, FieldSchema,
FieldType, FieldType,
FieldTypeSubtypes, FieldTypeSubtypes,
@ -106,9 +107,6 @@ describe.each([
mocks.licenses.useCloudFree() mocks.licenses.useCloudFree()
}) })
const loadRow = (id: string, tbl_Id: string, status = 200) =>
config.api.row.get(tbl_Id, id, { expectStatus: status })
const getRowUsage = async () => { const getRowUsage = async () => {
const { total } = await config.doInContext(undefined, () => const { total } = await config.doInContext(undefined, () =>
quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS) quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS)
@ -235,7 +233,7 @@ describe.each([
const res = await config.api.row.get(tableId, existing._id!) const res = await config.api.row.get(tableId, existing._id!)
expect(res.body).toEqual({ expect(res).toEqual({
...existing, ...existing,
...defaultRowFields, ...defaultRowFields,
}) })
@ -265,7 +263,7 @@ describe.each([
await config.createRow() await config.createRow()
await config.api.row.get(tableId, "1234567", { await config.api.row.get(tableId, "1234567", {
expectStatus: 404, status: 404,
}) })
}) })
@ -395,7 +393,7 @@ describe.each([
const createdRow = await config.createRow(row) const createdRow = await config.createRow(row)
const id = createdRow._id! const id = createdRow._id!
const saved = (await loadRow(id, table._id!)).body const saved = await config.api.row.get(table._id!, id)
expect(saved.stringUndefined).toBe(undefined) expect(saved.stringUndefined).toBe(undefined)
expect(saved.stringNull).toBe(null) expect(saved.stringNull).toBe(null)
@ -476,8 +474,8 @@ describe.each([
) )
const row = await config.api.row.get(table._id!, createRowResponse._id!) const row = await config.api.row.get(table._id!, createRowResponse._id!)
expect(row.body.Story).toBeUndefined() expect(row.Story).toBeUndefined()
expect(row.body).toEqual({ expect(row).toEqual({
...defaultRowFields, ...defaultRowFields,
OrderID: 1111, OrderID: 1111,
Country: "Aussy", Country: "Aussy",
@ -524,10 +522,10 @@ describe.each([
expect(row.name).toEqual("Updated Name") expect(row.name).toEqual("Updated Name")
expect(row.description).toEqual(existing.description) expect(row.description).toEqual(existing.description)
const savedRow = await loadRow(row._id!, table._id!) const savedRow = await config.api.row.get(table._id!, row._id!)
expect(savedRow.body.description).toEqual(existing.description) expect(savedRow.description).toEqual(existing.description)
expect(savedRow.body.name).toEqual("Updated Name") expect(savedRow.name).toEqual("Updated Name")
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
}) })
@ -543,7 +541,7 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: 1, name: 1,
}, },
{ expectStatus: 400 } { status: 400 }
) )
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
@ -582,8 +580,8 @@ describe.each([
}) })
let getResp = await config.api.row.get(table._id!, row._id!) let getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user1._id) expect(getResp.user1[0]._id).toEqual(user1._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id) expect(getResp.user2[0]._id).toEqual(user2._id)
let patchResp = await config.api.row.patch(table._id!, { let patchResp = await config.api.row.patch(table._id!, {
_id: row._id!, _id: row._id!,
@ -595,8 +593,8 @@ describe.each([
expect(patchResp.user2[0]._id).toEqual(user2._id) expect(patchResp.user2[0]._id).toEqual(user2._id)
getResp = await config.api.row.get(table._id!, row._id!) getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user2._id) expect(getResp.user1[0]._id).toEqual(user2._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id) expect(getResp.user2[0]._id).toEqual(user2._id)
}) })
it("should be able to update relationships when both columns are same name", async () => { it("should be able to update relationships when both columns are same name", async () => {
@ -609,7 +607,7 @@ describe.each([
description: "test", description: "test",
relationship: [row._id], relationship: [row._id],
}) })
row = (await config.api.row.get(table._id!, row._id!)).body row = await config.api.row.get(table._id!, row._id!)
expect(row.relationship.length).toBe(1) expect(row.relationship.length).toBe(1)
const resp = await config.api.row.patch(table._id!, { const resp = await config.api.row.patch(table._id!, {
_id: row._id!, _id: row._id!,
@ -632,8 +630,10 @@ describe.each([
const createdRow = await config.createRow() const createdRow = await config.createRow()
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.row.delete(table._id!, [createdRow]) const res = await config.api.row.bulkDelete(table._id!, {
expect(res.body[0]._id).toEqual(createdRow._id) rows: [createdRow],
})
expect(res[0]._id).toEqual(createdRow._id)
await assertRowUsage(rowUsage - 1) await assertRowUsage(rowUsage - 1)
}) })
}) })
@ -682,10 +682,12 @@ describe.each([
const row2 = await config.createRow() const row2 = await config.createRow()
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.row.delete(table._id!, [row1, row2]) const res = await config.api.row.bulkDelete(table._id!, {
rows: [row1, row2],
})
expect(res.body.length).toEqual(2) expect(res.length).toEqual(2)
await loadRow(row1._id!, table._id!, 404) await config.api.row.get(table._id!, row1._id!, { status: 404 })
await assertRowUsage(rowUsage - 2) await assertRowUsage(rowUsage - 2)
}) })
@ -697,14 +699,12 @@ describe.each([
]) ])
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.row.delete(table._id!, [ const res = await config.api.row.bulkDelete(table._id!, {
row1, rows: [row1, row2._id!, { _id: row3._id }],
row2._id, })
{ _id: row3._id },
])
expect(res.body.length).toEqual(3) expect(res.length).toEqual(3)
await loadRow(row1._id!, table._id!, 404) await config.api.row.get(table._id!, row1._id!, { status: 404 })
await assertRowUsage(rowUsage - 3) await assertRowUsage(rowUsage - 3)
}) })
@ -712,34 +712,36 @@ describe.each([
const row1 = await config.createRow() const row1 = await config.createRow()
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.row.delete(table._id!, row1) const res = await config.api.row.delete(table._id!, row1 as DeleteRow)
expect(res.body.id).toEqual(row1._id) expect(res.id).toEqual(row1._id)
await loadRow(row1._id!, table._id!, 404) await config.api.row.get(table._id!, row1._id!, { status: 404 })
await assertRowUsage(rowUsage - 1) await assertRowUsage(rowUsage - 1)
}) })
it("Should ignore malformed/invalid delete requests", async () => { it("Should ignore malformed/invalid delete requests", async () => {
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.row.delete( await config.api.row.delete(table._id!, { not: "valid" } as any, {
table._id!, status: 400,
{ not: "valid" }, body: {
{ expectStatus: 400 } message: "Invalid delete rows request",
) },
expect(res.body.message).toEqual("Invalid delete rows request") })
const res2 = await config.api.row.delete( await config.api.row.delete(table._id!, { rows: 123 } as any, {
table._id!, status: 400,
{ rows: 123 }, body: {
{ expectStatus: 400 } message: "Invalid delete rows request",
) },
expect(res2.body.message).toEqual("Invalid delete rows request") })
const res3 = await config.api.row.delete(table._id!, "invalid", { await config.api.row.delete(table._id!, "invalid" as any, {
expectStatus: 400, status: 400,
body: {
message: "Invalid delete rows request",
},
}) })
expect(res3.body.message).toEqual("Invalid delete rows request")
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
}) })
@ -757,16 +759,16 @@ describe.each([
const row = await config.createRow() const row = await config.createRow()
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.legacyView.get(table._id!) const rows = await config.api.legacyView.get(table._id!)
expect(res.body.length).toEqual(1) expect(rows.length).toEqual(1)
expect(res.body[0]._id).toEqual(row._id) expect(rows[0]._id).toEqual(row._id)
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
}) })
it("should throw an error if view doesn't exist", async () => { it("should throw an error if view doesn't exist", async () => {
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
await config.api.legacyView.get("derp", { expectStatus: 404 }) await config.api.legacyView.get("derp", { status: 404 })
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
}) })
@ -781,9 +783,9 @@ describe.each([
const row = await config.createRow() const row = await config.createRow()
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
const res = await config.api.legacyView.get(view.name) const rows = await config.api.legacyView.get(view.name)
expect(res.body.length).toEqual(1) expect(rows.length).toEqual(1)
expect(res.body[0]._id).toEqual(row._id) expect(rows[0]._id).toEqual(row._id)
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
}) })
@ -841,8 +843,8 @@ describe.each([
linkedTable._id!, linkedTable._id!,
secondRow._id! secondRow._id!
) )
expect(resBasic.body.link.length).toBe(1) expect(resBasic.link.length).toBe(1)
expect(resBasic.body.link[0]).toEqual({ expect(resBasic.link[0]).toEqual({
_id: firstRow._id, _id: firstRow._id,
primaryDisplay: firstRow.name, primaryDisplay: firstRow.name,
}) })
@ -852,10 +854,10 @@ describe.each([
linkedTable._id!, linkedTable._id!,
secondRow._id! secondRow._id!
) )
expect(resEnriched.body.link.length).toBe(1) expect(resEnriched.link.length).toBe(1)
expect(resEnriched.body.link[0]._id).toBe(firstRow._id) expect(resEnriched.link[0]._id).toBe(firstRow._id)
expect(resEnriched.body.link[0].name).toBe("Test Contact") expect(resEnriched.link[0].name).toBe("Test Contact")
expect(resEnriched.body.link[0].description).toBe("original description") expect(resEnriched.link[0].description).toBe("original description")
await assertRowUsage(rowUsage) await assertRowUsage(rowUsage)
}) })
}) })
@ -903,7 +905,7 @@ describe.each([
const res = await config.api.row.exportRows(table._id!, { const res = await config.api.row.exportRows(table._id!, {
rows: [existing._id!], rows: [existing._id!],
}) })
const results = JSON.parse(res.text) const results = JSON.parse(res)
expect(results.length).toEqual(1) expect(results.length).toEqual(1)
const row = results[0] const row = results[0]
@ -922,7 +924,7 @@ describe.each([
rows: [existing._id!], rows: [existing._id!],
columns: ["_id"], columns: ["_id"],
}) })
const results = JSON.parse(res.text) const results = JSON.parse(res)
expect(results.length).toEqual(1) expect(results.length).toEqual(1)
const row = results[0] const row = results[0]
@ -1000,7 +1002,7 @@ describe.each([
}) })
const row = await config.api.row.get(table._id!, newRow._id!) const row = await config.api.row.get(table._id!, newRow._id!)
expect(row.body).toEqual({ expect(row).toEqual({
name: data.name, name: data.name,
surname: data.surname, surname: data.surname,
address: data.address, address: data.address,
@ -1010,9 +1012,9 @@ describe.each([
id: newRow.id, id: newRow.id,
...defaultRowFields, ...defaultRowFields,
}) })
expect(row.body._viewId).toBeUndefined() expect(row._viewId).toBeUndefined()
expect(row.body.age).toBeUndefined() expect(row.age).toBeUndefined()
expect(row.body.jobTitle).toBeUndefined() expect(row.jobTitle).toBeUndefined()
}) })
}) })
@ -1042,7 +1044,7 @@ describe.each([
}) })
const row = await config.api.row.get(tableId, newRow._id!) const row = await config.api.row.get(tableId, newRow._id!)
expect(row.body).toEqual({ expect(row).toEqual({
...newRow, ...newRow,
name: newData.name, name: newData.name,
address: newData.address, address: newData.address,
@ -1051,9 +1053,9 @@ describe.each([
id: newRow.id, id: newRow.id,
...defaultRowFields, ...defaultRowFields,
}) })
expect(row.body._viewId).toBeUndefined() expect(row._viewId).toBeUndefined()
expect(row.body.age).toBeUndefined() expect(row.age).toBeUndefined()
expect(row.body.jobTitle).toBeUndefined() expect(row.jobTitle).toBeUndefined()
}) })
}) })
@ -1071,12 +1073,12 @@ describe.each([
const createdRow = await config.createRow() const createdRow = await config.createRow()
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
await config.api.row.delete(view.id, [createdRow]) await config.api.row.bulkDelete(view.id, { rows: [createdRow] })
await assertRowUsage(rowUsage - 1) await assertRowUsage(rowUsage - 1)
await config.api.row.get(tableId, createdRow._id!, { await config.api.row.get(tableId, createdRow._id!, {
expectStatus: 404, status: 404,
}) })
}) })
@ -1097,17 +1099,17 @@ describe.each([
]) ])
const rowUsage = await getRowUsage() const rowUsage = await getRowUsage()
await config.api.row.delete(view.id, [rows[0], rows[2]]) await config.api.row.bulkDelete(view.id, { rows: [rows[0], rows[2]] })
await assertRowUsage(rowUsage - 2) await assertRowUsage(rowUsage - 2)
await config.api.row.get(tableId, rows[0]._id!, { await config.api.row.get(tableId, rows[0]._id!, {
expectStatus: 404, status: 404,
}) })
await config.api.row.get(tableId, rows[2]._id!, { await config.api.row.get(tableId, rows[2]._id!, {
expectStatus: 404, status: 404,
}) })
await config.api.row.get(tableId, rows[1]._id!, { expectStatus: 200 }) await config.api.row.get(tableId, rows[1]._id!, { status: 200 })
}) })
}) })
@ -1154,8 +1156,8 @@ describe.each([
const createViewResponse = await config.createView() const createViewResponse = await config.createView()
const response = await config.api.viewV2.search(createViewResponse.id) const response = await config.api.viewV2.search(createViewResponse.id)
expect(response.body.rows).toHaveLength(10) expect(response.rows).toHaveLength(10)
expect(response.body).toEqual({ expect(response).toEqual({
rows: expect.arrayContaining( rows: expect.arrayContaining(
rows.map(r => ({ rows.map(r => ({
_viewId: createViewResponse.id, _viewId: createViewResponse.id,
@ -1206,8 +1208,8 @@ describe.each([
const response = await config.api.viewV2.search(createViewResponse.id) const response = await config.api.viewV2.search(createViewResponse.id)
expect(response.body.rows).toHaveLength(5) expect(response.rows).toHaveLength(5)
expect(response.body).toEqual({ expect(response).toEqual({
rows: expect.arrayContaining( rows: expect.arrayContaining(
expectedRows.map(r => ({ expectedRows.map(r => ({
_viewId: createViewResponse.id, _viewId: createViewResponse.id,
@ -1328,8 +1330,8 @@ describe.each([
createViewResponse.id createViewResponse.id
) )
expect(response.body.rows).toHaveLength(4) expect(response.rows).toHaveLength(4)
expect(response.body.rows).toEqual( expect(response.rows).toEqual(
expected.map(name => expect.objectContaining({ name })) expected.map(name => expect.objectContaining({ name }))
) )
} }
@ -1357,8 +1359,8 @@ describe.each([
} }
) )
expect(response.body.rows).toHaveLength(4) expect(response.rows).toHaveLength(4)
expect(response.body.rows).toEqual( expect(response.rows).toEqual(
expected.map(name => expect.objectContaining({ name })) expected.map(name => expect.objectContaining({ name }))
) )
} }
@ -1382,8 +1384,8 @@ describe.each([
}) })
const response = await config.api.viewV2.search(view.id) const response = await config.api.viewV2.search(view.id)
expect(response.body.rows).toHaveLength(10) expect(response.rows).toHaveLength(10)
expect(response.body.rows).toEqual( expect(response.rows).toEqual(
expect.arrayContaining( expect.arrayContaining(
rows.map(r => ({ rows.map(r => ({
...(isInternal ...(isInternal
@ -1402,7 +1404,7 @@ describe.each([
const createViewResponse = await config.createView() const createViewResponse = await config.createView()
const response = await config.api.viewV2.search(createViewResponse.id) const response = await config.api.viewV2.search(createViewResponse.id)
expect(response.body.rows).toHaveLength(0) expect(response.rows).toHaveLength(0)
}) })
it("respects the limit parameter", async () => { it("respects the limit parameter", async () => {
@ -1417,7 +1419,7 @@ describe.each([
query: {}, query: {},
}) })
expect(response.body.rows).toHaveLength(limit) expect(response.rows).toHaveLength(limit)
}) })
it("can handle pagination", async () => { it("can handle pagination", async () => {
@ -1426,7 +1428,7 @@ describe.each([
const createViewResponse = await config.createView() const createViewResponse = await config.createView()
const allRows = (await config.api.viewV2.search(createViewResponse.id)) const allRows = (await config.api.viewV2.search(createViewResponse.id))
.body.rows .rows
const firstPageResponse = await config.api.viewV2.search( const firstPageResponse = await config.api.viewV2.search(
createViewResponse.id, createViewResponse.id,
@ -1436,7 +1438,7 @@ describe.each([
query: {}, query: {},
} }
) )
expect(firstPageResponse.body).toEqual({ expect(firstPageResponse).toEqual({
rows: expect.arrayContaining(allRows.slice(0, 4)), rows: expect.arrayContaining(allRows.slice(0, 4)),
totalRows: isInternal ? 10 : undefined, totalRows: isInternal ? 10 : undefined,
hasNextPage: true, hasNextPage: true,
@ -1448,12 +1450,12 @@ describe.each([
{ {
paginate: true, paginate: true,
limit: 4, limit: 4,
bookmark: firstPageResponse.body.bookmark, bookmark: firstPageResponse.bookmark,
query: {}, query: {},
} }
) )
expect(secondPageResponse.body).toEqual({ expect(secondPageResponse).toEqual({
rows: expect.arrayContaining(allRows.slice(4, 8)), rows: expect.arrayContaining(allRows.slice(4, 8)),
totalRows: isInternal ? 10 : undefined, totalRows: isInternal ? 10 : undefined,
hasNextPage: true, hasNextPage: true,
@ -1465,11 +1467,11 @@ describe.each([
{ {
paginate: true, paginate: true,
limit: 4, limit: 4,
bookmark: secondPageResponse.body.bookmark, bookmark: secondPageResponse.bookmark,
query: {}, query: {},
} }
) )
expect(lastPageResponse.body).toEqual({ expect(lastPageResponse).toEqual({
rows: expect.arrayContaining(allRows.slice(8)), rows: expect.arrayContaining(allRows.slice(8)),
totalRows: isInternal ? 10 : undefined, totalRows: isInternal ? 10 : undefined,
hasNextPage: false, hasNextPage: false,
@ -1489,7 +1491,7 @@ describe.each([
email: "joe@joe.com", email: "joe@joe.com",
roles: {}, roles: {},
}, },
{ expectStatus: 400 } { status: 400 }
) )
expect(response.message).toBe("Cannot create new user entry.") expect(response.message).toBe("Cannot create new user entry.")
}) })
@ -1516,58 +1518,52 @@ describe.each([
it("does not allow public users to fetch by default", async () => { it("does not allow public users to fetch by default", async () => {
await config.publish() await config.publish()
await config.api.viewV2.search(viewId, undefined, { await config.api.viewV2.publicSearch(viewId, undefined, {
expectStatus: 403, status: 403,
usePublicUser: true,
}) })
}) })
it("allow public users to fetch when permissions are explicit", async () => { it("allow public users to fetch when permissions are explicit", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: viewId, resourceId: viewId,
}) })
await config.publish() await config.publish()
const response = await config.api.viewV2.search(viewId, undefined, { const response = await config.api.viewV2.publicSearch(viewId)
usePublicUser: true,
})
expect(response.body.rows).toHaveLength(10) expect(response.rows).toHaveLength(10)
}) })
it("allow public users to fetch when permissions are inherited", async () => { it("allow public users to fetch when permissions are inherited", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: tableId, resourceId: tableId,
}) })
await config.publish() await config.publish()
const response = await config.api.viewV2.search(viewId, undefined, { const response = await config.api.viewV2.publicSearch(viewId)
usePublicUser: true,
})
expect(response.body.rows).toHaveLength(10) expect(response.rows).toHaveLength(10)
}) })
it("respects inherited permissions, not allowing not public views from public tables", async () => { it("respects inherited permissions, not allowing not public views from public tables", async () => {
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.PUBLIC, roleId: roles.BUILTIN_ROLE_IDS.PUBLIC,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: tableId, resourceId: tableId,
}) })
await config.api.permission.set({ await config.api.permission.add({
roleId: roles.BUILTIN_ROLE_IDS.POWER, roleId: roles.BUILTIN_ROLE_IDS.POWER,
level: PermissionLevel.READ, level: PermissionLevel.READ,
resourceId: viewId, resourceId: viewId,
}) })
await config.publish() await config.publish()
await config.api.viewV2.search(viewId, undefined, { await config.api.viewV2.publicSearch(viewId, undefined, {
usePublicUser: true, status: 403,
expectStatus: 403,
}) })
}) })
}) })
@ -1754,7 +1750,7 @@ describe.each([
} }
const row = await config.api.row.save(tableId, rowData) const row = await config.api.row.save(tableId, rowData)
const { body: retrieved } = await config.api.row.get(tableId, row._id!) const retrieved = await config.api.row.get(tableId, row._id!)
expect(retrieved).toEqual({ expect(retrieved).toEqual({
name: rowData.name, name: rowData.name,
description: rowData.description, description: rowData.description,
@ -1781,7 +1777,7 @@ describe.each([
} }
const row = await config.api.row.save(tableId, rowData) const row = await config.api.row.save(tableId, rowData)
const { body: retrieved } = await config.api.row.get(tableId, row._id!) const retrieved = await config.api.row.get(tableId, row._id!)
expect(retrieved).toEqual({ expect(retrieved).toEqual({
name: rowData.name, name: rowData.name,
description: rowData.description, description: rowData.description,

View File

@ -663,8 +663,7 @@ describe("/tables", () => {
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toBeDefined()
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const resp = await config.api.row.get(table._id!, testRow._id!) const migratedRow = await config.api.row.get(table._id!, testRow._id!)
const migratedRow = resp.body as Row
expect(migratedRow["user column"]).toBeDefined() expect(migratedRow["user column"]).toBeDefined()
expect(migratedRow["user relationship"]).not.toBeDefined() expect(migratedRow["user relationship"]).not.toBeDefined()
@ -716,15 +715,13 @@ describe("/tables", () => {
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toBeDefined()
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const row1Migrated = (await config.api.row.get(table._id!, row1._id!)) const row1Migrated = await config.api.row.get(table._id!, row1._id!)
.body as Row
expect(row1Migrated["user relationship"]).not.toBeDefined() expect(row1Migrated["user relationship"]).not.toBeDefined()
expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual( expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual(
expect.arrayContaining([users[0]._id, users[1]._id]) expect.arrayContaining([users[0]._id, users[1]._id])
) )
const row2Migrated = (await config.api.row.get(table._id!, row2._id!)) const row2Migrated = await config.api.row.get(table._id!, row2._id!)
.body as Row
expect(row2Migrated["user relationship"]).not.toBeDefined() expect(row2Migrated["user relationship"]).not.toBeDefined()
expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual( expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual(
expect.arrayContaining([users[1]._id, users[2]._id]) expect.arrayContaining([users[1]._id, users[2]._id])
@ -773,15 +770,13 @@ describe("/tables", () => {
expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user column"]).toBeDefined()
expect(migratedTable.schema["user relationship"]).not.toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined()
const row1Migrated = (await config.api.row.get(table._id!, row1._id!)) const row1Migrated = await config.api.row.get(table._id!, row1._id!)
.body as Row
expect(row1Migrated["user relationship"]).not.toBeDefined() expect(row1Migrated["user relationship"]).not.toBeDefined()
expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual( expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual(
expect.arrayContaining([users[0]._id, users[1]._id]) expect.arrayContaining([users[0]._id, users[1]._id])
) )
const row2Migrated = (await config.api.row.get(table._id!, row2._id!)) const row2Migrated = await config.api.row.get(table._id!, row2._id!)
.body as Row
expect(row2Migrated["user relationship"]).not.toBeDefined() expect(row2Migrated["user relationship"]).not.toBeDefined()
expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual([ expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual([
users[2]._id, users[2]._id,
@ -831,7 +826,7 @@ describe("/tables", () => {
subtype: FieldSubtype.USERS, subtype: FieldSubtype.USERS,
}, },
}, },
{ expectStatus: 400 } { status: 400 }
) )
}) })
@ -846,7 +841,7 @@ describe("/tables", () => {
subtype: FieldSubtype.USERS, subtype: FieldSubtype.USERS,
}, },
}, },
{ expectStatus: 400 } { status: 400 }
) )
}) })
@ -861,7 +856,7 @@ describe("/tables", () => {
subtype: FieldSubtype.USERS, subtype: FieldSubtype.USERS,
}, },
}, },
{ expectStatus: 400 } { status: 400 }
) )
}) })
@ -880,7 +875,7 @@ describe("/tables", () => {
subtype: FieldSubtype.USERS, subtype: FieldSubtype.USERS,
}, },
}, },
{ expectStatus: 400 } { status: 400 }
) )
}) })
}) })

View File

@ -90,7 +90,7 @@ describe("/users", () => {
}) })
await config.api.user.update( await config.api.user.update(
{ ...user, roleId: roles.BUILTIN_ROLE_IDS.POWER }, { ...user, roleId: roles.BUILTIN_ROLE_IDS.POWER },
{ expectStatus: 409 } { status: 409 }
) )
}) })
}) })

View File

@ -177,7 +177,7 @@ describe.each([
} }
await config.api.viewV2.create(newView, { await config.api.viewV2.create(newView, {
expectStatus: 201, status: 201,
}) })
}) })
}) })
@ -275,7 +275,7 @@ describe.each([
const tableId = table._id! const tableId = table._id!
await config.api.viewV2.update( await config.api.viewV2.update(
{ ...view, id: generator.guid() }, { ...view, id: generator.guid() },
{ expectStatus: 404 } { status: 404 }
) )
expect(await config.api.table.get(tableId)).toEqual( expect(await config.api.table.get(tableId)).toEqual(
@ -304,7 +304,7 @@ describe.each([
}, },
], ],
}, },
{ expectStatus: 404 } { status: 404 }
) )
expect(await config.api.table.get(tableId)).toEqual( expect(await config.api.table.get(tableId)).toEqual(
@ -326,12 +326,10 @@ describe.each([
...viewV1, ...viewV1,
}, },
{ {
expectStatus: 400, status: 400,
handleResponse: r => { body: {
expect(r.body).toEqual({ message: "Only views V2 can be updated",
message: "Only views V2 can be updated", status: 400,
status: 400,
})
}, },
} }
) )
@ -403,7 +401,7 @@ describe.each([
} as Record<string, FieldSchema>, } as Record<string, FieldSchema>,
}, },
{ {
expectStatus: 200, status: 200,
} }
) )
}) })

View File

@ -30,9 +30,9 @@ describe("migrations", () => {
const appId = config.getAppId() const appId = config.getAppId()
const response = await config.api.application.getRaw(appId) await config.api.application.get(appId, {
headersNotPresent: [Header.MIGRATING_APP],
expect(response.headers[Header.MIGRATING_APP]).toBeUndefined() })
}) })
it("accessing an app that has pending migrations will attach the migrating header", async () => { it("accessing an app that has pending migrations will attach the migrating header", async () => {
@ -46,8 +46,10 @@ describe("migrations", () => {
func: async () => {}, func: async () => {},
}) })
const response = await config.api.application.getRaw(appId) await config.api.application.get(appId, {
headers: {
expect(response.headers[Header.MIGRATING_APP]).toEqual(appId) [Header.MIGRATING_APP]: appId,
},
})
}) })
}) })

View File

@ -24,7 +24,7 @@ describe("test the create row action", () => {
expect(res.id).toBeDefined() expect(res.id).toBeDefined()
expect(res.revision).toBeDefined() expect(res.revision).toBeDefined()
expect(res.success).toEqual(true) expect(res.success).toEqual(true)
const gottenRow = await config.getRow(table._id, res.id) const gottenRow = await config.api.row.get(table._id, res.id)
expect(gottenRow.name).toEqual("test") expect(gottenRow.name).toEqual("test")
expect(gottenRow.description).toEqual("test") expect(gottenRow.description).toEqual("test")
}) })

View File

@ -36,7 +36,7 @@ describe("test the update row action", () => {
it("should be able to run the action", async () => { it("should be able to run the action", async () => {
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs) const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
expect(res.success).toEqual(true) expect(res.success).toEqual(true)
const updatedRow = await config.getRow(table._id!, res.id) const updatedRow = await config.api.row.get(table._id!, res.id)
expect(updatedRow.name).toEqual("Updated name") expect(updatedRow.name).toEqual("Updated name")
expect(updatedRow.description).not.toEqual("") expect(updatedRow.description).not.toEqual("")
}) })
@ -87,8 +87,8 @@ describe("test the update row action", () => {
}) })
let getResp = await config.api.row.get(table._id!, row._id!) let getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user1._id) expect(getResp.user1[0]._id).toEqual(user1._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id) expect(getResp.user2[0]._id).toEqual(user2._id)
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
rowId: row._id, rowId: row._id,
@ -103,8 +103,8 @@ describe("test the update row action", () => {
expect(stepResp.success).toEqual(true) expect(stepResp.success).toEqual(true)
getResp = await config.api.row.get(table._id!, row._id!) getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user2._id) expect(getResp.user1[0]._id).toEqual(user2._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id) expect(getResp.user2[0]._id).toEqual(user2._id)
}) })
it("should overwrite links if those links are not set and we ask it do", async () => { it("should overwrite links if those links are not set and we ask it do", async () => {
@ -140,8 +140,8 @@ describe("test the update row action", () => {
}) })
let getResp = await config.api.row.get(table._id!, row._id!) let getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user1._id) expect(getResp.user1[0]._id).toEqual(user1._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id) expect(getResp.user2[0]._id).toEqual(user2._id)
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, { let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
rowId: row._id, rowId: row._id,
@ -163,7 +163,7 @@ describe("test the update row action", () => {
expect(stepResp.success).toEqual(true) expect(stepResp.success).toEqual(true)
getResp = await config.api.row.get(table._id!, row._id!) getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user2._id) expect(getResp.user1[0]._id).toEqual(user2._id)
expect(getResp.body.user2).toBeUndefined() expect(getResp.user2).toBeUndefined()
}) })
}) })

View File

@ -100,7 +100,7 @@ describe("test the link controller", () => {
const { _id } = await config.createRow( const { _id } = await config.createRow(
basicLinkedRow(t1._id!, row._id!, linkField) basicLinkedRow(t1._id!, row._id!, linkField)
) )
return config.getRow(t1._id!, _id!) return config.api.row.get(t1._id!, _id!)
} }
it("should be able to confirm if two table schemas are equal", async () => { it("should be able to confirm if two table schemas are equal", async () => {

View File

@ -398,7 +398,7 @@ describe("postgres integrations", () => {
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(res.body).toEqual(updatedRow) expect(res.body).toEqual(updatedRow)
const persistedRow = await config.getRow( const persistedRow = await config.api.row.get(
primaryPostgresTable._id!, primaryPostgresTable._id!,
row.id row.id
) )
@ -1040,28 +1040,37 @@ describe("postgres integrations", () => {
describe("POST /api/datasources/verify", () => { describe("POST /api/datasources/verify", () => {
it("should be able to verify the connection", async () => { it("should be able to verify the connection", async () => {
const response = await config.api.datasource.verify({ await config.api.datasource.verify(
datasource: await databaseTestProviders.postgres.datasource(), {
}) datasource: await databaseTestProviders.postgres.datasource(),
expect(response.status).toBe(200) },
expect(response.body.connected).toBe(true) {
body: {
connected: true,
},
}
)
}) })
it("should state an invalid datasource cannot connect", async () => { it("should state an invalid datasource cannot connect", async () => {
const dbConfig = await databaseTestProviders.postgres.datasource() const dbConfig = await databaseTestProviders.postgres.datasource()
const response = await config.api.datasource.verify({ await config.api.datasource.verify(
datasource: { {
...dbConfig, datasource: {
config: { ...dbConfig,
...dbConfig.config, config: {
password: "wrongpassword", ...dbConfig.config,
password: "wrongpassword",
},
}, },
}, },
}) {
body: {
expect(response.status).toBe(200) connected: false,
expect(response.body.connected).toBe(false) error: 'password authentication failed for user "postgres"',
expect(response.body.error).toBeDefined() },
}
)
}) })
}) })

View File

@ -98,7 +98,10 @@ describe("sdk >> rows >> internal", () => {
}, },
}) })
const persistedRow = await config.getRow(table._id!, response.row._id!) const persistedRow = await config.api.row.get(
table._id!,
response.row._id!
)
expect(persistedRow).toEqual({ expect(persistedRow).toEqual({
...row, ...row,
type: "row", type: "row",
@ -157,7 +160,10 @@ describe("sdk >> rows >> internal", () => {
}, },
}) })
const persistedRow = await config.getRow(table._id!, response.row._id!) const persistedRow = await config.api.row.get(
table._id!,
response.row._id!
)
expect(persistedRow).toEqual({ expect(persistedRow).toEqual({
...row, ...row,
type: "row", type: "row",

View File

@ -712,11 +712,6 @@ export default class TestConfiguration {
return this.api.row.save(tableId, config) return this.api.row.save(tableId, config)
} }
async getRow(tableId: string, rowId: string): Promise<Row> {
const res = await this.api.row.get(tableId, rowId)
return res.body
}
async getRows(tableId: string) { async getRows(tableId: string) {
if (!tableId && this.table) { if (!tableId && this.table) {
tableId = this.table._id! tableId = this.table._id!

View File

@ -1,193 +1,133 @@
import { Response } from "supertest"
import { import {
App, App,
PublishResponse,
type CreateAppRequest, type CreateAppRequest,
type FetchAppDefinitionResponse, type FetchAppDefinitionResponse,
type FetchAppPackageResponse, type FetchAppPackageResponse,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
import { AppStatus } from "../../../db/utils" import { AppStatus } from "../../../db/utils"
import { constants } from "@budibase/backend-core" import { constants } from "@budibase/backend-core"
export class ApplicationAPI extends TestAPI { export class ApplicationAPI extends TestAPI {
constructor(config: TestConfiguration) { create = async (
super(config) app: CreateAppRequest,
expectations?: Expectations
): Promise<App> => {
const files = app.templateFile ? { templateFile: app.templateFile } : {}
delete app.templateFile
return await this._post<App>("/api/applications", {
fields: app,
files,
expectations,
})
} }
create = async (app: CreateAppRequest): Promise<App> => { delete = async (
const request = this.request appId: string,
.post("/api/applications") expectations?: Expectations
.set(this.config.defaultHeaders()) ): Promise<void> => {
.expect("Content-Type", /json/) await this._delete(`/api/applications/${appId}`, { expectations })
for (const key of Object.keys(app)) {
request.field(key, (app as any)[key])
}
if (app.templateFile) {
request.attach("templateFile", app.templateFile)
}
const result = await request
if (result.statusCode !== 200) {
throw new Error(JSON.stringify(result.body))
}
return result.body as App
} }
delete = async (appId: string): Promise<void> => { publish = async (appId: string): Promise<PublishResponse> => {
await this.request return await this._post<PublishResponse>(
.delete(`/api/applications/${appId}`) `/api/applications/${appId}/publish`,
.set(this.config.defaultHeaders()) {
.expect(200) // While the publish endpoint does take an :appId parameter, it doesn't
} // use it. It uses the appId from the context.
headers: {
publish = async ( [constants.Header.APP_ID]: appId,
appId: string },
): Promise<{ _id: string; status: string; appUrl: string }> => { }
// While the publish endpoint does take an :appId parameter, it doesn't )
// use it. It uses the appId from the context.
let headers = {
...this.config.defaultHeaders(),
[constants.Header.APP_ID]: appId,
}
const result = await this.request
.post(`/api/applications/${appId}/publish`)
.set(headers)
.expect("Content-Type", /json/)
.expect(200)
return result.body as { _id: string; status: string; appUrl: string }
} }
unpublish = async (appId: string): Promise<void> => { unpublish = async (appId: string): Promise<void> => {
await this.request await this._post(`/api/applications/${appId}/unpublish`, {
.post(`/api/applications/${appId}/unpublish`) expectations: { status: 204 },
.set(this.config.defaultHeaders()) })
.expect(204)
} }
sync = async ( sync = async (
appId: string, appId: string,
{ statusCode }: { statusCode: number } = { statusCode: 200 } expectations?: Expectations
): Promise<{ message: string }> => { ): Promise<{ message: string }> => {
const result = await this.request return await this._post<{ message: string }>(
.post(`/api/applications/${appId}/sync`) `/api/applications/${appId}/sync`,
.set(this.config.defaultHeaders()) { expectations }
.expect("Content-Type", /json/) )
.expect(statusCode)
return result.body
} }
getRaw = async (appId: string): Promise<Response> => { get = async (appId: string, expectations?: Expectations): Promise<App> => {
// While the appPackage endpoint does take an :appId parameter, it doesn't return await this._get<App>(`/api/applications/${appId}`, {
// use it. It uses the appId from the context. // While the get endpoint does take an :appId parameter, it doesn't use
let headers = { // it. It uses the appId from the context.
...this.config.defaultHeaders(), headers: {
[constants.Header.APP_ID]: appId, [constants.Header.APP_ID]: appId,
} },
const result = await this.request expectations,
.get(`/api/applications/${appId}/appPackage`) })
.set(headers)
.expect("Content-Type", /json/)
.expect(200)
return result
}
get = async (appId: string): Promise<App> => {
const result = await this.getRaw(appId)
return result.body.application as App
} }
getDefinition = async ( getDefinition = async (
appId: string appId: string,
expectations?: Expectations
): Promise<FetchAppDefinitionResponse> => { ): Promise<FetchAppDefinitionResponse> => {
const result = await this.request return await this._get<FetchAppDefinitionResponse>(
.get(`/api/applications/${appId}/definition`) `/api/applications/${appId}/definition`,
.set(this.config.defaultHeaders()) { expectations }
.expect("Content-Type", /json/) )
.expect(200)
return result.body as FetchAppDefinitionResponse
} }
getAppPackage = async (appId: string): Promise<FetchAppPackageResponse> => { getAppPackage = async (
const result = await this.request appId: string,
.get(`/api/applications/${appId}/appPackage`) expectations?: Expectations
.set(this.config.defaultHeaders()) ): Promise<FetchAppPackageResponse> => {
.expect("Content-Type", /json/) return await this._get<FetchAppPackageResponse>(
.expect(200) `/api/applications/${appId}/appPackage`,
return result.body { expectations }
)
} }
update = async ( update = async (
appId: string, appId: string,
app: { name?: string; url?: string } app: { name?: string; url?: string },
expectations?: Expectations
): Promise<App> => { ): Promise<App> => {
const request = this.request return await this._put<App>(`/api/applications/${appId}`, {
.put(`/api/applications/${appId}`) fields: app,
.set(this.config.defaultHeaders()) expectations,
.expect("Content-Type", /json/) })
for (const key of Object.keys(app)) {
request.field(key, (app as any)[key])
}
const result = await request
if (result.statusCode !== 200) {
throw new Error(JSON.stringify(result.body))
}
return result.body as App
} }
updateClient = async (appId: string): Promise<void> => { updateClient = async (
// While the updateClient endpoint does take an :appId parameter, it doesn't appId: string,
// use it. It uses the appId from the context. expectations?: Expectations
let headers = { ): Promise<void> => {
...this.config.defaultHeaders(), await this._post(`/api/applications/${appId}/client/update`, {
[constants.Header.APP_ID]: appId, // While the updateClient endpoint does take an :appId parameter, it doesn't
} // use it. It uses the appId from the context.
const response = await this.request headers: {
.post(`/api/applications/${appId}/client/update`) [constants.Header.APP_ID]: appId,
.set(headers) },
.expect("Content-Type", /json/) expectations,
})
if (response.statusCode !== 200) {
throw new Error(JSON.stringify(response.body))
}
} }
revertClient = async (appId: string): Promise<void> => { revertClient = async (appId: string): Promise<void> => {
// While the revertClient endpoint does take an :appId parameter, it doesn't await this._post(`/api/applications/${appId}/client/revert`, {
// use it. It uses the appId from the context. // While the revertClient endpoint does take an :appId parameter, it doesn't
let headers = { // use it. It uses the appId from the context.
...this.config.defaultHeaders(), headers: {
[constants.Header.APP_ID]: appId, [constants.Header.APP_ID]: appId,
} },
const response = await this.request })
.post(`/api/applications/${appId}/client/revert`)
.set(headers)
.expect("Content-Type", /json/)
if (response.statusCode !== 200) {
throw new Error(JSON.stringify(response.body))
}
} }
fetch = async ({ status }: { status?: AppStatus } = {}): Promise<App[]> => { fetch = async ({ status }: { status?: AppStatus } = {}): Promise<App[]> => {
let query = [] return await this._get<App[]>("/api/applications", {
if (status) { query: { status },
query.push(`status=${status}`) })
}
const result = await this.request
.get(`/api/applications${query.length ? `?${query.join("&")}` : ""}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
return result.body as App[]
} }
} }

View File

@ -1,35 +1,16 @@
import { import { ProcessAttachmentResponse } from "@budibase/types"
APIError, import { Expectations, TestAPI } from "./base"
Datasource,
ProcessAttachmentResponse,
} from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import fs from "fs" import fs from "fs"
export class AttachmentAPI extends TestAPI { export class AttachmentAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
process = async ( process = async (
name: string, name: string,
file: Buffer | fs.ReadStream | string, file: Buffer | fs.ReadStream | string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<ProcessAttachmentResponse> => { ): Promise<ProcessAttachmentResponse> => {
const result = await this.request return await this._post(`/api/attachments/process`, {
.post(`/api/attachments/process`) files: { file: { name, file } },
.attach("file", file, name) expectations,
.set(this.config.defaultHeaders()) })
if (result.statusCode !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
result.statusCode
}, body: ${JSON.stringify(result.body)}`
)
}
return result.body
} }
} }

View File

@ -2,42 +2,38 @@ import {
CreateAppBackupResponse, CreateAppBackupResponse,
ImportAppBackupResponse, ImportAppBackupResponse,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
export class BackupAPI extends TestAPI { export class BackupAPI extends TestAPI {
constructor(config: TestConfiguration) { exportBasicBackup = async (appId: string, expectations?: Expectations) => {
super(config) const exp = {
} ...expectations,
headers: {
exportBasicBackup = async (appId: string) => { ...expectations?.headers,
const result = await this.request "Content-Type": "application/gzip",
.post(`/api/backups/export?appId=${appId}`) },
.set(this.config.defaultHeaders())
.expect("Content-Type", /application\/gzip/)
.expect(200)
return {
body: result.body as Buffer,
headers: result.headers,
} }
return await this._post<Buffer>(`/api/backups/export`, {
query: { appId },
expectations: exp,
})
} }
createBackup = async (appId: string) => { createBackup = async (appId: string, expectations?: Expectations) => {
const result = await this.request return await this._post<CreateAppBackupResponse>(
.post(`/api/apps/${appId}/backups`) `/api/apps/${appId}/backups`,
.set(this.config.defaultHeaders()) { expectations }
.expect("Content-Type", /json/) )
.expect(200)
return result.body as CreateAppBackupResponse
} }
waitForBackupToComplete = async (appId: string, backupId: string) => { waitForBackupToComplete = async (appId: string, backupId: string) => {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 1000)) await new Promise(resolve => setTimeout(resolve, 1000))
const result = await this.request const response = await this._requestRaw(
.get(`/api/apps/${appId}/backups/${backupId}/file`) "get",
.set(this.config.defaultHeaders()) `/api/apps/${appId}/backups/${backupId}/file`
if (result.status === 200) { )
if (response.status === 200) {
return return
} }
} }
@ -46,13 +42,12 @@ export class BackupAPI extends TestAPI {
importBackup = async ( importBackup = async (
appId: string, appId: string,
backupId: string backupId: string,
expectations?: Expectations
): Promise<ImportAppBackupResponse> => { ): Promise<ImportAppBackupResponse> => {
const result = await this.request return await this._post<ImportAppBackupResponse>(
.post(`/api/apps/${appId}/backups/${backupId}/import`) `/api/apps/${appId}/backups/${backupId}/import`,
.set(this.config.defaultHeaders()) { expectations }
.expect("Content-Type", /json/) )
.expect(200)
return result.body as ImportAppBackupResponse
} }
} }

View File

@ -1,17 +1,196 @@
import TestConfiguration from "../TestConfiguration" import TestConfiguration from "../TestConfiguration"
import { SuperTest, Test } from "supertest" import { SuperTest, Test, Response } from "supertest"
import { ReadStream } from "fs"
export interface TestAPIOpts { type Headers = Record<string, string | string[] | undefined>
headers?: any type Method = "get" | "post" | "put" | "patch" | "delete"
export interface AttachedFile {
name: string
file: Buffer | ReadStream | string
}
function isAttachedFile(file: any): file is AttachedFile {
if (file === undefined) {
return false
}
const attachedFile = file as AttachedFile
return (
Object.hasOwnProperty.call(attachedFile, "file") &&
Object.hasOwnProperty.call(attachedFile, "name")
)
}
export interface Expectations {
status?: number status?: number
headers?: Record<string, string | RegExp>
headersNotPresent?: string[]
body?: Record<string, any>
}
export interface RequestOpts {
headers?: Headers
query?: Record<string, string | undefined>
body?: Record<string, any>
fields?: Record<string, any>
files?: Record<
string,
Buffer | ReadStream | string | AttachedFile | undefined
>
expectations?: Expectations
publicUser?: boolean
} }
export abstract class TestAPI { export abstract class TestAPI {
config: TestConfiguration config: TestConfiguration
request: SuperTest<Test> request: SuperTest<Test>
protected constructor(config: TestConfiguration) { constructor(config: TestConfiguration) {
this.config = config this.config = config
this.request = config.request! this.request = config.request!
} }
protected _get = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
return await this._request<T>("get", url, opts)
}
protected _post = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
return await this._request<T>("post", url, opts)
}
protected _put = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
return await this._request<T>("put", url, opts)
}
protected _patch = async <T>(url: string, opts?: RequestOpts): Promise<T> => {
return await this._request<T>("patch", url, opts)
}
protected _delete = async <T>(
url: string,
opts?: RequestOpts
): Promise<T> => {
return await this._request<T>("delete", url, opts)
}
protected _requestRaw = async (
method: "get" | "post" | "put" | "patch" | "delete",
url: string,
opts?: RequestOpts
): Promise<Response> => {
const {
headers = {},
query = {},
body,
fields = {},
files = {},
expectations,
publicUser = false,
} = opts || {}
const { status = 200 } = expectations || {}
const expectHeaders = expectations?.headers || {}
if (status !== 204 && !expectHeaders["Content-Type"]) {
expectHeaders["Content-Type"] = /^application\/json/
}
let queryParams = []
for (const [key, value] of Object.entries(query)) {
if (value) {
queryParams.push(`${key}=${value}`)
}
}
if (queryParams.length) {
url += `?${queryParams.join("&")}`
}
const headersFn = publicUser
? this.config.publicHeaders.bind(this.config)
: this.config.defaultHeaders.bind(this.config)
let request = this.request[method](url).set(
headersFn({
"x-budibase-include-stacktrace": "true",
})
)
if (headers) {
request = request.set(headers)
}
if (body) {
request = request.send(body)
}
for (const [key, value] of Object.entries(fields)) {
request = request.field(key, value)
}
for (const [key, value] of Object.entries(files)) {
if (isAttachedFile(value)) {
request = request.attach(key, value.file, value.name)
} else {
request = request.attach(key, value as any)
}
}
if (expectations?.headers) {
for (const [key, value] of Object.entries(expectations.headers)) {
if (value === undefined) {
throw new Error(
`Got an undefined expected value for header "${key}", if you want to check for the absence of a header, use headersNotPresent`
)
}
request = request.expect(key, value as any)
}
}
return await request
}
protected _request = async <T>(
method: Method,
url: string,
opts?: RequestOpts
): Promise<T> => {
const { expectations } = opts || {}
const { status = 200 } = expectations || {}
const response = await this._requestRaw(method, url, opts)
if (response.status !== status) {
let message = `Expected status ${status} but got ${response.status}`
const stack = response.body.stack
delete response.body.stack
if (response.body) {
message += `\n\nBody:`
const body = JSON.stringify(response.body, null, 2)
for (const line of body.split("\n")) {
message += `\n⏐ ${line}`
}
}
if (stack) {
message += `\n\nStack from request handler:`
for (const line of stack.split("\n")) {
message += `\n⏐ ${line}`
}
}
throw new Error(message)
}
if (expectations?.headersNotPresent) {
for (const header of expectations.headersNotPresent) {
if (response.headers[header]) {
throw new Error(
`Expected header ${header} not to be present, found value "${response.headers[header]}"`
)
}
}
}
if (expectations?.body) {
expect(response.body).toMatchObject(expectations.body)
}
return response.body
}
} }

View File

@ -1,63 +1,48 @@
import { import {
CreateDatasourceRequest,
Datasource, Datasource,
VerifyDatasourceRequest, VerifyDatasourceRequest,
CreateDatasourceResponse,
UpdateDatasourceResponse,
UpdateDatasourceRequest,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
import supertest from "supertest"
export class DatasourceAPI extends TestAPI { export class DatasourceAPI extends TestAPI {
constructor(config: TestConfiguration) { create = async (
super(config)
}
create = async <B extends boolean = false>(
config: Datasource, config: Datasource,
{ expectations?: Expectations
expectStatus, ): Promise<Datasource> => {
rawResponse, const response = await this._post<CreateDatasourceResponse>(
}: { expectStatus?: number; rawResponse?: B } = {} `/api/datasources`,
): Promise<B extends false ? Datasource : supertest.Response> => { {
const body: CreateDatasourceRequest = { body: {
datasource: config, datasource: config,
tablesFilter: [], tablesFilter: [],
} },
const result = await this.request expectations,
.post(`/api/datasources`) }
.send(body) )
.set(this.config.defaultHeaders()) return response.datasource
.expect("Content-Type", /json/)
.expect(expectStatus || 200)
if (rawResponse) {
return result as any
}
return result.body.datasource
} }
update = async ( update = async (
datasource: Datasource, datasource: UpdateDatasourceRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<Datasource> => { ): Promise<Datasource> => {
const result = await this.request const response = await this._put<UpdateDatasourceResponse>(
.put(`/api/datasources/${datasource._id}`) `/api/datasources/${datasource._id}`,
.send(datasource) { body: datasource, expectations }
.set(this.config.defaultHeaders()) )
.expect("Content-Type", /json/) return response.datasource
.expect(expectStatus)
return result.body.datasource as Datasource
} }
verify = async ( verify = async (
data: VerifyDatasourceRequest, data: VerifyDatasourceRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ) => {
const result = await this.request return await this._post(`/api/datasources/verify`, {
.post(`/api/datasources/verify`) body: data,
.send(data) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/)
.expect(expectStatus)
return result
} }
} }

View File

@ -1,16 +1,8 @@
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base" import { Row } from "@budibase/types"
export class LegacyViewAPI extends TestAPI { export class LegacyViewAPI extends TestAPI {
constructor(config: TestConfiguration) { get = async (id: string, expectations?: Expectations) => {
super(config) return await this._get<Row[]>(`/api/views/${id}`, { expectations })
}
get = async (id: string, { expectStatus } = { expectStatus: 200 }) => {
return await this.request
.get(`/api/views/${id}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
} }
} }

View File

@ -1,52 +1,39 @@
import { AnyDocument, PermissionLevel } from "@budibase/types" import {
import TestConfiguration from "../TestConfiguration" AddPermissionRequest,
import { TestAPI } from "./base" AddPermissionResponse,
GetResourcePermsResponse,
RemovePermissionRequest,
RemovePermissionResponse,
} from "@budibase/types"
import { Expectations, TestAPI } from "./base"
export class PermissionAPI extends TestAPI { export class PermissionAPI extends TestAPI {
constructor(config: TestConfiguration) { get = async (resourceId: string, expectations?: Expectations) => {
super(config) return await this._get<GetResourcePermsResponse>(
`/api/permission/${resourceId}`,
{ expectations }
)
} }
get = async ( add = async (
resourceId: string, request: AddPermissionRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ): Promise<AddPermissionResponse> => {
return this.request const { roleId, resourceId, level } = request
.get(`/api/permission/${resourceId}`) return await this._post<AddPermissionResponse>(
.set(this.config.defaultHeaders()) `/api/permission/${roleId}/${resourceId}/${level}`,
.expect("Content-Type", /json/) { expectations }
.expect(expectStatus) )
}
set = async (
{
roleId,
resourceId,
level,
}: { roleId: string; resourceId: string; level: PermissionLevel },
{ expectStatus } = { expectStatus: 200 }
): Promise<any> => {
const res = await this.request
.post(`/api/permission/${roleId}/${resourceId}/${level}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
return res.body
} }
revoke = async ( revoke = async (
{ request: RemovePermissionRequest,
roleId, expectations?: Expectations
resourceId,
level,
}: { roleId: string; resourceId: string; level: PermissionLevel },
{ expectStatus } = { expectStatus: 200 }
) => { ) => {
const res = await this.request const { roleId, resourceId, level } = request
.delete(`/api/permission/${roleId}/${resourceId}/${level}`) return await this._delete<RemovePermissionResponse>(
.set(this.config.defaultHeaders()) `/api/permission/${roleId}/${resourceId}/${level}`,
.expect("Content-Type", /json/) { expectations }
.expect(expectStatus) )
return res
} }
} }

View File

@ -1,60 +1,32 @@
import TestConfiguration from "../TestConfiguration"
import { import {
Query, Query,
QueryPreview, ExecuteQueryRequest,
type ExecuteQueryRequest, ExecuteQueryResponse,
type ExecuteQueryResponse, PreviewQueryRequest,
PreviewQueryResponse,
} from "@budibase/types" } from "@budibase/types"
import { TestAPI } from "./base" import { TestAPI } from "./base"
export class QueryAPI extends TestAPI { export class QueryAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
create = async (body: Query): Promise<Query> => { create = async (body: Query): Promise<Query> => {
const res = await this.request return await this._post<Query>(`/api/queries`, { body })
.post(`/api/queries`)
.set(this.config.defaultHeaders())
.send(body)
.expect("Content-Type", /json/)
if (res.status !== 200) {
throw new Error(JSON.stringify(res.body))
}
return res.body as Query
} }
execute = async ( execute = async (
queryId: string, queryId: string,
body?: ExecuteQueryRequest body?: ExecuteQueryRequest
): Promise<ExecuteQueryResponse> => { ): Promise<ExecuteQueryResponse> => {
const res = await this.request return await this._post<ExecuteQueryResponse>(
.post(`/api/v2/queries/${queryId}`) `/api/v2/queries/${queryId}`,
.set(this.config.defaultHeaders()) {
.send(body) body,
.expect("Content-Type", /json/) }
)
if (res.status !== 200) {
throw new Error(JSON.stringify(res.body))
}
return res.body
} }
previewQuery = async (queryPreview: QueryPreview) => { previewQuery = async (queryPreview: PreviewQueryRequest) => {
const res = await this.request return await this._post<PreviewQueryResponse>(`/api/queries/preview`, {
.post(`/api/queries/preview`) body: queryPreview,
.send(queryPreview) })
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
if (res.status !== 200) {
throw new Error(JSON.stringify(res.body))
}
return res.body
} }
} }

View File

@ -8,162 +8,140 @@ import {
BulkImportResponse, BulkImportResponse,
SearchRowResponse, SearchRowResponse,
SearchParams, SearchParams,
DeleteRowRequest,
DeleteRows,
DeleteRow,
ExportRowsResponse,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
export class RowAPI extends TestAPI { export class RowAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
get = async ( get = async (
sourceId: string, sourceId: string,
rowId: string, rowId: string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ) => {
const request = this.request return await this._get<Row>(`/api/${sourceId}/rows/${rowId}`, {
.get(`/api/${sourceId}/rows/${rowId}`) expectations,
.set(this.config.defaultHeaders()) })
.expect(expectStatus)
if (expectStatus !== 404) {
request.expect("Content-Type", /json/)
}
return request
} }
getEnriched = async ( getEnriched = async (
sourceId: string, sourceId: string,
rowId: string, rowId: string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ) => {
const request = this.request return await this._get<Row>(`/api/${sourceId}/${rowId}/enrich`, {
.get(`/api/${sourceId}/${rowId}/enrich`) expectations,
.set(this.config.defaultHeaders()) })
.expect(expectStatus)
if (expectStatus !== 404) {
request.expect("Content-Type", /json/)
}
return request
} }
save = async ( save = async (
tableId: string, tableId: string,
row: SaveRowRequest, row: SaveRowRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<Row> => { ): Promise<Row> => {
const resp = await this.request return await this._post<Row>(`/api/${tableId}/rows`, {
.post(`/api/${tableId}/rows`) body: row,
.send(row) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/)
if (resp.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
resp.status
}, body: ${JSON.stringify(resp.body)}`
)
}
return resp.body as Row
} }
validate = async ( validate = async (
sourceId: string, sourceId: string,
row: SaveRowRequest, row: SaveRowRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<ValidateResponse> => { ): Promise<ValidateResponse> => {
const resp = await this.request return await this._post<ValidateResponse>(
.post(`/api/${sourceId}/rows/validate`) `/api/${sourceId}/rows/validate`,
.send(row) {
.set(this.config.defaultHeaders()) body: row,
.expect("Content-Type", /json/) expectations,
.expect(expectStatus) }
return resp.body as ValidateResponse )
} }
patch = async ( patch = async (
sourceId: string, sourceId: string,
row: PatchRowRequest, row: PatchRowRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<Row> => { ): Promise<Row> => {
let resp = await this.request return await this._patch<Row>(`/api/${sourceId}/rows`, {
.patch(`/api/${sourceId}/rows`) body: row,
.send(row) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/)
if (resp.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
resp.status
}, body: ${JSON.stringify(resp.body)}`
)
}
return resp.body as Row
} }
delete = async ( delete = async (
sourceId: string, sourceId: string,
rows: Row | string | (Row | string)[], row: DeleteRow,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ) => {
return this.request return await this._delete<Row>(`/api/${sourceId}/rows`, {
.delete(`/api/${sourceId}/rows`) body: row,
.send(Array.isArray(rows) ? { rows } : rows) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/) }
.expect(expectStatus)
bulkDelete = async (
sourceId: string,
body: DeleteRows,
expectations?: Expectations
) => {
return await this._delete<Row[]>(`/api/${sourceId}/rows`, {
body,
expectations,
})
} }
fetch = async ( fetch = async (
sourceId: string, sourceId: string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<Row[]> => { ): Promise<Row[]> => {
const request = this.request return await this._get<Row[]>(`/api/${sourceId}/rows`, {
.get(`/api/${sourceId}/rows`) expectations,
.set(this.config.defaultHeaders()) })
.expect(expectStatus)
return (await request).body
} }
exportRows = async ( exportRows = async (
tableId: string, tableId: string,
body: ExportRowsRequest, body: ExportRowsRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
) => { ) => {
const request = this.request const response = await this._requestRaw(
.post(`/api/${tableId}/rows/exportRows?format=json`) "post",
.set(this.config.defaultHeaders()) `/api/${tableId}/rows/exportRows`,
.send(body) {
.expect("Content-Type", /json/) body,
.expect(expectStatus) query: { format: "json" },
return request expectations,
}
)
return response.text
} }
bulkImport = async ( bulkImport = async (
tableId: string, tableId: string,
body: BulkImportRequest, body: BulkImportRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<BulkImportResponse> => { ): Promise<BulkImportResponse> => {
let request = this.request return await this._post<BulkImportResponse>(
.post(`/api/tables/${tableId}/import`) `/api/tables/${tableId}/import`,
.send(body) {
.set(this.config.defaultHeaders()) body,
.expect(expectStatus) expectations,
return (await request).body }
)
} }
search = async ( search = async (
sourceId: string, sourceId: string,
params?: SearchParams, params?: SearchParams,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<SearchRowResponse> => { ): Promise<SearchRowResponse> => {
const request = this.request return await this._post<SearchRowResponse>(`/api/${sourceId}/search`, {
.post(`/api/${sourceId}/search`) body: params,
.send(params) expectations,
.set(this.config.defaultHeaders()) })
.expect(expectStatus)
return (await request).body
} }
} }

View File

@ -1,18 +1,8 @@
import TestConfiguration from "../TestConfiguration"
import { Screen } from "@budibase/types" import { Screen } from "@budibase/types"
import { TestAPI } from "./base" import { Expectations, TestAPI } from "./base"
export class ScreenAPI extends TestAPI { export class ScreenAPI extends TestAPI {
constructor(config: TestConfiguration) { list = async (expectations?: Expectations): Promise<Screen[]> => {
super(config) return await this._get<Screen[]>(`/api/screens`, { expectations })
}
list = async (): Promise<Screen[]> => {
const res = await this.request
.get(`/api/screens`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(200)
return res.body as Screen[]
} }
} }

View File

@ -5,74 +5,38 @@ import {
SaveTableResponse, SaveTableResponse,
Table, Table,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
export class TableAPI extends TestAPI { export class TableAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
save = async ( save = async (
data: SaveTableRequest, data: SaveTableRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<SaveTableResponse> => { ): Promise<SaveTableResponse> => {
const res = await this.request return await this._post<SaveTableResponse>("/api/tables", {
.post(`/api/tables`) body: data,
.send(data) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/)
if (res.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body
} }
fetch = async ( fetch = async (expectations?: Expectations): Promise<Table[]> => {
{ expectStatus } = { expectStatus: 200 } return await this._get<Table[]>("/api/tables", { expectations })
): Promise<Table[]> => {
const res = await this.request
.get(`/api/tables`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
return res.body
} }
get = async ( get = async (
tableId: string, tableId: string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<Table> => { ): Promise<Table> => {
const res = await this.request return await this._get<Table>(`/api/tables/${tableId}`, { expectations })
.get(`/api/tables/${tableId}`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
return res.body
} }
migrate = async ( migrate = async (
tableId: string, tableId: string,
data: MigrateRequest, data: MigrateRequest,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<MigrateResponse> => { ): Promise<MigrateResponse> => {
const res = await this.request return await this._post<MigrateResponse>(`/api/tables/${tableId}/migrate`, {
.post(`/api/tables/${tableId}/migrate`) body: data,
.send(data) expectations,
.set(this.config.defaultHeaders()) })
if (res.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body
} }
} }

View File

@ -4,154 +4,79 @@ import {
Flags, Flags,
UserMetadata, UserMetadata,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
import { DocumentInsertResponse } from "@budibase/nano" import { DocumentInsertResponse } from "@budibase/nano"
export class UserAPI extends TestAPI { export class UserAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
fetch = async ( fetch = async (
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<FetchUserMetadataResponse> => { ): Promise<FetchUserMetadataResponse> => {
const res = await this.request return await this._get<FetchUserMetadataResponse>("/api/users/metadata", {
.get(`/api/users/metadata`) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/)
if (res.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body
} }
find = async ( find = async (
id: string, id: string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<FindUserMetadataResponse> => { ): Promise<FindUserMetadataResponse> => {
const res = await this.request return await this._get<FindUserMetadataResponse>(
.get(`/api/users/metadata/${id}`) `/api/users/metadata/${id}`,
.set(this.config.defaultHeaders()) {
.expect("Content-Type", /json/) expectations,
}
if (res.status !== expectStatus) { )
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body
} }
update = async ( update = async (
user: UserMetadata, user: UserMetadata,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<DocumentInsertResponse> => { ): Promise<DocumentInsertResponse> => {
const res = await this.request return await this._put<DocumentInsertResponse>("/api/users/metadata", {
.put(`/api/users/metadata`) body: user,
.set(this.config.defaultHeaders()) expectations,
.send(user) })
.expect("Content-Type", /json/)
if (res.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body as DocumentInsertResponse
} }
updateSelf = async ( updateSelf = async (
user: UserMetadata, user: UserMetadata,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<DocumentInsertResponse> => { ): Promise<DocumentInsertResponse> => {
const res = await this.request return await this._post<DocumentInsertResponse>(
.post(`/api/users/metadata/self`) "/api/users/metadata/self",
.set(this.config.defaultHeaders()) {
.send(user) body: user,
.expect("Content-Type", /json/) expectations,
}
if (res.status !== expectStatus) { )
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body as DocumentInsertResponse
} }
destroy = async ( destroy = async (
id: string, id: string,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<{ message: string }> => { ): Promise<{ message: string }> => {
const res = await this.request return await this._delete<{ message: string }>(
.delete(`/api/users/metadata/${id}`) `/api/users/metadata/${id}`,
.set(this.config.defaultHeaders()) {
.expect("Content-Type", /json/) expectations,
}
if (res.status !== expectStatus) { )
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body as { message: string }
} }
setFlag = async ( setFlag = async (
flag: string, flag: string,
value: any, value: any,
{ expectStatus } = { expectStatus: 200 } expectations?: Expectations
): Promise<{ message: string }> => { ): Promise<{ message: string }> => {
const res = await this.request return await this._post<{ message: string }>(`/api/users/flags`, {
.post(`/api/users/flags`) body: { flag, value },
.set(this.config.defaultHeaders()) expectations,
.send({ flag, value }) })
.expect("Content-Type", /json/)
if (res.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body as { message: string }
} }
getFlags = async ( getFlags = async (expectations?: Expectations): Promise<Flags> => {
{ expectStatus } = { expectStatus: 200 } return await this._get<Flags>(`/api/users/flags`, {
): Promise<Flags> => { expectations,
const res = await this.request })
.get(`/api/users/flags`)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
if (res.status !== expectStatus) {
throw new Error(
`Expected status ${expectStatus} but got ${
res.status
} with body ${JSON.stringify(res.body)}`
)
}
return res.body as Flags
} }
} }

View File

@ -3,21 +3,16 @@ import {
UpdateViewRequest, UpdateViewRequest,
ViewV2, ViewV2,
SearchViewRowRequest, SearchViewRowRequest,
PaginatedSearchRowResponse,
} from "@budibase/types" } from "@budibase/types"
import TestConfiguration from "../TestConfiguration" import { Expectations, TestAPI } from "./base"
import { TestAPI } from "./base"
import { generator } from "@budibase/backend-core/tests" import { generator } from "@budibase/backend-core/tests"
import { Response } from "superagent"
import sdk from "../../../sdk" import sdk from "../../../sdk"
export class ViewV2API extends TestAPI { export class ViewV2API extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
create = async ( create = async (
viewData?: Partial<CreateViewRequest>, viewData?: Partial<CreateViewRequest>,
{ expectStatus } = { expectStatus: 201 } expectations?: Expectations
): Promise<ViewV2> => { ): Promise<ViewV2> => {
let tableId = viewData?.tableId let tableId = viewData?.tableId
if (!tableId && !this.config.table) { if (!tableId && !this.config.table) {
@ -30,43 +25,36 @@ export class ViewV2API extends TestAPI {
name: generator.guid(), name: generator.guid(),
...viewData, ...viewData,
} }
const result = await this.request
.post(`/api/v2/views`) const exp: Expectations = {
.send(view) status: 201,
.set(this.config.defaultHeaders()) ...expectations,
.expect("Content-Type", /json/) }
.expect(expectStatus)
return result.body.data as ViewV2 const resp = await this._post<{ data: ViewV2 }>("/api/v2/views", {
body: view,
expectations: exp,
})
return resp.data
} }
update = async ( update = async (
view: UpdateViewRequest, view: UpdateViewRequest,
{ expectations?: Expectations
expectStatus,
handleResponse,
}: {
expectStatus: number
handleResponse?: (response: Response) => void
} = { expectStatus: 200 }
): Promise<ViewV2> => { ): Promise<ViewV2> => {
const result = await this.request const resp = await this._put<{ data: ViewV2 }>(`/api/v2/views/${view.id}`, {
.put(`/api/v2/views/${view.id}`) body: view,
.send(view) expectations,
.set(this.config.defaultHeaders()) })
.expect("Content-Type", /json/) return resp.data
.expect(expectStatus)
if (handleResponse) {
handleResponse(result)
}
return result.body.data as ViewV2
} }
delete = async (viewId: string, { expectStatus } = { expectStatus: 204 }) => { delete = async (viewId: string, expectations?: Expectations) => {
return this.request const exp = {
.delete(`/api/v2/views/${viewId}`) status: 204,
.set(this.config.defaultHeaders()) ...expectations,
.expect(expectStatus) }
return await this._delete(`/api/v2/views/${viewId}`, { expectations: exp })
} }
get = async (viewId: string) => { get = async (viewId: string) => {
@ -78,17 +66,29 @@ export class ViewV2API extends TestAPI {
search = async ( search = async (
viewId: string, viewId: string,
params?: SearchViewRowRequest, params?: SearchViewRowRequest,
{ expectStatus = 200, usePublicUser = false } = {} expectations?: Expectations
) => { ) => {
return this.request return await this._post<PaginatedSearchRowResponse>(
.post(`/api/v2/views/${viewId}/search`) `/api/v2/views/${viewId}/search`,
.send(params) {
.set( body: params,
usePublicUser expectations,
? this.config.publicHeaders() }
: this.config.defaultHeaders() )
) }
.expect("Content-Type", /json/)
.expect(expectStatus) publicSearch = async (
viewId: string,
params?: SearchViewRowRequest,
expectations?: Expectations
) => {
return await this._post<PaginatedSearchRowResponse>(
`/api/v2/views/${viewId}/search`,
{
body: params,
expectations,
publicUser: true,
}
)
} }
} }

View File

@ -1,4 +1,4 @@
import { PlanType } from "../../../sdk" import { PermissionLevel, PlanType } from "../../../sdk"
export interface ResourcePermissionInfo { export interface ResourcePermissionInfo {
role: string role: string
@ -14,3 +14,21 @@ export interface GetResourcePermsResponse {
export interface GetDependantResourcesResponse { export interface GetDependantResourcesResponse {
resourceByType?: Record<string, number> resourceByType?: Record<string, number>
} }
export interface AddedPermission {
_id?: string
rev?: string
error?: string
reason?: string
}
export type AddPermissionResponse = AddedPermission[]
export interface AddPermissionRequest {
roleId: string
resourceId: string
level: PermissionLevel
}
export interface RemovePermissionRequest extends AddPermissionRequest {}
export interface RemovePermissionResponse extends AddPermissionResponse {}

View File

@ -1,6 +1,6 @@
import { SearchFilters, SearchParams } from "../../../sdk" import { SearchFilters, SearchParams } from "../../../sdk"
import { Row } from "../../../documents" import { Row } from "../../../documents"
import { SortOrder } from "../../../api" import { PaginationResponse, SortOrder } from "../../../api"
import { ReadStream } from "fs" import { ReadStream } from "fs"
export interface SaveRowRequest extends Row {} export interface SaveRowRequest extends Row {}
@ -31,6 +31,10 @@ export interface SearchRowResponse {
rows: any[] rows: any[]
} }
export interface PaginatedSearchRowResponse
extends SearchRowResponse,
PaginationResponse {}
export interface ExportRowsRequest { export interface ExportRowsRequest {
rows: string[] rows: string[]
columns?: string[] columns?: string[]

View File

@ -27,3 +27,9 @@ export interface FetchAppPackageResponse {
clientLibPath: string clientLibPath: string
hasLock: boolean hasLock: boolean
} }
export interface PublishResponse {
_id: string
status: string
appUrl: string
}

View File

@ -13,3 +13,4 @@ export * from "./searchFilter"
export * from "./cookies" export * from "./cookies"
export * from "./automation" export * from "./automation"
export * from "./layout" export * from "./layout"
export * from "./query"

View File

@ -0,0 +1,20 @@
import { QueryPreview, QuerySchema } from "../../documents"
export interface PreviewQueryRequest extends QueryPreview {}
export interface PreviewQueryResponse {
rows: any[]
nestedSchemaFields: { [key: string]: { [key: string]: string | QuerySchema } }
schema: { [key: string]: string | QuerySchema }
info: any
extra: any
}
export interface ExecuteQueryRequest {
parameters?: { [key: string]: string }
pagination?: any
}
export interface ExecuteQueryResponse {
data: Record<string, any>[]
}

View File

@ -62,22 +62,6 @@ export interface PaginationValues {
limit: number | null limit: number | null
} }
export interface PreviewQueryRequest extends Omit<Query, "parameters"> {
parameters: {}
flags?: {
urlName?: boolean
}
}
export interface ExecuteQueryRequest {
parameters?: { [key: string]: string }
pagination?: any
}
export interface ExecuteQueryResponse {
data: Row[]
}
export enum HttpMethod { export enum HttpMethod {
GET = "GET", GET = "GET",
POST = "POST", POST = "POST",