diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte
index 54171309de..c40041c7d6 100644
--- a/packages/client/src/components/app/forms/RelationshipField.svelte
+++ b/packages/client/src/components/app/forms/RelationshipField.svelte
@@ -1,18 +1,15 @@
{#if fieldState}
option.primaryDisplay}
getOptionValue={option => option._id}
+ {options}
{placeholder}
+ {autocomplete}
bind:searchTerm
- loading={$fetch.loading}
bind:open
+ on:change={handleChange}
+ on:loadMore={() => fetch?.nextPage()}
/>
{/if}
diff --git a/packages/frontend-core/src/fetch/GroupUserFetch.ts b/packages/frontend-core/src/fetch/GroupUserFetch.ts
index 5b07e9decb..ca28a4dfa1 100644
--- a/packages/frontend-core/src/fetch/GroupUserFetch.ts
+++ b/packages/frontend-core/src/fetch/GroupUserFetch.ts
@@ -4,7 +4,7 @@ import { GroupUserDatasource, InternalTable } from "@budibase/types"
interface GroupUserQuery {
groupId: string
- emailSearch: string
+ emailSearch?: string
}
interface GroupUserDefinition {
diff --git a/packages/frontend-core/src/fetch/UserFetch.ts b/packages/frontend-core/src/fetch/UserFetch.ts
index e72ed8997d..995071b587 100644
--- a/packages/frontend-core/src/fetch/UserFetch.ts
+++ b/packages/frontend-core/src/fetch/UserFetch.ts
@@ -9,8 +9,8 @@ import {
} from "@budibase/types"
interface UserFetchQuery {
- appId: string
- paginated: boolean
+ appId?: string
+ paginated?: boolean
}
interface UserDefinition {
diff --git a/packages/server/package.json b/packages/server/package.json
index 9a70ecba9c..38dd746c4f 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -156,6 +156,7 @@
"@types/pouchdb": "6.4.2",
"@types/server-destroy": "1.0.1",
"@types/supertest": "2.0.14",
+ "@types/swagger-jsdoc": "^6.0.4",
"@types/tar": "6.1.5",
"@types/tmp": "0.2.6",
"@types/uuid": "8.3.4",
diff --git a/packages/server/specs/generate.ts b/packages/server/specs/generate.ts
index 8f6376195f..1176f187a7 100644
--- a/packages/server/specs/generate.ts
+++ b/packages/server/specs/generate.ts
@@ -4,11 +4,11 @@ import { examples, schemas } from "./resources"
import * as parameters from "./parameters"
import * as security from "./security"
-const swaggerJsdoc = require("swagger-jsdoc")
+import swaggerJsdoc from "swagger-jsdoc"
const VARIABLES = {}
-const options = {
+const opts: swaggerJsdoc.Options = {
definition: {
openapi: "3.0.0",
info: {
@@ -58,30 +58,27 @@ const options = {
}
function writeFile(output: any, filename: string) {
- try {
- const path = join(__dirname, filename)
- let spec = output
- if (filename.endsWith("json")) {
- spec = JSON.stringify(output, null, 2)
- }
- // input the static variables
- for (let [key, replacement] of Object.entries(VARIABLES)) {
- spec = spec.replace(new RegExp(`{${key}}`, "g"), replacement)
- }
- writeFileSync(path, spec)
- console.log(`Wrote spec to ${path}`)
- return path
- } catch (err) {
- console.error("Error writing spec file", err)
+ const path = join(__dirname, filename)
+ let spec = output
+ if (filename.endsWith("json")) {
+ spec = JSON.stringify(output, null, 2)
}
+ // input the static variables
+ for (let [key, replacement] of Object.entries(VARIABLES)) {
+ spec = spec.replace(new RegExp(`{${key}}`, "g"), replacement)
+ }
+ writeFileSync(path, spec)
+ console.log(`Wrote spec to ${path}`)
+ return path
+}
+
+export function spec() {
+ return swaggerJsdoc({ ...opts, format: ".json" })
}
export function run() {
- const outputJSON = swaggerJsdoc(options)
- options.format = ".yaml"
- const outputYAML = swaggerJsdoc(options)
- writeFile(outputJSON, "openapi.json")
- return writeFile(outputYAML, "openapi.yaml")
+ writeFile(swaggerJsdoc({ ...opts, format: ".json" }), "openapi.json")
+ return writeFile(swaggerJsdoc({ ...opts, format: ".yaml" }), "openapi.yaml")
}
if (require.main === module) {
diff --git a/packages/server/specs/resources/error.ts b/packages/server/specs/resources/error.ts
new file mode 100644
index 0000000000..0d42963b73
--- /dev/null
+++ b/packages/server/specs/resources/error.ts
@@ -0,0 +1,21 @@
+import { object } from "./utils"
+import Resource from "./utils/Resource"
+
+const errorSchema = object({
+ status: {
+ type: "number",
+ description: "The HTTP status code of the error.",
+ },
+ message: {
+ type: "string",
+ description: "A descriptive message about the error.",
+ },
+})
+
+export default new Resource()
+ .setExamples({
+ error: {},
+ })
+ .setSchemas({
+ error: errorSchema,
+ })
diff --git a/packages/server/src/api/controllers/integration.ts b/packages/server/src/api/controllers/integration.ts
index f26f8dcc52..555bf72e53 100644
--- a/packages/server/src/api/controllers/integration.ts
+++ b/packages/server/src/api/controllers/integration.ts
@@ -9,6 +9,7 @@ import {
const DISABLED_EXTERNAL_INTEGRATIONS = [
SourceName.AIRTABLE,
SourceName.BUDIBASE,
+ SourceName.ARANGODB,
]
export async function fetch(ctx: UserCtx) {
diff --git a/packages/server/src/api/controllers/public/users.ts b/packages/server/src/api/controllers/public/users.ts
index 4265c7ac22..c0cd3248a2 100644
--- a/packages/server/src/api/controllers/public/users.ts
+++ b/packages/server/src/api/controllers/public/users.ts
@@ -48,7 +48,7 @@ function getUser(ctx: UserCtx, userId?: string) {
if (userId) {
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)
}
diff --git a/packages/server/src/api/routes/public/index.ts b/packages/server/src/api/routes/public/index.ts
index 531192811c..8e4533f156 100644
--- a/packages/server/src/api/routes/public/index.ts
+++ b/packages/server/src/api/routes/public/index.ts
@@ -12,6 +12,7 @@ import { paramResource, paramSubResource } from "../../../middleware/resourceId"
import { PermissionLevel, PermissionType } from "@budibase/types"
import { CtxFn } from "./utils/Endpoint"
import mapperMiddleware from "./middleware/mapper"
+import testErrorHandling from "./middleware/testErrorHandling"
import env from "../../../environment"
import { middleware, redis } from "@budibase/backend-core"
import { SelectableDatabase } from "@budibase/backend-core/src/redis/utils"
@@ -144,6 +145,10 @@ function applyRoutes(
// add the output mapper middleware
addMiddleware(endpoints.read, 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.write)
}
diff --git a/packages/server/src/api/routes/public/middleware/testErrorHandling.ts b/packages/server/src/api/routes/public/middleware/testErrorHandling.ts
new file mode 100644
index 0000000000..b22acae8a8
--- /dev/null
+++ b/packages/server/src/api/routes/public/middleware/testErrorHandling.ts
@@ -0,0 +1,28 @@
+import { Ctx } from "@budibase/types"
+import environment from "../../../../environment"
+
+export default () => {
+ if (!environment.isTest()) {
+ throw new Error("This middleware is only for testing")
+ }
+
+ return async (ctx: Ctx, next: any) => {
+ try {
+ await next()
+ } catch (err: any) {
+ if (!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 }
+ }
+ }
+}
diff --git a/packages/server/src/api/routes/public/tests/compare.spec.ts b/packages/server/src/api/routes/public/tests/compare.spec.ts
index ccf248f360..b4f5f20e2c 100644
--- a/packages/server/src/api/routes/public/tests/compare.spec.ts
+++ b/packages/server/src/api/routes/public/tests/compare.spec.ts
@@ -2,184 +2,174 @@ import jestOpenAPI from "jest-openapi"
import { run as generateSchema } from "../../../../../specs/generate"
import * as setup from "../../tests/utilities"
import { generateMakeRequest } from "./utils"
-import { Table, App, Row, User } from "@budibase/types"
+import { Table, App, Row } from "@budibase/types"
+import nock from "nock"
+import environment from "../../../../environment"
const yamlPath = generateSchema()
jestOpenAPI(yamlPath!)
-let config = setup.getConfig()
-let apiKey: string, table: Table, app: App, makeRequest: any
+describe("compare", () => {
+ let config = setup.getConfig()
+ let apiKey: string, table: Table, app: App, makeRequest: any
-beforeAll(async () => {
- app = await config.init()
- table = await config.upsertTable()
- apiKey = await config.generateApiKey()
- makeRequest = generateMakeRequest(apiKey)
-})
-
-afterAll(setup.afterAll)
-
-describe("check the applications endpoints", () => {
- it("should allow retrieving applications through search", async () => {
- const res = await makeRequest("post", "/applications/search")
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow creating an application", async () => {
- const res = await makeRequest(
- "post",
- "/applications",
- {
- name: "new App",
- },
- null
- )
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow updating an application", async () => {
- const app = config.getApp()
- const appId = config.getAppId()
- const res = await makeRequest(
- "put",
- `/applications/${appId}`,
- {
- ...app,
- name: "updated app name",
- },
- appId
- )
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow retrieving an application", async () => {
- const res = await makeRequest("get", `/applications/${config.getAppId()}`)
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow deleting an application", async () => {
- const res = await makeRequest(
- "delete",
- `/applications/${config.getAppId()}`
- )
- expect(res).toSatisfyApiSpec()
- })
-})
-
-describe("check the tables endpoints", () => {
- it("should allow retrieving tables through search", async () => {
- await config.createApp("new app 1")
+ beforeAll(async () => {
+ app = await config.init()
table = await config.upsertTable()
- const res = await makeRequest("post", "/tables/search")
- expect(res).toSatisfyApiSpec()
+ apiKey = await config.generateApiKey()
+ makeRequest = generateMakeRequest(apiKey)
})
- it("should allow creating a table", async () => {
- const res = await makeRequest("post", "/tables", {
- name: "table name",
- primaryDisplay: "column1",
- schema: {
- column1: {
- type: "string",
- constraints: {},
+ afterAll(setup.afterAll)
+
+ beforeEach(() => {
+ nock.cleanAll()
+ })
+
+ describe("check the applications endpoints", () => {
+ it("should allow retrieving applications through search", async () => {
+ const res = await makeRequest("post", "/applications/search")
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow creating an application", async () => {
+ const res = await makeRequest(
+ "post",
+ "/applications",
+ {
+ name: "new App",
},
- },
+ null
+ )
+ expect(res).toSatisfyApiSpec()
})
- expect(res).toSatisfyApiSpec()
- })
- it("should allow updating a table", async () => {
- const updated = { ...table, _rev: undefined, name: "new name" }
- const res = await makeRequest("put", `/tables/${table._id}`, updated)
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow retrieving a table", async () => {
- const res = await makeRequest("get", `/tables/${table._id}`)
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow deleting a table", async () => {
- const res = await makeRequest("delete", `/tables/${table._id}`)
- expect(res).toSatisfyApiSpec()
- })
-})
-
-describe("check the rows endpoints", () => {
- let row: Row
- it("should allow retrieving rows through search", async () => {
- table = await config.upsertTable()
- const res = await makeRequest("post", `/tables/${table._id}/rows/search`, {
- query: {},
+ it("should allow updating an application", async () => {
+ const app = config.getApp()
+ const appId = config.getAppId()
+ const res = await makeRequest(
+ "put",
+ `/applications/${appId}`,
+ {
+ ...app,
+ name: "updated app name",
+ },
+ appId
+ )
+ expect(res).toSatisfyApiSpec()
})
- expect(res).toSatisfyApiSpec()
- })
- it("should allow creating a row", async () => {
- const res = await makeRequest("post", `/tables/${table._id}/rows`, {
- name: "test row",
+ it("should allow retrieving an application", async () => {
+ const res = await makeRequest("get", `/applications/${config.getAppId()}`)
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow deleting an application", async () => {
+ nock(environment.WORKER_URL!)
+ .delete(`/api/global/roles/${config.getProdAppId()}`)
+ .reply(200, {})
+
+ const res = await makeRequest(
+ "delete",
+ `/applications/${config.getAppId()}`
+ )
+ expect(res).toSatisfyApiSpec()
})
- expect(res).toSatisfyApiSpec()
- row = res.body.data
})
- it("should allow updating a row", async () => {
- const res = await makeRequest(
- "put",
- `/tables/${table._id}/rows/${row._id}`,
- {
- name: "test row updated",
- }
- )
- expect(res).toSatisfyApiSpec()
+ describe("check the tables endpoints", () => {
+ it("should allow retrieving tables through search", async () => {
+ await config.createApp("new app 1")
+ table = await config.upsertTable()
+ const res = await makeRequest("post", "/tables/search")
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow creating a table", async () => {
+ const res = await makeRequest("post", "/tables", {
+ name: "table name",
+ primaryDisplay: "column1",
+ schema: {
+ column1: {
+ type: "string",
+ constraints: {},
+ },
+ },
+ })
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow updating a table", async () => {
+ const updated = { ...table, _rev: undefined, name: "new name" }
+ const res = await makeRequest("put", `/tables/${table._id}`, updated)
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow retrieving a table", async () => {
+ const res = await makeRequest("get", `/tables/${table._id}`)
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow deleting a table", async () => {
+ const res = await makeRequest("delete", `/tables/${table._id}`)
+ expect(res).toSatisfyApiSpec()
+ })
})
- it("should allow retrieving a row", async () => {
- const res = await makeRequest("get", `/tables/${table._id}/rows/${row._id}`)
- expect(res).toSatisfyApiSpec()
+ describe("check the rows endpoints", () => {
+ let row: Row
+ it("should allow retrieving rows through search", async () => {
+ table = await config.upsertTable()
+ const res = await makeRequest(
+ "post",
+ `/tables/${table._id}/rows/search`,
+ {
+ query: {},
+ }
+ )
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow creating a row", async () => {
+ const res = await makeRequest("post", `/tables/${table._id}/rows`, {
+ name: "test row",
+ })
+ expect(res).toSatisfyApiSpec()
+ row = res.body.data
+ })
+
+ it("should allow updating a row", async () => {
+ const res = await makeRequest(
+ "put",
+ `/tables/${table._id}/rows/${row._id}`,
+ {
+ name: "test row updated",
+ }
+ )
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow retrieving a row", async () => {
+ const res = await makeRequest(
+ "get",
+ `/tables/${table._id}/rows/${row._id}`
+ )
+ expect(res).toSatisfyApiSpec()
+ })
+
+ it("should allow deleting a row", async () => {
+ const res = await makeRequest(
+ "delete",
+ `/tables/${table._id}/rows/${row._id}`
+ )
+ expect(res).toSatisfyApiSpec()
+ })
})
- it("should allow deleting a row", async () => {
- const res = await makeRequest(
- "delete",
- `/tables/${table._id}/rows/${row._id}`
- )
- expect(res).toSatisfyApiSpec()
- })
-})
-
-describe("check the users endpoints", () => {
- let user: User
- it("should allow retrieving users through search", async () => {
- user = await config.createUser()
- const res = await makeRequest("post", "/users/search")
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow creating a user", async () => {
- const res = await makeRequest("post", "/users")
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow updating a user", async () => {
- const res = await makeRequest("put", `/users/${user._id}`)
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow retrieving a user", async () => {
- const res = await makeRequest("get", `/users/${user._id}`)
- expect(res).toSatisfyApiSpec()
- })
-
- it("should allow deleting a user", async () => {
- const res = await makeRequest("delete", `/users/${user._id}`)
- expect(res).toSatisfyApiSpec()
- })
-})
-
-describe("check the queries endpoints", () => {
- it("should allow retrieving queries through search", async () => {
- const res = await makeRequest("post", "/queries/search")
- expect(res).toSatisfyApiSpec()
+ describe("check the queries endpoints", () => {
+ it("should allow retrieving queries through search", async () => {
+ const res = await makeRequest("post", "/queries/search")
+ expect(res).toSatisfyApiSpec()
+ })
})
})
diff --git a/packages/server/src/api/routes/public/tests/users.spec.ts b/packages/server/src/api/routes/public/tests/users.spec.ts
index 4ca9ff8104..bb3e4b6fba 100644
--- a/packages/server/src/api/routes/public/tests/users.spec.ts
+++ b/packages/server/src/api/routes/public/tests/users.spec.ts
@@ -1,132 +1,143 @@
import * as setup from "../../tests/utilities"
-import { generateMakeRequest, MakeRequestResponse } from "./utils"
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 TestConfiguration from "../../../../tests/utilities/TestConfiguration"
+import { mockWorkerUserAPI } from "./utils"
-import * as workerRequests from "../../../../utilities/workerRequests"
+describe("public users API", () => {
+ const config = new TestConfiguration()
+ let globalUser: User
-const mockedWorkerReq = jest.mocked(workerRequests)
-
-let config = setup.getConfig()
-let apiKey: string, globalUser: User, makeRequest: MakeRequestResponse
-
-beforeAll(async () => {
- await config.init()
- globalUser = await config.globalUser()
- apiKey = await config.generateApiKey(globalUser._id)
- makeRequest = generateMakeRequest(apiKey)
- mockedWorkerReq.readGlobalUser.mockImplementation(() =>
- Promise.resolve(globalUser)
- )
-})
-
-afterAll(setup.afterAll)
-
-function base() {
- return {
- tenantId: config.getTenantId(),
- firstName: "Test",
- lastName: "Test",
- }
-}
-
-function updateMock() {
- mockedWorkerReq.readGlobalUser.mockImplementation(ctx => ctx.request.body)
-}
-
-describe("check user endpoints", () => {
- it("should not allow a user to update their own roles", async () => {
- const res = await makeRequest("put", `/users/${globalUser._id}`, {
- ...globalUser,
- roles: {
- app_1: "ADMIN",
- },
- })
- expect(
- mockedWorkerReq.saveGlobalUser.mock.lastCall?.[0].body.data.roles["app_1"]
- ).toBeUndefined()
- expect(res.status).toBe(200)
- expect(res.body.data.roles["app_1"]).toBeUndefined()
+ beforeAll(async () => {
+ await config.init()
})
- it("should not allow a user to delete themselves", async () => {
- const res = await makeRequest("delete", `/users/${globalUser._id}`)
- expect(res.status).toBe(405)
- expect(mockedWorkerReq.deleteGlobalUser.mock.lastCall).toBeUndefined()
- })
-})
-
-describe("no user role update in free", () => {
- beforeAll(() => {
- updateMock()
- })
-
- it("should not allow 'roles' to be updated", async () => {
- const res = await makeRequest("post", "/users", {
- ...base(),
- roles: { app_a: "BASIC" },
- })
- expect(res.status).toBe(200)
- expect(res.body.data.roles["app_a"]).toBeUndefined()
- expect(res.body.message).toBeDefined()
- })
-
- it("should not allow 'admin' to be updated", async () => {
- const res = await makeRequest("post", "/users", {
- ...base(),
- admin: { global: true },
- })
- expect(res.status).toBe(200)
- expect(res.body.data.admin).toBeUndefined()
- expect(res.body.message).toBeDefined()
- })
-
- it("should not allow 'builder' to be updated", async () => {
- const res = await makeRequest("post", "/users", {
- ...base(),
- builder: { global: true },
- })
- expect(res.status).toBe(200)
- expect(res.body.data.builder).toBeUndefined()
- expect(res.body.message).toBeDefined()
- })
-})
-
-describe("no user role update in business", () => {
- beforeAll(() => {
- updateMock()
- mocks.licenses.useExpandedPublicApi()
- })
-
- it("should allow 'roles' to be updated", async () => {
- const res = await makeRequest("post", "/users", {
- ...base(),
- roles: { app_a: "BASIC" },
- })
- expect(res.status).toBe(200)
- expect(res.body.data.roles["app_a"]).toBe("BASIC")
- expect(res.body.message).toBeUndefined()
- })
-
- it("should allow 'admin' to be updated", async () => {
- mocks.licenses.useExpandedPublicApi()
- const res = await makeRequest("post", "/users", {
- ...base(),
- admin: { global: true },
- })
- expect(res.status).toBe(200)
- expect(res.body.data.admin.global).toBe(true)
- expect(res.body.message).toBeUndefined()
- })
-
- it("should allow 'builder' to be updated", async () => {
- mocks.licenses.useExpandedPublicApi()
- const res = await makeRequest("post", "/users", {
- ...base(),
- builder: { global: true },
- })
- expect(res.status).toBe(200)
- expect(res.body.data.builder.global).toBe(true)
- expect(res.body.message).toBeUndefined()
+ afterAll(setup.afterAll)
+
+ beforeEach(async () => {
+ globalUser = await config.globalUser()
+
+ nock.cleanAll()
+ mockWorkerUserAPI(globalUser)
+ })
+
+ describe("read", () => {
+ it("should allow a user to read themselves", async () => {
+ const user = await config.api.user.find(globalUser._id!)
+ expect(user._id).toBe(globalUser._id)
+ })
+
+ it("should allow a user to read another user", async () => {
+ const otherUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: {},
+ })
+ const user = await config.withUser(globalUser, () =>
+ config.api.public.user.find(otherUser._id!)
+ )
+ expect(user._id).toBe(otherUser._id)
+ })
+ })
+
+ describe("create", () => {
+ it("can successfully create a new user", async () => {
+ const email = generator.email({ domain: "example.com" })
+ const newUser = await config.api.public.user.create({
+ email,
+ roles: {},
+ })
+ expect(newUser.email).toBe(email)
+ expect(newUser._id).toBeDefined()
+ })
+
+ describe("role creation on free tier", () => {
+ it("should not allow 'roles' to be updated", async () => {
+ const newUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: { app_a: "BASIC" },
+ })
+ expect(newUser.roles["app_a"]).toBeUndefined()
+ })
+
+ it("should not allow 'admin' to be updated", async () => {
+ const newUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: {},
+ admin: { global: true },
+ })
+ expect(newUser.admin).toBeUndefined()
+ })
+
+ it("should not allow 'builder' to be updated", async () => {
+ const newUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: {},
+ builder: { global: true },
+ })
+ expect(newUser.builder).toBeUndefined()
+ })
+ })
+
+ describe("role creation on business tier", () => {
+ beforeAll(() => {
+ mocks.licenses.useExpandedPublicApi()
+ })
+
+ it("should allow 'roles' to be updated", async () => {
+ const newUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: { app_a: "BASIC" },
+ })
+ expect(newUser.roles["app_a"]).toBe("BASIC")
+ })
+
+ it("should allow 'admin' to be updated", async () => {
+ const newUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: {},
+ admin: { global: true },
+ })
+ expect(newUser.admin?.global).toBe(true)
+ })
+
+ it("should allow 'builder' to be updated", async () => {
+ const newUser = await config.api.public.user.create({
+ email: generator.email({ domain: "example.com" }),
+ roles: {},
+ builder: { global: true },
+ })
+ expect(newUser.builder?.global).toBe(true)
+ })
+ })
+ })
+
+ describe("update", () => {
+ it("can update a user", async () => {
+ const updatedUser = await config.api.public.user.update({
+ ...globalUser,
+ email: `updated-${globalUser.email}`,
+ })
+ expect(updatedUser.email).toBe(`updated-${globalUser.email}`)
+ })
+
+ it("should not allow a user to update their own roles", async () => {
+ await config.withUser(globalUser, () =>
+ config.api.public.user.update({
+ ...globalUser,
+ roles: { app_1: "ADMIN" },
+ })
+ )
+ const updatedUser = await config.api.user.find(globalUser._id!)
+ expect(updatedUser.roles?.app_1).toBeUndefined()
+ })
+ })
+
+ describe("delete", () => {
+ it("should not allow a user to delete themselves", async () => {
+ await config.withUser(globalUser, () =>
+ config.api.public.user.destroy(globalUser._id!, { status: 405 })
+ )
+ })
})
})
diff --git a/packages/server/src/api/routes/public/tests/utils.ts b/packages/server/src/api/routes/public/tests/utils.ts
index 4fb048a540..4985c0db58 100644
--- a/packages/server/src/api/routes/public/tests/utils.ts
+++ b/packages/server/src/api/routes/public/tests/utils.ts
@@ -1,6 +1,10 @@
import * as setup from "../../tests/utilities"
import { checkSlashesInUrl } from "../../../../utilities"
import supertest from "supertest"
+import { User } from "@budibase/types"
+import environment from "../../../../environment"
+import nock from "nock"
+import { generator } from "@budibase/backend-core/tests"
export type HttpMethod = "post" | "get" | "put" | "delete" | "patch"
@@ -91,3 +95,43 @@ export function generateMakeRequestWithFormData(
return res
}
}
+
+export function mockWorkerUserAPI(...seedUsers: User[]) {
+ const users: Record = {
+ ...seedUsers.reduce((acc, user) => {
+ acc[user._id!] = user
+ return acc
+ }, {} as Record),
+ }
+
+ nock(environment.WORKER_URL!)
+ .get(new RegExp(`/api/global/users/.*`))
+ .reply(200, (uri, body) => {
+ const id = uri.split("/").pop()
+ return users[id!]
+ })
+ .persist()
+
+ nock(environment.WORKER_URL!)
+ .post(`/api/global/users`)
+ .reply(200, (uri, body) => {
+ const newUser = body as User
+ if (!newUser._id) {
+ newUser._id = `us_${generator.guid()}`
+ }
+ users[newUser._id!] = newUser
+ return newUser
+ })
+ .persist()
+
+ nock(environment.WORKER_URL!)
+ .put(new RegExp(`/api/global/users/.*`))
+ .reply(200, (uri, body) => {
+ const id = uri.split("/").pop()!
+ const updatedUser = body as User
+ const existingUser = users[id] || {}
+ users[id] = { ...existingUser, ...updatedUser }
+ return users[id]
+ })
+ .persist()
+}
diff --git a/packages/server/src/api/routes/tests/environmentVariables.spec.ts b/packages/server/src/api/routes/tests/environmentVariables.spec.ts
index beb6012c9c..c663e2e26e 100644
--- a/packages/server/src/api/routes/tests/environmentVariables.spec.ts
+++ b/packages/server/src/api/routes/tests/environmentVariables.spec.ts
@@ -1,153 +1,106 @@
-const pg = require("pg")
-
-jest.mock("pg", () => {
- return {
- Client: jest.fn().mockImplementation(() => ({
- connect: jest.fn(),
- query: jest.fn().mockImplementation(() => ({ rows: [] })),
- end: jest.fn().mockImplementation((fn: any) => fn()),
- })),
- queryMock: jest.fn().mockImplementation(() => {}),
- on: jest.fn(),
- }
-})
-import * as setup from "./utilities"
+import { structures } from "./utilities"
import { mocks } from "@budibase/backend-core/tests"
-import { env, events } from "@budibase/backend-core"
-import { QueryPreview } from "@budibase/types"
+import { setEnv } from "@budibase/backend-core"
+import { Datasource } from "@budibase/types"
+import TestConfiguration from "../../../tests/utilities/TestConfiguration"
+import {
+ DatabaseName,
+ datasourceDescribe,
+} from "../../../integrations/tests/utils"
-const structures = setup.structures
+const describes = datasourceDescribe({ only: [DatabaseName.POSTGRES] })
-env._set("ENCRYPTION_KEY", "budibase")
-mocks.licenses.useEnvironmentVariables()
+if (describes.length > 0) {
+ describe.each(describes)("/api/env/variables", ({ dsProvider }) => {
+ const config = new TestConfiguration()
-describe("/api/env/variables", () => {
- let request = setup.getRequest()
- let config = setup.getConfig()
+ let rawDatasource: Datasource
+ let restoreEnv: () => void
- afterAll(setup.afterAll)
+ beforeAll(async () => {
+ await config.init()
+ restoreEnv = setEnv({ ENCRYPTION_KEY: "budibase" })
+ mocks.licenses.useEnvironmentVariables()
- beforeAll(async () => {
- await config.init()
- })
+ const ds = await dsProvider()
+ rawDatasource = ds.rawDatasource!
+ })
- it("should be able check the status of env var API", async () => {
- const res = await request
- .get(`/api/env/variables/status`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
+ afterAll(() => {
+ restoreEnv()
+ })
- expect(res.body.encryptionKeyAvailable).toEqual(true)
- })
-
- it("should be able to create an environment variable", async () => {
- await request
- .post(`/api/env/variables`)
- .send(structures.basicEnvironmentVariable("test", "test"))
- .set(config.defaultHeaders())
- .expect(200)
- })
-
- it("should be able to fetch the 'test' variable name", async () => {
- const res = await request
- .get(`/api/env/variables`)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(res.body.variables.length).toEqual(1)
- expect(res.body.variables[0]).toEqual("test")
- })
-
- it("should be able to update the environment variable 'test'", async () => {
- const varName = "test"
- await request
- .patch(`/api/env/variables/${varName}`)
- .send(structures.basicEnvironmentVariable("test", "test1"))
- .set(config.defaultHeaders())
- .expect(200)
- })
-
- it("should be able to delete the environment variable 'test'", async () => {
- const varName = "test"
- await request
- .delete(`/api/env/variables/${varName}`)
- .set(config.defaultHeaders())
- .expect(200)
- })
-
- it("should create a datasource (using the environment variable) and query", async () => {
- const datasourceBase = structures.basicDatasource()
- await request
- .post(`/api/env/variables`)
- .send(structures.basicEnvironmentVariable("test", "test"))
- .set(config.defaultHeaders())
-
- datasourceBase.datasource.config = {
- password: "{{ env.test }}",
- }
- const response = await request
- .post(`/api/datasources`)
- .send(datasourceBase)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(response.body.datasource._id).toBeDefined()
-
- const response2 = await request
- .post(`/api/queries`)
- .send(structures.basicQuery(response.body.datasource._id))
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(response2.body._id).toBeDefined()
- })
-
- it("should run a query preview and check the mocked results", async () => {
- const datasourceBase = structures.basicDatasource()
- await request
- .post(`/api/env/variables`)
- .send(structures.basicEnvironmentVariable("test", "test"))
- .set(config.defaultHeaders())
-
- datasourceBase.datasource.config = {
- password: "{{ env.test }}",
- }
- const response = await request
- .post(`/api/datasources`)
- .send(datasourceBase)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(response.body.datasource._id).toBeDefined()
-
- const queryPreview: QueryPreview = {
- datasourceId: response.body.datasource._id,
- parameters: [],
- fields: {},
- queryVerb: "read",
- name: response.body.datasource.name,
- transformer: null,
- schema: {},
- readable: true,
- }
- const res = await request
- .post(`/api/queries/preview`)
- .send(queryPreview)
- .set(config.defaultHeaders())
- .expect("Content-Type", /json/)
- .expect(200)
- expect(res.body.rows.length).toEqual(0)
- expect(events.query.previewed).toHaveBeenCalledTimes(1)
- // API doesn't include config in response
- delete response.body.datasource.config
- expect(events.query.previewed).toHaveBeenCalledWith(
- response.body.datasource,
- {
- ...queryPreview,
- nullDefaultSupport: true,
+ beforeEach(async () => {
+ const { variables } = await config.api.environment.fetch()
+ for (const variable of variables) {
+ await config.api.environment.destroy(variable)
}
- )
- expect(pg.Client).toHaveBeenCalledWith({ password: "test", ssl: undefined })
+
+ await config.api.environment.create({
+ name: "test",
+ production: rawDatasource.config!.password,
+ development: rawDatasource.config!.password,
+ })
+ })
+
+ it("should be able check the status of env var API", async () => {
+ const { encryptionKeyAvailable } = await config.api.environment.status()
+ expect(encryptionKeyAvailable).toEqual(true)
+ })
+
+ it("should be able to fetch the 'test' variable name", async () => {
+ const { variables } = await config.api.environment.fetch()
+ expect(variables.length).toEqual(1)
+ expect(variables[0]).toEqual("test")
+ })
+
+ it("should be able to update the environment variable 'test'", async () => {
+ await config.api.environment.update("test", {
+ production: "test1",
+ development: "test1",
+ })
+ })
+
+ it("should be able to delete the environment variable 'test'", async () => {
+ await config.api.environment.destroy("test")
+ })
+
+ it("should create a datasource (using the environment variable) and query", async () => {
+ const datasource = await config.api.datasource.create({
+ ...structures.basicDatasource().datasource,
+ config: {
+ ...rawDatasource.config,
+ password: "{{ env.test }}",
+ },
+ })
+
+ const query = await config.api.query.save({
+ ...structures.basicQuery(datasource._id!),
+ fields: { sql: "SELECT 1" },
+ })
+ expect(query._id).toBeDefined()
+ })
+
+ it("should run a query preview and check the mocked results", async () => {
+ const datasource = await config.api.datasource.create({
+ ...structures.basicDatasource().datasource,
+ config: {
+ ...rawDatasource.config,
+ password: "{{ env.test }}",
+ },
+ })
+
+ const query = await config.api.query.save({
+ ...structures.basicQuery(datasource._id!),
+ fields: { sql: "SELECT 1 as id" },
+ })
+
+ const { rows } = await config.api.query.preview({
+ ...query,
+ queryId: query._id!,
+ })
+
+ expect(rows).toEqual([{ id: 1 }])
+ })
})
-})
+}
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index caa651f3bb..e115297ee9 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -6,6 +6,7 @@ import {
docIds,
MAX_VALID_DATE,
MIN_VALID_DATE,
+ setEnv,
SQLITE_DESIGN_DOC_ID,
utils,
withEnv as withCoreEnv,
@@ -43,19 +44,7 @@ import { generator, structures, mocks } from "@budibase/backend-core/tests"
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
import { generateRowIdField } from "../../../integrations/utils"
import { cloneDeep } from "lodash/fp"
-
-jest.mock("@budibase/pro", () => ({
- ...jest.requireActual("@budibase/pro"),
- ai: {
- LargeLanguageModel: {
- forCurrentTenant: async () => ({
- llm: {},
- run: jest.fn(() => `Mock LLM Response`),
- buildPromptFromAIOperation: jest.fn(),
- }),
- },
- },
-}))
+import { mockChatGPTResponse } from "../../../tests/utilities/mocks/openai"
const descriptions = datasourceDescribe({ plus: true })
@@ -1896,11 +1885,15 @@ if (descriptions.length) {
!isInMemory &&
describe("AI Column", () => {
const UNEXISTING_AI_COLUMN = "Real LLM Response"
+ let envCleanup: () => void
beforeAll(async () => {
mocks.licenses.useBudibaseAI()
mocks.licenses.useAICustomConfigs()
+ envCleanup = setEnv({ OPENAI_API_KEY: "mock" })
+ mockChatGPTResponse("Mock LLM Response")
+
tableOrViewId = await createTableOrView({
product: { name: "product", type: FieldType.STRING },
ai: {
@@ -1917,6 +1910,10 @@ if (descriptions.length) {
])
})
+ afterAll(() => {
+ envCleanup()
+ })
+
describe("equal", () => {
it("successfully finds rows based on AI column", async () => {
await expectQuery({
diff --git a/packages/server/src/api/routes/tests/utilities/index.ts b/packages/server/src/api/routes/tests/utilities/index.ts
index dcb8ccd6c0..944a56d7ba 100644
--- a/packages/server/src/api/routes/tests/utilities/index.ts
+++ b/packages/server/src/api/routes/tests/utilities/index.ts
@@ -3,44 +3,6 @@ import supertest from "supertest"
export * as structures from "../../../../tests/utilities/structures"
-function user() {
- return {
- _id: "user",
- _rev: "rev",
- createdAt: Date.now(),
- email: "test@example.com",
- roles: {},
- tenantId: "default",
- status: "active",
- }
-}
-
-jest.mock("../../../../utilities/workerRequests", () => ({
- getGlobalUsers: jest.fn(() => {
- return {
- _id: "us_uuid1",
- }
- }),
- getGlobalSelf: jest.fn(() => {
- return {
- _id: "us_uuid1",
- }
- }),
- allGlobalUsers: jest.fn(() => {
- return [user()]
- }),
- readGlobalUser: jest.fn(() => {
- return user()
- }),
- saveGlobalUser: jest.fn(() => {
- return { _id: "user", _rev: "rev" }
- }),
- deleteGlobalUser: jest.fn(() => {
- return { message: "deleted user" }
- }),
- removeAppFromUserRoles: jest.fn(),
-}))
-
export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
diff --git a/packages/server/src/automations/tests/steps/bash.spec.ts b/packages/server/src/automations/tests/steps/bash.spec.ts
index a2172c3578..79ddf67a75 100644
--- a/packages/server/src/automations/tests/steps/bash.spec.ts
+++ b/packages/server/src/automations/tests/steps/bash.spec.ts
@@ -16,6 +16,7 @@ describe("Execute Bash Automations", () => {
name: "test row",
description: "test description",
})
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/createRow.spec.ts b/packages/server/src/automations/tests/steps/createRow.spec.ts
index 01ce227f36..6da142434a 100644
--- a/packages/server/src/automations/tests/steps/createRow.spec.ts
+++ b/packages/server/src/automations/tests/steps/createRow.spec.ts
@@ -33,6 +33,7 @@ describe("test the create row action", () => {
name: "test",
description: "test",
}
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/delay.spec.ts b/packages/server/src/automations/tests/steps/delay.spec.ts
index 173beccbda..03f6a528a6 100644
--- a/packages/server/src/automations/tests/steps/delay.spec.ts
+++ b/packages/server/src/automations/tests/steps/delay.spec.ts
@@ -6,6 +6,7 @@ describe("test the delay logic", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/deleteRow.spec.ts b/packages/server/src/automations/tests/steps/deleteRow.spec.ts
index 8c141f82da..5ec5e6035e 100644
--- a/packages/server/src/automations/tests/steps/deleteRow.spec.ts
+++ b/packages/server/src/automations/tests/steps/deleteRow.spec.ts
@@ -13,6 +13,7 @@ describe("test the delete row action", () => {
await config.init()
table = await config.api.table.save(basicTable())
row = await config.api.row.save(table._id!, {})
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/discord.spec.ts b/packages/server/src/automations/tests/steps/discord.spec.ts
index 9618a0c994..8e2af09721 100644
--- a/packages/server/src/automations/tests/steps/discord.spec.ts
+++ b/packages/server/src/automations/tests/steps/discord.spec.ts
@@ -7,6 +7,7 @@ describe("test the outgoing webhook action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/executeQuery.spec.ts b/packages/server/src/automations/tests/steps/executeQuery.spec.ts
index a51d335902..d65c30c789 100644
--- a/packages/server/src/automations/tests/steps/executeQuery.spec.ts
+++ b/packages/server/src/automations/tests/steps/executeQuery.spec.ts
@@ -26,6 +26,7 @@ if (descriptions.length) {
const ds = await dsProvider()
datasource = ds.datasource!
client = ds.client!
+ await config.api.automation.deleteAll()
})
beforeEach(async () => {
diff --git a/packages/server/src/automations/tests/steps/executeScript.spec.ts b/packages/server/src/automations/tests/steps/executeScript.spec.ts
index 117c2341ba..ea5b8dc6b2 100644
--- a/packages/server/src/automations/tests/steps/executeScript.spec.ts
+++ b/packages/server/src/automations/tests/steps/executeScript.spec.ts
@@ -13,6 +13,7 @@ describe("Execute Script Automations", () => {
await config.init()
table = await config.api.table.save(basicTable())
await config.api.row.save(table._id!, {})
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/filter.spec.ts b/packages/server/src/automations/tests/steps/filter.spec.ts
index da1f6e4702..ba7fb3e1b7 100644
--- a/packages/server/src/automations/tests/steps/filter.spec.ts
+++ b/packages/server/src/automations/tests/steps/filter.spec.ts
@@ -26,6 +26,7 @@ describe("test the filter logic", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/loop.spec.ts b/packages/server/src/automations/tests/steps/loop.spec.ts
index 2bdf33b253..34fc175c71 100644
--- a/packages/server/src/automations/tests/steps/loop.spec.ts
+++ b/packages/server/src/automations/tests/steps/loop.spec.ts
@@ -22,10 +22,7 @@ describe("Attempt to run a basic loop automation", () => {
})
beforeEach(async () => {
- const { automations } = await config.api.automation.fetch()
- for (const automation of automations) {
- await config.api.automation.delete(automation)
- }
+ await config.api.automation.deleteAll()
table = await config.api.table.save(basicTable())
await config.api.row.save(table._id!, {})
diff --git a/packages/server/src/automations/tests/steps/make.spec.ts b/packages/server/src/automations/tests/steps/make.spec.ts
index bbc0c3791a..3f473560ed 100644
--- a/packages/server/src/automations/tests/steps/make.spec.ts
+++ b/packages/server/src/automations/tests/steps/make.spec.ts
@@ -7,6 +7,7 @@ describe("test the outgoing webhook action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/n8n.spec.ts b/packages/server/src/automations/tests/steps/n8n.spec.ts
index 4ee3123d98..754015baa1 100644
--- a/packages/server/src/automations/tests/steps/n8n.spec.ts
+++ b/packages/server/src/automations/tests/steps/n8n.spec.ts
@@ -8,6 +8,7 @@ describe("test the outgoing webhook action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/openai.spec.ts b/packages/server/src/automations/tests/steps/openai.spec.ts
index d5f002571d..a06c633e5e 100644
--- a/packages/server/src/automations/tests/steps/openai.spec.ts
+++ b/packages/server/src/automations/tests/steps/openai.spec.ts
@@ -16,6 +16,7 @@ describe("test the openai action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
beforeEach(() => {
diff --git a/packages/server/src/automations/tests/steps/outgoingWebhook.spec.ts b/packages/server/src/automations/tests/steps/outgoingWebhook.spec.ts
index 85ccfb8eac..a62bb4721a 100644
--- a/packages/server/src/automations/tests/steps/outgoingWebhook.spec.ts
+++ b/packages/server/src/automations/tests/steps/outgoingWebhook.spec.ts
@@ -8,6 +8,7 @@ describe("test the outgoing webhook action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/queryRows.spec.ts b/packages/server/src/automations/tests/steps/queryRows.spec.ts
index f6d756e770..66b445a69b 100644
--- a/packages/server/src/automations/tests/steps/queryRows.spec.ts
+++ b/packages/server/src/automations/tests/steps/queryRows.spec.ts
@@ -21,6 +21,7 @@ describe("Test a query step automation", () => {
}
await config.api.row.save(table._id!, row)
await config.api.row.save(table._id!, row)
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/sendSmtpEmail.spec.ts b/packages/server/src/automations/tests/steps/sendSmtpEmail.spec.ts
index 7aff612a97..821d7a1326 100644
--- a/packages/server/src/automations/tests/steps/sendSmtpEmail.spec.ts
+++ b/packages/server/src/automations/tests/steps/sendSmtpEmail.spec.ts
@@ -28,6 +28,7 @@ describe("test the outgoing webhook action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/serverLog.spec.ts b/packages/server/src/automations/tests/steps/serverLog.spec.ts
index 82f097d0da..4ddcdf4d9a 100644
--- a/packages/server/src/automations/tests/steps/serverLog.spec.ts
+++ b/packages/server/src/automations/tests/steps/serverLog.spec.ts
@@ -6,6 +6,7 @@ describe("test the server log action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/triggerAutomationRun.spec.ts b/packages/server/src/automations/tests/steps/triggerAutomationRun.spec.ts
index 8d4a29c2b6..44aee65e61 100644
--- a/packages/server/src/automations/tests/steps/triggerAutomationRun.spec.ts
+++ b/packages/server/src/automations/tests/steps/triggerAutomationRun.spec.ts
@@ -9,6 +9,7 @@ describe("Test triggering an automation from another automation", () => {
beforeAll(async () => {
await automation.init()
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(async () => {
diff --git a/packages/server/src/automations/tests/steps/updateRow.spec.ts b/packages/server/src/automations/tests/steps/updateRow.spec.ts
index a2f1825099..833ff99bfa 100644
--- a/packages/server/src/automations/tests/steps/updateRow.spec.ts
+++ b/packages/server/src/automations/tests/steps/updateRow.spec.ts
@@ -23,6 +23,7 @@ describe("test the update row action", () => {
await config.init()
table = await config.createTable()
row = await config.createRow()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/steps/zapier.spec.ts b/packages/server/src/automations/tests/steps/zapier.spec.ts
index e6b5417563..436738388f 100644
--- a/packages/server/src/automations/tests/steps/zapier.spec.ts
+++ b/packages/server/src/automations/tests/steps/zapier.spec.ts
@@ -7,6 +7,7 @@ describe("test the outgoing webhook action", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/triggers/appAction.spec.ts b/packages/server/src/automations/tests/triggers/appAction.spec.ts
index 2247868c44..97c34b35dc 100644
--- a/packages/server/src/automations/tests/triggers/appAction.spec.ts
+++ b/packages/server/src/automations/tests/triggers/appAction.spec.ts
@@ -9,6 +9,8 @@ describe("app action trigger", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
+
automation = await createAutomationBuilder(config)
.onAppAction()
.serverLog({
diff --git a/packages/server/src/automations/tests/triggers/cron.spec.ts b/packages/server/src/automations/tests/triggers/cron.spec.ts
index 8db9cb425e..ae6652033e 100644
--- a/packages/server/src/automations/tests/triggers/cron.spec.ts
+++ b/packages/server/src/automations/tests/triggers/cron.spec.ts
@@ -16,6 +16,7 @@ describe("cron trigger", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
})
afterAll(() => {
diff --git a/packages/server/src/automations/tests/triggers/rowDeleted.spec.ts b/packages/server/src/automations/tests/triggers/rowDeleted.spec.ts
index df6b28b31d..a93e0f8683 100644
--- a/packages/server/src/automations/tests/triggers/rowDeleted.spec.ts
+++ b/packages/server/src/automations/tests/triggers/rowDeleted.spec.ts
@@ -11,6 +11,7 @@ describe("row deleted trigger", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
table = await config.api.table.save(basicTable())
automation = await createAutomationBuilder(config)
.onRowDeleted({ tableId: table._id! })
diff --git a/packages/server/src/automations/tests/triggers/rowSaved.spec.ts b/packages/server/src/automations/tests/triggers/rowSaved.spec.ts
index 874abb8872..573a8dd392 100644
--- a/packages/server/src/automations/tests/triggers/rowSaved.spec.ts
+++ b/packages/server/src/automations/tests/triggers/rowSaved.spec.ts
@@ -11,6 +11,7 @@ describe("row saved trigger", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
table = await config.api.table.save(basicTable())
automation = await createAutomationBuilder(config)
.onRowSaved({ tableId: table._id! })
diff --git a/packages/server/src/automations/tests/triggers/rowUpdated.spec.ts b/packages/server/src/automations/tests/triggers/rowUpdated.spec.ts
index 672335f65a..bb896636a6 100644
--- a/packages/server/src/automations/tests/triggers/rowUpdated.spec.ts
+++ b/packages/server/src/automations/tests/triggers/rowUpdated.spec.ts
@@ -11,6 +11,7 @@ describe("row updated trigger", () => {
beforeAll(async () => {
await config.init()
+ await config.api.automation.deleteAll()
table = await config.api.table.save(basicTable())
automation = await createAutomationBuilder(config)
.onRowUpdated({ tableId: table._id! })
diff --git a/packages/server/src/automations/tests/triggers/webhook.spec.ts b/packages/server/src/automations/tests/triggers/webhook.spec.ts
index 9649846830..77d63a7ffa 100644
--- a/packages/server/src/automations/tests/triggers/webhook.spec.ts
+++ b/packages/server/src/automations/tests/triggers/webhook.spec.ts
@@ -37,6 +37,7 @@ describe("Webhook trigger test", () => {
beforeEach(async () => {
await config.init()
+ await config.api.automation.deleteAll()
table = await config.createTable()
})
diff --git a/packages/server/src/integrations/arangodb.ts b/packages/server/src/integrations/arangodb.ts
index 0127d25632..76a5fb1d69 100644
--- a/packages/server/src/integrations/arangodb.ts
+++ b/packages/server/src/integrations/arangodb.ts
@@ -9,6 +9,11 @@ import {
import { Database, aql } from "arangojs"
+/**
+ * @deprecated 3rd March 2025
+ * datasource disabled - this datasource is marked for deprecation and removal
+ */
+
interface ArangodbConfig {
url: string
username: string
diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts
index de700d631d..de05e300e6 100644
--- a/packages/server/src/integrations/index.ts
+++ b/packages/server/src/integrations/index.ts
@@ -33,15 +33,17 @@ const DEFINITIONS: Record = {
[SourceName.COUCHDB]: couchdb.schema,
[SourceName.SQL_SERVER]: sqlServer.schema,
[SourceName.S3]: s3.schema,
- [SourceName.AIRTABLE]: airtable.schema,
[SourceName.MYSQL]: mysql.schema,
- [SourceName.ARANGODB]: arangodb.schema,
[SourceName.REST]: rest.schema,
[SourceName.FIRESTORE]: firebase.schema,
[SourceName.GOOGLE_SHEETS]: googlesheets.schema,
[SourceName.REDIS]: redis.schema,
[SourceName.SNOWFLAKE]: snowflake.schema,
[SourceName.ORACLE]: oracle.schema,
+ /* deprecated - not available through UI */
+ [SourceName.ARANGODB]: arangodb.schema,
+ [SourceName.AIRTABLE]: airtable.schema,
+ /* un-used */
[SourceName.BUDIBASE]: undefined,
}
@@ -56,15 +58,17 @@ const INTEGRATIONS: Record =
[SourceName.COUCHDB]: couchdb.integration,
[SourceName.SQL_SERVER]: sqlServer.integration,
[SourceName.S3]: s3.integration,
- [SourceName.AIRTABLE]: airtable.integration,
[SourceName.MYSQL]: mysql.integration,
- [SourceName.ARANGODB]: arangodb.integration,
[SourceName.REST]: rest.integration,
[SourceName.FIRESTORE]: firebase.integration,
[SourceName.GOOGLE_SHEETS]: googlesheets.integration,
[SourceName.REDIS]: redis.integration,
[SourceName.SNOWFLAKE]: snowflake.integration,
[SourceName.ORACLE]: oracle.integration,
+ /* deprecated - not available through UI */
+ [SourceName.ARANGODB]: arangodb.integration,
+ [SourceName.AIRTABLE]: airtable.integration,
+ /* un-used */
[SourceName.BUDIBASE]: undefined,
}
diff --git a/packages/server/src/integrations/tests/airtable.spec.ts b/packages/server/src/integrations/tests/airtable.spec.ts
deleted file mode 100644
index 367e31e8a0..0000000000
--- a/packages/server/src/integrations/tests/airtable.spec.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { default as AirtableIntegration } from "../airtable"
-
-jest.mock("airtable")
-
-class TestConfiguration {
- integration: any
- client: any
-
- constructor(config: any = {}) {
- this.integration = new AirtableIntegration.integration(config)
- this.client = {
- create: jest.fn(),
- select: jest.fn(() => ({
- firstPage: jest.fn(() => []),
- })),
- update: jest.fn(),
- destroy: jest.fn(),
- }
- this.integration.client = () => this.client
- }
-}
-
-describe("Airtable Integration", () => {
- let config: any
-
- beforeEach(() => {
- config = new TestConfiguration()
- })
-
- it("calls the create method with the correct params", async () => {
- await config.integration.create({
- table: "test",
- json: {},
- })
- expect(config.client.create).toHaveBeenCalledWith([
- {
- fields: {},
- },
- ])
- })
-
- it("calls the read method with the correct params", async () => {
- await config.integration.read({
- table: "test",
- view: "Grid view",
- })
- expect(config.client.select).toHaveBeenCalledWith({
- maxRecords: 10,
- view: "Grid view",
- })
- })
-
- it("calls the update method with the correct params", async () => {
- await config.integration.update({
- table: "table",
- id: "123",
- json: {
- name: "test",
- },
- })
- expect(config.client.update).toHaveBeenCalledWith([
- {
- id: "123",
- fields: { name: "test" },
- },
- ])
- })
-
- it("calls the delete method with the correct params", async () => {
- const ids = [1, 2, 3, 4]
- await config.integration.delete({
- ids,
- })
- expect(config.client.destroy).toHaveBeenCalledWith(ids)
- })
-})
diff --git a/packages/server/src/integrations/tests/arangodb.spec.ts b/packages/server/src/integrations/tests/arangodb.spec.ts
deleted file mode 100644
index 6ac242d8db..0000000000
--- a/packages/server/src/integrations/tests/arangodb.spec.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { default as ArangoDBIntegration } from "../arangodb"
-
-jest.mock("arangojs")
-
-class TestConfiguration {
- integration: any
-
- constructor(config: any = {}) {
- this.integration = new ArangoDBIntegration.integration(config)
- }
-}
-
-describe("ArangoDB Integration", () => {
- let config: any
-
- beforeEach(() => {
- config = new TestConfiguration()
- })
-
- it("calls the create method with the correct params", async () => {
- const body = {
- json: "Hello",
- }
-
- await config.integration.create(body)
- expect(config.integration.client.query).toHaveBeenCalledWith(
- `INSERT Hello INTO collection RETURN NEW`
- )
- })
-
- it("calls the read method with the correct params", async () => {
- const query = {
- sql: `test`,
- }
- await config.integration.read(query)
- expect(config.integration.client.query).toHaveBeenCalledWith(query.sql)
- })
-})
diff --git a/packages/server/src/tests/utilities/TestConfiguration.ts b/packages/server/src/tests/utilities/TestConfiguration.ts
index edb397169d..219a428f05 100644
--- a/packages/server/src/tests/utilities/TestConfiguration.ts
+++ b/packages/server/src/tests/utilities/TestConfiguration.ts
@@ -67,6 +67,7 @@ import {
View,
Webhook,
WithRequired,
+ DevInfo,
} from "@budibase/types"
import API from "./api"
@@ -248,7 +249,7 @@ export default class TestConfiguration {
}
}
- async withUser(user: User, f: () => Promise) {
+ async withUser(user: User, f: () => Promise): Promise {
const oldUser = this.user
this.user = user
try {
@@ -469,7 +470,10 @@ export default class TestConfiguration {
}
}
- defaultHeaders(extras = {}, prodApp = false) {
+ defaultHeaders(
+ extras: Record = {},
+ prodApp = false
+ ) {
const tenantId = this.getTenantId()
const user = this.getUser()
const authObj: AuthToken = {
@@ -498,10 +502,13 @@ export default class TestConfiguration {
}
}
- publicHeaders({ prodApp = true } = {}) {
+ publicHeaders({
+ prodApp = true,
+ extras = {},
+ }: { prodApp?: boolean; extras?: Record } = {}) {
const appId = prodApp ? this.prodAppId : this.appId
- const headers: any = {
+ const headers: Record = {
Accept: "application/json",
Cookie: "",
}
@@ -514,6 +521,7 @@ export default class TestConfiguration {
return {
...headers,
...this.temporaryHeaders,
+ ...extras,
}
}
@@ -577,17 +585,17 @@ export default class TestConfiguration {
}
const db = tenancy.getTenantDB(this.getTenantId())
const id = dbCore.generateDevInfoID(userId)
- let devInfo: any
- try {
- devInfo = await db.get(id)
- } catch (err) {
- devInfo = { _id: id, userId }
+ const devInfo = await db.tryGet(id)
+ if (devInfo && devInfo.apiKey) {
+ return devInfo.apiKey
}
- devInfo.apiKey = encryption.encrypt(
+
+ const apiKey = encryption.encrypt(
`${this.getTenantId()}${dbCore.SEPARATOR}${newid()}`
)
- await db.put(devInfo)
- return devInfo.apiKey
+ const newDevInfo: DevInfo = { _id: id, userId, apiKey }
+ await db.put(newDevInfo)
+ return apiKey
}
// APP
diff --git a/packages/server/src/tests/utilities/api/automation.ts b/packages/server/src/tests/utilities/api/automation.ts
index c4438560ae..6bb4c9760c 100644
--- a/packages/server/src/tests/utilities/api/automation.ts
+++ b/packages/server/src/tests/utilities/api/automation.ts
@@ -133,4 +133,11 @@ export class AutomationAPI extends TestAPI {
}
)
}
+
+ deleteAll = async (expectations?: Expectations): Promise => {
+ const { automations } = await this.fetch()
+ await Promise.all(
+ automations.map(automation => this.delete(automation, expectations))
+ )
+ }
}
diff --git a/packages/server/src/tests/utilities/api/base.ts b/packages/server/src/tests/utilities/api/base.ts
index 39ac5cefc0..9b47cfb820 100644
--- a/packages/server/src/tests/utilities/api/base.ts
+++ b/packages/server/src/tests/utilities/api/base.ts
@@ -1,8 +1,12 @@
+import jestOpenAPI from "jest-openapi"
+import { spec } from "../../../../specs/generate"
import TestConfiguration from "../TestConfiguration"
import request, { SuperTest, Test, Response } from "supertest"
import { ReadStream } from "fs"
import { getServer } from "../../../app"
+jestOpenAPI(spec() as any)
+
type Headers = Record
type Method = "get" | "post" | "put" | "patch" | "delete"
@@ -46,6 +50,7 @@ export interface RequestOpts {
export abstract class TestAPI {
config: TestConfiguration
request: SuperTest
+ prefix = ""
constructor(config: TestConfiguration) {
this.config = config
@@ -53,26 +58,26 @@ export abstract class TestAPI {
}
protected _get = async (url: string, opts?: RequestOpts): Promise => {
- return await this._request("get", url, opts)
+ return await this._request("get", `${this.prefix}${url}`, opts)
}
protected _post = async (url: string, opts?: RequestOpts): Promise => {
- return await this._request("post", url, opts)
+ return await this._request("post", `${this.prefix}${url}`, opts)
}
protected _put = async (url: string, opts?: RequestOpts): Promise => {
- return await this._request("put", url, opts)
+ return await this._request("put", `${this.prefix}${url}`, opts)
}
protected _patch = async (url: string, opts?: RequestOpts): Promise => {
- return await this._request("patch", url, opts)
+ return await this._request("patch", `${this.prefix}${url}`, opts)
}
protected _delete = async (
url: string,
opts?: RequestOpts
): Promise => {
- return await this._request("delete", url, opts)
+ return await this._request("delete", `${this.prefix}${url}`, opts)
}
protected _requestRaw = async (
@@ -88,7 +93,6 @@ export abstract class TestAPI {
fields = {},
files = {},
expectations,
- publicUser = false,
} = opts || {}
const { status = 200 } = expectations || {}
const expectHeaders = expectations?.headers || {}
@@ -97,7 +101,7 @@ export abstract class TestAPI {
expectHeaders["Content-Type"] = /^application\/json/
}
- let queryParams = []
+ let queryParams: string[] = []
for (const [key, value] of Object.entries(query)) {
if (value) {
queryParams.push(`${key}=${value}`)
@@ -107,18 +111,10 @@ export abstract class TestAPI {
url += `?${queryParams.join("&")}`
}
- const headersFn = publicUser
- ? (_extras = {}) =>
- this.config.publicHeaders.bind(this.config)({
- prodApp: opts?.useProdApp,
- })
- : (extras = {}) =>
- this.config.defaultHeaders.bind(this.config)(extras, opts?.useProdApp)
-
const app = getServer()
let req = request(app)[method](url)
req = req.set(
- headersFn({
+ await this.getHeaders(opts, {
"x-budibase-include-stacktrace": "true",
})
)
@@ -167,10 +163,18 @@ export abstract class TestAPI {
}
}
- protected _checkResponse = (
- response: Response,
- expectations?: Expectations
- ) => {
+ protected async getHeaders(
+ opts?: RequestOpts,
+ extras?: Record
+ ): Promise> {
+ if (opts?.publicUser) {
+ return this.config.publicHeaders({ prodApp: opts?.useProdApp, extras })
+ } else {
+ return this.config.defaultHeaders(extras, opts?.useProdApp)
+ }
+ }
+
+ protected _checkResponse(response: Response, expectations?: Expectations) {
const { status = 200 } = expectations || {}
if (response.status !== status) {
@@ -236,3 +240,34 @@ export abstract class TestAPI {
).body
}
}
+
+export abstract class PublicAPI extends TestAPI {
+ prefix = "/api/public/v1"
+
+ protected async getHeaders(
+ opts?: RequestOpts,
+ extras?: Record
+ ): Promise> {
+ const apiKey = await this.config.generateApiKey()
+
+ const headers: Record = {
+ Accept: "application/json",
+ Host: this.config.tenantHost(),
+ "x-budibase-api-key": apiKey,
+ "x-budibase-app-id": this.config.getAppId(),
+ ...extras,
+ }
+
+ return headers
+ }
+
+ protected _checkResponse(response: Response, expectations?: Expectations) {
+ const checked = super._checkResponse(response, expectations)
+ if (checked.status >= 200 && checked.status < 300) {
+ // We don't seem to have documented our errors yet, so for the time being
+ // we'll only do the schema check for successful responses.
+ expect(checked).toSatisfyApiSpec()
+ }
+ return checked
+ }
+}
diff --git a/packages/server/src/tests/utilities/api/environment.ts b/packages/server/src/tests/utilities/api/environment.ts
new file mode 100644
index 0000000000..152336b316
--- /dev/null
+++ b/packages/server/src/tests/utilities/api/environment.ts
@@ -0,0 +1,51 @@
+import { Expectations, TestAPI } from "./base"
+import {
+ CreateEnvironmentVariableRequest,
+ CreateEnvironmentVariableResponse,
+ GetEnvironmentVariablesResponse,
+ StatusEnvironmentVariableResponse,
+ UpdateEnvironmentVariableRequest,
+} from "@budibase/types"
+
+export class EnvironmentAPI extends TestAPI {
+ create = async (
+ body: CreateEnvironmentVariableRequest,
+ expectations?: Expectations
+ ) => {
+ return await this._post(
+ `/api/env/variables`,
+ { body, expectations }
+ )
+ }
+
+ status = async (expectations?: Expectations) => {
+ return await this._get(
+ `/api/env/variables/status`,
+ { expectations }
+ )
+ }
+
+ fetch = async (expectations?: Expectations) => {
+ return await this._get(
+ `/api/env/variables`,
+ { expectations }
+ )
+ }
+
+ update = async (
+ varName: string,
+ body: UpdateEnvironmentVariableRequest,
+ expectations?: Expectations
+ ) => {
+ return await this._patch(`/api/env/variables/${varName}`, {
+ body,
+ expectations,
+ })
+ }
+
+ destroy = async (varName: string, expectations?: Expectations) => {
+ return await this._delete(`/api/env/variables/${varName}`, {
+ expectations,
+ })
+ }
+}
diff --git a/packages/server/src/tests/utilities/api/index.ts b/packages/server/src/tests/utilities/api/index.ts
index 2fdf726b6c..4c96f36b43 100644
--- a/packages/server/src/tests/utilities/api/index.ts
+++ b/packages/server/src/tests/utilities/api/index.ts
@@ -17,6 +17,8 @@ import { RowActionAPI } from "./rowAction"
import { AutomationAPI } from "./automation"
import { PluginAPI } from "./plugin"
import { WebhookAPI } from "./webhook"
+import { EnvironmentAPI } from "./environment"
+import { UserPublicAPI } from "./public/user"
export default class API {
application: ApplicationAPI
@@ -24,6 +26,7 @@ export default class API {
automation: AutomationAPI
backup: BackupAPI
datasource: DatasourceAPI
+ environment: EnvironmentAPI
legacyView: LegacyViewAPI
permission: PermissionAPI
plugin: PluginAPI
@@ -38,12 +41,17 @@ export default class API {
viewV2: ViewV2API
webhook: WebhookAPI
+ public: {
+ user: UserPublicAPI
+ }
+
constructor(config: TestConfiguration) {
this.application = new ApplicationAPI(config)
this.attachment = new AttachmentAPI(config)
this.automation = new AutomationAPI(config)
this.backup = new BackupAPI(config)
this.datasource = new DatasourceAPI(config)
+ this.environment = new EnvironmentAPI(config)
this.legacyView = new LegacyViewAPI(config)
this.permission = new PermissionAPI(config)
this.plugin = new PluginAPI(config)
@@ -57,5 +65,8 @@ export default class API {
this.user = new UserAPI(config)
this.viewV2 = new ViewV2API(config)
this.webhook = new WebhookAPI(config)
+ this.public = {
+ user: new UserPublicAPI(config),
+ }
}
}
diff --git a/packages/server/src/tests/utilities/api/public/user.ts b/packages/server/src/tests/utilities/api/public/user.ts
new file mode 100644
index 0000000000..4f5fccc740
--- /dev/null
+++ b/packages/server/src/tests/utilities/api/public/user.ts
@@ -0,0 +1,34 @@
+import { UnsavedUser, User } from "@budibase/types"
+import { Expectations, PublicAPI } from "../base"
+
+export class UserPublicAPI extends PublicAPI {
+ find = async (id: string, expectations?: Expectations): Promise => {
+ const response = await this._get<{ data: User }>(`/users/${id}`, {
+ expectations,
+ })
+ return response.data
+ }
+
+ update = async (user: User, expectations?: Expectations): Promise => {
+ const response = await this._put<{ data: User }>(`/users/${user._id}`, {
+ body: user,
+ expectations,
+ })
+ return response.data
+ }
+
+ destroy = async (id: string, expectations?: Expectations): Promise => {
+ return await this._delete(`/users/${id}`, { expectations })
+ }
+
+ create = async (
+ user: UnsavedUser,
+ expectations?: Expectations
+ ): Promise => {
+ const response = await this._post<{ data: User }>("/users", {
+ body: user,
+ expectations,
+ })
+ return response.data
+ }
+}
diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts
index 38d60e1c11..1679334cab 100644
--- a/packages/server/src/tests/utilities/structures.ts
+++ b/packages/server/src/tests/utilities/structures.ts
@@ -37,6 +37,7 @@ import {
DeepPartial,
FilterCondition,
AutomationTriggerResult,
+ CreateEnvironmentVariableRequest,
} from "@budibase/types"
import { LoopInput } from "../../definitions/automations"
import { merge } from "lodash"
@@ -574,7 +575,7 @@ export function basicEnvironmentVariable(
name: string,
prod: string,
dev?: string
-) {
+): CreateEnvironmentVariableRequest {
return {
name,
production: prod,
diff --git a/yarn.lock b/yarn.lock
index 279c4067a4..5173e81408 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2796,9 +2796,9 @@
through2 "^2.0.0"
"@budibase/pro@npm:@budibase/pro@latest":
- version "3.4.20"
- resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-3.4.20.tgz#0d855d6ed8fe92fd178c74a8963d879cc124b034"
- integrity sha512-hUteGvhMOKjBo0fluxcqNs7d4x8OU5W8Oqqrm7eIS9Ohe7ala2iWNCcrj+x+S9CavIm6s7JZZnAewa2Maiz2zQ==
+ version "3.4.22"
+ resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-3.4.22.tgz#943f23cb7056041bc1f433ee60b3d093145e7a4a"
+ integrity sha512-Du3iZsmRLopfoi2SvxQyY1P2Su3Nw0WbITOrKmZFsVLjZ9MzzTZs0Ph/SJHzrfJpM7rn9+8788BLSf3Z3l9KcQ==
dependencies:
"@anthropic-ai/sdk" "^0.27.3"
"@budibase/backend-core" "*"
@@ -7142,6 +7142,11 @@
dependencies:
"@types/superagent" "*"
+"@types/swagger-jsdoc@^6.0.4":
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz#bb4f60f3a5f103818e022f2e29ff8935113fb83d"
+ integrity sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==
+
"@types/tar-fs@2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-2.0.1.tgz#6391dcad1b03dea2d79fac07371585ab54472bb1"