@budibase/backend-core 2.30.2 → 2.30.3

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 (57) hide show
  1. package/dist/index.js +267 -89
  2. package/dist/index.js.map +3 -3
  3. package/dist/index.js.meta.json +1 -1
  4. package/dist/package.json +5 -5
  5. package/dist/plugins.js.meta.json +1 -1
  6. package/dist/src/environment.d.ts +4 -0
  7. package/dist/src/environment.js +27 -1
  8. package/dist/src/environment.js.map +1 -1
  9. package/dist/src/events/processors/posthog/PosthogProcessor.d.ts +1 -1
  10. package/dist/src/events/processors/posthog/PosthogProcessor.js +2 -2
  11. package/dist/src/events/processors/posthog/PosthogProcessor.js.map +1 -1
  12. package/dist/src/features/index.d.ts +29 -26
  13. package/dist/src/features/index.js +195 -79
  14. package/dist/src/features/index.js.map +1 -1
  15. package/dist/src/index.d.ts +1 -1
  16. package/dist/src/index.js +3 -1
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/redis/redis.d.ts +1 -0
  19. package/dist/src/redis/redis.js +4 -0
  20. package/dist/src/redis/redis.js.map +1 -1
  21. package/dist/src/security/auth.js +1 -1
  22. package/dist/src/security/auth.js.map +1 -1
  23. package/dist/src/sql/sqlTable.js +23 -8
  24. package/dist/src/sql/sqlTable.js.map +1 -1
  25. package/dist/tests/core/utilities/mocks/index.d.ts +0 -2
  26. package/dist/tests/core/utilities/mocks/index.js +1 -7
  27. package/dist/tests/core/utilities/mocks/index.js.map +1 -1
  28. package/dist/tests/core/utilities/structures/users.js +1 -1
  29. package/dist/tests/core/utilities/structures/users.js.map +1 -1
  30. package/dist/tests/jestSetup.js +7 -2
  31. package/dist/tests/jestSetup.js.map +1 -1
  32. package/package.json +5 -5
  33. package/src/environment.ts +29 -0
  34. package/src/events/processors/posthog/PosthogProcessor.ts +1 -1
  35. package/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +16 -22
  36. package/src/features/index.ts +231 -81
  37. package/src/features/tests/features.spec.ts +204 -60
  38. package/src/index.ts +1 -1
  39. package/src/middleware/passport/sso/tests/oidc.spec.ts +4 -12
  40. package/src/middleware/passport/sso/tests/sso.spec.ts +10 -12
  41. package/src/plugin/tests/validation.spec.ts +168 -42
  42. package/src/redis/redis.ts +4 -0
  43. package/src/redis/tests/redis.spec.ts +5 -2
  44. package/src/security/auth.ts +1 -1
  45. package/src/security/tests/auth.spec.ts +2 -2
  46. package/src/sql/sqlTable.ts +21 -7
  47. package/tests/core/utilities/mocks/index.ts +0 -2
  48. package/tests/core/utilities/structures/users.ts +1 -1
  49. package/tests/jestSetup.ts +10 -3
  50. package/dist/tests/core/utilities/mocks/fetch.d.ts +0 -32
  51. package/dist/tests/core/utilities/mocks/fetch.js +0 -15
  52. package/dist/tests/core/utilities/mocks/fetch.js.map +0 -1
  53. package/dist/tests/core/utilities/mocks/posthog.d.ts +0 -0
  54. package/dist/tests/core/utilities/mocks/posthog.js +0 -9
  55. package/dist/tests/core/utilities/mocks/posthog.js.map +0 -1
  56. package/tests/core/utilities/mocks/fetch.ts +0 -17
  57. package/tests/core/utilities/mocks/posthog.ts +0 -7
@@ -1,86 +1,230 @@
1
- import { defaultFlags, fetch, get, Flags } from "../"
1
+ import { IdentityContext, IdentityType, UserCtx } from "@budibase/types"
2
+ import { Flag, FlagSet, FlagValues, init } from "../"
2
3
  import { context } from "../.."
