@budibase/worker 2.3.18-alpha.3 → 2.3.18-alpha.30

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.
@@ -1,4 +1,9 @@
1
- import { checkInviteCode } from "../../../utilities/redis"
1
+ import {
2
+ checkInviteCode,
3
+ getInviteCodes,
4
+ updateInviteCode,
5
+ } from "../../../utilities/redis"
6
+ // import sdk from "../../../sdk"
2
7
  import * as userSdk from "../../../sdk/users"
3
8
  import env from "../../../environment"
4
9
  import {
@@ -28,6 +33,7 @@ import {
28
33
  platform,
29
34
  } from "@budibase/backend-core"
30
35
  import { checkAnyUserExists } from "../../../utilities/users"
36
+ import { isEmailConfigured } from "../../../utilities/email"
31
37
 
32
38
  const MAX_USERS_UPLOAD_LIMIT = 1000
33
39
 
@@ -179,16 +185,28 @@ export const destroy = async (ctx: any) => {
179
185
  }
180
186
  }
181
187
 
188
+ export const getAppUsers = async (ctx: any) => {
189
+ const body = ctx.request.body as SearchUsersRequest
190
+ const users = await userSdk.getUsersByAppAccess(body?.appId)
191
+
192
+ ctx.body = { data: users }
193
+ }
194
+
182
195
  export const search = async (ctx: any) => {
183
196
  const body = ctx.request.body as SearchUsersRequest
184
- const paginated = await userSdk.paginatedUsers(body)
185
- // user hashed password shouldn't ever be returned
186
- for (let user of paginated.data) {
187
- if (user) {
188
- delete user.password
197
+
198
+ if (body.paginated === false) {
199
+ await getAppUsers(ctx)
200
+ } else {
201
+ const paginated = await userSdk.paginatedUsers(body)
202
+ // user hashed password shouldn't ever be returned
203
+ for (let user of paginated.data) {
204
+ if (user) {
205
+ delete user.password
206
+ }
189
207
  }
208
+ ctx.body = paginated
190
209
  }
191
- ctx.body = paginated
192
210
  }
193
211
 
194
212
  // called internally by app server user fetch
@@ -218,9 +236,71 @@ export const tenantUserLookup = async (ctx: any) => {
218
236
  }
219
237
  }
220
238
 
239
+ /*
240
+ Encapsulate the app user onboarding flows here.
241
+ */
242
+ export const onboardUsers = async (ctx: any) => {
243
+ const request = ctx.request.body as InviteUsersRequest | BulkUserRequest
244
+ const isBulkCreate = "create" in request
245
+
246
+ const emailConfigured = await isEmailConfigured()
247
+
248
+ let onboardingResponse
249
+
250
+ if (isBulkCreate) {
251
+ // @ts-ignore
252
+ const { users, groups, roles } = request.create
253
+ const assignUsers = users.map((user: User) => (user.roles = roles))
254
+ onboardingResponse = await userSdk.bulkCreate(assignUsers, groups)
255
+ ctx.body = onboardingResponse
256
+ } else if (emailConfigured) {
257
+ onboardingResponse = await inviteMultiple(ctx)
258
+ } else if (!emailConfigured) {
259
+ const inviteRequest = ctx.request.body as InviteUsersRequest
260
+
261
+ let createdPasswords: any = {}
262
+
263
+ const users: User[] = inviteRequest.map(invite => {
264
+ let password = Math.random().toString(36).substring(2, 22)
265
+
266
+ // Temp password to be passed to the user.
267
+ createdPasswords[invite.email] = password
268
+
269
+ return {
270
+ email: invite.email,
271
+ password,
272
+ forceResetPassword: true,
273
+ roles: invite.userInfo.apps,
274
+ admin: { global: false },
275
+ builder: { global: false },
276
+ tenantId: tenancy.getTenantId(),
277
+ }
278
+ })
279
+ let bulkCreateReponse = await userSdk.bulkCreate(users, [])
280
+
281
+ // Apply temporary credentials
282
+ let createWithCredentials = {
283
+ ...bulkCreateReponse,
284
+ successful: bulkCreateReponse?.successful.map(user => {
285
+ return {
286
+ ...user,
287
+ password: createdPasswords[user.email],
288
+ }
289
+ }),
290
+ created: true,
291
+ }
292
+
293
+ ctx.body = createWithCredentials
294
+ } else {
295
+ ctx.throw(400, "User onboarding failed")
296
+ }
297
+ }
298
+
221
299
  export const invite = async (ctx: any) => {
222
300
  const request = ctx.request.body as InviteUserRequest
223
- const response = await userSdk.invite([request])
301
+
302
+ let multiRequest = [request] as InviteUsersRequest
303
+ const response = await userSdk.invite(multiRequest)
224
304
 
225
305
  // explicitly throw for single user invite
226
306
  if (response.unsuccessful.length) {
@@ -234,6 +314,8 @@ export const invite = async (ctx: any) => {
234
314
 
235
315
  ctx.body = {
236
316
  message: "Invitation has been sent.",
317
+ successful: response.successful,
318
+ unsuccessful: response.unsuccessful,
237
319
  }
238
320
  }
239
321
 
@@ -255,6 +337,53 @@ export const checkInvite = async (ctx: any) => {
255
337
  }
256
338
  }
257
339
 
340
+ export const getUserInvites = async (ctx: any) => {
341
+ let invites
342
+ try {
343
+ // Restricted to the currently authenticated tenant
344
+ invites = await getInviteCodes([ctx.user.tenantId])
345
+ } catch (e) {
346
+ ctx.throw(400, "There was a problem fetching invites")
347
+ }
348
+ ctx.body = invites
349
+ }
350
+
351
+ export const updateInvite = async (ctx: any) => {
352
+ const { code } = ctx.params
353
+ let updateBody = { ...ctx.request.body }
354
+
355
+ delete updateBody.email
356
+
357
+ let invite
358
+ try {
359
+ invite = await checkInviteCode(code, false)
360
+ if (!invite) {
361
+ throw new Error("The invite could not be retrieved")
362
+ }
363
+ } catch (e) {
364
+ ctx.throw(400, "There was a problem with the invite")
365
+ }
366
+
367
+ let updated = {
368
+ ...invite,
369
+ }
370
+
371
+ if (!updateBody?.apps || !Object.keys(updateBody?.apps).length) {
372
+ updated.info.apps = []
373
+ } else {
374
+ updated.info = {
375
+ ...invite.info,
376
+ apps: {
377
+ ...invite.info.apps,
378
+ ...updateBody.apps,
379
+ },
380
+ }
381
+ }
382
+
383
+ await updateInviteCode(code, updated)
384
+ ctx.body = { ...invite }
385
+ }
386
+
258
387
  export const inviteAccept = async (
259
388
  ctx: Ctx<AcceptUserInviteRequest, AcceptUserInviteResponse>
260
389
  ) => {
@@ -263,13 +392,23 @@ export const inviteAccept = async (
263
392
  // info is an extension of the user object that was stored by global
264
393
  const { email, info }: any = await checkInviteCode(inviteCode)
265
394
  const user = await tenancy.doInTenant(info.tenantId, async () => {
266
- const saved = await userSdk.save({
395
+ let request = {
267
396
  firstName,
268
397
  lastName,
269
398
  password,
270
399
  email,
400
+ roles: info.apps,
401
+ tenantId: info.tenantId,
402
+ }
403
+
404
+ delete info.apps
405
+
406
+ request = {
407
+ ...request,
271
408
  ...info,
272
- })
409
+ }
410
+
411
+ const saved = await userSdk.save(request)
273
412
  const db = tenancy.getGlobalDB()
274
413
  const user = await db.get(saved._id)
275
414
  await events.user.inviteAccepted(user)
@@ -34,8 +34,8 @@ function settingValidation() {
34
34
  function googleValidation() {
35
35
  // prettier-ignore
36
36
  return Joi.object({
37
- clientID: Joi.when('activated', { is: true, then: Joi.string().required() }),
38
- clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }),
37
+ clientID: Joi.string().required(),
38
+ clientSecret: Joi.string().required(),
39
39
  activated: Joi.boolean().required(),
40
40
  }).unknown(true)
41
41
  }
@@ -45,12 +45,12 @@ function oidcValidation() {
45
45
  return Joi.object({
46
46
  configs: Joi.array().items(
47
47
  Joi.object({
48
- clientID: Joi.when('activated', { is: true, then: Joi.string().required() }),
49
- clientSecret: Joi.when('activated', { is: true, then: Joi.string().required() }),
50
- configUrl: Joi.when('activated', { is: true, then: Joi.string().required() }),
48
+ clientID: Joi.string().required(),
49
+ clientSecret: Joi.string().required(),
50
+ configUrl: Joi.string().required(),
51
51
  logo: Joi.string().allow("", null),
52
52
  name: Joi.string().allow("", null),
53
- uuid: Joi.when('activated', { is: true, then: Joi.string().required() }),
53
+ uuid: Joi.string().required(),
54
54
  activated: Joi.boolean().required(),
55
55
  scopes: Joi.array().optional()
56
56
  })
@@ -104,13 +104,7 @@ router
104
104
  controller.save
105
105
  )
106
106
  .delete("/api/global/configs/:id/:rev", auth.adminOnly, controller.destroy)
107
- .get("/api/global/configs", controller.fetch)
108
107
  .get("/api/global/configs/checklist", controller.configChecklist)
109
- .get(
110
- "/api/global/configs/all/:type",
111
- buildConfigGetValidation(),
112
- controller.fetch
113
- )
114
108
  .get("/api/global/configs/public", controller.publicSettings)
115
109
  .get("/api/global/configs/public/oidc", controller.publicOidc)
116
110
  .get("/api/global/configs/:type", buildConfigGetValidation(), controller.find)
@@ -0,0 +1,111 @@
1
+ import { mocks, structures } from "@budibase/backend-core/tests"
2
+ import { context, events } from "@budibase/backend-core"
3
+ import { Event, IdentityType } from "@budibase/types"
4
+ import { TestConfiguration } from "../../../../tests"
5
+
6
+ mocks.licenses.useAuditLogs()
7
+
8
+ const BASE_IDENTITY = {
9
+ account: undefined,
10
+ type: IdentityType.USER,
11
+ }
12
+ const USER_AUDIT_LOG_COUNT = 3
13
+ const APP_ID = "app_1"
14
+
15
+ describe("/api/global/auditlogs", () => {
16
+ const config = new TestConfiguration()
17
+
18
+ beforeAll(async () => {
19
+ await config.beforeAll()
20
+ })
21
+
22
+ afterAll(async () => {
23
+ await config.afterAll()
24
+ })
25
+
26
+ describe("POST /api/global/auditlogs/search", () => {
27
+ it("should be able to fire some events (create audit logs)", async () => {
28
+ await context.doInTenant(config.tenantId, async () => {
29
+ const userId = config.user!._id!
30
+ const identity = {
31
+ ...BASE_IDENTITY,
32
+ _id: userId,
33
+ tenantId: config.tenantId,
34
+ }
35
+ await context.doInIdentityContext(identity, async () => {
36
+ for (let i = 0; i < USER_AUDIT_LOG_COUNT; i++) {
37
+ await events.user.created(structures.users.user())
38
+ }
39
+ await context.doInAppContext(APP_ID, async () => {
40
+ await events.app.created(structures.apps.app(APP_ID))
41
+ })
42
+ // fetch the user created events
43
+ const response = await config.api.auditLogs.search({
44
+ events: [Event.USER_CREATED],
45
+ })
46
+ expect(response.data).toBeDefined()
47
+ // there will be an initial event which comes from the default user creation
48
+ expect(response.data.length).toBe(USER_AUDIT_LOG_COUNT + 1)
49
+ })
50
+ })
51
+ })
52
+
53
+ it("should be able to search by event", async () => {
54
+ const response = await config.api.auditLogs.search({
55
+ events: [Event.USER_CREATED],
56
+ })
57
+ expect(response.data.length).toBeGreaterThan(0)
58
+ for (let log of response.data) {
59
+ expect(log.event).toBe(Event.USER_CREATED)
60
+ }
61
+ })
62
+
63
+ it("should be able to search by time range (frozen)", async () => {
64
+ // this is frozen, only need to add 1 and minus 1
65
+ const now = new Date()
66
+ const start = new Date()
67
+ start.setSeconds(now.getSeconds() - 1)
68
+ const end = new Date()
69
+ end.setSeconds(now.getSeconds() + 1)
70
+ const response = await config.api.auditLogs.search({
71
+ startDate: start.toISOString(),
72
+ endDate: end.toISOString(),
73
+ })
74
+ expect(response.data.length).toBeGreaterThan(0)
75
+ for (let log of response.data) {
76
+ expect(log.timestamp).toBe(now.toISOString())
77
+ }
78
+ })
79
+
80
+ it("should be able to search by user ID", async () => {
81
+ const userId = config.user!._id!
82
+ const response = await config.api.auditLogs.search({
83
+ userIds: [userId],
84
+ })
85
+ expect(response.data.length).toBeGreaterThan(0)
86
+ for (let log of response.data) {
87
+ expect(log.user._id).toBe(userId)
88
+ }
89
+ })
90
+
91
+ it("should be able to search by app ID", async () => {
92
+ const response = await config.api.auditLogs.search({
93
+ appIds: [APP_ID],
94
+ })
95
+ expect(response.data.length).toBeGreaterThan(0)
96
+ for (let log of response.data) {
97
+ expect(log.app?._id).toBe(APP_ID)
98
+ }
99
+ })
100
+
101
+ it("should be able to search by full string", async () => {
102
+ const response = await config.api.auditLogs.search({
103
+ fullSearch: "User",
104
+ })
105
+ expect(response.data.length).toBeGreaterThan(0)
106
+ for (let log of response.data) {
107
+ expect(log.name.includes("User")).toBe(true)
108
+ }
109
+ })
110
+ })
111
+ })
@@ -106,12 +106,12 @@ describe("/api/global/auth", () => {
106
106
  tenantId,
107
107
  email,
108
108
  password,
109
- { status: 400 }
109
+ { status: 403 }
110
110
  )
111
111
 
112
112
  expect(response.body).toEqual({
113
- message: "SSO user cannot login using password",
114
- status: 400,
113
+ message: "Invalid credentials",
114
+ status: 403,
115
115
  })
116
116
  }
117
117
 
@@ -170,18 +170,8 @@ describe("/api/global/auth", () => {
170
170
  async function testSSOUser() {
171
171
  const { res } = await config.api.auth.requestPasswordReset(
172
172
  sendMailMock,
173
- user.email,
174
- { status: 400 }
173
+ user.email
175
174
  )
176
-
177
- expect(res.body).toEqual({
178
- message: "SSO user cannot reset password",
179
- status: 400,
180
- error: {
181
- code: "http",
182
- type: "generic",
183
- },
184
- })
185
175
  expect(sendMailMock).not.toHaveBeenCalled()
186
176
  }
187
177
 
@@ -367,7 +357,7 @@ describe("/api/global/auth", () => {
367
357
 
368
358
  const res = await config.api.configs.OIDCCallback(configId, preAuthRes)
369
359
 
370
- expect(events.auth.login).toBeCalledWith("oidc")
360
+ expect(events.auth.login).toBeCalledWith("oidc", "oauth@example.com")
371
361
  expect(events.auth.login).toBeCalledTimes(1)
372
362
  expect(res.status).toBe(302)
373
363
  const location: string = res.get("location")
@@ -2,7 +2,8 @@
2
2
  jest.mock("nodemailer")
3
3
  import { TestConfiguration, structures, mocks } from "../../../../tests"
4
4
  mocks.email.mock()
5
- import { Config, events } from "@budibase/backend-core"
5
+ import { events } from "@budibase/backend-core"
6
+ import { GetPublicSettingsResponse, Config, ConfigType } from "@budibase/types"
6
7
 
7
8
  describe("configs", () => {
8
9
  const config = new TestConfiguration()
@@ -19,22 +20,29 @@ describe("configs", () => {
19
20
  await config.afterAll()
20
21
  })
21
22
 
22
- describe("post /api/global/configs", () => {
23
- const saveConfig = async (conf: any, _id?: string, _rev?: string) => {
24
- const data = {
25
- ...conf,
26
- _id,
27
- _rev,
28
- }
29
-
30
- const res = await config.api.configs.saveConfig(data)
31
-
32
- return {
33
- ...data,
34
- ...res.body,
35
- }
23
+ const saveConfig = async (conf: Config, _id?: string, _rev?: string) => {
24
+ const data = {
25
+ ...conf,
26
+ _id,
27
+ _rev,
36
28
  }
37
-
29
+ const res = await config.api.configs.saveConfig(data)
30
+ return {
31
+ ...data,
32
+ ...res.body,
33
+ }
34
+ }
35
+
36
+ const saveSettingsConfig = async (
37
+ conf?: any,
38
+ _id?: string,
39
+ _rev?: string
40
+ ) => {
41
+ const settingsConfig = structures.configs.settings(conf)
42
+ return saveConfig(settingsConfig, _id, _rev)
43
+ }
44
+
45
+ describe("POST /api/global/configs", () => {
38
46
  describe("google", () => {
39
47
  const saveGoogleConfig = async (
40
48
  conf?: any,
@@ -49,20 +57,20 @@ describe("configs", () => {
49
57
  it("should create activated google config", async () => {
50
58
  await saveGoogleConfig()
51
59
  expect(events.auth.SSOCreated).toBeCalledTimes(1)
52
- expect(events.auth.SSOCreated).toBeCalledWith(Config.GOOGLE)
60
+ expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.GOOGLE)
53
61
  expect(events.auth.SSODeactivated).not.toBeCalled()
54
62
  expect(events.auth.SSOActivated).toBeCalledTimes(1)
55
- expect(events.auth.SSOActivated).toBeCalledWith(Config.GOOGLE)
56
- await config.deleteConfig(Config.GOOGLE)
63
+ expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.GOOGLE)
64
+ await config.deleteConfig(ConfigType.GOOGLE)
57
65
  })
58
66
 
59
67
  it("should create deactivated google config", async () => {
60
68
  await saveGoogleConfig({ activated: false })
61
69
  expect(events.auth.SSOCreated).toBeCalledTimes(1)
62
- expect(events.auth.SSOCreated).toBeCalledWith(Config.GOOGLE)
70
+ expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.GOOGLE)
63
71
  expect(events.auth.SSOActivated).not.toBeCalled()
64
72
  expect(events.auth.SSODeactivated).not.toBeCalled()
65
- await config.deleteConfig(Config.GOOGLE)
73
+ await config.deleteConfig(ConfigType.GOOGLE)
66
74
  })
67
75
  })
68
76
 
@@ -76,11 +84,11 @@ describe("configs", () => {
76
84
  googleConf._rev
77
85
  )
78
86
  expect(events.auth.SSOUpdated).toBeCalledTimes(1)
79
- expect(events.auth.SSOUpdated).toBeCalledWith(Config.GOOGLE)
87
+ expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.GOOGLE)
80
88
  expect(events.auth.SSOActivated).not.toBeCalled()
81
89
  expect(events.auth.SSODeactivated).toBeCalledTimes(1)
82
- expect(events.auth.SSODeactivated).toBeCalledWith(Config.GOOGLE)
83
- await config.deleteConfig(Config.GOOGLE)
90
+ expect(events.auth.SSODeactivated).toBeCalledWith(ConfigType.GOOGLE)
91
+ await config.deleteConfig(ConfigType.GOOGLE)
84
92
  })
85
93
 
86
94
  it("should update google config to activated", async () => {
@@ -92,11 +100,11 @@ describe("configs", () => {
92
100
  googleConf._rev
93
101
  )
94
102
  expect(events.auth.SSOUpdated).toBeCalledTimes(1)
95
- expect(events.auth.SSOUpdated).toBeCalledWith(Config.GOOGLE)
103
+ expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.GOOGLE)
96
104
  expect(events.auth.SSODeactivated).not.toBeCalled()
97
105
  expect(events.auth.SSOActivated).toBeCalledTimes(1)
98
- expect(events.auth.SSOActivated).toBeCalledWith(Config.GOOGLE)
99
- await config.deleteConfig(Config.GOOGLE)
106
+ expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.GOOGLE)
107
+ await config.deleteConfig(ConfigType.GOOGLE)
100
108
  })
101
109
  })
102
110
  })
@@ -115,20 +123,20 @@ describe("configs", () => {
115
123
  it("should create activated OIDC config", async () => {
116
124
  await saveOIDCConfig()
117
125
  expect(events.auth.SSOCreated).toBeCalledTimes(1)
118
- expect(events.auth.SSOCreated).toBeCalledWith(Config.OIDC)
126
+ expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.OIDC)
119
127
  expect(events.auth.SSODeactivated).not.toBeCalled()
120
128
  expect(events.auth.SSOActivated).toBeCalledTimes(1)
121
- expect(events.auth.SSOActivated).toBeCalledWith(Config.OIDC)
122
- await config.deleteConfig(Config.OIDC)
129
+ expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.OIDC)
130
+ await config.deleteConfig(ConfigType.OIDC)
123
131
  })
124
132
 
125
133
  it("should create deactivated OIDC config", async () => {
126
134
  await saveOIDCConfig({ activated: false })
127
135
  expect(events.auth.SSOCreated).toBeCalledTimes(1)
128
- expect(events.auth.SSOCreated).toBeCalledWith(Config.OIDC)
136
+ expect(events.auth.SSOCreated).toBeCalledWith(ConfigType.OIDC)
129
137
  expect(events.auth.SSOActivated).not.toBeCalled()
130
138
  expect(events.auth.SSODeactivated).not.toBeCalled()
131
- await config.deleteConfig(Config.OIDC)
139
+ await config.deleteConfig(ConfigType.OIDC)
132
140
  })
133
141
  })
134
142
 
@@ -142,11 +150,11 @@ describe("configs", () => {
142
150
  oidcConf._rev
143
151
  )
144
152
  expect(events.auth.SSOUpdated).toBeCalledTimes(1)
145
- expect(events.auth.SSOUpdated).toBeCalledWith(Config.OIDC)
153
+ expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.OIDC)
146
154
  expect(events.auth.SSOActivated).not.toBeCalled()
147
155
  expect(events.auth.SSODeactivated).toBeCalledTimes(1)
148
- expect(events.auth.SSODeactivated).toBeCalledWith(Config.OIDC)
149
- await config.deleteConfig(Config.OIDC)
156
+ expect(events.auth.SSODeactivated).toBeCalledWith(ConfigType.OIDC)
157
+ await config.deleteConfig(ConfigType.OIDC)
150
158
  })
151
159
 
152
160
  it("should update OIDC config to activated", async () => {
@@ -158,11 +166,11 @@ describe("configs", () => {
158
166
  oidcConf._rev
159
167
  )
160
168
  expect(events.auth.SSOUpdated).toBeCalledTimes(1)
161
- expect(events.auth.SSOUpdated).toBeCalledWith(Config.OIDC)
169
+ expect(events.auth.SSOUpdated).toBeCalledWith(ConfigType.OIDC)
162
170
  expect(events.auth.SSODeactivated).not.toBeCalled()
163
171
  expect(events.auth.SSOActivated).toBeCalledTimes(1)
164
- expect(events.auth.SSOActivated).toBeCalledWith(Config.OIDC)
165
- await config.deleteConfig(Config.OIDC)
172
+ expect(events.auth.SSOActivated).toBeCalledWith(ConfigType.OIDC)
173
+ await config.deleteConfig(ConfigType.OIDC)
166
174
  })
167
175
  })
168
176
  })
@@ -179,11 +187,11 @@ describe("configs", () => {
179
187
 
180
188
  describe("create", () => {
181
189
  it("should create SMTP config", async () => {
182
- await config.deleteConfig(Config.SMTP)
190
+ await config.deleteConfig(ConfigType.SMTP)
183
191
  await saveSMTPConfig()
184
192
  expect(events.email.SMTPUpdated).not.toBeCalled()
185
193
  expect(events.email.SMTPCreated).toBeCalledTimes(1)
186
- await config.deleteConfig(Config.SMTP)
194
+ await config.deleteConfig(ConfigType.SMTP)
187
195
  })
188
196
  })
189
197
 
@@ -194,24 +202,15 @@ describe("configs", () => {
194
202
  await saveSMTPConfig(smtpConf.config, smtpConf._id, smtpConf._rev)
195
203
  expect(events.email.SMTPCreated).not.toBeCalled()
196
204
  expect(events.email.SMTPUpdated).toBeCalledTimes(1)
197
- await config.deleteConfig(Config.SMTP)
205
+ await config.deleteConfig(ConfigType.SMTP)
198
206
  })
199
207
  })
200
208
  })
201
209
 
202
210
  describe("settings", () => {
203
- const saveSettingsConfig = async (
204
- conf?: any,
205
- _id?: string,
206
- _rev?: string
207
- ) => {
208
- const settingsConfig = structures.configs.settings(conf)
209
- return saveConfig(settingsConfig, _id, _rev)
210
- }
211
-
212
211
  describe("create", () => {
213
212
  it("should create settings config with default settings", async () => {
214
- await config.deleteConfig(Config.SETTINGS)
213
+ await config.deleteConfig(ConfigType.SETTINGS)
215
214
 
216
215
  await saveSettingsConfig()
217
216
 
@@ -222,7 +221,7 @@ describe("configs", () => {
222
221
 
223
222
  it("should create settings config with non-default settings", async () => {
224
223
  config.selfHosted()
225
- await config.deleteConfig(Config.SETTINGS)
224
+ await config.deleteConfig(ConfigType.SETTINGS)
226
225
  const conf = {
227
226
  company: "acme",
228
227
  logoUrl: "http://example.com",
@@ -241,7 +240,7 @@ describe("configs", () => {
241
240
  describe("update", () => {
242
241
  it("should update settings config", async () => {
243
242
  config.selfHosted()
244
- await config.deleteConfig(Config.SETTINGS)
243
+ await config.deleteConfig(ConfigType.SETTINGS)
245
244
  const settingsConfig = await saveSettingsConfig()
246
245
  settingsConfig.config.company = "acme"
247
246
  settingsConfig.config.logoUrl = "http://example.com"
@@ -262,14 +261,43 @@ describe("configs", () => {
262
261
  })
263
262
  })
264
263
 
265
- it("should return the correct checklist status based on the state of the budibase installation", async () => {
266
- await config.saveSmtpConfig()
264
+ describe("GET /api/global/configs/checklist", () => {
265
+ it("should return the correct checklist", async () => {
266
+ await config.saveSmtpConfig()
267
+
268
+ const res = await config.api.configs.getConfigChecklist()
269
+ const checklist = res.body
267
270
 
268
- const res = await config.api.configs.getConfigChecklist()
269
- const checklist = res.body
271
+ expect(checklist.apps.checked).toBeFalsy()
272
+ expect(checklist.smtp.checked).toBeTruthy()
273
+ expect(checklist.adminUser.checked).toBeTruthy()
274
+ })
275
+ })
270
276
 
271
- expect(checklist.apps.checked).toBeFalsy()
272
- expect(checklist.smtp.checked).toBeTruthy()
273
- expect(checklist.adminUser.checked).toBeTruthy()
277
+ describe("GET /api/global/configs/public", () => {
278
+ it("should return the expected public settings", async () => {
279
+ await saveSettingsConfig()
280
+
281
+ const res = await config.api.configs.getPublicSettings()
282
+ const body = res.body as GetPublicSettingsResponse
283
+
284
+ const expected = {
285
+ _id: "config_settings",
286
+ type: "settings",
287
+ config: {
288
+ company: "Budibase",
289
+ logoUrl: "",
290
+ analyticsEnabled: false,
291
+ google: true,
292
+ googleCallbackUrl: `http://localhost:10000/api/global/auth/${config.tenantId}/google/callback`,
293
+ isSSOEnforced: false,
294
+ oidc: false,
295
+ oidcCallbackUrl: `http://localhost:10000/api/global/auth/${config.tenantId}/oidc/callback`,
296
+ platformUrl: "http://localhost:10000",
297
+ },
298
+ }
299
+ delete body._rev
300
+ expect(body).toEqual(expected)
301
+ })
274
302
  })
275
303
  })
@@ -1,3 +1,4 @@
1
+ jest.unmock("node-fetch")
1
2
  import { TestConfiguration } from "../../../../tests"
2
3
  import { EmailTemplatePurpose } from "../../../../constants"
3
4
  const nodemailer = require("nodemailer")