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 * as errors from "../errors"
import environment from "../environment"
export async function errorHandling(ctx: any, next: any) {
try {
@ -14,15 +15,19 @@ export async function errorHandling(ctx: any, next: any) {
console.error(err)
}
const error = errors.getPublicError(err)
const body: APIError = {
let error: APIError = {
message: err.message,
status: status,
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,
ResourcePermissionInfo,
GetDependantResourcesResponse,
AddPermissionResponse,
AddPermissionRequest,
RemovePermissionRequest,
RemovePermissionResponse,
} from "@budibase/types"
import { getRoleParams } from "../../db/utils"
import {
@ -16,9 +20,9 @@ import {
import { removeFromArray } from "../../utilities"
import sdk from "../../sdk"
const PermissionUpdateType = {
REMOVE: "remove",
ADD: "add",
const enum PermissionUpdateType {
REMOVE = "remove",
ADD = "add",
}
const SUPPORTED_LEVELS = CURRENTLY_SUPPORTED_LEVELS
@ -39,7 +43,7 @@ async function updatePermissionOnRole(
resourceId,
level,
}: { roleId: string; resourceId: string; level: PermissionLevel },
updateType: string
updateType: PermissionUpdateType
) {
const allowedAction = await sdk.permissions.resourceActionAllowed({
resourceId,
@ -107,11 +111,15 @@ async function updatePermissionOnRole(
}
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
resp._id = roles.getExternalRoleID(resp.id, version)
delete resp.id
return resp
const _id = roles.getExternalRoleID(resp.id, version)
return {
_id,
rev: resp.rev,
error: resp.error,
reason: resp.reason,
}
})
}
@ -189,13 +197,14 @@ export async function getDependantResources(
}
}
export async function addPermission(ctx: UserCtx) {
ctx.body = await updatePermissionOnRole(ctx.params, PermissionUpdateType.ADD)
export async function addPermission(ctx: UserCtx<void, AddPermissionResponse>) {
const params: AddPermissionRequest = ctx.params
ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.ADD)
}
export async function removePermission(ctx: UserCtx) {
ctx.body = await updatePermissionOnRole(
ctx.params,
PermissionUpdateType.REMOVE
)
export async function removePermission(
ctx: UserCtx<void, RemovePermissionResponse>
) {
const params: RemovePermissionRequest = ctx.params
ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.REMOVE)
}

View File

@ -17,10 +17,12 @@ import {
QueryPreview,
QuerySchema,
FieldType,
type ExecuteQueryRequest,
type ExecuteQueryResponse,
type Row,
ExecuteQueryRequest,
ExecuteQueryResponse,
Row,
QueryParameter,
PreviewQueryRequest,
PreviewQueryResponse,
} from "@budibase/types"
import { ValidQueryNameRegex, utils as JsonUtils } from "@budibase/shared-core"
@ -134,14 +136,16 @@ function enrichParameters(
return requestParameters
}
export async function preview(ctx: UserCtx) {
export async function preview(
ctx: UserCtx<PreviewQueryRequest, PreviewQueryResponse>
) {
const { datasource, envVars } = await sdk.datasources.getWithEnvVars(
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
// 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
if (queryId && !existingSchema) {
@ -266,9 +270,7 @@ export async function preview(ctx: UserCtx) {
},
}
const { rows, keys, info, extra } = (await Runner.run(
inputs
)) as QueryResponse
const { rows, keys, info, extra } = await Runner.run<QueryResponse>(inputs)
const { previewSchema, nestedSchemaFields } = getSchemaFields(rows, 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
delete datasource.config
await events.query.previewed(datasource, query)
await events.query.previewed(datasource, ctx.request.body)
ctx.body = {
rows,
nestedSchemaFields,
@ -295,7 +297,10 @@ export async function preview(ctx: UserCtx) {
}
async function execute(
ctx: UserCtx<ExecuteQueryRequest, ExecuteQueryResponse | Row[]>,
ctx: UserCtx<
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
>,
opts: any = { rowsOnly: false, isAutomation: false }
) {
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 })
}
export async function executeV2(
ctx: UserCtx,
ctx: UserCtx<
ExecuteQueryRequest,
ExecuteQueryResponse | Record<string, any>[]
>,
{ isAutomation }: { isAutomation?: boolean } = {}
) {
return execute(ctx, { rowsOnly: false, isAutomation })
}
const removeDynamicVariables = async (queryId: any) => {
const removeDynamicVariables = async (queryId: string) => {
const db = context.getAppDB()
const query = await db.get<Query>(queryId)
const datasource = await sdk.datasources.get(query.datasourceId)
@ -384,7 +394,7 @@ const removeDynamicVariables = async (queryId: any) => {
export async function destroy(ctx: UserCtx) {
const db = context.getAppDB()
const queryId = ctx.params.queryId
const queryId = ctx.params.queryId as string
await removeDynamicVariables(queryId)
const query = await db.get<Query>(queryId)
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)
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
}

View File

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

View File

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

View File

@ -19,11 +19,8 @@ describe("/backups", () => {
describe("/api/backups/export", () => {
it("should be able to export app", async () => {
const { body, headers } = await config.api.backup.exportBasicBackup(
config.getAppId()!
)
const body = await config.api.backup.exportBasicBackup(config.getAppId()!)
expect(body instanceof Buffer).toBe(true)
expect(headers["content-type"]).toEqual("application/gzip")
expect(events.app.exported).toBeCalledTimes(1)
})
@ -38,15 +35,13 @@ describe("/backups", () => {
it("should infer the app name from the app", async () => {
tk.freeze(mocks.date.MOCK_DATE)
const { headers } = await config.api.backup.exportBasicBackup(
config.getAppId()!
)
expect(headers["content-disposition"]).toEqual(
`attachment; filename="${
config.getApp().name
}-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
)
await config.api.backup.exportBasicBackup(config.getAppId()!, {
headers: {
"content-disposition": `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
row = await config.createRow()
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,
resourceId: table._id,
level: PermissionLevel.READ,
@ -88,13 +88,13 @@ describe("/permission", () => {
})
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,
resourceId: table._id,
level: PermissionLevel.WRITE,
})
const res = await config.api.permission.get(table._id)
expect(res.body).toEqual({
expect(res).toEqual({
permissions: {
read: { permissionType: "EXPLICIT", role: STD_ROLE_ID },
write: { permissionType: "EXPLICIT", role: HIGHER_ROLE_ID },
@ -117,16 +117,19 @@ describe("/permission", () => {
level: PermissionLevel.READ,
})
const response = await config.api.permission.set(
await config.api.permission.add(
{
roleId: STD_ROLE_ID,
resourceId: table._id,
level: PermissionLevel.EXECUTE,
},
{ expectStatus: 403 }
)
expect(response.message).toEqual(
"You are not allowed to 'read' the resource type 'datasource'"
{
status: 403,
body: {
message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
)
})
})
@ -138,9 +141,9 @@ describe("/permission", () => {
resourceId: table._id,
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)
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 () => {
@ -156,10 +159,13 @@ describe("/permission", () => {
resourceId: table._id,
level: PermissionLevel.EXECUTE,
},
{ expectStatus: 403 }
)
expect(response.body.message).toEqual(
"You are not allowed to 'read' the resource type 'datasource'"
{
status: 403,
body: {
message:
"You are not allowed to 'read' the resource type 'datasource'",
},
}
)
})
})
@ -181,10 +187,8 @@ describe("/permission", () => {
// replicate changes before checking permissions
await config.publish()
const res = await config.api.viewV2.search(view.id, undefined, {
usePublicUser: true,
})
expect(res.body.rows[0]._id).toEqual(row._id)
const res = await config.api.viewV2.publicSearch(view.id)
expect(res.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 () => {
@ -196,14 +200,11 @@ describe("/permission", () => {
// replicate changes before checking permissions
await config.publish()
await config.api.viewV2.search(view.id, undefined, {
expectStatus: 403,
usePublicUser: true,
})
await config.api.viewV2.publicSearch(view.id, undefined, { status: 403 })
})
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,
resourceId: view.id,
level: PermissionLevel.READ,
@ -216,15 +217,14 @@ describe("/permission", () => {
// replicate changes before checking permissions
await config.publish()
await config.api.viewV2.search(view.id, undefined, {
expectStatus: 403,
usePublicUser: true,
await config.api.viewV2.publicSearch(view.id, undefined, {
status: 403,
})
})
it("should use the view permissions if the flag is on", async () => {
mocks.licenses.useViewPermissions()
await config.api.permission.set({
await config.api.permission.add({
roleId: STD_ROLE_ID,
resourceId: view.id,
level: PermissionLevel.READ,
@ -237,10 +237,8 @@ describe("/permission", () => {
// replicate changes before checking permissions
await config.publish()
const res = await config.api.viewV2.search(view.id, undefined, {
usePublicUser: true,
})
expect(res.body.rows[0]._id).toEqual(row._id)
const res = await config.api.viewV2.publicSearch(view.id)
expect(res.rows[0]._id).toEqual(row._id)
})
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)
expect(res.body).toEqual({
expect(res).toEqual({
permissions: {
read: {
permissionType: "BASE",

View File

@ -397,15 +397,16 @@ describe("/queries", () => {
})
it("should fail with invalid integration type", async () => {
const response = await config.api.datasource.create(
{
...basicDatasource().datasource,
source: "INVALID_INTEGRATION" as SourceName,
const datasource: Datasource = {
...basicDatasource().datasource,
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 () => {
const table = await config.createTable()
await config.api.permission.set({
await config.api.permission.add({
roleId: BUILTIN_ROLE_IDS.POWER,
resourceId: table._id,
level: PermissionLevel.READ,

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ describe("test the create row action", () => {
expect(res.id).toBeDefined()
expect(res.revision).toBeDefined()
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.description).toEqual("test")
})

View File

@ -36,7 +36,7 @@ describe("test the update row action", () => {
it("should be able to run the action", async () => {
const res = await setup.runStep(setup.actions.UPDATE_ROW.stepId, inputs)
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.description).not.toEqual("")
})
@ -87,8 +87,8 @@ describe("test the update row action", () => {
})
let getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user1._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id)
expect(getResp.user1[0]._id).toEqual(user1._id)
expect(getResp.user2[0]._id).toEqual(user2._id)
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
rowId: row._id,
@ -103,8 +103,8 @@ describe("test the update row action", () => {
expect(stepResp.success).toEqual(true)
getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user2._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id)
expect(getResp.user1[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 () => {
@ -140,8 +140,8 @@ describe("test the update row action", () => {
})
let getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user1._id)
expect(getResp.body.user2[0]._id).toEqual(user2._id)
expect(getResp.user1[0]._id).toEqual(user1._id)
expect(getResp.user2[0]._id).toEqual(user2._id)
let stepResp = await setup.runStep(setup.actions.UPDATE_ROW.stepId, {
rowId: row._id,
@ -163,7 +163,7 @@ describe("test the update row action", () => {
expect(stepResp.success).toEqual(true)
getResp = await config.api.row.get(table._id!, row._id!)
expect(getResp.body.user1[0]._id).toEqual(user2._id)
expect(getResp.body.user2).toBeUndefined()
expect(getResp.user1[0]._id).toEqual(user2._id)
expect(getResp.user2).toBeUndefined()
})
})

View File

@ -100,7 +100,7 @@ describe("test the link controller", () => {
const { _id } = await config.createRow(
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 () => {

View File

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

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({
...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({
...row,
type: "row",

View File

@ -712,11 +712,6 @@ export default class TestConfiguration {
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) {
if (!tableId && this.table) {
tableId = this.table._id!

View File

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

View File

@ -1,35 +1,16 @@
import {
APIError,
Datasource,
ProcessAttachmentResponse,
} from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import { ProcessAttachmentResponse } from "@budibase/types"
import { Expectations, TestAPI } from "./base"
import fs from "fs"
export class AttachmentAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
process = async (
name: string,
file: Buffer | fs.ReadStream | string,
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<ProcessAttachmentResponse> => {
const result = await this.request
.post(`/api/attachments/process`)
.attach("file", file, name)
.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
return await this._post(`/api/attachments/process`, {
files: { file: { name, file } },
expectations,
})
}
}

View File

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

View File

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

View File

@ -1,16 +1,8 @@
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import { Expectations, TestAPI } from "./base"
import { Row } from "@budibase/types"
export class LegacyViewAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
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)
get = async (id: string, expectations?: Expectations) => {
return await this._get<Row[]>(`/api/views/${id}`, { expectations })
}
}

View File

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

View File

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

View File

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

View File

@ -1,18 +1,8 @@
import TestConfiguration from "../TestConfiguration"
import { Screen } from "@budibase/types"
import { TestAPI } from "./base"
import { Expectations, TestAPI } from "./base"
export class ScreenAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
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[]
list = async (expectations?: Expectations): Promise<Screen[]> => {
return await this._get<Screen[]>(`/api/screens`, { expectations })
}
}

View File

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

View File

@ -4,154 +4,79 @@ import {
Flags,
UserMetadata,
} from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import { Expectations, TestAPI } from "./base"
import { DocumentInsertResponse } from "@budibase/nano"
export class UserAPI extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
fetch = async (
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<FetchUserMetadataResponse> => {
const res = await this.request
.get(`/api/users/metadata`)
.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
return await this._get<FetchUserMetadataResponse>("/api/users/metadata", {
expectations,
})
}
find = async (
id: string,
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<FindUserMetadataResponse> => {
const res = await this.request
.get(`/api/users/metadata/${id}`)
.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
return await this._get<FindUserMetadataResponse>(
`/api/users/metadata/${id}`,
{
expectations,
}
)
}
update = async (
user: UserMetadata,
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<DocumentInsertResponse> => {
const res = await this.request
.put(`/api/users/metadata`)
.set(this.config.defaultHeaders())
.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
return await this._put<DocumentInsertResponse>("/api/users/metadata", {
body: user,
expectations,
})
}
updateSelf = async (
user: UserMetadata,
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<DocumentInsertResponse> => {
const res = await this.request
.post(`/api/users/metadata/self`)
.set(this.config.defaultHeaders())
.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
return await this._post<DocumentInsertResponse>(
"/api/users/metadata/self",
{
body: user,
expectations,
}
)
}
destroy = async (
id: string,
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<{ message: string }> => {
const res = await this.request
.delete(`/api/users/metadata/${id}`)
.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 { message: string }
return await this._delete<{ message: string }>(
`/api/users/metadata/${id}`,
{
expectations,
}
)
}
setFlag = async (
flag: string,
value: any,
{ expectStatus } = { expectStatus: 200 }
expectations?: Expectations
): Promise<{ message: string }> => {
const res = await this.request
.post(`/api/users/flags`)
.set(this.config.defaultHeaders())
.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 }
return await this._post<{ message: string }>(`/api/users/flags`, {
body: { flag, value },
expectations,
})
}
getFlags = async (
{ expectStatus } = { expectStatus: 200 }
): Promise<Flags> => {
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
getFlags = async (expectations?: Expectations): Promise<Flags> => {
return await this._get<Flags>(`/api/users/flags`, {
expectations,
})
}
}

View File

@ -3,21 +3,16 @@ import {
UpdateViewRequest,
ViewV2,
SearchViewRowRequest,
PaginatedSearchRowResponse,
} from "@budibase/types"
import TestConfiguration from "../TestConfiguration"
import { TestAPI } from "./base"
import { Expectations, TestAPI } from "./base"
import { generator } from "@budibase/backend-core/tests"
import { Response } from "superagent"
import sdk from "../../../sdk"
export class ViewV2API extends TestAPI {
constructor(config: TestConfiguration) {
super(config)
}
create = async (
viewData?: Partial<CreateViewRequest>,
{ expectStatus } = { expectStatus: 201 }
expectations?: Expectations
): Promise<ViewV2> => {
let tableId = viewData?.tableId
if (!tableId && !this.config.table) {
@ -30,43 +25,36 @@ export class ViewV2API extends TestAPI {
name: generator.guid(),
...viewData,
}
const result = await this.request
.post(`/api/v2/views`)
.send(view)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
return result.body.data as ViewV2
const exp: Expectations = {
status: 201,
...expectations,
}
const resp = await this._post<{ data: ViewV2 }>("/api/v2/views", {
body: view,
expectations: exp,
})
return resp.data
}
update = async (
view: UpdateViewRequest,
{
expectStatus,
handleResponse,
}: {
expectStatus: number
handleResponse?: (response: Response) => void
} = { expectStatus: 200 }
expectations?: Expectations
): Promise<ViewV2> => {
const result = await this.request
.put(`/api/v2/views/${view.id}`)
.send(view)
.set(this.config.defaultHeaders())
.expect("Content-Type", /json/)
.expect(expectStatus)
if (handleResponse) {
handleResponse(result)
}
return result.body.data as ViewV2
const resp = await this._put<{ data: ViewV2 }>(`/api/v2/views/${view.id}`, {
body: view,
expectations,
})
return resp.data
}
delete = async (viewId: string, { expectStatus } = { expectStatus: 204 }) => {
return this.request
.delete(`/api/v2/views/${viewId}`)
.set(this.config.defaultHeaders())
.expect(expectStatus)
delete = async (viewId: string, expectations?: Expectations) => {
const exp = {
status: 204,
...expectations,
}
return await this._delete(`/api/v2/views/${viewId}`, { expectations: exp })
}
get = async (viewId: string) => {
@ -78,17 +66,29 @@ export class ViewV2API extends TestAPI {
search = async (
viewId: string,
params?: SearchViewRowRequest,
{ expectStatus = 200, usePublicUser = false } = {}
expectations?: Expectations
) => {
return this.request
.post(`/api/v2/views/${viewId}/search`)
.send(params)
.set(
usePublicUser
? this.config.publicHeaders()
: this.config.defaultHeaders()
)
.expect("Content-Type", /json/)
.expect(expectStatus)
return await this._post<PaginatedSearchRowResponse>(
`/api/v2/views/${viewId}/search`,
{
body: params,
expectations,
}
)
}
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 {
role: string
@ -14,3 +14,21 @@ export interface GetResourcePermsResponse {
export interface GetDependantResourcesResponse {
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 { Row } from "../../../documents"
import { SortOrder } from "../../../api"
import { PaginationResponse, SortOrder } from "../../../api"
import { ReadStream } from "fs"
export interface SaveRowRequest extends Row {}
@ -31,6 +31,10 @@ export interface SearchRowResponse {
rows: any[]
}
export interface PaginatedSearchRowResponse
extends SearchRowResponse,
PaginationResponse {}
export interface ExportRowsRequest {
rows: string[]
columns?: string[]

View File

@ -27,3 +27,9 @@ export interface FetchAppPackageResponse {
clientLibPath: string
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 "./automation"
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
}
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 {
GET = "GET",
POST = "POST",