Introduce test error propagation to public API endpoints.

This commit is contained in:
Sam Rose 2025-02-26 12:02:35 +00:00
parent 433c20d80c
commit 5b285940f1
No known key found for this signature in database
5 changed files with 58 additions and 18 deletions

View File

@ -48,7 +48,7 @@ function getUser(ctx: UserCtx, userId?: string) {
if (userId) { if (userId) {
ctx.params = { userId } ctx.params = { userId }
} else if (!ctx.params?.userId) { } else if (!ctx.params?.userId) {
throw "No user ID provided for getting" throw new Error("No user ID provided for getting")
} }
return readGlobalUser(ctx) return readGlobalUser(ctx)
} }

View File

@ -12,6 +12,7 @@ import { paramResource, paramSubResource } from "../../../middleware/resourceId"
import { PermissionLevel, PermissionType } from "@budibase/types" import { PermissionLevel, PermissionType } from "@budibase/types"
import { CtxFn } from "./utils/Endpoint" import { CtxFn } from "./utils/Endpoint"
import mapperMiddleware from "./middleware/mapper" import mapperMiddleware from "./middleware/mapper"
import testErrorHandling from "./middleware/testErrorHandling"
import env from "../../../environment" import env from "../../../environment"
import { middleware, redis } from "@budibase/backend-core" import { middleware, redis } from "@budibase/backend-core"
import { SelectableDatabase } from "@budibase/backend-core/src/redis/utils" import { SelectableDatabase } from "@budibase/backend-core/src/redis/utils"
@ -144,6 +145,10 @@ function applyRoutes(
// add the output mapper middleware // add the output mapper middleware
addMiddleware(endpoints.read, mapperMiddleware, { output: true }) addMiddleware(endpoints.read, mapperMiddleware, { output: true })
addMiddleware(endpoints.write, mapperMiddleware, { output: true }) addMiddleware(endpoints.write, mapperMiddleware, { output: true })
if (env.isTest()) {
addMiddleware(endpoints.read, testErrorHandling)
addMiddleware(endpoints.write, testErrorHandling)
}
addToRouter(endpoints.read) addToRouter(endpoints.read)
addToRouter(endpoints.write) addToRouter(endpoints.write)
} }

View File

@ -0,0 +1,24 @@
import { Ctx } from "@budibase/types"
import environment from "../../../../environment"
export default async (ctx: Ctx, next: any) => {
try {
await next()
} catch (err: any) {
if (
!(environment.isTest() && ctx.headers["x-budibase-include-stacktrace"])
) {
throw err
}
const status = err.status || err.statusCode || 500
let error = err
while (error.cause) {
error = error.cause
}
ctx.status = status
ctx.body = { status, message: error.message, stack: error.stack }
}
}

View File

@ -1,12 +1,13 @@
import * as setup from "../../tests/utilities" import * as setup from "../../tests/utilities"
import { User } from "@budibase/types" import { User } from "@budibase/types"
import { mocks } from "@budibase/backend-core/tests" import { generator, mocks } from "@budibase/backend-core/tests"
import nock from "nock" import nock from "nock"
import environment from "../../../../environment" import environment from "../../../../environment"
import TestConfiguration from "../../../../tests/utilities/TestConfiguration" import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
const config = new TestConfiguration() const config = new TestConfiguration()
let globalUser: User let globalUser: User
let users: Record<string, User> = {}
beforeAll(async () => { beforeAll(async () => {
await config.init() await config.init()
@ -16,25 +17,26 @@ afterAll(setup.afterAll)
beforeEach(async () => { beforeEach(async () => {
globalUser = await config.globalUser() globalUser = await config.globalUser()
users[globalUser._id!] = globalUser
nock.cleanAll() nock.cleanAll()
nock(environment.WORKER_URL!) nock(environment.WORKER_URL!)
.get(`/api/global/users/${globalUser._id}`) .get(new RegExp(`/api/global/users/.*`))
.reply(200, (uri, body) => { .reply(200, (uri, body) => {
return globalUser const id = uri.split("/").pop()
return users[id!]
}) })
.persist() .persist()
nock(environment.WORKER_URL!) nock(environment.WORKER_URL!)
.post(`/api/global/users`) .post(`/api/global/users`)
.reply(200, (uri, body) => { .reply(200, (uri, body) => {
const updatedUser = body as User const newUser = body as User
if (updatedUser._id === globalUser._id) { if (!newUser._id) {
globalUser = updatedUser newUser._id = `us_${generator.guid()}`
return globalUser
} else {
throw new Error("User not found")
} }
users[newUser._id!] = newUser
return newUser
}) })
.persist() .persist()
}) })
@ -47,7 +49,7 @@ function base() {
} }
} }
describe.only("check user endpoints", () => { describe("check user endpoints", () => {
it("should not allow a user to update their own roles", async () => { it("should not allow a user to update their own roles", async () => {
await config.withUser(globalUser, () => await config.withUser(globalUser, () =>
config.api.public.user.update({ config.api.public.user.update({
@ -67,14 +69,12 @@ describe.only("check user endpoints", () => {
}) })
describe("no user role update in free", () => { describe("no user role update in free", () => {
it("should not allow 'roles' to be updated", async () => { it.only("should not allow 'roles' to be updated", async () => {
const res = await makeRequest("post", "/users", { const newUser = await config.api.public.user.create({
...base(), email: generator.email({ domain: "example.com" }),
roles: { app_a: "BASIC" }, roles: { app_a: "BASIC" },
}) })
expect(res.status).toBe(200) expect(newUser.roles["app_a"]).toBeUndefined()
expect(res.body.data.roles["app_a"]).toBeUndefined()
expect(res.body.message).toBeDefined()
}) })
it("should not allow 'admin' to be updated", async () => { it("should not allow 'admin' to be updated", async () => {

View File

@ -1,4 +1,4 @@
import { User } from "@budibase/types" import { UnsavedUser, User } from "@budibase/types"
import { Expectations, PublicAPI } from "../base" import { Expectations, PublicAPI } from "../base"
export class UserPublicAPI extends PublicAPI { export class UserPublicAPI extends PublicAPI {
@ -16,4 +16,15 @@ export class UserPublicAPI extends PublicAPI {
destroy = async (id: string, expectations?: Expectations): Promise<void> => { destroy = async (id: string, expectations?: Expectations): Promise<void> => {
return await this._delete(`/users/${id}`, { expectations }) return await this._delete(`/users/${id}`, { expectations })
} }
create = async (
user: UnsavedUser,
expectations?: Expectations
): Promise<User> => {
const response = await this._post<{ data: User }>("/users", {
body: user,
expectations,
})
return response.data
}
} }