Merge branch 'master' into fix/13177
This commit is contained in:
commit
5afb5f2490
|
@ -1 +1 @@
|
|||
Subproject commit 806b6fd5c11c284ebf4a01627d75db939f0f8152
|
||||
Subproject commit 0c050591c21d3b67dc0c9225d60cc9e2324c8dac
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,13 +59,13 @@
|
|||
isReadonly: () => readonly,
|
||||
getType: () => column.schema.type,
|
||||
getValue: () => row[column.name],
|
||||
setValue: (value, options = { save: true }) => {
|
||||
setValue: (value, options = { apply: true }) => {
|
||||
validation.actions.setError(cellId, null)
|
||||
updateValue({
|
||||
rowId: row._id,
|
||||
column: column.name,
|
||||
value,
|
||||
save: options?.save,
|
||||
apply: options?.apply,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
|
|
@ -217,14 +217,14 @@
|
|||
const type = $focusedCellAPI.getType()
|
||||
if (type === "number" && keyCodeIsNumber(keyCode)) {
|
||||
// Update the value locally but don't save it yet
|
||||
$focusedCellAPI.setValue(parseInt(key), { save: false })
|
||||
$focusedCellAPI.setValue(parseInt(key), { apply: false })
|
||||
$focusedCellAPI.focus()
|
||||
} else if (
|
||||
["string", "barcodeqr", "longform"].includes(type) &&
|
||||
(keyCodeIsLetter(keyCode) || keyCodeIsNumber(keyCode))
|
||||
) {
|
||||
// Update the value locally but don't save it yet
|
||||
$focusedCellAPI.setValue(key, { save: false })
|
||||
$focusedCellAPI.setValue(key, { apply: false })
|
||||
$focusedCellAPI.focus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -327,29 +327,31 @@ export const createActions = context => {
|
|||
get(fetch)?.getInitialData()
|
||||
}
|
||||
|
||||
// Patches a row with some changes
|
||||
const updateRow = async (rowId, changes, options = { save: true }) => {
|
||||
// Checks if a changeset for a row actually mutates the row or not
|
||||
const changesAreValid = (row, changes) => {
|
||||
const columns = Object.keys(changes || {})
|
||||
if (!row || !columns.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ensure there is at least 1 column that creates a difference
|
||||
return columns.some(column => row[column] !== changes[column])
|
||||
}
|
||||
|
||||
// Patches a row with some changes in local state, and returns whether a
|
||||
// valid pending change was made or not
|
||||
const stashRowChanges = (rowId, changes) => {
|
||||
const $rows = get(rows)
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
const index = $rowLookupMap[rowId]
|
||||
const row = $rows[index]
|
||||
if (index == null || !Object.keys(changes || {}).length) {
|
||||
return
|
||||
|
||||
// Check this is a valid change
|
||||
if (!row || !changesAreValid(row, changes)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Abandon if no changes
|
||||
let same = true
|
||||
for (let column of Object.keys(changes)) {
|
||||
if (row[column] !== changes[column]) {
|
||||
same = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (same) {
|
||||
return
|
||||
}
|
||||
|
||||
// Immediately update state so that the change is reflected
|
||||
// Add change to cache
|
||||
rowChangeCache.update(state => ({
|
||||
...state,
|
||||
[rowId]: {
|
||||
|
@ -357,26 +359,30 @@ export const createActions = context => {
|
|||
...changes,
|
||||
},
|
||||
}))
|
||||
return true
|
||||
}
|
||||
|
||||
// Stop here if we don't want to persist the change
|
||||
if (!options?.save) {
|
||||
// Saves any pending changes to a row
|
||||
const applyRowChanges = async rowId => {
|
||||
const $rows = get(rows)
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
const index = $rowLookupMap[rowId]
|
||||
const row = $rows[index]
|
||||
if (row == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Save change
|
||||
try {
|
||||
inProgressChanges.update(state => ({
|
||||
...state,
|
||||
[rowId]: true,
|
||||
}))
|
||||
// Mark as in progress
|
||||
inProgressChanges.update(state => ({ ...state, [rowId]: true }))
|
||||
|
||||
// Update row
|
||||
const saved = await datasource.actions.updateRow({
|
||||
...cleanRow(row),
|
||||
...get(rowChangeCache)[rowId],
|
||||
})
|
||||
const changes = get(rowChangeCache)[rowId]
|
||||
const newRow = { ...cleanRow(row), ...changes }
|
||||
const saved = await datasource.actions.updateRow(newRow)
|
||||
|
||||
// Update state after a successful change
|
||||
// Update row state after a successful change
|
||||
if (saved?._id) {
|
||||
rows.update(state => {
|
||||
state[index] = saved
|
||||
|
@ -386,6 +392,8 @@ export const createActions = context => {
|
|||
// Handle users table edge case
|
||||
await refreshRow(saved.id)
|
||||
}
|
||||
|
||||
// Wipe row change cache now that we've saved the row
|
||||
rowChangeCache.update(state => {
|
||||
delete state[rowId]
|
||||
return state
|
||||
|
@ -393,15 +401,17 @@ export const createActions = context => {
|
|||
} catch (error) {
|
||||
handleValidationError(rowId, error)
|
||||
}
|
||||
inProgressChanges.update(state => ({
|
||||
...state,
|
||||
[rowId]: false,
|
||||
}))
|
||||
|
||||
// Mark as completed
|
||||
inProgressChanges.update(state => ({ ...state, [rowId]: false }))
|
||||
}
|
||||
|
||||
// Updates a value of a row
|
||||
const updateValue = async ({ rowId, column, value, save = true }) => {
|
||||
return await updateRow(rowId, { [column]: value }, { save })
|
||||
const updateValue = async ({ rowId, column, value, apply = true }) => {
|
||||
const success = stashRowChanges(rowId, { [column]: value })
|
||||
if (success && apply) {
|
||||
await applyRowChanges(rowId)
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes an array of rows
|
||||
|
@ -411,9 +421,7 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Actually delete rows
|
||||
rowsToDelete.forEach(row => {
|
||||
delete row.__idx
|
||||
})
|
||||
rowsToDelete.forEach(row => delete row.__idx)
|
||||
await datasource.actions.deleteRows(rowsToDelete)
|
||||
|
||||
// Update state
|
||||
|
@ -433,7 +441,7 @@ export const createActions = context => {
|
|||
newRow = newRows[i]
|
||||
|
||||
// Ensure we have a unique _id.
|
||||
// This means generating one for non DS+, overriting any that may already
|
||||
// This means generating one for non DS+, overwriting any that may already
|
||||
// exist as we cannot allow duplicates.
|
||||
if (!$isDatasourcePlus) {
|
||||
newRow._id = Helpers.uuid()
|
||||
|
@ -494,7 +502,7 @@ export const createActions = context => {
|
|||
duplicateRow,
|
||||
getRow,
|
||||
updateValue,
|
||||
updateRow,
|
||||
applyRowChanges,
|
||||
deleteRows,
|
||||
hasRow,
|
||||
loadNextPage,
|
||||
|
@ -508,7 +516,14 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
export const initialise = context => {
|
||||
const { rowChangeCache, inProgressChanges, previousFocusedRowId } = context
|
||||
const {
|
||||
rowChangeCache,
|
||||
inProgressChanges,
|
||||
previousFocusedRowId,
|
||||
previousFocusedCellId,
|
||||
rows,
|
||||
validation,
|
||||
} = context
|
||||
|
||||
// Wipe the row change cache when changing row
|
||||
previousFocusedRowId.subscribe(id => {
|
||||
|
@ -519,4 +534,15 @@ export const initialise = context => {
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Ensure any unsaved changes are saved when changing cell
|
||||
previousFocusedCellId.subscribe(async id => {
|
||||
const rowId = id?.split("-")[0]
|
||||
const hasErrors = validation.actions.rowHasErrors(rowId)
|
||||
const hasChanges = Object.keys(get(rowChangeCache)[rowId] || {}).length > 0
|
||||
const isSavingChanges = get(inProgressChanges)[rowId]
|
||||
if (rowId && !hasErrors && hasChanges && !isSavingChanges) {
|
||||
await rows.actions.applyRowChanges(rowId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const createStores = context => {
|
|||
const hoveredRowId = writable(null)
|
||||
const rowHeight = writable(get(props).fixedRowHeight || DefaultRowHeight)
|
||||
const previousFocusedRowId = writable(null)
|
||||
const previousFocusedCellId = writable(null)
|
||||
const gridFocused = writable(false)
|
||||
const isDragging = writable(false)
|
||||
const buttonColumnWidth = writable(0)
|
||||
|
@ -48,6 +49,7 @@ export const createStores = context => {
|
|||
focusedCellAPI,
|
||||
focusedRowId,
|
||||
previousFocusedRowId,
|
||||
previousFocusedCellId,
|
||||
hoveredRowId,
|
||||
rowHeight,
|
||||
gridFocused,
|
||||
|
@ -129,6 +131,7 @@ export const initialise = context => {
|
|||
const {
|
||||
focusedRowId,
|
||||
previousFocusedRowId,
|
||||
previousFocusedCellId,
|
||||
rows,
|
||||
focusedCellId,
|
||||
selectedRows,
|
||||
|
@ -181,6 +184,13 @@ export const initialise = context => {
|
|||
lastFocusedRowId = id
|
||||
})
|
||||
|
||||
// Remember the last focused cell ID so that we can store the previous one
|
||||
let lastFocusedCellId = null
|
||||
focusedCellId.subscribe(id => {
|
||||
previousFocusedCellId.set(lastFocusedCellId)
|
||||
lastFocusedCellId = id
|
||||
})
|
||||
|
||||
// Remove hovered row when a cell is selected
|
||||
focusedCellId.subscribe(cell => {
|
||||
if (cell && get(hoveredRowId)) {
|
||||
|
|
|
@ -1,8 +1,23 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { writable, get, derived } from "svelte/store"
|
||||
|
||||
// Normally we would break out actions into the explicit "createActions"
|
||||
// function, but for validation all these actions are pure so can go into
|
||||
// "createStores" instead to make dependency ordering simpler
|
||||
export const createStores = () => {
|
||||
const validation = writable({})
|
||||
|
||||
// Derive which rows have errors so that we can use that info later
|
||||
const rowErrorMap = derived(validation, $validation => {
|
||||
let map = {}
|
||||
Object.entries($validation).forEach(([key, error]) => {
|
||||
// Extract row ID from all errored cell IDs
|
||||
if (error) {
|
||||
map[key.split("-")[0]] = true
|
||||
}
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
const setError = (cellId, error) => {
|
||||
if (!cellId) {
|
||||
return
|
||||
|
@ -13,11 +28,16 @@ export const createStores = () => {
|
|||
}))
|
||||
}
|
||||
|
||||
const rowHasErrors = rowId => {
|
||||
return get(rowErrorMap)[rowId]
|
||||
}
|
||||
|
||||
return {
|
||||
validation: {
|
||||
...validation,
|
||||
actions: {
|
||||
setError,
|
||||
rowHasErrors,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 183b35d3acd42433dcb2d32bcd89a36abe13afec
|
||||
Subproject commit 22a278da720d92991dabdcd4cb6c96e7abe29781
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
BulkImportRequest,
|
||||
BulkImportResponse,
|
||||
Operation,
|
||||
RenameColumn,
|
||||
SaveTableRequest,
|
||||
SaveTableResponse,
|
||||
Table,
|
||||
|
@ -25,9 +26,11 @@ function getDatasourceId(table: Table) {
|
|||
return breakExternalTableId(table._id).datasourceId
|
||||
}
|
||||
|
||||
export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||
export async function save(
|
||||
ctx: UserCtx<SaveTableRequest, SaveTableResponse>,
|
||||
renaming?: RenameColumn
|
||||
) {
|
||||
const inputs = ctx.request.body
|
||||
const renaming = inputs?._rename
|
||||
const adding = inputs?._add
|
||||
// can't do this right now
|
||||
delete inputs.rows
|
||||
|
|
|
@ -74,8 +74,15 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
|||
const appId = ctx.appId
|
||||
const table = ctx.request.body
|
||||
const isImport = table.rows
|
||||
const renaming = ctx.request.body._rename
|
||||
|
||||
let savedTable = await pickApi({ table }).save(ctx)
|
||||
const api = pickApi({ table })
|
||||
// do not pass _rename or _add if saving to CouchDB
|
||||
if (api === internal) {
|
||||
delete ctx.request.body._add
|
||||
delete ctx.request.body._rename
|
||||
}
|
||||
let savedTable = await api.save(ctx, renaming)
|
||||
if (!table._id) {
|
||||
await events.table.created(savedTable)
|
||||
savedTable = sdk.tables.enrichViewSchemas(savedTable)
|
||||
|
|
|
@ -12,11 +12,12 @@ import {
|
|||
} from "@budibase/types"
|
||||
import sdk from "../../../sdk"
|
||||
|
||||
export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
||||
export async function save(
|
||||
ctx: UserCtx<SaveTableRequest, SaveTableResponse>,
|
||||
renaming?: RenameColumn
|
||||
) {
|
||||
const { rows, ...rest } = ctx.request.body
|
||||
let tableToSave: Table & {
|
||||
_rename?: RenameColumn
|
||||
} = {
|
||||
let tableToSave: Table = {
|
||||
_id: generateTableID(),
|
||||
...rest,
|
||||
// Ensure these fields are populated, even if not sent in the request
|
||||
|
@ -28,15 +29,12 @@ export async function save(ctx: UserCtx<SaveTableRequest, SaveTableResponse>) {
|
|||
tableToSave.views = {}
|
||||
}
|
||||
|
||||
const renaming = tableToSave._rename
|
||||
delete tableToSave._rename
|
||||
|
||||
try {
|
||||
const { table } = await sdk.tables.internal.save(tableToSave, {
|
||||
user: ctx.user,
|
||||
rowsToImport: rows,
|
||||
tableId: ctx.request.body._id,
|
||||
renaming: renaming,
|
||||
renaming,
|
||||
})
|
||||
|
||||
return table
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"`,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -26,6 +26,7 @@ import { TableToBuild } from "../../../tests/utilities/TestConfiguration"
|
|||
tk.freeze(mocks.date.MOCK_DATE)
|
||||
|
||||
const { basicTable } = setup.structures
|
||||
const ISO_REGEX_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
|
||||
|
||||
describe("/tables", () => {
|
||||
let request = setup.getRequest()
|
||||
|
@ -285,6 +286,35 @@ describe("/tables", () => {
|
|||
expect(res.body.schema.roleId).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it("should add a new column for an internal DB table", async () => {
|
||||
const saveTableRequest: SaveTableRequest = {
|
||||
_add: {
|
||||
name: "NEW_COLUMN",
|
||||
},
|
||||
...basicTable(),
|
||||
}
|
||||
|
||||
const response = await request
|
||||
.post(`/api/tables`)
|
||||
.send(saveTableRequest)
|
||||
.set(config.defaultHeaders())
|
||||
.expect("Content-Type", /json/)
|
||||
.expect(200)
|
||||
|
||||
const expectedResponse = {
|
||||
...saveTableRequest,
|
||||
_rev: expect.stringMatching(/^\d-.+/),
|
||||
_id: expect.stringMatching(/^ta_.+/),
|
||||
createdAt: expect.stringMatching(ISO_REGEX_PATTERN),
|
||||
updatedAt: expect.stringMatching(ISO_REGEX_PATTERN),
|
||||
views: {},
|
||||
}
|
||||
delete expectedResponse._add
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body).toEqual(expectedResponse)
|
||||
})
|
||||
})
|
||||
|
||||
describe("import", () => {
|
||||
|
@ -663,8 +693,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 +745,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 +800,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 +856,7 @@ describe("/tables", () => {
|
|||
subtype: FieldSubtype.USERS,
|
||||
},
|
||||
},
|
||||
{ expectStatus: 400 }
|
||||
{ status: 400 }
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -846,7 +871,7 @@ describe("/tables", () => {
|
|||
subtype: FieldSubtype.USERS,
|
||||
},
|
||||
},
|
||||
{ expectStatus: 400 }
|
||||
{ status: 400 }
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -861,7 +886,7 @@ describe("/tables", () => {
|
|||
subtype: FieldSubtype.USERS,
|
||||
},
|
||||
},
|
||||
{ expectStatus: 400 }
|
||||
{ status: 400 }
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -880,7 +905,7 @@ describe("/tables", () => {
|
|||
subtype: FieldSubtype.USERS,
|
||||
},
|
||||
},
|
||||
{ expectStatus: 400 }
|
||||
{ status: 400 }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -90,7 +90,7 @@ describe("/users", () => {
|
|||
})
|
||||
await config.api.user.update(
|
||||
{ ...user, roleId: roles.BUILTIN_ROLE_IDS.POWER },
|
||||
{ expectStatus: 409 }
|
||||
{ status: 409 }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -116,28 +116,38 @@ describe("mysql 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.mysql.datasource(),
|
||||
})
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body.connected).toBe(true)
|
||||
await config.api.datasource.verify(
|
||||
{
|
||||
datasource: await databaseTestProviders.mysql.datasource(),
|
||||
},
|
||||
{
|
||||
body: {
|
||||
connected: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should state an invalid datasource cannot connect", async () => {
|
||||
const dbConfig = await databaseTestProviders.mysql.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:
|
||||
"Access denied for the specified user. User does not have the necessary privileges or the provided credentials are incorrect. Please verify the credentials, and ensure that the user has appropriate permissions.",
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -231,6 +241,9 @@ describe("mysql integrations", () => {
|
|||
await databaseTestProviders.mysql.datasource()
|
||||
).config!
|
||||
)
|
||||
mysqlDatasource = await config.api.datasource.create(
|
||||
await databaseTestProviders.mysql.datasource()
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -238,13 +251,6 @@ describe("mysql integrations", () => {
|
|||
})
|
||||
|
||||
it("will emit the datasource entity schema with externalType to the front-end when adding a new column", async () => {
|
||||
mysqlDatasource = (
|
||||
await makeRequest(
|
||||
"post",
|
||||
`/api/datasources/${mysqlDatasource._id}/schema`
|
||||
)
|
||||
).body.datasource
|
||||
|
||||
const addColumnToTable: TableRequest = {
|
||||
type: "table",
|
||||
sourceType: TableSourceType.EXTERNAL,
|
||||
|
@ -305,5 +311,53 @@ describe("mysql integrations", () => {
|
|||
emitDatasourceUpdateMock.mock.calls[0][1]
|
||||
expect(emittedDatasource.entities!["table"]).toEqual(expectedTable)
|
||||
})
|
||||
|
||||
it("will rename a column", async () => {
|
||||
await makeRequest("post", "/api/tables/", primaryMySqlTable)
|
||||
|
||||
let renameColumnOnTable: TableRequest = {
|
||||
...primaryMySqlTable,
|
||||
schema: {
|
||||
id: {
|
||||
name: "id",
|
||||
type: FieldType.AUTO,
|
||||
autocolumn: true,
|
||||
externalType: "unsigned integer",
|
||||
},
|
||||
name: {
|
||||
name: "name",
|
||||
type: FieldType.STRING,
|
||||
externalType: "text",
|
||||
},
|
||||
description: {
|
||||
name: "description",
|
||||
type: FieldType.STRING,
|
||||
externalType: "text",
|
||||
},
|
||||
age: {
|
||||
name: "age",
|
||||
type: FieldType.NUMBER,
|
||||
externalType: "float(8,2)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const response = await makeRequest(
|
||||
"post",
|
||||
"/api/tables/",
|
||||
renameColumnOnTable
|
||||
)
|
||||
mysqlDatasource = (
|
||||
await makeRequest(
|
||||
"post",
|
||||
`/api/datasources/${mysqlDatasource._id}/schema`
|
||||
)
|
||||
).body.datasource
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(
|
||||
Object.keys(mysqlDatasource.entities![primaryMySqlTable.name].schema)
|
||||
).toEqual(["id", "name", "description", "age"])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"',
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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 },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -27,3 +27,9 @@ export interface FetchAppPackageResponse {
|
|||
clientLibPath: string
|
||||
hasLock: boolean
|
||||
}
|
||||
|
||||
export interface PublishResponse {
|
||||
_id: string
|
||||
status: string
|
||||
appUrl: string
|
||||
}
|
||||
|
|
|
@ -13,3 +13,4 @@ export * from "./searchFilter"
|
|||
export * from "./cookies"
|
||||
export * from "./automation"
|
||||
export * from "./layout"
|
||||
export * from "./query"
|
||||
|
|
|
@ -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>[]
|
||||
}
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue