@budibase/worker 2.5.5 → 2.5.6-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,7 +48,7 @@ describe("/api/global/users", () => {
48
48
  400
49
49
  )
50
50
 
51
- expect(res.body.message).toBe("Unavailable")
51
+ expect(res.body.message).toBe(`Unavailable`)
52
52
  expect(sendMailMock).toHaveBeenCalledTimes(0)
53
53
  expect(code).toBeUndefined()
54
54
  expect(events.user.invited).toBeCalledTimes(0)
@@ -225,7 +225,9 @@ describe("/api/global/users", () => {
225
225
 
226
226
  const response = await config.api.users.saveUser(user, 400)
227
227
 
228
- expect(response.body.message).toBe(`Unavailable`)
228
+ expect(response.body.message).toBe(
229
+ `Email already in use: '${user.email}'`
230
+ )
229
231
  expect(events.user.created).toBeCalledTimes(0)
230
232
  })
231
233
 
@@ -237,7 +239,9 @@ describe("/api/global/users", () => {
237
239
  delete user._id
238
240
  const response = await config.api.users.saveUser(user, 400)
239
241
 
240
- expect(response.body.message).toBe(`Unavailable`)
242
+ expect(response.body.message).toBe(
243
+ `Email already in use: '${user.email}'`
244
+ )
241
245
  expect(events.user.created).toBeCalledTimes(0)
242
246
  })
243
247
  })
@@ -249,7 +253,9 @@ describe("/api/global/users", () => {
249
253
 
250
254
  const response = await config.api.users.saveUser(user, 400)
251
255
 
252
- expect(response.body.message).toBe(`Unavailable`)
256
+ expect(response.body.message).toBe(
257
+ `Email already in use: '${user.email}'`
258
+ )
253
259
  expect(events.user.created).toBeCalledTimes(0)
254
260
  })
255
261
 
@@ -1,5 +1,5 @@
1
1
  import Router from "@koa/router"
2
- import { api } from "@budibase/pro"
2
+ import { api as pro } from "@budibase/pro"
3
3
  import userRoutes from "./global/users"
4
4
  import configRoutes from "./global/configs"
5
5
  import workspaceRoutes from "./global/workspaces"
@@ -17,9 +17,6 @@ import migrationRoutes from "./system/migrations"
17
17
  import accountRoutes from "./system/accounts"
18
18
  import restoreRoutes from "./system/restore"
19
19
 
20
- let userGroupRoutes = api.groups
21
- let auditLogRoutes = api.auditLogs
22
-
23
20
  export const routes: Router[] = [
24
21
  configRoutes,
25
22
  userRoutes,
@@ -33,10 +30,11 @@ export const routes: Router[] = [
33
30
  statusRoutes,
34
31
  selfRoutes,
35
32
  licenseRoutes,
36
- userGroupRoutes,
37
- auditLogRoutes,
33
+ pro.groups,
34
+ pro.auditLogs,
38
35
  migrationRoutes,
39
36
  accountRoutes,
40
37
  restoreRoutes,
41
38
  eventRoutes,
39
+ pro.scim,
42
40
  ]
@@ -47,7 +47,6 @@ const environment = {
47
47
  // flags
48
48
  NODE_ENV: process.env.NODE_ENV,
49
49
  SELF_HOSTED: !!parseInt(process.env.SELF_HOSTED || ""),
50
- LOG_LEVEL: process.env.LOG_LEVEL,
51
50
  MULTI_TENANCY: process.env.MULTI_TENANCY,
52
51
  DISABLE_ACCOUNT_PORTAL: process.env.DISABLE_ACCOUNT_PORTAL,
53
52
  SMTP_FALLBACK_ENABLED: process.env.SMTP_FALLBACK_ENABLED,
package/src/index.ts CHANGED
@@ -2,10 +2,6 @@ if (process.env.DD_APM_ENABLED) {
2
2
  require("./ddApm")
3
3
  }
4
4
 
5
- if (process.env.ELASTIC_APM_ENABLED) {
6
- require("./elasticApm")
7
- }
8
-
9
5
  // need to load environment first
10
6
  import env from "./environment"
11
7
  import { Scope } from "@sentry/node"
@@ -31,10 +27,11 @@ import api from "./api"
31
27
  import * as redis from "./utilities/redis"
32
28
  const Sentry = require("@sentry/node")
33
29
  const koaSession = require("koa-session")
34
- const logger = require("koa-pino-logger")
35
30
  const { userAgent } = require("koa-useragent")
36
31
 
37
32
  import destroyable from "server-destroy"
33
+ import { initPro } from "./initPro"
34
+ import { handleScimBody } from "./middleware/handleScimBody"
38
35
 
39
36
  // configure events to use the pro audit log write
40
37
  // can't integrate directly into backend-core due to cyclic issues
@@ -54,10 +51,12 @@ const app: Application = new Koa()
54
51
  app.keys = ["secret", "key"]
55
52
 
56
53
  // set up top level koa middleware
54
+ app.use(handleScimBody)
57
55
  app.use(koaBody({ multipart: true }))
56
+
58
57
  app.use(koaSession(app))
59
- app.use(middleware.logging)
60
- app.use(logger(logging.pinoSettings()))
58
+ app.use(middleware.correlation)
59
+ app.use(middleware.pino)
61
60
  app.use(userAgent)
62
61
 
63
62
  // authentication
@@ -108,6 +107,7 @@ const shutdown = () => {
108
107
 
109
108
  export default server.listen(parseInt(env.PORT || "4002"), async () => {
110
109
  console.log(`Worker running on ${JSON.stringify(server.address())}`)
110
+ await initPro()
111
111
  await redis.init()
112
112
  })
113
113
 
package/src/initPro.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { sdk as proSdk } from "@budibase/pro"
2
+ import * as userSdk from "./sdk/users"
3
+
4
+ export const initPro = async () => {
5
+ await proSdk.init({
6
+ scimUserServiceConfig: {
7
+ functions: {
8
+ saveUser: userSdk.save,
9
+ removeUser: (id: string) => userSdk.destroy(id),
10
+ },
11
+ },
12
+ })
13
+ }
@@ -0,0 +1,12 @@
1
+ import { Ctx } from "@budibase/types"
2
+
3
+ export const handleScimBody = (ctx: Ctx, next: any) => {
4
+ var type = ctx.req.headers["content-type"] || ""
5
+ type = type.split(";")[0]
6
+
7
+ if (type === "application/scim+json") {
8
+ ctx.req.headers["content-type"] = "application/json"
9
+ }
10
+
11
+ return next()
12
+ }
@@ -1,5 +1,4 @@
1
1
  import env from "../../environment"
2
- import * as apps from "../../utilities/appService"
3
2
  import * as eventHelpers from "./events"
4
3
  import {
5
4
  accounts,
@@ -15,6 +14,8 @@ import {
15
14
  utils,
16
15
  ViewName,
17
16
  env as coreEnv,
17
+ context,
18
+ EmailUnavailableError,
18
19
  } from "@budibase/backend-core"
19
20
  import {
20
21
  AccountMetadata,
@@ -28,7 +29,6 @@ import {
28
29
  PlatformUser,
29
30
  PlatformUserByEmail,
30
31
  RowResponse,
31
- SearchUsersRequest,
32
32
  User,
33
33
  SaveUserOpts,
34
34
  Account,
@@ -38,8 +38,6 @@ import { EmailTemplatePurpose } from "../../constants"
38
38
  import * as pro from "@budibase/pro"
39
39
  import * as accountSdk from "../accounts"
40
40
 
41
- const PAGE_LIMIT = 8
42
-
43
41
  export const allUsers = async () => {
44
42
  const db = tenancy.getGlobalDB()
45
43
  const response = await db.allDocs(
@@ -69,43 +67,6 @@ export const getUsersByAppAccess = async (appId?: string) => {
69
67
  return response
70
68
  }
71
69
 
72
- export const paginatedUsers = async ({
73
- page,
74
- email,
75
- appId,
76
- }: SearchUsersRequest = {}) => {
77
- const db = tenancy.getGlobalDB()
78
- // get one extra document, to have the next page
79
- const opts: any = {
80
- include_docs: true,
81
- limit: PAGE_LIMIT + 1,
82
- }
83
- // add a startkey if the page was specified (anchor)
84
- if (page) {
85
- opts.startkey = page
86
- }
87
- // property specifies what to use for the page/anchor
88
- let userList,
89
- property = "_id",
90
- getKey
91
- if (appId) {
92
- userList = await usersCore.searchGlobalUsersByApp(appId, opts)
93
- getKey = (doc: any) => usersCore.getGlobalUserByAppPage(appId, doc)
94
- } else if (email) {
95
- userList = await usersCore.searchGlobalUsersByEmail(email, opts)
96
- property = "email"
97
- } else {
98
- // no search, query allDocs
99
- const response = await db.allDocs(dbUtils.getGlobalUserParams(null, opts))
100
- userList = response.rows.map((row: any) => row.doc)
101
- }
102
- return dbUtils.pagination(userList, PAGE_LIMIT, {
103
- paginate: true,
104
- property,
105
- getKey,
106
- })
107
- }
108
-
109
70
  export async function getUserByEmail(email: string) {
110
71
  return usersCore.getGlobalUserByEmail(email)
111
72
  }
@@ -198,7 +159,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
198
159
  if (env.MULTI_TENANCY) {
199
160
  const tenantUser = await getPlatformUser(email)
200
161
  if (tenantUser != null && tenantUser.tenantId !== tenantId) {
201
- throw `Unavailable`
162
+ throw new EmailUnavailableError(email)
202
163
  }
203
164
  }
204
165
 
@@ -206,7 +167,7 @@ const validateUniqueUser = async (email: string, tenantId: string) => {
206
167
  if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
207
168
  const account = await accounts.getAccount(email)
208
169
  if (account && account.verified && account.tenantId !== tenantId) {
209
- throw `Unavailable`
170
+ throw new EmailUnavailableError(email)
210
171
  }
211
172
  }
212
173
  }
@@ -277,7 +238,7 @@ export const save = async (
277
238
  // no id was specified - load from email instead
278
239
  dbUser = await usersCore.getGlobalUserByEmail(email)
279
240
  if (dbUser && dbUser._id !== _id) {
280
- throw `Unavailable`
241
+ throw new EmailUnavailableError(email)
281
242
  }
282
243
  }
283
244
 
@@ -317,9 +278,6 @@ export const save = async (
317
278
  await platform.users.addUser(tenantId, builtUser._id!, builtUser.email)
318
279
  await cache.user.invalidateUser(response.id)
319
280
 
320
- // let server know to sync user
321
- await apps.syncUserInApps(_id, dbUser)
322
-
323
281
  await Promise.all(groupPromises)
324
282
 
325
283
  // finally returned the saved user from the db
@@ -469,7 +427,6 @@ export const bulkCreate = async (
469
427
  // instead of relying on looping tenant creation
470
428
  await platform.users.addUser(tenantId, user._id, user.email)
471
429
  await eventHelpers.handleSaveEvents(user, undefined)
472
- await apps.syncUserInApps(user._id)
473
430
  }
474
431
 
475
432
  const saved = usersToBulkSave.map(user => {
@@ -583,7 +540,7 @@ export const bulkDelete = async (
583
540
  return response
584
541
  }
585
542
 
586
- export const destroy = async (id: string, currentUser: any) => {
543
+ export const destroy = async (id: string) => {
587
544
  const db = tenancy.getGlobalDB()
588
545
  const dbUser = (await db.get(id)) as User
589
546
  const userId = dbUser._id as string
@@ -593,7 +550,7 @@ export const destroy = async (id: string, currentUser: any) => {
593
550
  const email = dbUser.email
594
551
  const account = await accounts.getAccount(email)
595
552
  if (account) {
596
- if (email === currentUser.email) {
553
+ if (dbUser.userId === context.getIdentity()!._id) {
597
554
  throw new HTTPError('Please visit "Account" to delete this user', 400)
598
555
  } else {
599
556
  throw new HTTPError("Account holder cannot be deleted", 400)
@@ -608,8 +565,6 @@ export const destroy = async (id: string, currentUser: any) => {
608
565
  await eventHelpers.handleDeleteEvents(dbUser)
609
566
  await cache.user.invalidateUser(userId)
610
567
  await sessions.invalidateSessions(userId, { reason: "deletion" })
611
- // let server know to sync user
612
- await apps.syncUserInApps(userId, dbUser)
613
568
  }
614
569
 
615
570
  const bulkDeleteProcessing = async (dbUser: User) => {
@@ -618,8 +573,6 @@ const bulkDeleteProcessing = async (dbUser: User) => {
618
573
  await eventHelpers.handleDeleteEvents(dbUser)
619
574
  await cache.user.invalidateUser(userId)
620
575
  await sessions.invalidateSessions(userId, { reason: "bulk-deletion" })
621
- // let server know to sync user
622
- await apps.syncUserInApps(userId, dbUser)
623
576
  }
624
577
 
625
578
  export const invite = async (
@@ -20,9 +20,18 @@ import {
20
20
  auth,
21
21
  constants,
22
22
  env as coreEnv,
23
+ db as dbCore,
24
+ encryption,
25
+ utils,
23
26
  } from "@budibase/backend-core"
24
27
  import structures, { CSRF_TOKEN } from "./structures"
25
- import { SaveUserResponse, User, AuthToken } from "@budibase/types"
28
+ import {
29
+ SaveUserResponse,
30
+ User,
31
+ AuthToken,
32
+ SCIMConfig,
33
+ ConfigType,
34
+ } from "@budibase/types"
26
35
  import API from "./api"
27
36
 
28
37
  class TestConfiguration {
@@ -31,6 +40,7 @@ class TestConfiguration {
31
40
  api: API
32
41
  tenantId: string
33
42
  user?: User
43
+ apiKey?: string
34
44
  userPassword = "test"
35
45
 
36
46
  constructor(opts: { openServer: boolean } = { openServer: true }) {
@@ -49,6 +59,12 @@ class TestConfiguration {
49
59
  this.api = new API(this)
50
60
  }
51
61
 
62
+ async useNewTenant() {
63
+ this.tenantId = structures.tenant.id()
64
+
65
+ await this.beforeAll()
66
+ }
67
+
52
68
  getRequest() {
53
69
  return this.request
54
70
  }
@@ -201,6 +217,12 @@ class TestConfiguration {
201
217
  return { [constants.Header.API_KEY]: coreEnv.INTERNAL_API_KEY }
202
218
  }
203
219
 
220
+ bearerAPIHeaders() {
221
+ return {
222
+ [constants.Header.AUTHORIZATION]: `Bearer ${this.apiKey}`,
223
+ }
224
+ }
225
+
204
226
  adminOnlyResponse = () => {
205
227
  return { message: "Admin user only endpoint.", status: 403 }
206
228
  }
@@ -213,6 +235,20 @@ class TestConfiguration {
213
235
  })
214
236
  await context.doInTenant(this.tenantId!, async () => {
215
237
  this.user = await this.createUser(user)
238
+
239
+ const db = context.getGlobalDB()
240
+
241
+ const id = dbCore.generateDevInfoID(this.user._id)
242
+ // TODO: dry
243
+ this.apiKey = encryption.encrypt(
244
+ `${this.tenantId}${dbCore.SEPARATOR}${utils.newid()}`
245
+ )
246
+ const devInfo = {
247
+ _id: id,
248
+ userId: this.user._id,
249
+ apiKey: this.apiKey,
250
+ }
251
+ await db.put(devInfo)
216
252
  })
217
253
  }
218
254
 
@@ -305,6 +341,19 @@ class TestConfiguration {
305
341
  controllers.config.save
306
342
  )
307
343
  }
344
+
345
+ // CONFIGS - SCIM
346
+
347
+ async setSCIMConfig(enabled: boolean) {
348
+ await this.deleteConfig(Config.SCIM)
349
+ const config: SCIMConfig = {
350
+ type: ConfigType.SCIM,
351
+ config: { enabled },
352
+ }
353
+
354
+ await this._req(config, null, controllers.config.save)
355
+ return config
356
+ }
308
357
  }
309
358
 
310
359
  export default TestConfiguration
@@ -15,6 +15,9 @@ import { RolesAPI } from "./roles"
15
15
  import { TemplatesAPI } from "./templates"
16
16
  import { LicenseAPI } from "./license"
17
17
  import { AuditLogAPI } from "./auditLogs"
18
+ import { ScimUsersAPI } from "./scim/users"
19
+ import { ScimGroupsAPI } from "./scim/groups"
20
+
18
21
  export default class API {
19
22
  accounts: AccountAPI
20
23
  auth: AuthAPI
@@ -32,6 +35,8 @@ export default class API {
32
35
  templates: TemplatesAPI
33
36
  license: LicenseAPI
34
37
  auditLogs: AuditLogAPI
38
+ scimUsersAPI: ScimUsersAPI
39
+ scimGroupsAPI: ScimGroupsAPI
35
40
 
36
41
  constructor(config: TestConfiguration) {
37
42
  this.accounts = new AccountAPI(config)
@@ -50,5 +55,7 @@ export default class API {
50
55
  this.templates = new TemplatesAPI(config)
51
56
  this.license = new LicenseAPI(config)
52
57
  this.auditLogs = new AuditLogAPI(config)
58
+ this.scimUsersAPI = new ScimUsersAPI(config)
59
+ this.scimGroupsAPI = new ScimGroupsAPI(config)
53
60
  }
54
61
  }
@@ -0,0 +1,101 @@
1
+ import {
2
+ ScimCreateGroupRequest,
3
+ ScimGroupListResponse,
4
+ ScimGroupResponse,
5
+ ScimUpdateRequest,
6
+ } from "@budibase/types"
7
+ import TestConfiguration from "../../TestConfiguration"
8
+ import { RequestSettings, ScimTestAPI } from "./shared"
9
+
10
+ export class ScimGroupsAPI extends ScimTestAPI {
11
+ constructor(config: TestConfiguration) {
12
+ super(config)
13
+ }
14
+
15
+ get = async (
16
+ requestSettings?: Partial<RequestSettings> & {
17
+ params?: {
18
+ startIndex?: number
19
+ pageSize?: number
20
+ filter?: string
21
+ excludedAttributes?: string
22
+ }
23
+ }
24
+ ) => {
25
+ let url = `/api/global/scim/v2/groups?`
26
+ const params = requestSettings?.params
27
+ if (params?.pageSize) {
28
+ url += `count=${params.pageSize}&`
29
+ }
30
+ if (params?.startIndex) {
31
+ url += `startIndex=${params.startIndex}&`
32
+ }
33
+ if (params?.filter) {
34
+ url += `filter=${params.filter}&`
35
+ }
36
+ if (params?.excludedAttributes) {
37
+ url += `excludedAttributes=${params.excludedAttributes}&`
38
+ }
39
+ const res = await this.call(url, "get", requestSettings)
40
+ return res.body as ScimGroupListResponse
41
+ }
42
+
43
+ post = async (
44
+ {
45
+ body,
46
+ }: {
47
+ body: ScimCreateGroupRequest
48
+ },
49
+ requestSettings?: Partial<RequestSettings>
50
+ ) => {
51
+ const res = await this.call(
52
+ `/api/global/scim/v2/groups`,
53
+ "post",
54
+ requestSettings,
55
+ body
56
+ )
57
+
58
+ return res.body as ScimGroupResponse
59
+ }
60
+
61
+ find = async (
62
+ id: string,
63
+ requestSettings?: Partial<RequestSettings> & { qs?: string }
64
+ ) => {
65
+ const res = await this.call(
66
+ `/api/global/scim/v2/groups/${id}?${requestSettings?.qs}`,
67
+ "get",
68
+ requestSettings
69
+ )
70
+ return res.body as ScimGroupResponse
71
+ }
72
+
73
+ delete = async (id: string, requestSettings?: Partial<RequestSettings>) => {
74
+ const res = await this.call(
75
+ `/api/global/scim/v2/groups/${id}`,
76
+ "delete",
77
+ requestSettings
78
+ )
79
+ return res.body as ScimGroupResponse
80
+ }
81
+
82
+ patch = async (
83
+ {
84
+ id,
85
+ body,
86
+ }: {
87
+ id: string
88
+ body: ScimUpdateRequest
89
+ },
90
+ requestSettings?: Partial<RequestSettings>
91
+ ) => {
92
+ const res = await this.call(
93
+ `/api/global/scim/v2/groups/${id}`,
94
+ "patch",
95
+ requestSettings,
96
+ body
97
+ )
98
+
99
+ return res.body as ScimGroupResponse
100
+ }
101
+ }
@@ -0,0 +1,44 @@
1
+ import TestConfiguration from "../../TestConfiguration"
2
+ import { TestAPI } from "../base"
3
+
4
+ const defaultConfig = {
5
+ expect: 200,
6
+ setHeaders: true,
7
+ skipContentTypeCheck: false,
8
+ }
9
+
10
+ export type RequestSettings = typeof defaultConfig
11
+
12
+ export abstract class ScimTestAPI extends TestAPI {
13
+ constructor(config: TestConfiguration) {
14
+ super(config)
15
+ }
16
+
17
+ call = (
18
+ url: string,
19
+ method: "get" | "post" | "patch" | "delete",
20
+ requestSettings?: Partial<RequestSettings>,
21
+ body?: object
22
+ ) => {
23
+ const { expect, setHeaders } = { ...defaultConfig, ...requestSettings }
24
+ let request = this.request[method](url).expect(expect)
25
+
26
+ request = request.set(
27
+ "content-type",
28
+ "application/scim+json; charset=utf-8"
29
+ )
30
+
31
+ if (method !== "delete" && !requestSettings?.skipContentTypeCheck) {
32
+ request = request.expect("Content-Type", /json/)
33
+ }
34
+
35
+ if (body) {
36
+ request = request.send(body)
37
+ }
38
+
39
+ if (setHeaders) {
40
+ request = request.set(this.config.bearerAPIHeaders())
41
+ }
42
+ return request
43
+ }
44
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ ScimUserListResponse,
3
+ ScimCreateUserRequest,
4
+ ScimUserResponse,
5
+ ScimUpdateRequest,
6
+ } from "@budibase/types"
7
+ import TestConfiguration from "../../TestConfiguration"
8
+ import { RequestSettings, ScimTestAPI } from "./shared"
9
+
10
+ export class ScimUsersAPI extends ScimTestAPI {
11
+ constructor(config: TestConfiguration) {
12
+ super(config)
13
+ }
14
+
15
+ get = async (
16
+ requestSettings?: Partial<RequestSettings> & {
17
+ params?: {
18
+ startIndex?: number
19
+ pageSize?: number
20
+ filter?: string
21
+ }
22
+ }
23
+ ) => {
24
+ let url = `/api/global/scim/v2/users?`
25
+ const params = requestSettings?.params
26
+ if (params?.pageSize) {
27
+ url += `count=${params.pageSize}&`
28
+ }
29
+ if (params?.startIndex) {
30
+ url += `startIndex=${params.startIndex}&`
31
+ }
32
+ if (params?.filter) {
33
+ url += `filter=${params.filter}&`
34
+ }
35
+ const res = await this.call(url, "get", requestSettings)
36
+ return res.body as ScimUserListResponse
37
+ }
38
+
39
+ find = async (id: string, requestSettings?: Partial<RequestSettings>) => {
40
+ const res = await this.call(
41
+ `/api/global/scim/v2/users/${id}`,
42
+ "get",
43
+ requestSettings
44
+ )
45
+ return res.body as ScimUserResponse
46
+ }
47
+
48
+ post = async (
49
+ {
50
+ body,
51
+ }: {
52
+ body: ScimCreateUserRequest
53
+ },
54
+ requestSettings?: Partial<RequestSettings>
55
+ ) => {
56
+ const res = await this.call(
57
+ `/api/global/scim/v2/users`,
58
+ "post",
59
+ requestSettings,
60
+ body
61
+ )
62
+
63
+ return res.body as ScimUserResponse
64
+ }
65
+
66
+ patch = async (
67
+ {
68
+ id,
69
+ body,
70
+ }: {
71
+ id: string
72
+ body: ScimUpdateRequest
73
+ },
74
+ requestSettings?: Partial<RequestSettings>
75
+ ) => {
76
+ const res = await this.call(
77
+ `/api/global/scim/v2/users/${id}`,
78
+ "patch",
79
+ requestSettings,
80
+ body
81
+ )
82
+
83
+ return res.body as ScimUserResponse
84
+ }
85
+
86
+ delete = async (id: string, requestSettings?: Partial<RequestSettings>) => {
87
+ const res = await this.call(
88
+ `/api/global/scim/v2/users/${id}`,
89
+ "delete",
90
+ requestSettings
91
+ )
92
+ return res.body as ScimUserResponse
93
+ }
94
+ }
@@ -2,7 +2,6 @@ process.env.SELF_HOSTED = "0"
2
2
  process.env.NODE_ENV = "jest"
3
3
  process.env.JWT_SECRET = "test-jwtsecret"
4
4
  process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
5
- process.env.ENABLE_4XX_HTTP_LOGGING = "0"
6
5
  process.env.MULTI_TENANCY = "1"
7
6
  process.env.MINIO_URL = "http://localhost"
8
7
  process.env.MINIO_ACCESS_KEY = "test"
@@ -1,5 +1,3 @@
1
- import "./logging"
2
-
3
1
  import { mocks, testContainerUtils } from "@budibase/backend-core/tests"
4
2
  import env from "../environment"
5
3
  import { env as coreEnv, timers } from "@budibase/backend-core"