alepha 0.14.4 → 0.15.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.
- package/README.md +44 -102
- package/dist/api/audits/index.d.ts +331 -443
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +2 -2
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +0 -113
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +2 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +151 -262
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/notifications/index.browser.js +4 -4
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +164 -276
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +4 -4
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +265 -377
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/users/index.browser.js +1 -2
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +195 -301
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +203 -184
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +1 -2
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cache/redis/index.js +2 -2
- package/dist/cache/redis/index.js.map +1 -1
- package/dist/cli/index.d.ts +5900 -165
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1481 -639
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +8 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +29 -25
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +563 -54
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +175 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +564 -54
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +563 -54
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +4 -4
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +89 -42
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +129 -33
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +7969 -2
- package/dist/fake/index.d.ts.map +1 -1
- package/dist/fake/index.js +22 -22
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts +134 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js +253 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/redis/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +1 -2
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -5
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +19 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +28 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/chunk-DH6iiROE.js +38 -0
- package/dist/orm/index.browser.js +9 -9
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.bun.js +2821 -0
- package/dist/orm/index.bun.js.map +1 -0
- package/dist/orm/index.d.ts +318 -169
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +2086 -1776
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +4 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/redis/index.bun.js +285 -0
- package/dist/redis/index.bun.js.map +1 -0
- package/dist/redis/index.d.ts +13 -31
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +18 -38
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts.map +1 -1
- package/dist/router/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +83 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +393 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.browser.js +5 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +598 -112
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1808 -97
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +1200 -175
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +1268 -37
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +6 -3
- package/dist/server/cache/index.d.ts.map +1 -1
- package/dist/server/cache/index.js +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +115 -13
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +321 -139
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +0 -1
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +0 -1
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/links/index.browser.js +9 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.d.ts +1 -2
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +14 -7
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +514 -1
- package/dist/server/metrics/index.d.ts.map +1 -1
- package/dist/server/metrics/index.js +4462 -4
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/proxy/index.d.ts +0 -1
- package/dist/server/proxy/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/static/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +1 -2
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +1 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -1
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +10 -10
- package/dist/sms/index.js.map +1 -1
- package/dist/thread/index.d.ts +0 -1
- package/dist/thread/index.d.ts.map +1 -1
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/dist/vite/index.d.ts +6315 -149
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +140 -469
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +9 -9
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +28 -28
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +9 -9
- package/dist/websocket/index.js.map +1 -1
- package/package.json +13 -18
- package/src/api/files/controllers/AdminFileStatsController.ts +0 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +5 -0
- package/src/api/users/controllers/{UserRealmController.ts → RealmController.ts} +11 -11
- package/src/api/users/entities/users.ts +1 -1
- package/src/api/users/index.ts +8 -8
- package/src/api/users/primitives/{$userRealm.ts → $realm.ts} +17 -19
- package/src/api/users/providers/{UserRealmProvider.ts → RealmProvider.ts} +26 -30
- package/src/api/users/schemas/{userRealmConfigSchema.ts → realmConfigSchema.ts} +2 -2
- package/src/api/users/services/CredentialService.ts +7 -7
- package/src/api/users/services/IdentityService.ts +4 -4
- package/src/api/users/services/RegistrationService.spec.ts +25 -27
- package/src/api/users/services/RegistrationService.ts +38 -27
- package/src/api/users/services/SessionCrudService.ts +3 -3
- package/src/api/users/services/SessionService.spec.ts +3 -3
- package/src/api/users/services/SessionService.ts +27 -18
- package/src/api/users/services/UserService.ts +7 -7
- package/src/batch/providers/BatchProvider.ts +1 -2
- package/src/cli/apps/AlephaCli.ts +2 -2
- package/src/cli/apps/AlephaPackageBuilderCli.ts +47 -20
- package/src/cli/assets/apiHelloControllerTs.ts +19 -0
- package/src/cli/assets/apiIndexTs.ts +16 -0
- package/src/cli/assets/biomeJson.ts +2 -1
- package/src/cli/assets/claudeMd.ts +308 -0
- package/src/cli/assets/dummySpecTs.ts +2 -1
- package/src/cli/assets/editorconfig.ts +2 -1
- package/src/cli/assets/mainBrowserTs.ts +4 -3
- package/src/cli/assets/mainCss.ts +24 -0
- package/src/cli/assets/mainServerTs.ts +24 -0
- package/src/cli/assets/tsconfigJson.ts +2 -1
- package/src/cli/assets/webAppRouterTs.ts +16 -0
- package/src/cli/assets/webHelloComponentTsx.ts +20 -0
- package/src/cli/assets/webIndexTs.ts +16 -0
- package/src/cli/atoms/appEntryOptions.ts +13 -0
- package/src/cli/atoms/buildOptions.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +1 -1
- package/src/cli/commands/build.ts +97 -61
- package/src/cli/commands/db.ts +21 -18
- package/src/cli/commands/deploy.ts +17 -5
- package/src/cli/commands/dev.ts +26 -47
- package/src/cli/commands/gen/env.ts +1 -1
- package/src/cli/commands/init.ts +79 -25
- package/src/cli/commands/lint.ts +9 -3
- package/src/cli/commands/test.ts +8 -2
- package/src/cli/commands/typecheck.ts +5 -1
- package/src/cli/commands/verify.ts +4 -2
- package/src/cli/defineConfig.ts +9 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/providers/AppEntryProvider.ts +131 -0
- package/src/cli/providers/ViteBuildProvider.ts +82 -0
- package/src/cli/providers/ViteDevServerProvider.ts +350 -0
- package/src/cli/providers/ViteTemplateProvider.ts +27 -0
- package/src/cli/services/AlephaCliUtils.ts +72 -602
- package/src/cli/services/PackageManagerUtils.ts +308 -0
- package/src/cli/services/ProjectScaffolder.ts +329 -0
- package/src/command/helpers/Runner.ts +15 -3
- package/src/core/Alepha.ts +2 -8
- package/src/core/__tests__/Alepha-graph.spec.ts +4 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/primitives/$hook.ts +6 -2
- package/src/core/primitives/$module.spec.ts +4 -0
- package/src/core/primitives/$module.ts +12 -0
- package/src/core/providers/AlsProvider.ts +1 -1
- package/src/core/providers/CodecManager.spec.ts +12 -6
- package/src/core/providers/CodecManager.ts +26 -6
- package/src/core/providers/EventManager.ts +169 -13
- package/src/core/providers/KeylessJsonSchemaCodec.spec.ts +878 -0
- package/src/core/providers/KeylessJsonSchemaCodec.ts +789 -0
- package/src/core/providers/SchemaValidator.spec.ts +236 -0
- package/src/core/providers/StateManager.spec.ts +27 -16
- package/src/email/providers/LocalEmailProvider.spec.ts +111 -87
- package/src/email/providers/LocalEmailProvider.ts +52 -15
- package/src/email/providers/NodemailerEmailProvider.ts +167 -56
- package/src/file/errors/FileError.ts +7 -0
- package/src/file/index.ts +9 -1
- package/src/file/providers/MemoryFileSystemProvider.ts +393 -0
- package/src/logger/providers/PrettyFormatterProvider.ts +0 -9
- package/src/mcp/errors/McpError.ts +30 -0
- package/src/mcp/index.ts +3 -0
- package/src/mcp/transports/SseMcpTransport.ts +16 -6
- package/src/orm/index.browser.ts +1 -19
- package/src/orm/index.bun.ts +77 -0
- package/src/orm/index.shared-server.ts +22 -0
- package/src/orm/index.shared.ts +15 -0
- package/src/orm/index.ts +19 -39
- package/src/orm/providers/DrizzleKitProvider.ts +3 -5
- package/src/orm/providers/drivers/BunPostgresProvider.ts +3 -5
- package/src/orm/providers/drivers/BunSqliteProvider.ts +1 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +4 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +4 -0
- package/src/orm/providers/drivers/PglitePostgresProvider.ts +4 -0
- package/src/orm/services/Repository.ts +19 -0
- package/src/redis/index.bun.ts +35 -0
- package/src/redis/providers/BunRedisProvider.ts +12 -43
- package/src/redis/providers/BunRedisSubscriberProvider.ts +2 -3
- package/src/redis/providers/NodeRedisProvider.ts +16 -34
- package/src/{server/security → security}/__tests__/BasicAuth.spec.ts +11 -11
- package/src/{server/security → security}/__tests__/ServerSecurityProvider-realm.spec.ts +21 -16
- package/src/{server/security/providers → security/__tests__}/ServerSecurityProvider.spec.ts +5 -5
- package/src/security/index.browser.ts +5 -0
- package/src/security/index.ts +90 -7
- package/src/security/primitives/{$realm.spec.ts → $issuer.spec.ts} +11 -11
- package/src/security/primitives/{$realm.ts → $issuer.ts} +20 -17
- package/src/security/primitives/$role.ts +5 -5
- package/src/security/primitives/$serviceAccount.spec.ts +5 -5
- package/src/security/primitives/$serviceAccount.ts +3 -3
- package/src/{server/security → security}/providers/ServerSecurityProvider.ts +5 -7
- package/src/server/auth/primitives/$auth.ts +10 -10
- package/src/server/auth/primitives/$authCredentials.ts +3 -3
- package/src/server/auth/primitives/$authGithub.ts +3 -3
- package/src/server/auth/primitives/$authGoogle.ts +3 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +13 -13
- package/src/server/cache/providers/ServerCacheProvider.ts +1 -1
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -3
- package/src/server/core/index.ts +1 -1
- package/src/server/core/providers/BunHttpServerProvider.ts +1 -1
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +125 -0
- package/src/server/core/providers/NodeHttpServerProvider.ts +92 -24
- package/src/server/core/providers/ServerBodyParserProvider.ts +19 -23
- package/src/server/core/providers/ServerLoggerProvider.ts +23 -19
- package/src/server/core/providers/ServerProvider.ts +144 -24
- package/src/server/core/providers/ServerRouterProvider.ts +259 -115
- package/src/server/core/providers/ServerTimingProvider.ts +2 -2
- package/src/server/links/atoms/apiLinksAtom.ts +7 -0
- package/src/server/links/index.browser.ts +2 -0
- package/src/server/links/index.ts +3 -1
- package/src/server/links/providers/LinkProvider.ts +1 -1
- package/src/server/swagger/index.ts +1 -1
- package/src/sms/providers/LocalSmsProvider.spec.ts +153 -111
- package/src/sms/providers/LocalSmsProvider.ts +8 -7
- package/src/vite/index.ts +3 -2
- package/src/vite/tasks/buildClient.ts +0 -1
- package/src/vite/tasks/buildServer.ts +80 -22
- package/src/vite/tasks/copyAssets.ts +5 -4
- package/src/vite/tasks/generateCloudflare.ts +7 -0
- package/src/vite/tasks/generateSitemap.ts +64 -23
- package/src/vite/tasks/index.ts +0 -2
- package/src/vite/tasks/prerenderPages.ts +49 -24
- package/dist/server/security/index.browser.js +0 -13
- package/dist/server/security/index.browser.js.map +0 -1
- package/dist/server/security/index.d.ts +0 -173
- package/dist/server/security/index.d.ts.map +0 -1
- package/dist/server/security/index.js +0 -311
- package/dist/server/security/index.js.map +0 -1
- package/src/cli/assets/appRouterTs.ts +0 -9
- package/src/cli/assets/indexHtml.ts +0 -15
- package/src/cli/assets/mainTs.ts +0 -13
- package/src/cli/commands/format.ts +0 -17
- package/src/server/security/index.browser.ts +0 -10
- package/src/server/security/index.ts +0 -94
- package/src/vite/helpers/boot.ts +0 -106
- package/src/vite/plugins/viteAlephaDev.ts +0 -177
- package/src/vite/tasks/devServer.ts +0 -69
- package/src/vite/tasks/runAlepha.ts +0 -270
- /package/src/{server/security → security}/primitives/$basicAuth.ts +0 -0
- /package/src/{server/security → security}/providers/ServerBasicAuthProvider.ts +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { Alepha, t } from "alepha";
|
|
2
|
+
import { describe, test } from "vitest";
|
|
3
|
+
import { SchemaValidator } from "./SchemaValidator.ts";
|
|
4
|
+
|
|
5
|
+
describe("SchemaValidator", () => {
|
|
6
|
+
describe("Basic validation", () => {
|
|
7
|
+
test("should validate simple objects", async ({ expect }) => {
|
|
8
|
+
const alepha = Alepha.create();
|
|
9
|
+
const validator = alepha.inject(SchemaValidator);
|
|
10
|
+
|
|
11
|
+
const schema = t.object({
|
|
12
|
+
name: t.text(),
|
|
13
|
+
age: t.integer(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const data = {
|
|
17
|
+
name: "Alice",
|
|
18
|
+
age: 30,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const result = validator.validate(schema, data);
|
|
22
|
+
expect(result).toEqual(data);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("should trim strings when schema has trim option", async ({
|
|
26
|
+
expect,
|
|
27
|
+
}) => {
|
|
28
|
+
const alepha = Alepha.create();
|
|
29
|
+
const validator = alepha.inject(SchemaValidator);
|
|
30
|
+
|
|
31
|
+
const schema = t.object({
|
|
32
|
+
name: t.text({ trim: true }),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const data = {
|
|
36
|
+
name: " Alice ",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const result = validator.validate(schema, data);
|
|
40
|
+
expect(result.name).toBe("Alice");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("should convert null to undefined for non-nullable fields", async ({
|
|
44
|
+
expect,
|
|
45
|
+
}) => {
|
|
46
|
+
const alepha = Alepha.create();
|
|
47
|
+
const validator = alepha.inject(SchemaValidator);
|
|
48
|
+
|
|
49
|
+
const schema = t.object({
|
|
50
|
+
name: t.text(),
|
|
51
|
+
bio: t.optional(t.text()),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const data = {
|
|
55
|
+
name: "Alice",
|
|
56
|
+
bio: null,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const result = validator.validate(schema, data);
|
|
60
|
+
expect(result.name).toBe("Alice");
|
|
61
|
+
expect(result.bio).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("Security - Prototype Pollution Protection", () => {
|
|
66
|
+
test("should filter out __proto__ key from input data", async ({
|
|
67
|
+
expect,
|
|
68
|
+
}) => {
|
|
69
|
+
const alepha = Alepha.create();
|
|
70
|
+
const validator = alepha.inject(SchemaValidator);
|
|
71
|
+
|
|
72
|
+
const schema = t.object({
|
|
73
|
+
name: t.text(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Input data with __proto__ key via JSON.parse (bypasses literal protection)
|
|
77
|
+
const data = JSON.parse('{"name":"Alice","__proto__":{"isAdmin":true}}');
|
|
78
|
+
|
|
79
|
+
const result = validator.validate(schema, data);
|
|
80
|
+
|
|
81
|
+
// __proto__ should not be an own property in the result
|
|
82
|
+
expect(result.name).toBe("Alice");
|
|
83
|
+
// Using hasOwnProperty because 'in' operator has special behavior for __proto__
|
|
84
|
+
expect(Object.hasOwn(result, "__proto__")).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("should filter out constructor key from input data", async ({
|
|
88
|
+
expect,
|
|
89
|
+
}) => {
|
|
90
|
+
const alepha = Alepha.create();
|
|
91
|
+
const validator = alepha.inject(SchemaValidator);
|
|
92
|
+
|
|
93
|
+
const schema = t.object({
|
|
94
|
+
name: t.text(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const data = {
|
|
98
|
+
name: "Alice",
|
|
99
|
+
constructor: { prototype: { isAdmin: true } },
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const result = validator.validate(schema, data);
|
|
103
|
+
|
|
104
|
+
expect(result.name).toBe("Alice");
|
|
105
|
+
expect(Object.hasOwn(result, "constructor")).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("should filter out prototype key from input data", async ({
|
|
109
|
+
expect,
|
|
110
|
+
}) => {
|
|
111
|
+
const alepha = Alepha.create();
|
|
112
|
+
const validator = alepha.inject(SchemaValidator);
|
|
113
|
+
|
|
114
|
+
const schema = t.object({
|
|
115
|
+
name: t.text(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const data = {
|
|
119
|
+
name: "Alice",
|
|
120
|
+
prototype: { isAdmin: true },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = validator.validate(schema, data);
|
|
124
|
+
|
|
125
|
+
expect(result.name).toBe("Alice");
|
|
126
|
+
expect(Object.hasOwn(result, "prototype")).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("should filter unsafe keys from nested objects", async ({
|
|
130
|
+
expect,
|
|
131
|
+
}) => {
|
|
132
|
+
const alepha = Alepha.create();
|
|
133
|
+
const validator = alepha.inject(SchemaValidator);
|
|
134
|
+
|
|
135
|
+
const schema = t.object({
|
|
136
|
+
user: t.object({
|
|
137
|
+
name: t.text(),
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const data = {
|
|
142
|
+
user: {
|
|
143
|
+
name: "Alice",
|
|
144
|
+
__proto__: { isAdmin: true },
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const result = validator.validate(schema, data);
|
|
149
|
+
|
|
150
|
+
expect(result.user.name).toBe("Alice");
|
|
151
|
+
expect(Object.hasOwn(result.user, "__proto__")).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("should not pollute Object.prototype", async ({ expect }) => {
|
|
155
|
+
const alepha = Alepha.create();
|
|
156
|
+
const validator = alepha.inject(SchemaValidator);
|
|
157
|
+
|
|
158
|
+
const schema = t.object({
|
|
159
|
+
name: t.text(),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Attempt to pollute Object.prototype via __proto__
|
|
163
|
+
const maliciousData = JSON.parse(
|
|
164
|
+
'{"name":"Alice","__proto__":{"polluted":"yes"}}',
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
validator.validate(schema, maliciousData);
|
|
168
|
+
|
|
169
|
+
// Object.prototype should not be polluted
|
|
170
|
+
expect(({} as any).polluted).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("should create objects without prototype chain", async ({
|
|
174
|
+
expect,
|
|
175
|
+
}) => {
|
|
176
|
+
const alepha = Alepha.create();
|
|
177
|
+
const validator = alepha.inject(SchemaValidator);
|
|
178
|
+
|
|
179
|
+
const schema = t.object({
|
|
180
|
+
name: t.text(),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const data = {
|
|
184
|
+
name: "Alice",
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const result = validator.validate(schema, data);
|
|
188
|
+
|
|
189
|
+
// The result should not have Object.prototype methods directly accessible
|
|
190
|
+
// via hasOwnProperty (it should use Object.create(null))
|
|
191
|
+
expect(result.name).toBe("Alice");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("beforeParse", () => {
|
|
196
|
+
test("should handle arrays correctly", async ({ expect }) => {
|
|
197
|
+
const alepha = Alepha.create();
|
|
198
|
+
const validator = alepha.inject(SchemaValidator);
|
|
199
|
+
|
|
200
|
+
const schema = t.object({
|
|
201
|
+
tags: t.array(t.text({ trim: true })),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const data = {
|
|
205
|
+
tags: [" tag1 ", " tag2 "],
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const result = validator.validate(schema, data);
|
|
209
|
+
expect(result.tags).toEqual(["tag1", "tag2"]);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("should delete undefined values when option is enabled", async ({
|
|
213
|
+
expect,
|
|
214
|
+
}) => {
|
|
215
|
+
const alepha = Alepha.create();
|
|
216
|
+
const validator = alepha.inject(SchemaValidator);
|
|
217
|
+
|
|
218
|
+
const schema = t.object({
|
|
219
|
+
name: t.text(),
|
|
220
|
+
bio: t.optional(t.text()),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const data = {
|
|
224
|
+
name: "Alice",
|
|
225
|
+
bio: undefined,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const result = validator.validate(schema, data, {
|
|
229
|
+
deleteUndefined: true,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(result.name).toBe("Alice");
|
|
233
|
+
expect("bio" in result).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -209,12 +209,16 @@ describe("StateManager", () => {
|
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
it("should set values in ALS when context exists", async () => {
|
|
212
|
-
|
|
213
|
-
alepha.context.run(
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
// Note: AlsProvider spreads the store object, so we check values inside the callback
|
|
213
|
+
const result = alepha.context.run(
|
|
214
|
+
() => {
|
|
215
|
+
stateManager.set("name", "ALS Value");
|
|
216
|
+
return stateManager.get("name");
|
|
217
|
+
},
|
|
218
|
+
{ context: "test-context" },
|
|
219
|
+
);
|
|
216
220
|
|
|
217
|
-
expect(
|
|
221
|
+
expect(result).toBe("ALS Value");
|
|
218
222
|
});
|
|
219
223
|
|
|
220
224
|
it("should set values in local store when no ALS context", () => {
|
|
@@ -267,14 +271,16 @@ describe("StateManager", () => {
|
|
|
267
271
|
stateManager.set("name", "Local Value");
|
|
268
272
|
|
|
269
273
|
// In ALS context, ALS values should take priority
|
|
270
|
-
|
|
271
|
-
const result = alepha.context.run(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
// Note: AlsProvider spreads the store object, so we check values inside the callback
|
|
275
|
+
const result = alepha.context.run(
|
|
276
|
+
() => {
|
|
277
|
+
stateManager.set("name", "ALS Priority");
|
|
278
|
+
return stateManager.get("name");
|
|
279
|
+
},
|
|
280
|
+
{ context: "test-context" },
|
|
281
|
+
);
|
|
275
282
|
|
|
276
283
|
expect(result).toBe("ALS Priority");
|
|
277
|
-
expect(store.name).toBe("ALS Priority");
|
|
278
284
|
});
|
|
279
285
|
|
|
280
286
|
it("should not skip event emission when values are the same", async () => {
|
|
@@ -291,12 +297,17 @@ describe("StateManager", () => {
|
|
|
291
297
|
});
|
|
292
298
|
|
|
293
299
|
it("should delete values from ALS when context exists", async () => {
|
|
294
|
-
|
|
295
|
-
alepha.context.run(
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
// Note: AlsProvider spreads the store object, so we check values inside the callback
|
|
301
|
+
const result = alepha.context.run(
|
|
302
|
+
() => {
|
|
303
|
+
stateManager.set("name", "To Delete");
|
|
304
|
+
stateManager.del("name");
|
|
305
|
+
return stateManager.get("name");
|
|
306
|
+
},
|
|
307
|
+
{ context: "test-context" },
|
|
308
|
+
);
|
|
298
309
|
|
|
299
|
-
expect(
|
|
310
|
+
expect(result).toBeUndefined();
|
|
300
311
|
});
|
|
301
312
|
|
|
302
313
|
it("should clear only local store, not ALS", async () => {
|
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { FileSystemProvider, MemoryFileSystemProvider } from "alepha/file";
|
|
3
3
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
4
|
import { EmailError } from "../errors/EmailError.ts";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
LocalEmailProvider,
|
|
7
|
+
localEmailOptions,
|
|
8
|
+
} from "../providers/LocalEmailProvider.ts";
|
|
6
9
|
|
|
7
|
-
//
|
|
8
|
-
vi.mock("node:fs/promises");
|
|
9
|
-
vi.mock("node:path");
|
|
10
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
vi.mock("alepha/logger", () => ({
|
|
13
|
-
$logger: () => ({
|
|
14
|
-
debug: vi.fn(),
|
|
15
|
-
info: vi.fn(),
|
|
16
|
-
error: vi.fn(),
|
|
17
|
-
}),
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
const mockedFs = vi.mocked(fs);
|
|
21
|
-
const mockedPath = vi.mocked(path);
|
|
12
|
+
const DEFAULT_DIRECTORY = localEmailOptions.options.default.directory;
|
|
22
13
|
|
|
23
14
|
describe("LocalEmailProvider", () => {
|
|
24
|
-
let provider: LocalEmailProvider;
|
|
25
|
-
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
// Setup default path.join mock
|
|
29
|
-
mockedPath.join.mockImplementation((...args) => args.join("/"));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
15
|
describe("send", () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
provider = new LocalEmailProvider({ directory: "test-emails" });
|
|
35
|
-
});
|
|
36
|
-
|
|
37
16
|
test("should successfully send email to local file", async () => {
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
const alepha = Alepha.create().with({
|
|
18
|
+
provide: FileSystemProvider,
|
|
19
|
+
use: MemoryFileSystemProvider,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
23
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
24
|
+
await alepha.start();
|
|
40
25
|
|
|
41
26
|
const to = "test@example.com";
|
|
42
27
|
const subject = "Test Subject";
|
|
@@ -48,19 +33,20 @@ describe("LocalEmailProvider", () => {
|
|
|
48
33
|
body,
|
|
49
34
|
});
|
|
50
35
|
|
|
51
|
-
expect(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
expect(mockedFs.writeFile).toHaveBeenCalledWith(
|
|
55
|
-
expect.stringContaining("test@example.com"),
|
|
56
|
-
expect.stringContaining(subject),
|
|
57
|
-
"utf8",
|
|
58
|
-
);
|
|
36
|
+
expect(memoryFs.writeFileCalls).toHaveLength(1);
|
|
37
|
+
expect(memoryFs.writeFileCalls[0].path).toContain("test@example.com");
|
|
38
|
+
expect(memoryFs.writeFileCalls[0].data).toContain(subject);
|
|
59
39
|
});
|
|
60
40
|
|
|
61
41
|
test("should create proper filename with sanitized email and timestamp", async () => {
|
|
62
|
-
|
|
63
|
-
|
|
42
|
+
const alepha = Alepha.create().with({
|
|
43
|
+
provide: FileSystemProvider,
|
|
44
|
+
use: MemoryFileSystemProvider,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
48
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
49
|
+
await alepha.start();
|
|
64
50
|
|
|
65
51
|
const to = "user+test@example.com";
|
|
66
52
|
const subject = "Test Subject";
|
|
@@ -76,17 +62,24 @@ describe("LocalEmailProvider", () => {
|
|
|
76
62
|
body,
|
|
77
63
|
});
|
|
78
64
|
|
|
79
|
-
expect(
|
|
80
|
-
|
|
65
|
+
expect(memoryFs.joinCalls).toHaveLength(1);
|
|
66
|
+
expect(memoryFs.joinCalls[0]).toEqual([
|
|
67
|
+
DEFAULT_DIRECTORY,
|
|
81
68
|
"user_test@example.com+2023-01-01T12-00-00-000Z.html",
|
|
82
|
-
);
|
|
69
|
+
]);
|
|
83
70
|
|
|
84
71
|
vi.useRealTimers();
|
|
85
72
|
});
|
|
86
73
|
|
|
87
74
|
test("should sanitize special characters in email address", async () => {
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
const alepha = Alepha.create().with({
|
|
76
|
+
provide: FileSystemProvider,
|
|
77
|
+
use: MemoryFileSystemProvider,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
81
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
82
|
+
await alepha.start();
|
|
90
83
|
|
|
91
84
|
const to = "user<script>@example.com";
|
|
92
85
|
const subject = "Test Subject";
|
|
@@ -98,15 +91,21 @@ describe("LocalEmailProvider", () => {
|
|
|
98
91
|
body,
|
|
99
92
|
});
|
|
100
93
|
|
|
101
|
-
expect(
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
expect(memoryFs.joinCalls).toHaveLength(1);
|
|
95
|
+
expect(memoryFs.joinCalls[0][1]).toMatch(
|
|
96
|
+
/user_script_@example\.com\+.+\.html/,
|
|
104
97
|
);
|
|
105
98
|
});
|
|
106
99
|
|
|
107
100
|
test("should create proper HTML content", async () => {
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
const alepha = Alepha.create().with({
|
|
102
|
+
provide: FileSystemProvider,
|
|
103
|
+
use: MemoryFileSystemProvider,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
107
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
108
|
+
await alepha.start();
|
|
110
109
|
|
|
111
110
|
const to = "test@example.com";
|
|
112
111
|
const subject = "Test <Subject>";
|
|
@@ -118,8 +117,7 @@ describe("LocalEmailProvider", () => {
|
|
|
118
117
|
body,
|
|
119
118
|
});
|
|
120
119
|
|
|
121
|
-
const
|
|
122
|
-
const htmlContent = writeCall[1] as string;
|
|
120
|
+
const htmlContent = memoryFs.writeFileCalls[0].data;
|
|
123
121
|
|
|
124
122
|
expect(htmlContent).toContain("<!DOCTYPE html>");
|
|
125
123
|
expect(htmlContent).toContain("Test <Subject>"); // escaped subject
|
|
@@ -130,36 +128,17 @@ describe("LocalEmailProvider", () => {
|
|
|
130
128
|
expect(htmlContent).toContain("Sent:");
|
|
131
129
|
});
|
|
132
130
|
|
|
133
|
-
test("should throw EmailError when
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const subject = "Test Subject";
|
|
139
|
-
const body = "<p>Test body</p>";
|
|
131
|
+
test("should throw EmailError when writeFile fails", async () => {
|
|
132
|
+
const alepha = Alepha.create().with({
|
|
133
|
+
provide: FileSystemProvider,
|
|
134
|
+
use: MemoryFileSystemProvider,
|
|
135
|
+
});
|
|
140
136
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
subject,
|
|
145
|
-
body,
|
|
146
|
-
}),
|
|
147
|
-
).rejects.toThrow(EmailError);
|
|
148
|
-
await expect(
|
|
149
|
-
provider.send({
|
|
150
|
-
to,
|
|
151
|
-
subject,
|
|
152
|
-
body,
|
|
153
|
-
}),
|
|
154
|
-
).rejects.toThrow(
|
|
155
|
-
"Failed to save email to local file: Permission denied",
|
|
156
|
-
);
|
|
157
|
-
});
|
|
137
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
138
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
139
|
+
await alepha.start();
|
|
158
140
|
|
|
159
|
-
|
|
160
|
-
mockedFs.mkdir.mockResolvedValue(undefined);
|
|
161
|
-
const writeError = new Error("Disk full");
|
|
162
|
-
mockedFs.writeFile.mockRejectedValue(writeError);
|
|
141
|
+
memoryFs.writeFileError = new Error("Disk full");
|
|
163
142
|
|
|
164
143
|
const to = "test@example.com";
|
|
165
144
|
const subject = "Test Subject";
|
|
@@ -172,6 +151,7 @@ describe("LocalEmailProvider", () => {
|
|
|
172
151
|
body,
|
|
173
152
|
}),
|
|
174
153
|
).rejects.toThrow(EmailError);
|
|
154
|
+
|
|
175
155
|
await expect(
|
|
176
156
|
provider.send({
|
|
177
157
|
to,
|
|
@@ -182,8 +162,16 @@ describe("LocalEmailProvider", () => {
|
|
|
182
162
|
});
|
|
183
163
|
|
|
184
164
|
test("should handle non-Error exceptions", async () => {
|
|
185
|
-
|
|
186
|
-
|
|
165
|
+
const alepha = Alepha.create().with({
|
|
166
|
+
provide: FileSystemProvider,
|
|
167
|
+
use: MemoryFileSystemProvider,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
171
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
172
|
+
await alepha.start();
|
|
173
|
+
|
|
174
|
+
memoryFs.writeFileError = "String error" as unknown as Error;
|
|
187
175
|
|
|
188
176
|
const to = "test@example.com";
|
|
189
177
|
const subject = "Test Subject";
|
|
@@ -196,6 +184,7 @@ describe("LocalEmailProvider", () => {
|
|
|
196
184
|
body,
|
|
197
185
|
}),
|
|
198
186
|
).rejects.toThrow(EmailError);
|
|
187
|
+
|
|
199
188
|
await expect(
|
|
200
189
|
provider.send({
|
|
201
190
|
to,
|
|
@@ -204,11 +193,39 @@ describe("LocalEmailProvider", () => {
|
|
|
204
193
|
}),
|
|
205
194
|
).rejects.toThrow("Failed to save email to local file: String error");
|
|
206
195
|
});
|
|
196
|
+
|
|
197
|
+
test("should handle multiple recipients", async () => {
|
|
198
|
+
const alepha = Alepha.create().with({
|
|
199
|
+
provide: FileSystemProvider,
|
|
200
|
+
use: MemoryFileSystemProvider,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const provider = alepha.inject(LocalEmailProvider);
|
|
204
|
+
const memoryFs = alepha.inject(MemoryFileSystemProvider);
|
|
205
|
+
await alepha.start();
|
|
206
|
+
|
|
207
|
+
await provider.send({
|
|
208
|
+
to: ["user1@example.com", "user2@example.com"],
|
|
209
|
+
subject: "Broadcast",
|
|
210
|
+
body: "<p>Hello all</p>",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(memoryFs.writeFileCalls).toHaveLength(2);
|
|
214
|
+
expect(memoryFs.writeFileCalls[0].path).toContain("user1@example.com");
|
|
215
|
+
expect(memoryFs.writeFileCalls[1].path).toContain("user2@example.com");
|
|
216
|
+
});
|
|
207
217
|
});
|
|
208
218
|
|
|
209
219
|
describe("createEmailHtml", () => {
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
let provider: LocalEmailProvider;
|
|
221
|
+
|
|
222
|
+
beforeEach(async () => {
|
|
223
|
+
const alepha = Alepha.create().with({
|
|
224
|
+
provide: FileSystemProvider,
|
|
225
|
+
use: MemoryFileSystemProvider,
|
|
226
|
+
});
|
|
227
|
+
provider = alepha.inject(LocalEmailProvider);
|
|
228
|
+
await alepha.start();
|
|
212
229
|
});
|
|
213
230
|
|
|
214
231
|
test("should create proper HTML structure", () => {
|
|
@@ -290,8 +307,15 @@ describe("LocalEmailProvider", () => {
|
|
|
290
307
|
});
|
|
291
308
|
|
|
292
309
|
describe("escapeHtml", () => {
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
let provider: LocalEmailProvider;
|
|
311
|
+
|
|
312
|
+
beforeEach(async () => {
|
|
313
|
+
const alepha = Alepha.create().with({
|
|
314
|
+
provide: FileSystemProvider,
|
|
315
|
+
use: MemoryFileSystemProvider,
|
|
316
|
+
});
|
|
317
|
+
provider = alepha.inject(LocalEmailProvider);
|
|
318
|
+
await alepha.start();
|
|
295
319
|
});
|
|
296
320
|
|
|
297
321
|
test("should escape ampersands", () => {
|