@budibase/worker 1.2.58 → 1.3.1

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.
Files changed (42) hide show
  1. package/package.json +7 -6
  2. package/scripts/jestSetup.js +6 -0
  3. package/src/api/controllers/global/auth.ts +14 -1
  4. package/src/api/controllers/global/users.ts +26 -55
  5. package/src/api/controllers/system/accounts.ts +21 -0
  6. package/src/api/{index.js → index.ts} +12 -16
  7. package/src/api/routes/global/configs.js +1 -0
  8. package/src/api/routes/{tests/auth.spec.js → global/tests/auth.spec.ts} +32 -56
  9. package/src/api/routes/{tests/configs.spec.js → global/tests/configs.spec.ts} +76 -47
  10. package/src/api/routes/{tests/email.spec.js → global/tests/email.spec.ts} +7 -15
  11. package/src/api/routes/{tests/realEmail.spec.js → global/tests/realEmail.spec.ts} +20 -21
  12. package/src/api/routes/{tests/self.spec.js → global/tests/self.spec.ts} +10 -18
  13. package/src/api/routes/global/tests/users.spec.ts +512 -0
  14. package/src/api/routes/index.js +2 -0
  15. package/src/api/routes/system/accounts.ts +19 -0
  16. package/src/api/routes/system/tests/accounts.spec.ts +57 -0
  17. package/src/{environment.js → environment.ts} +10 -9
  18. package/src/index.ts +6 -4
  19. package/src/sdk/accounts/accounts.ts +53 -0
  20. package/src/sdk/accounts/index.ts +1 -0
  21. package/src/sdk/index.ts +1 -0
  22. package/src/sdk/users/users.ts +271 -77
  23. package/src/tests/TestConfiguration.ts +273 -0
  24. package/src/tests/api/accounts.ts +28 -0
  25. package/src/tests/api/auth.ts +48 -0
  26. package/src/tests/api/configs.ts +40 -0
  27. package/src/tests/api/email.ts +24 -0
  28. package/src/tests/api/index.ts +25 -0
  29. package/src/tests/api/self.ts +21 -0
  30. package/src/tests/api/users.ts +111 -0
  31. package/src/tests/index.ts +14 -0
  32. package/src/tests/mocks/index.ts +7 -0
  33. package/src/tests/structures/accounts.ts +24 -0
  34. package/src/tests/structures/index.ts +20 -0
  35. package/src/tests/structures/users.ts +12 -4
  36. package/src/utilities/redis.js +1 -0
  37. package/scripts/load/users.js +0 -97
  38. package/src/api/routes/tests/users.spec.js +0 -390
  39. package/src/tests/TestConfiguration.js +0 -231
  40. package/src/tests/index.js +0 -12
  41. package/src/tests/mocks/index.js +0 -5
  42. package/src/tests/structures/index.js +0 -14