3
- import env from "../../environment"
4
+ import environment, { withEnv } from "../../environment"
5
+ import nodeFetch from "node-fetch"
6
+ import nock from "nock"
4
7
 
5
- async function withFlags<T>(flags: string, f: () => T): Promise<T> {
6
- const oldFlags = env.TENANT_FEATURE_FLAGS
7
- env._set("TENANT_FEATURE_FLAGS", flags)
8
- try {
9
- return await f()
10
- } finally {
11
- env._set("TENANT_FEATURE_FLAGS", oldFlags)
12
- }
8
+ const schema = {
9
+ TEST_BOOLEAN: Flag.boolean(false),
10
+ TEST_STRING: Flag.string("default value"),
11
+ TEST_NUMBER: Flag.number(0),
12
+ }
13
+ const flags = new FlagSet(schema)
14
+
15
+ interface TestCase {
16
+ it: string
17
+ identity?: Partial<IdentityContext>
18
+ environmentFlags?: string
19
+ posthogFlags?: PostHogFlags
20
+ licenseFlags?: Array<string>
21
+ expected?: Partial<FlagValues<typeof schema>>
22
+ errorMessage?: string | RegExp
23
+ }
24
+
25
+ interface PostHogFlags {
26
+ featureFlags?: Record<string, boolean>
27
+ featureFlagPayloads?: Record<string, string>
28
+ }
29
+
30
+ function mockPosthogFlags(flags: PostHogFlags) {
31
+ nock("https://us.i.posthog.com")
32
+ .post("/decide/?v=3", body => {
33
+ return body.token === "test" && body.distinct_id === "us_1234"
34
+ })
35
+ .reply(200, flags)
36
+ .persist()
13
37
  }
14
38
 
15
39
  describe("feature flags", () => {
16
- interface TestCase {
17
- tenant: string
18
- flags: string
19
- expected: Partial<Flags>
20
- }
40
+ beforeEach(() => {
41
+ nock.cleanAll()
42
+ })
21
43
 
22
44
  it.each<TestCase>([
23
45
  {
24
- tenant: "tenant1",
25
- flags: "tenant1:ONBOARDING_TOUR",
26
- expected: { ONBOARDING_TOUR: true },
46
+ it: "should should find a simple boolean flag in the environment",
47
+ environmentFlags: "default:TEST_BOOLEAN",
48
+ expected: { TEST_BOOLEAN: true },
49
+ },
50
+ {
51
+ it: "should should find a simple netgative boolean flag in the environment",
52
+ environmentFlags: "default:!TEST_BOOLEAN",
53
+ expected: { TEST_BOOLEAN: false },
54
+ },
55
+ {
56
+ it: "should should match stars in the environment",
57
+ environmentFlags: "*:TEST_BOOLEAN",
58
+ expected: { TEST_BOOLEAN: true },
59
+ },
60
+ {
61
+ it: "should not match a different tenant's flags",
62
+ environmentFlags: "otherTenant:TEST_BOOLEAN",
63
+ expected: { TEST_BOOLEAN: false },
64
+ },
65
+ {
66
+ it: "should return the defaults when no flags are set",
67
+ expected: flags.defaults(),
68
+ },
69
+ {
70
+ it: "should fail when an environment flag is not recognised",
71
+ environmentFlags: "default:TEST_BOOLEAN,default:FOO",
72
+ errorMessage: "Feature: FOO is not an allowed option",
73
+ },
74
+ {
75
+ it: "should be able to read boolean flags from PostHog",
76
+ posthogFlags: {
77
+ featureFlags: { TEST_BOOLEAN: true },
78
+ },
79
+ expected: { TEST_BOOLEAN: true },
80
+ },
81
+ {
82
+ it: "should be able to read string flags from PostHog",
83
+ posthogFlags: {
84
+ featureFlags: { TEST_STRING: true },
85
+ featureFlagPayloads: { TEST_STRING: "test" },
86
+ },
87
+ expected: { TEST_STRING: "test" },
27
88
  },
28
89
  {
29
- tenant: "tenant1",
30
- flags: "tenant1:!ONBOARDING_TOUR",
31
- expected: { ONBOARDING_TOUR: false },
90
+ it: "should be able to read numeric flags from PostHog",
91
+ posthogFlags: {
92
+ featureFlags: { TEST_NUMBER: true },
93
+ featureFlagPayloads: { TEST_NUMBER: "123" },
94
+ },
95
+ expected: { TEST_NUMBER: 123 },
32
96
  },
33
97
  {
34
- tenant: "tenant1",
35
- flags: "*:ONBOARDING_TOUR",
36
- expected: { ONBOARDING_TOUR: true },
98
+ it: "should not be able to override a negative environment flag from PostHog",
99
+ environmentFlags: "default:!TEST_BOOLEAN",
100
+ posthogFlags: {
101
+ featureFlags: { TEST_BOOLEAN: true },
102
+ },
103
+ expected: { TEST_BOOLEAN: false },
37
104
  },
38
105
  {
39
- tenant: "tenant1",
40
- flags: "tenant2:ONBOARDING_TOUR",
41
- expected: { ONBOARDING_TOUR: false },
106
+ it: "should not be able to override a positive environment flag from PostHog",
107
+ environmentFlags: "default:TEST_BOOLEAN",
108
+ posthogFlags: {
109
+ featureFlags: {
110
+ TEST_BOOLEAN: false,
111
+ },
112
+ },
113
+ expected: { TEST_BOOLEAN: true },
42
114
  },
43
115
  {
44
- tenant: "tenant1",
45
- flags: "",
46
- expected: defaultFlags(),
116
+ it: "should be able to set boolean flags through the license",
117
+ licenseFlags: ["TEST_BOOLEAN"],
118
+ expected: { TEST_BOOLEAN: true },
119
+ },
120
+ {
121
+ it: "should not be able to override a negative environment flag from license",
122
+ environmentFlags: "default:!TEST_BOOLEAN",
123
+ licenseFlags: ["TEST_BOOLEAN"],
124
+ expected: { TEST_BOOLEAN: false },
125
+ },
126
+ {
127
+ it: "should not error on unrecognised PostHog flag",
128
+ posthogFlags: {
129
+ featureFlags: { UNDEFINED: true },
130
+ },
131
+ expected: flags.defaults(),
132
+ },
133
+ {
134
+ it: "should not error on unrecognised license flag",
135
+ licenseFlags: ["UNDEFINED"],
136
+ expected: flags.defaults(),
47
137
  },
48
138
  ])(
49
- 'should find flags $expected for $tenant with string "$flags"',
50
- ({ tenant, flags, expected }) =>
51
- context.doInTenant(tenant, () =>
52
- withFlags(flags, async () => {
53
- const flags = await fetch()
54
- expect(flags).toMatchObject(expected)
139
+ "$it",
140
+ async ({
141
+ identity,
142
+ environmentFlags,
143
+ posthogFlags,
144
+ licenseFlags,
145
+ expected,
146
+ errorMessage,
147
+ }) => {
148
+ const env: Partial<typeof environment> = {
149
+ TENANT_FEATURE_FLAGS: environmentFlags,
150
+ }
55
151
 
56
- for (const [key, expectedValue] of Object.entries(expected)) {
57
- const value = await get(key as keyof Flags)
58
- expect(value).toBe(expectedValue)
59
- }
152
+ if (posthogFlags) {
153
+ mockPosthogFlags(posthogFlags)
154
+ env.POSTHOG_TOKEN = "test"
155
+ env.POSTHOG_API_HOST = "https://us.i.posthog.com"
156
+ env.POSTHOG_PERSONAL_TOKEN = "test"
157
+ }
158
+
159
+ const ctx = { user: { license: { features: licenseFlags || [] } } }
160
+
161
+ await withEnv(env, async () => {
162
+ // We need to pass in node-fetch here otherwise nock won't get used
163
+ // because posthog-node uses axios under the hood.
164
+ init({
165
+ fetch: (url, opts) => {
166
+ return nodeFetch(url, opts)
167
+ },
60
168
  })
61
- )
62
- )
63
169
 
64
- interface FailedTestCase {
65
- tenant: string
66
- flags: string
67
- expected: string | RegExp
68
- }
170
+ const fullIdentity: IdentityContext = {
171
+ _id: "us_1234",
172
+ tenantId: "default",
173
+ type: IdentityType.USER,
174
+ email: "test@example.com",
175
+ firstName: "Test",
176
+ lastName: "User",
177
+ ...identity,
178
+ }
69
179
 
70
- it.each<FailedTestCase>([
71
- {
72
- tenant: "tenant1",
73
- flags: "tenant1:ONBOARDING_TOUR,tenant1:FOO",
74
- expected: "Feature: FOO is not an allowed option",
75
- },
76
- ])(
77
- "should fail with message \"$expected\" for $tenant with string '$flags'",
78
- async ({ tenant, flags, expected }) => {
79
- context.doInTenant(tenant, () =>
80
- withFlags(flags, async () => {
81
- await expect(fetch()).rejects.toThrow(expected)
180
+ await context.doInIdentityContext(fullIdentity, async () => {
181
+ if (errorMessage) {
182
+ await expect(flags.fetch(ctx as UserCtx)).rejects.toThrow(
183
+ errorMessage
184
+ )
185
+ } else if (expected) {
186
+ const values = await flags.fetch(ctx as UserCtx)
187
+ expect(values).toMatchObject(expected)
188
+
189
+ for (const [key, expectedValue] of Object.entries(expected)) {
190
+ const value = await flags.get(
191
+ key as keyof typeof schema,
192
+ ctx as UserCtx
193
+ )
194
+ expect(value).toBe(expectedValue)
195
+ }
196
+ } else {
197
+ throw new Error("No expected value")
198
+ }
82
199
  })
83
- )
200
+ })
84
201
  }
85
202
  )
203
+
204
+ it("should not error if PostHog is down", async () => {
205
+ const identity: IdentityContext = {
206
+ _id: "us_1234",
207
+ tenantId: "default",
208
+ type: IdentityType.USER,
209
+ email: "test@example.com",
210
+ firstName: "Test",
211
+ lastName: "User",
212
+ }
213
+
214
+ nock("https://us.i.posthog.com")
215
+ .post("/decide/?v=3", body => {
216
+ return body.token === "test" && body.distinct_id === "us_1234"
217
+ })
218
+ .reply(503)
219
+ .persist()
220
+
221
+ await withEnv(
222
+ { POSTHOG_TOKEN: "test", POSTHOG_API_HOST: "https://us.i.posthog.com" },
223
+ async () => {
224
+ await context.doInIdentityContext(identity, async () => {
225
+ await flags.fetch()
226
+ })
227
+ }
228
+ )
229
+ })
86
230
  })
package/src/index.ts CHANGED
@@ -27,7 +27,7 @@ export * as locks from "./redis/redlockImpl"
27
27
  export * as utils from "./utils"
28
28
  export * as errors from "./errors"
29
29
  export * as timers from "./timers"
30
- export { default as env } from "./environment"
30
+ export { default as env, withEnv, setEnv } from "./environment"
31
31
  export * as blacklist from "./blacklist"
32
32
  export * as docUpdates from "./docUpdates"
33
33
  export * from "./utils/Duration"
@@ -1,4 +1,4 @@
1
- import { generator, mocks, structures } from "../../../../../tests"
1
+ import { generator, structures } from "../../../../../tests"
2
2
  import {
3
3
  JwtClaims,
4
4
  OIDCInnerConfig,
@@ -7,6 +7,7 @@ import {
7
7
  } from "@budibase/types"
8
8
  import * as _sso from "../sso"
9
9
  import * as oidc from "../oidc"
10
+ import nock from "nock"
10
11
 
11
12
  jest.mock("@techpass/passport-openidconnect")
12
13
  const mockStrategy = require("@techpass/passport-openidconnect").Strategy
@@ -22,16 +23,9 @@ describe("oidc", () => {
22
23
  const oidcConfig: OIDCInnerConfig = structures.sso.oidcConfig()
23
24
  const wellKnownConfig = structures.sso.oidcWellKnownConfig()
24
25
 
25
- function mockRetrieveWellKnownConfig() {
26
- // mock the request to retrieve the oidc configuration
27
- mocks.fetch.mockReturnValue({
28
- ok: true,
29
- json: () => wellKnownConfig,
30
- })
31
- }
32
-
33
26
  beforeEach(() => {
34
- mockRetrieveWellKnownConfig()
27
+ nock.cleanAll()
28
+ nock(oidcConfig.configUrl).get("/").reply(200, wellKnownConfig)
35
29
  })
36
30
 
37
31
  describe("strategyFactory", () => {
@@ -42,8 +36,6 @@ describe("oidc", () => {
42
36
  )
43
37
  await oidc.strategyFactory(strategyConfiguration, mockSaveUser)
44
38
 
45
- expect(mocks.fetch).toHaveBeenCalledWith(oidcConfig.configUrl)
46
-
47
39
  const expectedOptions = {
48
40
  issuer: wellKnownConfig.issuer,
49
41
  authorizationURL: wellKnownConfig.authorization_endpoint,
@@ -1,10 +1,11 @@
1
- import { structures, mocks } from "../../../../../tests"
1
+ import { structures } from "../../../../../tests"
2
2
  import { testEnv } from "../../../../../tests/extra"
3
3
  import { SSOAuthDetails, User } from "@budibase/types"
4
4
 
5
5
  import { HTTPError } from "../../../../errors"
6
6
  import * as sso from "../sso"
7
7
  import * as context from "../../../../context"
8
+ import nock from "nock"
8
9
 
9
10
  const mockDone = jest.fn()
10
11
  const mockSaveUser = jest.fn()
@@ -23,6 +24,7 @@ describe("sso", () => {
23
24
  beforeEach(() => {
24
25
  jest.clearAllMocks()
25
26
  testEnv.singleTenant()
27
+ nock.cleanAll()
26
28
  })
27
29
 
28
30
  describe("validation", () => {
@@ -51,15 +53,6 @@ describe("sso", () => {
51
53
  })
52
54
  })
53
55
 
54
- function mockGetProfilePicture() {
55
- mocks.fetch.mockReturnValueOnce(
56
- Promise.resolve({
57
- status: 200,
58
- headers: { get: () => "image/" },
59
- })
60
- )
61
- }
62
-
63
56
  describe("when the user doesn't exist", () => {
64
57
  let user: User
65
58
  let details: SSOAuthDetails
@@ -68,7 +61,10 @@ describe("sso", () => {
68
61
  users.getById.mockImplementationOnce(() => {
69
62
  throw new HTTPError("", 404)
70
63
  })
71
- mockGetProfilePicture()
64
+
65
+ nock("http://example.com").get("/").reply(200, undefined, {
66
+ "Content-Type": "image/png",
67
+ })
72
68
 
73
69
  user = structures.users.user()
74
70
  delete user._rev
@@ -131,7 +127,9 @@ describe("sso", () => {
131
127
  existingUser = structures.users.user()
132
128
  existingUser._id = structures.uuid()
133
129
  details = structures.sso.authDetails(existingUser)
134
- mockGetProfilePicture()
130
+ nock("http://example.com").get("/").reply(200, undefined, {
131
+ "Content-Type": "image/png",
132
+ })
135
133
  })
136
134
 
137
135
  describe("exists by email", () => {
@@ -1,12 +1,129 @@
1
1
  import { validate } from "../utils"
2
2
  import fetch from "node-fetch"
3
3
  import { PluginType } from "@budibase/types"
4
+ import nock from "nock"
4
5
 
5
- const repoUrl =
6
- "https://raw.githubusercontent.com/Budibase/budibase-skeleton/master"
7
- const automationLink = `${repoUrl}/automation/schema.json.hbs`
8
- const componentLink = `${repoUrl}/component/schema.json.hbs`
9
- const datasourceLink = `${repoUrl}/datasource/schema.json.hbs`
6
+ const automationLink = `http://example.com/automation/schema.json`
7
+ const componentLink = `http://example.com/component/schema.json`
8
+ const datasourceLink = `http://example.com/datasource/schema.json`
9
+
10
+ function mockDatasourceSchema() {
11
+ nock("http://example.com")
12
+ .get("/datasource/schema.json")
13
+ .reply(200, {
14
+ type: "datasource",
15
+ metadata: {},
16
+ schema: {
17
+ docs: "https://docs.budibase.com",
18
+ friendlyName: "Basic HTTP",
19
+ type: "API",
20
+ description: "Performs a basic HTTP calls to a URL",
21
+ datasource: {
22
+ url: {
23
+ type: "string",
24
+ required: true,
25
+ },
26
+ cookie: {
27
+ type: "string",
28
+ required: false,
29
+ },
30
+ },
31
+ query: {
32
+ create: {
33
+ type: "json",
34
+ },
35
+ read: {
36
+ type: "fields",
37
+ fields: {
38
+ queryString: {
39
+ display: "Query string",
40
+ type: "string",
41
+ required: false,
42
+ },
43
+ },
44
+ },
45
+ update: {
46
+ type: "json",
47
+ },
48
+ delete: {
49
+ type: "fields",
50
+ fields: {
51
+ id: {
52
+ type: "string",
53
+ required: true,
54
+ },
55
+ },
56
+ },
57
+ },
58
+ },
59
+ })
60
+ }
61
+
62
+ function mockAutomationSchema() {
63
+ nock("http://example.com")
64
+ .get("/automation/schema.json")
65
+ .reply(200, {
66
+ type: "automation",
67
+ metadata: {},
68
+ schema: {
69
+ name: "{{ name }}",
70
+ tagline: "{{ description }}",
71
+ icon: "Actions",
72
+ description: "{{ description }}",
73
+ type: "action",
74
+ stepId: "{{ name }}",
75
+ inputs: {
76
+ text: "",
77
+ },
78
+ schema: {
79
+ inputs: {
80
+ properties: {
81
+ text: {
82
+ type: "string",
83
+ title: "Log",
84
+ },
85
+ },
86
+ required: ["text"],
87
+ },
88
+ outputs: {
89
+ properties: {
90
+ success: {
91
+ type: "boolean",
92
+ description: "Whether the action was successful",
93
+ },
94
+ message: {
95
+ type: "string",
96
+ description: "What was output",
97
+ },
98
+ },
99
+ required: ["success", "message"],
100
+ },
101
+ },
102
+ },
103
+ })
104
+ }
105
+
106
+ function mockComponentSchema() {
107
+ nock("http://example.com")
108
+ .get("/component/schema.json")
109
+ .reply(200, {
110
+ type: "component",
111
+ metadata: {},
112
+ schema: {
113
+ name: "{{ name }}",
114
+ friendlyName: "{{ name }}",
115
+ description: "{{ description }}",
116
+ icon: "Text",
117
+ settings: [
118
+ {
119
+ type: "text",
120
+ key: "text",
121
+ label: "Text",
122
+ },
123
+ ],
124
+ },
125
+ })
126
+ }
10
127
 
11
128
  async function getSchema(link: string) {
12
129
  const response = await fetch(link)
@@ -31,53 +148,62 @@ async function runTest(opts: { link?: string; schema?: any }) {
31
148
  return error
32
149
  }
33
150
 
34
- describe("it should be able to validate an automation schema", () => {
35
- it("should return automation skeleton schema is valid", async () => {
36
- const error = await runTest({ link: automationLink })
37
- expect(error).toBeUndefined()
151
+ describe("plugin validation", () => {
152
+ beforeEach(() => {
153
+ nock.cleanAll()
154
+ mockAutomationSchema()
155
+ mockComponentSchema()
156
+ mockDatasourceSchema()
38
157
  })
39
158
 
40
- it("should fail given invalid automation schema", async () => {
41
- const error = await runTest({
42
- schema: {
43
- type: PluginType.AUTOMATION,
44
- schema: {},
45
- },
159
+ describe("it should be able to validate an automation schema", () => {
160
+ it("should return automation skeleton schema is valid", async () => {
161
+ const error = await runTest({ link: automationLink })
162
+ expect(error).toBeUndefined()
46
163
  })
47
- expect(error).toBeDefined()
48
- })
49
- })
50
164
 
51
- describe("it should be able to validate a component schema", () => {
52
- it("should return component skeleton schema is valid", async () => {
53
- const error = await runTest({ link: componentLink })
54
- expect(error).toBeUndefined()
165
+ it("should fail given invalid automation schema", async () => {
166
+ const error = await runTest({
167
+ schema: {
168
+ type: PluginType.AUTOMATION,
169
+ schema: {},
170
+ },
171
+ })
172
+ expect(error).toBeDefined()
173
+ })
55
174
  })
56
175
 
57
- it("should fail given invalid component schema", async () => {
58
- const error = await runTest({
59
- schema: {
60
- type: PluginType.COMPONENT,
61
- schema: {},
62
- },
176
+ describe("it should be able to validate a component schema", () => {
177
+ it("should return component skeleton schema is valid", async () => {
178
+ const error = await runTest({ link: componentLink })
179
+ expect(error).toBeUndefined()
63
180
  })
64
- expect(error).toBeDefined()
65
- })
66
- })
67
181
 
68
- describe("it should be able to validate a datasource schema", () => {
69
- it("should return datasource skeleton schema is valid", async () => {
70
- const error = await runTest({ link: datasourceLink })
71
- expect(error).toBeUndefined()
182
+ it("should fail given invalid component schema", async () => {
183
+ const error = await runTest({
184
+ schema: {
185
+ type: PluginType.COMPONENT,
186
+ schema: {},
187
+ },
188
+ })
189
+ expect(error).toBeDefined()
190
+ })
72
191
  })
73
192
 
74
- it("should fail given invalid datasource schema", async () => {
75
- const error = await runTest({
76
- schema: {
77
- type: PluginType.DATASOURCE,
78
- schema: {},
79
- },
193
+ describe("it should be able to validate a datasource schema", () => {
194
+ it("should return datasource skeleton schema is valid", async () => {
195
+ const error = await runTest({ link: datasourceLink })
196
+ expect(error).toBeUndefined()
197
+ })
198
+
199
+ it("should fail given invalid datasource schema", async () => {
200
+ const error = await runTest({
201
+ schema: {
202
+ type: PluginType.DATASOURCE,
203
+ schema: {},
204
+ },
205
+ })
206
+ expect(error).toBeDefined()
80
207
  })
81
- expect(error).toBeDefined()
82
208
  })
83
209
  })
@@ -111,6 +111,10 @@ function init(selectDb = DEFAULT_SELECT_DB) {
111
111
  CLIENTS[selectDb] = client
112
112
  }
113
113
 
114
+ export function closeAll() {
115
+ Object.values(CLIENTS).forEach(client => client.disconnect())
116
+ }
117
+
114
118
  function waitForConnection(selectDb: number = DEFAULT_SELECT_DB) {
115
119
  return new Promise(resolve => {
116
120
  if (pickClient(selectDb) == null) {
@@ -1,6 +1,6 @@
1
1
  import { GenericContainer, StartedTestContainer } from "testcontainers"
2
2
  import { generator, structures } from "../../../tests"
3
- import RedisWrapper from "../redis"
3
+ import RedisWrapper, { closeAll } from "../redis"
4
4
  import { env } from "../.."
5
5
  import { randomUUID } from "crypto"
6
6
 
@@ -23,7 +23,10 @@ describe("redis", () => {
23
23
  env._set("REDIS_PASSWORD", 0)
24
24
  })
25
25
 
26
- afterAll(() => container?.stop())
26
+ afterAll(() => {
27
+ container?.stop()
28
+ closeAll()
29
+ })
27
30
 
28
31
  beforeEach(async () => {
29
32
  redis = new RedisWrapper(structures.db.id())
@@ -1,6 +1,6 @@
1
1
  import env from "../environment"
2
2
 
3
- export const PASSWORD_MIN_LENGTH = +(env.PASSWORD_MIN_LENGTH || 8)
3
+ export const PASSWORD_MIN_LENGTH = +(env.PASSWORD_MIN_LENGTH || 12)
4
4
  export const PASSWORD_MAX_LENGTH = +(env.PASSWORD_MAX_LENGTH || 512)
5
5
 
6
6
  export function validatePassword(