@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.
- package/dist/index.js +267 -89
- package/dist/index.js.map +3 -3
- package/dist/index.js.meta.json +1 -1
- package/dist/package.json +5 -5
- package/dist/plugins.js.meta.json +1 -1
- package/dist/src/environment.d.ts +4 -0
- package/dist/src/environment.js +27 -1
- package/dist/src/environment.js.map +1 -1
- package/dist/src/events/processors/posthog/PosthogProcessor.d.ts +1 -1
- package/dist/src/events/processors/posthog/PosthogProcessor.js +2 -2
- package/dist/src/events/processors/posthog/PosthogProcessor.js.map +1 -1
- package/dist/src/features/index.d.ts +29 -26
- package/dist/src/features/index.js +195 -79
- package/dist/src/features/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/redis/redis.d.ts +1 -0
- package/dist/src/redis/redis.js +4 -0
- package/dist/src/redis/redis.js.map +1 -1
- package/dist/src/security/auth.js +1 -1
- package/dist/src/security/auth.js.map +1 -1
- package/dist/src/sql/sqlTable.js +23 -8
- package/dist/src/sql/sqlTable.js.map +1 -1
- package/dist/tests/core/utilities/mocks/index.d.ts +0 -2
- package/dist/tests/core/utilities/mocks/index.js +1 -7
- package/dist/tests/core/utilities/mocks/index.js.map +1 -1
- package/dist/tests/core/utilities/structures/users.js +1 -1
- package/dist/tests/core/utilities/structures/users.js.map +1 -1
- package/dist/tests/jestSetup.js +7 -2
- package/dist/tests/jestSetup.js.map +1 -1
- package/package.json +5 -5
- package/src/environment.ts +29 -0
- package/src/events/processors/posthog/PosthogProcessor.ts +1 -1
- package/src/events/processors/posthog/tests/PosthogProcessor.spec.ts +16 -22
- package/src/features/index.ts +231 -81
- package/src/features/tests/features.spec.ts +204 -60
- package/src/index.ts +1 -1
- package/src/middleware/passport/sso/tests/oidc.spec.ts +4 -12
- package/src/middleware/passport/sso/tests/sso.spec.ts +10 -12
- package/src/plugin/tests/validation.spec.ts +168 -42
- package/src/redis/redis.ts +4 -0
- package/src/redis/tests/redis.spec.ts +5 -2
- package/src/security/auth.ts +1 -1
- package/src/security/tests/auth.spec.ts +2 -2
- package/src/sql/sqlTable.ts +21 -7
- package/tests/core/utilities/mocks/index.ts +0 -2
- package/tests/core/utilities/structures/users.ts +1 -1
- package/tests/jestSetup.ts +10 -3
- package/dist/tests/core/utilities/mocks/fetch.d.ts +0 -32
- package/dist/tests/core/utilities/mocks/fetch.js +0 -15
- package/dist/tests/core/utilities/mocks/fetch.js.map +0 -1
- package/dist/tests/core/utilities/mocks/posthog.d.ts +0 -0
- package/dist/tests/core/utilities/mocks/posthog.js +0 -9
- package/dist/tests/core/utilities/mocks/posthog.js.map +0 -1
- package/tests/core/utilities/mocks/fetch.ts +0 -17
- package/tests/core/utilities/mocks/posthog.ts +0 -7
|
@@ -1,86 +1,230 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IdentityContext, IdentityType, UserCtx } from "@budibase/types"
|
|
2
|
+
import { Flag, FlagSet, FlagValues, init } from "../"
|
|
2
3
|
import { context } from "../.."
|
|
3
|
-
import
|
|
4
|
+
import environment, { withEnv } from "../../environment"
|
|
5
|
+
import nodeFetch from "node-fetch"
|
|
6
|
+
import nock from "nock"
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
expected: Partial<Flags>
|
|
20
|
-
}
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
nock.cleanAll()
|
|
42
|
+
})
|
|
21
43
|
|
|
22
44
|
it.each<TestCase>([
|
|
23
45
|
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
expected: {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
expected:
|
|
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
|
-
|
|
50
|
-
({
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
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("
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
151
|
+
describe("plugin validation", () => {
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
nock.cleanAll()
|
|
154
|
+
mockAutomationSchema()
|
|
155
|
+
mockComponentSchema()
|
|
156
|
+
mockDatasourceSchema()
|
|
38
157
|
})
|
|
39
158
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
})
|
package/src/redis/redis.ts
CHANGED
|
@@ -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(() =>
|
|
26
|
+
afterAll(() => {
|
|
27
|
+
container?.stop()
|
|
28
|
+
closeAll()
|
|
29
|
+
})
|
|
27
30
|
|
|
28
31
|
beforeEach(async () => {
|
|
29
32
|
redis = new RedisWrapper(structures.db.id())
|
package/src/security/auth.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import env from "../environment"
|
|
2
2
|
|
|
3
|
-
export const PASSWORD_MIN_LENGTH = +(env.PASSWORD_MIN_LENGTH ||
|
|
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(
|