diff --git a/packages/backend-core/src/middleware/errorHandling.ts b/packages/backend-core/src/middleware/errorHandling.ts index ebdd4107e9..2b8f7195ed 100644 --- a/packages/backend-core/src/middleware/errorHandling.ts +++ b/packages/backend-core/src/middleware/errorHandling.ts @@ -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 } } diff --git a/packages/server/src/api/controllers/permission.ts b/packages/server/src/api/controllers/permission.ts index e2bd6c40e5..cdfa6d8b1c 100644 --- a/packages/server/src/api/controllers/permission.ts +++ b/packages/server/src/api/controllers/permission.ts @@ -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) { + 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 +) { + const params: RemovePermissionRequest = ctx.params + ctx.body = await updatePermissionOnRole(params, PermissionUpdateType.REMOVE) } diff --git a/packages/server/src/api/controllers/query/index.ts b/packages/server/src/api/controllers/query/index.ts index 973718ba48..3c21537484 100644 --- a/packages/server/src/api/controllers/query/index.ts +++ b/packages/server/src/api/controllers/query/index.ts @@ -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 +) { 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(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, + ctx: UserCtx< + ExecuteQueryRequest, + ExecuteQueryResponse | Record[] + >, 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[]> +) { return execute(ctx, { rowsOnly: true, isAutomation: false }) } export async function executeV2( - ctx: UserCtx, + ctx: UserCtx< + ExecuteQueryRequest, + ExecuteQueryResponse | Record[] + >, { 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(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(queryId) const datasource = await sdk.datasources.get(query.datasourceId) diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index ec56919d12..54c294c42b 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -211,7 +211,7 @@ export async function validate(ctx: Ctx) { } } -export async function fetchEnrichedRow(ctx: any) { +export async function fetchEnrichedRow(ctx: UserCtx) { const tableId = utils.getTableId(ctx) ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx) } diff --git a/packages/server/src/api/routes/tests/application.spec.ts b/packages/server/src/api/routes/tests/application.spec.ts index dbe4eb51ae..dc235dbd01 100644 --- a/packages/server/src/api/routes/tests/application.spec.ts +++ b/packages/server/src/api/routes/tests/application.spec.ts @@ -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( diff --git a/packages/server/src/api/routes/tests/attachment.spec.ts b/packages/server/src/api/routes/tests/attachment.spec.ts index e230b0688a..aa02ea898e 100644 --- a/packages/server/src/api/routes/tests/attachment.spec.ts +++ b/packages/server/src/api/routes/tests/attachment.spec.ts @@ -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") diff --git a/packages/server/src/api/routes/tests/backup.spec.ts b/packages/server/src/api/routes/tests/backup.spec.ts index becbeb5480..c862106d58 100644 --- a/packages/server/src/api/routes/tests/backup.spec.ts +++ b/packages/server/src/api/routes/tests/backup.spec.ts @@ -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"`, + }, + }) }) }) diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index 129bc00b44..1eabf6edbb 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -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", diff --git a/packages/server/src/api/routes/tests/queries/query.seq.spec.ts b/packages/server/src/api/routes/tests/queries/query.seq.spec.ts index 2bbc8366ea..c5cb188cbc 100644 --- a/packages/server/src/api/routes/tests/queries/query.seq.spec.ts +++ b/packages/server/src/api/routes/tests/queries/query.seq.spec.ts @@ -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.") + }) }) }) diff --git a/packages/server/src/api/routes/tests/role.spec.js b/packages/server/src/api/routes/tests/role.spec.js index a653b573b2..4575f9b213 100644 --- a/packages/server/src/api/routes/tests/role.spec.js +++ b/packages/server/src/api/routes/tests/role.spec.js @@ -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, diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 726e493b2d..c02159bb42 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -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, diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index ce119e56f0..29465145a9 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -663,8 +663,7 @@ describe("/tables", () => { expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined() - const resp = await config.api.row.get(table._id!, testRow._id!) - const migratedRow = resp.body as Row + const migratedRow = await config.api.row.get(table._id!, testRow._id!) expect(migratedRow["user column"]).toBeDefined() expect(migratedRow["user relationship"]).not.toBeDefined() @@ -716,15 +715,13 @@ describe("/tables", () => { expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined() - const row1Migrated = (await config.api.row.get(table._id!, row1._id!)) - .body as Row + const row1Migrated = await config.api.row.get(table._id!, row1._id!) expect(row1Migrated["user relationship"]).not.toBeDefined() expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual( expect.arrayContaining([users[0]._id, users[1]._id]) ) - const row2Migrated = (await config.api.row.get(table._id!, row2._id!)) - .body as Row + const row2Migrated = await config.api.row.get(table._id!, row2._id!) expect(row2Migrated["user relationship"]).not.toBeDefined() expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual( expect.arrayContaining([users[1]._id, users[2]._id]) @@ -773,15 +770,13 @@ describe("/tables", () => { expect(migratedTable.schema["user column"]).toBeDefined() expect(migratedTable.schema["user relationship"]).not.toBeDefined() - const row1Migrated = (await config.api.row.get(table._id!, row1._id!)) - .body as Row + const row1Migrated = await config.api.row.get(table._id!, row1._id!) expect(row1Migrated["user relationship"]).not.toBeDefined() expect(row1Migrated["user column"].map((r: Row) => r._id)).toEqual( expect.arrayContaining([users[0]._id, users[1]._id]) ) - const row2Migrated = (await config.api.row.get(table._id!, row2._id!)) - .body as Row + const row2Migrated = await config.api.row.get(table._id!, row2._id!) expect(row2Migrated["user relationship"]).not.toBeDefined() expect(row2Migrated["user column"].map((r: Row) => r._id)).toEqual([ users[2]._id, @@ -831,7 +826,7 @@ describe("/tables", () => { subtype: FieldSubtype.USERS, }, }, - { expectStatus: 400 } + { status: 400 } ) }) @@ -846,7 +841,7 @@ describe("/tables", () => { subtype: FieldSubtype.USERS, }, }, - { expectStatus: 400 } + { status: 400 } ) }) @@ -861,7 +856,7 @@ describe("/tables", () => { subtype: FieldSubtype.USERS, }, }, - { expectStatus: 400 } + { status: 400 } ) }) @@ -880,7 +875,7 @@ describe("/tables", () => { subtype: FieldSubtype.USERS, }, }, - { expectStatus: 400 } + { status: 400 } ) }) }) diff --git a/packages/server/src/api/routes/tests/user.spec.ts b/packages/server/src/api/routes/tests/user.spec.ts index 076ee064dc..ff8c0d54b3 100644 --- a/packages/server/src/api/routes/tests/user.spec.ts +++ b/packages/server/src/api/routes/tests/user.spec.ts @@ -90,7 +90,7 @@ describe("/users", () => { }) await config.api.user.update( { ...user, roleId: roles.BUILTIN_ROLE_IDS.POWER }, - { expectStatus: 409 } + { status: 409 } ) }) }) diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index b03a73ddda..5198e63338 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -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, }, { - expectStatus: 200, + status: 200, } ) }) diff --git a/packages/server/src/appMigrations/tests/migrations.spec.ts b/packages/server/src/appMigrations/tests/migrations.spec.ts index 5eb8535695..7af2346934 100644 --- a/packages/server/src/appMigrations/tests/migrations.spec.ts +++ b/packages/server/src/appMigrations/tests/migrations.spec.ts @@ -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, + }, + }) }) }) diff --git a/packages/server/src/automations/tests/createRow.spec.ts b/packages/server/src/automations/tests/createRow.spec.ts index 0615fcdd97..0098be39a5 100644 --- a/packages/server/src/automations/tests/createRow.spec.ts +++ b/packages/server/src/automations/tests/createRow.spec.ts @@ -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") }) diff --git a/packages/server/src/automations/tests/updateRow.spec.ts b/packages/server/src/automations/tests/updateRow.spec.ts index b64c52147d..76823e8a11 100644 --- a/packages/server/src/automations/tests/updateRow.spec.ts +++ b/packages/server/src/automations/tests/updateRow.spec.ts @@ -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() }) }) diff --git a/packages/server/src/db/tests/linkController.spec.ts b/packages/server/src/db/tests/linkController.spec.ts index ae1922db27..4f41fd3838 100644 --- a/packages/server/src/db/tests/linkController.spec.ts +++ b/packages/server/src/db/tests/linkController.spec.ts @@ -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 () => { diff --git a/packages/server/src/integration-test/postgres.spec.ts b/packages/server/src/integration-test/postgres.spec.ts index 0031fe1136..7c14bc2b69 100644 --- a/packages/server/src/integration-test/postgres.spec.ts +++ b/packages/server/src/integration-test/postgres.spec.ts @@ -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"', + }, + } + ) }) }) diff --git a/packages/server/src/sdk/app/rows/tests/internal.spec.ts b/packages/server/src/sdk/app/rows/tests/internal.spec.ts index 3908ef83ed..877bd1e6dc 100644 --- a/packages/server/src/sdk/app/rows/tests/internal.spec.ts +++ b/packages/server/src/sdk/app/rows/tests/internal.spec.ts @@ -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", diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts index 21605b7a5e..35ca2982c0 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.ts +++ b/packages/server/src/tests/utilities/TestConfiguration.ts @@ -712,11 +712,6 @@ export default class TestConfiguration { return this.api.row.save(tableId, config) } - async getRow(tableId: string, rowId: string): Promise { - const res = await this.api.row.get(tableId, rowId) - return res.body - } - async getRows(tableId: string) { if (!tableId && this.table) { tableId = this.table._id! diff --git a/packages/server/src/tests/utilities/api/application.ts b/packages/server/src/tests/utilities/api/application.ts index 3951bba667..da3d7cefd8 100644 --- a/packages/server/src/tests/utilities/api/application.ts +++ b/packages/server/src/tests/utilities/api/application.ts @@ -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 => { + const files = app.templateFile ? { templateFile: app.templateFile } : {} + delete app.templateFile + return await this._post("/api/applications", { + fields: app, + files, + expectations, + }) } - create = async (app: CreateAppRequest): Promise => { - 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 => { + await this._delete(`/api/applications/${appId}`, { expectations }) } - delete = async (appId: string): Promise => { - 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 => { + return await this._post( + `/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 => { - 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 => { - // 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 => { - const result = await this.getRaw(appId) - return result.body.application as App + get = async (appId: string, expectations?: Expectations): Promise => { + return await this._get(`/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 => { - 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( + `/api/applications/${appId}/definition`, + { expectations } + ) } - getAppPackage = async (appId: string): Promise => { - 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 => { + return await this._get( + `/api/applications/${appId}/appPackage`, + { expectations } + ) } update = async ( appId: string, - app: { name?: string; url?: string } + app: { name?: string; url?: string }, + expectations?: Expectations ): Promise => { - 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(`/api/applications/${appId}`, { + fields: app, + expectations, + }) } - updateClient = async (appId: string): Promise => { - // 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 => { + 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 => { - // 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 => { - 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("/api/applications", { + query: { status }, + }) } } diff --git a/packages/server/src/tests/utilities/api/attachment.ts b/packages/server/src/tests/utilities/api/attachment.ts index a466f1a67e..bb33ef04bb 100644 --- a/packages/server/src/tests/utilities/api/attachment.ts +++ b/packages/server/src/tests/utilities/api/attachment.ts @@ -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 => { - 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, + }) } } diff --git a/packages/server/src/tests/utilities/api/backup.ts b/packages/server/src/tests/utilities/api/backup.ts index 8cd1e58a29..7c01b57108 100644 --- a/packages/server/src/tests/utilities/api/backup.ts +++ b/packages/server/src/tests/utilities/api/backup.ts @@ -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(`/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( + `/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 => { - 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( + `/api/apps/${appId}/backups/${backupId}/import`, + { expectations } + ) } } diff --git a/packages/server/src/tests/utilities/api/base.ts b/packages/server/src/tests/utilities/api/base.ts index 34120277c0..3f534fba86 100644 --- a/packages/server/src/tests/utilities/api/base.ts +++ b/packages/server/src/tests/utilities/api/base.ts @@ -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 +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 + headersNotPresent?: string[] + body?: Record +} + +export interface RequestOpts { + headers?: Headers + query?: Record + body?: Record + fields?: Record + files?: Record< + string, + Buffer | ReadStream | string | AttachedFile | undefined + > + expectations?: Expectations + publicUser?: boolean } export abstract class TestAPI { config: TestConfiguration request: SuperTest - protected constructor(config: TestConfiguration) { + constructor(config: TestConfiguration) { this.config = config this.request = config.request! } + + protected _get = async (url: string, opts?: RequestOpts): Promise => { + return await this._request("get", url, opts) + } + + protected _post = async (url: string, opts?: RequestOpts): Promise => { + return await this._request("post", url, opts) + } + + protected _put = async (url: string, opts?: RequestOpts): Promise => { + return await this._request("put", url, opts) + } + + protected _patch = async (url: string, opts?: RequestOpts): Promise => { + return await this._request("patch", url, opts) + } + + protected _delete = async ( + url: string, + opts?: RequestOpts + ): Promise => { + return await this._request("delete", url, opts) + } + + protected _requestRaw = async ( + method: "get" | "post" | "put" | "patch" | "delete", + url: string, + opts?: RequestOpts + ): Promise => { + 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 ( + method: Method, + url: string, + opts?: RequestOpts + ): Promise => { + 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 + } } diff --git a/packages/server/src/tests/utilities/api/datasource.ts b/packages/server/src/tests/utilities/api/datasource.ts index bcd7a71089..06aa9b4e1e 100644 --- a/packages/server/src/tests/utilities/api/datasource.ts +++ b/packages/server/src/tests/utilities/api/datasource.ts @@ -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 ( + create = async ( config: Datasource, - { - expectStatus, - rawResponse, - }: { expectStatus?: number; rawResponse?: B } = {} - ): Promise => { - 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 => { + const response = await this._post( + `/api/datasources`, + { + body: { + datasource: config, + tablesFilter: [], + }, + expectations, + } + ) + return response.datasource } update = async ( - datasource: Datasource, - { expectStatus } = { expectStatus: 200 } + datasource: UpdateDatasourceRequest, + expectations?: Expectations ): Promise => { - 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( + `/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, + }) } } diff --git a/packages/server/src/tests/utilities/api/legacyView.ts b/packages/server/src/tests/utilities/api/legacyView.ts index 63981cec5e..38ef70d62a 100644 --- a/packages/server/src/tests/utilities/api/legacyView.ts +++ b/packages/server/src/tests/utilities/api/legacyView.ts @@ -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(`/api/views/${id}`, { expectations }) } } diff --git a/packages/server/src/tests/utilities/api/permission.ts b/packages/server/src/tests/utilities/api/permission.ts index ffa89e88f9..986796d9a1 100644 --- a/packages/server/src/tests/utilities/api/permission.ts +++ b/packages/server/src/tests/utilities/api/permission.ts @@ -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( + `/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 => { - 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 => { + const { roleId, resourceId, level } = request + return await this._post( + `/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( + `/api/permission/${roleId}/${resourceId}/${level}`, + { expectations } + ) } } diff --git a/packages/server/src/tests/utilities/api/query.ts b/packages/server/src/tests/utilities/api/query.ts index b0eac5c8b7..32866314ff 100644 --- a/packages/server/src/tests/utilities/api/query.ts +++ b/packages/server/src/tests/utilities/api/query.ts @@ -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 => { - 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(`/api/queries`, { body }) } execute = async ( queryId: string, body?: ExecuteQueryRequest ): Promise => { - 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( + `/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(`/api/queries/preview`, { + body: queryPreview, + }) } } diff --git a/packages/server/src/tests/utilities/api/row.ts b/packages/server/src/tests/utilities/api/row.ts index 936c906f9f..86664574cb 100644 --- a/packages/server/src/tests/utilities/api/row.ts +++ b/packages/server/src/tests/utilities/api/row.ts @@ -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(`/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(`/api/${sourceId}/${rowId}/enrich`, { + expectations, + }) } save = async ( tableId: string, row: SaveRowRequest, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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(`/api/${tableId}/rows`, { + body: row, + expectations, + }) } validate = async ( sourceId: string, row: SaveRowRequest, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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( + `/api/${sourceId}/rows/validate`, + { + body: row, + expectations, + } + ) } patch = async ( sourceId: string, row: PatchRowRequest, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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(`/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(`/api/${sourceId}/rows`, { + body: row, + expectations, + }) + } + + bulkDelete = async ( + sourceId: string, + body: DeleteRows, + expectations?: Expectations + ) => { + return await this._delete(`/api/${sourceId}/rows`, { + body, + expectations, + }) } fetch = async ( sourceId: string, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - const request = this.request - .get(`/api/${sourceId}/rows`) - .set(this.config.defaultHeaders()) - .expect(expectStatus) - - return (await request).body + return await this._get(`/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 => { - 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( + `/api/tables/${tableId}/import`, + { + body, + expectations, + } + ) } search = async ( sourceId: string, params?: SearchParams, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - const request = this.request - .post(`/api/${sourceId}/search`) - .send(params) - .set(this.config.defaultHeaders()) - .expect(expectStatus) - - return (await request).body + return await this._post(`/api/${sourceId}/search`, { + body: params, + expectations, + }) } } diff --git a/packages/server/src/tests/utilities/api/screen.ts b/packages/server/src/tests/utilities/api/screen.ts index 9245ffe4ba..c8d3e647d8 100644 --- a/packages/server/src/tests/utilities/api/screen.ts +++ b/packages/server/src/tests/utilities/api/screen.ts @@ -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 => { - 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 => { + return await this._get(`/api/screens`, { expectations }) } } diff --git a/packages/server/src/tests/utilities/api/table.ts b/packages/server/src/tests/utilities/api/table.ts index 5a9654e3bc..49105a3883 100644 --- a/packages/server/src/tests/utilities/api/table.ts +++ b/packages/server/src/tests/utilities/api/table.ts @@ -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 => { - 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("/api/tables", { + body: data, + expectations, + }) } - fetch = async ( - { expectStatus } = { expectStatus: 200 } - ): Promise => { - 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 => { + return await this._get("/api/tables", { expectations }) } get = async ( tableId: string, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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
(`/api/tables/${tableId}`, { expectations }) } migrate = async ( tableId: string, data: MigrateRequest, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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(`/api/tables/${tableId}/migrate`, { + body: data, + expectations, + }) } } diff --git a/packages/server/src/tests/utilities/api/user.ts b/packages/server/src/tests/utilities/api/user.ts index 2ed23c0461..bb3eae0542 100644 --- a/packages/server/src/tests/utilities/api/user.ts +++ b/packages/server/src/tests/utilities/api/user.ts @@ -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 => { - 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("/api/users/metadata", { + expectations, + }) } find = async ( id: string, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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( + `/api/users/metadata/${id}`, + { + expectations, + } + ) } update = async ( user: UserMetadata, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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("/api/users/metadata", { + body: user, + expectations, + }) } updateSelf = async ( user: UserMetadata, - { expectStatus } = { expectStatus: 200 } + expectations?: Expectations ): Promise => { - 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( + "/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 => { - 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 => { + return await this._get(`/api/users/flags`, { + expectations, + }) } } diff --git a/packages/server/src/tests/utilities/api/viewV2.ts b/packages/server/src/tests/utilities/api/viewV2.ts index 92a6d394bf..d4539e00b1 100644 --- a/packages/server/src/tests/utilities/api/viewV2.ts +++ b/packages/server/src/tests/utilities/api/viewV2.ts @@ -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, - { expectStatus } = { expectStatus: 201 } + expectations?: Expectations ): Promise => { 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 => { - 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( + `/api/v2/views/${viewId}/search`, + { + body: params, + expectations, + } + ) + } + + publicSearch = async ( + viewId: string, + params?: SearchViewRowRequest, + expectations?: Expectations + ) => { + return await this._post( + `/api/v2/views/${viewId}/search`, + { + body: params, + expectations, + publicUser: true, + } + ) } } diff --git a/packages/types/src/api/web/app/permission.ts b/packages/types/src/api/web/app/permission.ts index a8ab0e8084..88ff4e9d2f 100644 --- a/packages/types/src/api/web/app/permission.ts +++ b/packages/types/src/api/web/app/permission.ts @@ -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 } + +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 {} diff --git a/packages/types/src/api/web/app/rows.ts b/packages/types/src/api/web/app/rows.ts index 14e28e4a01..0a43182dfd 100644 --- a/packages/types/src/api/web/app/rows.ts +++ b/packages/types/src/api/web/app/rows.ts @@ -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[] diff --git a/packages/types/src/api/web/application.ts b/packages/types/src/api/web/application.ts index 87a0bd6ef9..3d33fce5b1 100644 --- a/packages/types/src/api/web/application.ts +++ b/packages/types/src/api/web/application.ts @@ -27,3 +27,9 @@ export interface FetchAppPackageResponse { clientLibPath: string hasLock: boolean } + +export interface PublishResponse { + _id: string + status: string + appUrl: string +} diff --git a/packages/types/src/api/web/index.ts b/packages/types/src/api/web/index.ts index 62d8ce8280..9a688a17a5 100644 --- a/packages/types/src/api/web/index.ts +++ b/packages/types/src/api/web/index.ts @@ -13,3 +13,4 @@ export * from "./searchFilter" export * from "./cookies" export * from "./automation" export * from "./layout" +export * from "./query" diff --git a/packages/types/src/api/web/query.ts b/packages/types/src/api/web/query.ts new file mode 100644 index 0000000000..3959cdea19 --- /dev/null +++ b/packages/types/src/api/web/query.ts @@ -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[] +} diff --git a/packages/types/src/documents/app/query.ts b/packages/types/src/documents/app/query.ts index b1b0a1d780..3227666bf3 100644 --- a/packages/types/src/documents/app/query.ts +++ b/packages/types/src/documents/app/query.ts @@ -62,22 +62,6 @@ export interface PaginationValues { limit: number | null } -export interface PreviewQueryRequest extends Omit { - 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",