@@ -0,0 +1,273 @@
1
+ import "./mocks"
2
+ import dbConfig from "../db"
3
+ dbConfig.init()
4
+ import env from "../environment"
5
+ import controllers from "./controllers"
6
+ const supertest = require("supertest")
7
+ import { Configs } from "../constants"
8
+ import {
9
+ users,
10
+ tenancy,
11
+ Cookies,
12
+ Headers,
13
+ sessions,
14
+ auth,
15
+ } from "@budibase/backend-core"
16
+ import { TENANT_ID, TENANT_1, CSRF_TOKEN } from "./structures"
17
+ import structures from "./structures"
18
+ import { CreateUserResponse, User, AuthToken } from "@budibase/types"
19
+
20
+ enum Mode {
21
+ ACCOUNT = "account",
22
+ SELF = "self",
23
+ }
24
+
25
+ class TestConfiguration {
26
+ server: any
27
+ request: any
28
+ defaultUser?: User
29
+ tenant1User?: User
30
+
31
+ constructor(
32
+ opts: { openServer: boolean; mode: Mode } = {
33
+ openServer: true,
34
+ mode: Mode.ACCOUNT,
35
+ }
36
+ ) {
37
+ if (opts.mode === Mode.ACCOUNT) {
38
+ this.modeAccount()
39
+ } else if (opts.mode === Mode.SELF) {
40
+ this.modeSelf()
41
+ }
42
+
43
+ if (opts.openServer) {
44
+ env.PORT = "0" // random port
45
+ this.server = require("../index")
46
+ // we need the request for logging in, involves cookies, hard to fake
47
+ this.request = supertest(this.server)
48
+ }
49
+ }
50
+
51
+ getRequest() {
52
+ return this.request
53
+ }
54
+
55
+ // MODES
56
+
57
+ modeAccount = () => {
58
+ env.SELF_HOSTED = false
59
+ // @ts-ignore
60
+ env.MULTI_TENANCY = true
61
+ // @ts-ignore
62
+ env.DISABLE_ACCOUNT_PORTAL = false
63
+ }
64
+
65
+ modeSelf = () => {
66
+ env.SELF_HOSTED = true
67
+ // @ts-ignore
68
+ env.MULTI_TENANCY = false
69
+ // @ts-ignore
70
+ env.DISABLE_ACCOUNT_PORTAL = true
71
+ }
72
+
73
+ // UTILS
74
+
75
+ async _req(config: any, params: any, controlFunc: any) {
76
+ const request: any = {}
77
+ // fake cookies, we don't need them
78
+ request.cookies = { set: () => {}, get: () => {} }
79
+ request.config = { jwtSecret: env.JWT_SECRET }
80
+ request.user = { tenantId: this.getTenantId() }
81
+ request.query = {}
82
+ request.request = {
83
+ body: config,
84
+ }
85
+ request.throw = (status: any, err: any) => {
86
+ throw { status, message: err }
87
+ }
88
+ if (params) {
89
+ request.params = params
90
+ }
91
+ await tenancy.doInTenant(this.getTenantId(), () => {
92
+ return controlFunc(request)
93
+ })
94
+ return request.body
95
+ }
96
+
97
+ // SETUP / TEARDOWN
98
+
99
+ async beforeAll() {
100
+ await this.createDefaultUser()
101
+ await this.createSession(this.defaultUser!)
102
+
103
+ await tenancy.doInTenant(TENANT_1, async () => {
104
+ await this.createTenant1User()
105
+ await this.createSession(this.tenant1User!)
106
+ })
107
+ }
108
+
109
+ async afterAll() {
110
+ if (this.server) {
111
+ await this.server.close()
112
+ }
113
+ }
114
+
115
+ // TENANCY
116
+
117
+ getTenantId() {
118
+ try {
119
+ return tenancy.getTenantId()
120
+ } catch (e: any) {
121
+ return TENANT_ID
122
+ }
123
+ }
124
+
125
+ // USER / AUTH
126
+
127
+ async createDefaultUser() {
128
+ const user = structures.users.adminUser({
129
+ email: "test@test.com",
130
+ password: "test",
131
+ })
132
+ this.defaultUser = await this.createUser(user)
133
+ }
134
+
135
+ async createTenant1User() {
136
+ const user = structures.users.adminUser({
137
+ email: "tenant1@test.com",
138
+ password: "test",
139
+ })
140
+ this.tenant1User = await this.createUser(user)
141
+ }
142
+
143
+ async createSession(user: User) {
144
+ await sessions.createASession(user._id!, {
145
+ sessionId: "sessionid",
146
+ tenantId: user.tenantId,
147
+ csrfToken: CSRF_TOKEN,
148
+ })
149
+ }
150
+
151
+ cookieHeader(cookies: any) {
152
+ return {
153
+ Cookie: [cookies],
154
+ }
155
+ }
156
+
157
+ authHeaders(user: User) {
158
+ const authToken: AuthToken = {
159
+ userId: user._id!,
160
+ sessionId: "sessionid",
161
+ tenantId: user.tenantId,
162
+ }
163
+ const authCookie = auth.jwt.sign(authToken, env.JWT_SECRET)
164
+ return {
165
+ Accept: "application/json",
166
+ ...this.cookieHeader([`${Cookies.Auth}=${authCookie}`]),
167
+ [Headers.CSRF_TOKEN]: CSRF_TOKEN,
168
+ }
169
+ }
170
+
171
+ defaultHeaders() {
172
+ const tenantId = this.getTenantId()
173
+ if (tenantId === TENANT_ID) {
174
+ return this.authHeaders(this.defaultUser!)
175
+ } else if (tenantId === TENANT_1) {
176
+ return this.authHeaders(this.tenant1User!)
177
+ } else {
178
+ throw new Error("could not determine auth headers to use")
179
+ }
180
+ }
181
+
182
+ async getUser(email: string): Promise<User> {
183
+ return tenancy.doInTenant(this.getTenantId(), () => {
184
+ return users.getGlobalUserByEmail(email)
185
+ })
186
+ }
187
+
188
+ async createUser(user?: User) {
189
+ if (!user) {
190
+ user = structures.users.user()
191
+ }
192
+ const response = await this._req(user, null, controllers.users.save)
193
+ const body = response as CreateUserResponse
194
+ return this.getUser(body.email)
195
+ }
196
+
197
+ // CONFIGS
198
+
199
+ async deleteConfig(type: any) {
200
+ try {
201
+ const cfg = await this._req(
202
+ null,
203
+ {
204
+ type,
205
+ },
206
+ controllers.config.find
207
+ )
208
+ if (cfg) {
209
+ await this._req(
210
+ null,
211
+ {
212
+ id: cfg._id,
213
+ rev: cfg._rev,
214
+ },
215
+ controllers.config.destroy
216
+ )
217
+ }
218
+ } catch (err) {
219
+ // don't need to handle error
220
+ }
221
+ }
222
+
223
+ // CONFIGS - SETTINGS
224
+
225
+ async saveSettingsConfig() {
226
+ await this.deleteConfig(Configs.SETTINGS)
227
+ await this._req(
228
+ structures.configs.settings(),
229
+ null,
230
+ controllers.config.save
231
+ )
232
+ }
233
+
234
+ // CONFIGS - GOOGLE
235
+
236
+ async saveGoogleConfig() {
237
+ await this.deleteConfig(Configs.GOOGLE)
238
+ await this._req(structures.configs.google(), null, controllers.config.save)
239
+ }
240
+
241
+ // CONFIGS - OIDC
242
+
243
+ getOIDConfigCookie(configId: string) {
244
+ const token = auth.jwt.sign(configId, env.JWT_SECRET)
245
+ return this.cookieHeader([[`${Cookies.OIDC_CONFIG}=${token}`]])
246
+ }
247
+
248
+ async saveOIDCConfig() {
249
+ await this.deleteConfig(Configs.OIDC)
250
+ const config = structures.configs.oidc()
251
+
252
+ await this._req(config, null, controllers.config.save)
253
+ return config
254
+ }
255
+
256
+ // CONFIGS - SMTP
257
+
258
+ async saveSmtpConfig() {
259
+ await this.deleteConfig(Configs.SMTP)
260
+ await this._req(structures.configs.smtp(), null, controllers.config.save)
261
+ }
262
+
263
+ async saveEtherealSmtpConfig() {
264
+ await this.deleteConfig(Configs.SMTP)
265
+ await this._req(
266
+ structures.configs.smtpEthereal(),
267
+ null,
268
+ controllers.config.save
269
+ )
270
+ }
271
+ }
272
+
273
+ export = TestConfiguration
@@ -0,0 +1,28 @@
1
+ import { Account, AccountMetadata } from "@budibase/types"
2
+ import TestConfiguration from "../TestConfiguration"
3
+
4
+ export class AccountAPI {
5
+ config: TestConfiguration
6
+ request: any
7
+
8
+ constructor(config: TestConfiguration) {
9
+ this.config = config
10
+ this.request = config.request
11
+ }
12
+
13
+ saveMetadata = async (account: Account) => {
14
+ const res = await this.request
15
+ .put(`/api/system/accounts/${account.accountId}/metadata`)
16
+ .send(account)
17
+ .set(this.config.defaultHeaders())
18
+ .expect("Content-Type", /json/)
19
+ .expect(200)
20
+ return res.body as AccountMetadata
21
+ }
22
+
23
+ destroyMetadata = (accountId: string) => {
24
+ return this.request
25
+ .del(`/api/system/accounts/${accountId}/metadata`)
26
+ .set(this.config.defaultHeaders())
27
+ }
28
+ }
@@ -0,0 +1,48 @@
1
+ import TestConfiguration from "../TestConfiguration"
2
+
3
+ export class AuthAPI {
4
+ config: TestConfiguration
5
+ request: any
6
+
7
+ constructor(config: TestConfiguration) {
8
+ this.config = config
9
+ this.request = config.request
10
+ }
11
+
12
+ updatePassword = (code: string) => {
13
+ return this.request
14
+ .post(`/api/global/auth/${this.config.getTenantId()}/reset/update`)
15
+ .send({
16
+ password: "newpassword",
17
+ resetCode: code,
18
+ })
19
+ .expect("Content-Type", /json/)
20
+ .expect(200)
21
+ }
22
+
23
+ logout = () => {
24
+ return this.request
25
+ .post("/api/global/auth/logout")
26
+ .set(this.config.defaultHeaders())
27
+ .expect(200)
28
+ }
29
+
30
+ requestPasswordReset = async (sendMailMock: any) => {
31
+ await this.config.saveSmtpConfig()
32
+ await this.config.saveSettingsConfig()
33
+ await this.config.createUser()
34
+ const res = await this.request
35
+ .post(`/api/global/auth/${this.config.getTenantId()}/reset`)
36
+ .send({
37
+ email: "test@test.com",
38
+ })
39
+ .expect("Content-Type", /json/)
40
+ .expect(200)
41
+ const emailCall = sendMailMock.mock.calls[0][0]
42
+ const parts = emailCall.html.split(
43
+ `http://localhost:10000/builder/auth/reset?code=`
44
+ )
45
+ const code = parts[1].split('"')[0].split("&")[0]
46
+ return { code, res }
47
+ }
48
+ }
@@ -0,0 +1,40 @@
1
+ import TestConfiguration from "../TestConfiguration"
2
+
3
+ export class ConfigAPI {
4
+ config: TestConfiguration
5
+ request: any
6
+
7
+ constructor(config: TestConfiguration) {
8
+ this.config = config
9
+ this.request = config.request
10
+ }
11
+
12
+ getConfigChecklist = () => {
13
+ return this.request
14
+ .get(`/api/global/configs/checklist`)
15
+ .set(this.config.defaultHeaders())
16
+ .expect("Content-Type", /json/)
17
+ .expect(200)
18
+ }
19
+
20
+ saveConfig = (data: any) => {
21
+ return this.request
22
+ .post(`/api/global/configs`)
23
+ .send(data)
24
+ .set(this.config.defaultHeaders())
25
+ .expect("Content-Type", /json/)
26
+ .expect(200)
27
+ }
28
+
29
+ OIDCCallback = (configId: string) => {
30
+ return this.request
31
+ .get(`/api/global/auth/${this.config.getTenantId()}/oidc/callback`)
32
+ .set(this.config.getOIDConfigCookie(configId))
33
+ }
34
+
35
+ getOIDCConfig = (configId: string) => {
36
+ return this.request.get(
37
+ `/api/global/auth/${this.config.getTenantId()}/oidc/configs/${configId}`
38
+ )
39
+ }
40
+ }
@@ -0,0 +1,24 @@
1
+ import TestConfiguration from "../TestConfiguration"
2
+
3
+ export class EmailAPI {
4
+ config: TestConfiguration
5
+ request: any
6
+
7
+ constructor(config: TestConfiguration) {
8
+ this.config = config
9
+ this.request = config.request
10
+ }
11
+
12
+ sendEmail = (purpose: string) => {
13
+ return this.request
14
+ .post(`/api/global/email/send`)
15
+ .send({
16
+ email: "test@test.com",
17
+ purpose,
18
+ tenantId: this.config.getTenantId(),
19
+ })
20
+ .set(this.config.defaultHeaders())
21
+ .expect("Content-Type", /json/)
22
+ .expect(200)
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+ import TestConfiguration from "../TestConfiguration"
2
+ import { AccountAPI } from "./accounts"
3
+ import { AuthAPI } from "./auth"
4
+ import { ConfigAPI } from "./configs"
5
+ import { EmailAPI } from "./email"
6
+ import { SelfAPI } from "./self"
7
+ import { UserAPI } from "./users"
8
+
9
+ export default class API {
10
+ accounts: AccountAPI
11
+ auth: AuthAPI
12
+ configs: ConfigAPI
13
+ emails: EmailAPI
14
+ self: SelfAPI
15
+ users: UserAPI
16
+
17
+ constructor(config: TestConfiguration) {
18
+ this.accounts = new AccountAPI(config)
19
+ this.auth = new AuthAPI(config)
20
+ this.configs = new ConfigAPI(config)
21
+ this.emails = new EmailAPI(config)
22
+ this.self = new SelfAPI(config)
23
+ this.users = new UserAPI(config)
24
+ }
25
+ }
@@ -0,0 +1,21 @@
1
+ import TestConfiguration from "../TestConfiguration"
2
+ import { User } from "@budibase/types"
3
+
4
+ export class SelfAPI {
5
+ config: TestConfiguration
6
+ request: any
7
+
8
+ constructor(config: TestConfiguration) {
9
+ this.config = config
10
+ this.request = config.request
11
+ }
12
+
13
+ updateSelf = (user: User) => {
14
+ return this.request
15
+ .post(`/api/global/self`)
16
+ .send(user)
17
+ .set(this.config.authHeaders(user))
18
+ .expect("Content-Type", /json/)
19
+ .expect(200)
20
+ }
21
+ }
@@ -0,0 +1,111 @@
1
+ import {
2
+ BulkCreateUsersRequest,
3
+ BulkCreateUsersResponse,
4
+ BulkDeleteUsersRequest,
5
+ CreateUserResponse,
6
+ InviteUsersRequest,
7
+ User,
8
+ UserDetails,
9
+ } from "@budibase/types"
10
+ import TestConfiguration from "../TestConfiguration"
11
+
12
+ export class UserAPI {
13
+ config: TestConfiguration
14
+ request: any
15
+
16
+ constructor(config: TestConfiguration) {
17
+ this.config = config
18
+ this.request = config.request
19
+ }
20
+
21
+ // INVITE
22
+
23
+ sendUserInvite = async (sendMailMock: any, email: string, status = 200) => {
24
+ await this.config.saveSmtpConfig()
25
+ await this.config.saveSettingsConfig()
26
+ const res = await this.request
27
+ .post(`/api/global/users/invite`)
28
+ .send({
29
+ email,
30
+ })
31
+ .set(this.config.defaultHeaders())
32
+ .expect("Content-Type", /json/)
33
+ .expect(status)
34
+
35
+ if (status !== 200) {
36
+ return { code: undefined, res }
37
+ }
38
+
39
+ const emailCall = sendMailMock.mock.calls[0][0]
40
+ // after this URL there should be a code
41
+ const parts = emailCall.html.split(
42
+ "http://localhost:10000/builder/invite?code="
43
+ )
44
+ const code = parts[1].split('"')[0].split("&")[0]
45
+ return { code, res }
46
+ }
47
+
48
+ acceptInvite = (code: string) => {
49
+ return this.request
50
+ .post(`/api/global/users/invite/accept`)
51
+ .send({
52
+ password: "newpassword",
53
+ inviteCode: code,
54
+ })
55
+ .expect("Content-Type", /json/)
56
+ .expect(200)
57
+ }
58
+
59
+ sendMultiUserInvite = async (request: InviteUsersRequest, status = 200) => {
60
+ await this.config.saveSmtpConfig()
61
+ await this.config.saveSettingsConfig()
62
+ return this.request
63
+ .post(`/api/global/users/multi/invite`)
64
+ .send(request)
65
+ .set(this.config.defaultHeaders())
66
+ .expect("Content-Type", /json/)
67
+ .expect(status)
68
+ }
69
+
70
+ // BULK
71
+
72
+ bulkCreateUsers = async (users: User[], groups: any[] = []) => {
73
+ const body: BulkCreateUsersRequest = { users, groups }
74
+ const res = await this.request
75
+ .post(`/api/global/users/bulkCreate`)
76
+ .send(body)
77
+ .set(this.config.defaultHeaders())
78
+ .expect("Content-Type", /json/)
79
+ .expect(200)
80
+
81
+ return res.body as BulkCreateUsersResponse
82
+ }
83
+
84
+ bulkDeleteUsers = (body: BulkDeleteUsersRequest, status?: number) => {
85
+ return this.request
86
+ .post(`/api/global/users/bulkDelete`)
87
+ .send(body)
88
+ .set(this.config.defaultHeaders())
89
+ .expect("Content-Type", /json/)
90
+ .expect(status ? status : 200)
91
+ }
92
+
93
+ // USER
94
+
95
+ saveUser = (user: User, status?: number) => {
96
+ return this.request
97
+ .post(`/api/global/users`)
98
+ .send(user)
99
+ .set(this.config.defaultHeaders())
100
+ .expect("Content-Type", /json/)
101
+ .expect(status ? status : 200)
102
+ }
103
+
104
+ deleteUser = (userId: string, status?: number) => {
105
+ return this.request
106
+ .delete(`/api/global/users/${userId}`)
107
+ .set(this.config.defaultHeaders())
108
+ .expect("Content-Type", /json/)
109
+ .expect(status ? status : 200)
110
+ }
111
+ }
@@ -0,0 +1,14 @@
1
+ import TestConfiguration from "./TestConfiguration"
2
+ import structures from "./structures"
3
+ import mocks from "./mocks"
4
+ import API from "./api"
5
+
6
+ const pkg = {
7
+ structures,
8
+ TENANT_1: structures.TENANT_1,
9
+ mocks,
10
+ TestConfiguration,
11
+ API,
12
+ }
13
+
14
+ export = pkg
@@ -0,0 +1,7 @@
1
+ const email = require("./email")
2
+ import { mocks as coreMocks } from "@budibase/backend-core/tests"
3
+
4
+ export = {
5
+ email,
6
+ ...coreMocks,
7
+ }
@@ -0,0 +1,24 @@
1
+ import { Account, AuthType, Hosting, CloudAccount } from "@budibase/types"
2
+ import { v4 as uuid } from "uuid"
3
+ import { utils } from "@budibase/backend-core"
4
+
5
+ export const account = (): Account => {
6
+ return {
7
+ email: `${uuid()}@test.com`,
8
+ tenantId: utils.newid(),
9
+ hosting: Hosting.SELF,
10
+ authType: AuthType.SSO,
11
+ accountId: uuid(),
12
+ createdAt: Date.now(),
13
+ verified: true,
14
+ verificationSent: true,
15
+ tier: "FREE",
16
+ }
17
+ }
18
+
19
+ export const cloudAccount = (): CloudAccount => {
20
+ return {
21
+ ...account(),
22
+ budibaseUserId: uuid(),
23
+ }
24
+ }
@@ -0,0 +1,20 @@
1
+ import configs from "./configs"
2
+ import * as users from "./users"
3
+ import * as groups from "./groups"
4
+ import * as accounts from "./accounts"
5
+
6
+ const TENANT_ID = "default"
7
+ const TENANT_1 = "tenant1"
8
+ const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
9
+
10
+ const pkg = {
11
+ configs,
12
+ users,
13
+ accounts,
14
+ TENANT_ID,
15
+ TENANT_1,
16
+ CSRF_TOKEN,
17
+ groups,
18
+ }
19
+
20
+ export = pkg
@@ -1,24 +1,32 @@
1
1
  export const email = "test@test.com"
2
+ import { AdminUser, BuilderUser, User } from "@budibase/types"
3
+ import { v4 as uuid } from "uuid"
2
4
 
3
- export const user = (userProps: any) => {
5
+ export const newEmail = () => {
6
+ return `${uuid()}@test.com`
7
+ }
8
+ export const user = (userProps?: any): User => {
4
9
  return {
5
- email: "test@test.com",
10
+ email: newEmail(),
6
11
  password: "test",
7
12
  roles: {},
8
13
  ...userProps,
9
14
  }
10
15
  }
11
16
 
12
- export const adminUser = (userProps: any) => {
17
+ export const adminUser = (userProps?: any): AdminUser => {
13
18
  return {
14
19
  ...user(userProps),
15
20
  admin: {
16
21
  global: true,
17
22
  },
23
+ builder: {
24
+ global: true,
25
+ },
18
26
  }
19
27
  }
20
28
 
21
- export const builderUser = (userProps: any) => {
29
+ export const builderUser = (userProps?: any): BuilderUser => {
22
30
  return {
23
31
  ...user(userProps),
24
32
  builder: {
@@ -55,6 +55,7 @@ exports.init = async () => {
55
55
  exports.shutdown = async () => {
56
56
  if (pwResetClient) await pwResetClient.finish()
57
57
  if (invitationClient) await invitationClient.finish()
58
+ console.log("Redis shutdown")
58
59
  }
59
60
 
60
61
  /**