@lenne.tech/nest-server 11.32.3 → 11.33.0
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/.claude/rules/architecture.md +16 -0
- package/.claude/rules/configurable-features.md +11 -3
- package/.claude/rules/testing.md +101 -1
- package/CLAUDE.md +5 -0
- package/FRAMEWORK-API.md +58 -3
- package/bin/migrate.js +13 -3
- package/dist/core/common/helpers/content-disposition.helper.d.ts +1 -0
- package/dist/core/common/helpers/content-disposition.helper.js +14 -0
- package/dist/core/common/helpers/content-disposition.helper.js.map +1 -0
- package/dist/core/common/helpers/file.helper.d.ts +21 -2
- package/dist/core/common/helpers/file.helper.js +69 -13
- package/dist/core/common/helpers/file.helper.js.map +1 -1
- package/dist/core/common/helpers/graceful-shutdown.helper.d.ts +2 -0
- package/dist/core/common/helpers/graceful-shutdown.helper.js +55 -0
- package/dist/core/common/helpers/graceful-shutdown.helper.js.map +1 -0
- package/dist/core/common/helpers/project-name.helper.d.ts +2 -0
- package/dist/core/common/helpers/project-name.helper.js +38 -0
- package/dist/core/common/helpers/project-name.helper.js.map +1 -0
- package/dist/core/common/interfaces/cron-job-config.interface.d.ts +1 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +39 -0
- package/dist/core/common/services/core-cron-jobs.initializer.d.ts +10 -0
- package/dist/core/common/services/core-cron-jobs.initializer.js +46 -0
- package/dist/core/common/services/core-cron-jobs.initializer.js.map +1 -0
- package/dist/core/common/services/core-cron-jobs.registry.d.ts +8 -0
- package/dist/core/common/services/core-cron-jobs.registry.js +12 -0
- package/dist/core/common/services/core-cron-jobs.registry.js.map +1 -0
- package/dist/core/common/services/core-cron-jobs.service.d.ts +37 -7
- package/dist/core/common/services/core-cron-jobs.service.js +278 -27
- package/dist/core/common/services/core-cron-jobs.service.js.map +1 -1
- package/dist/core/common/services/core-redis-pubsub.d.ts +20 -0
- package/dist/core/common/services/core-redis-pubsub.js +112 -0
- package/dist/core/common/services/core-redis-pubsub.js.map +1 -0
- package/dist/core/common/services/core-redis.service.d.ts +25 -0
- package/dist/core/common/services/core-redis.service.js +154 -0
- package/dist/core/common/services/core-redis.service.js.map +1 -0
- package/dist/core/common/services/core-s3.service.d.ts +51 -0
- package/dist/core/common/services/core-s3.service.js +261 -0
- package/dist/core/common/services/core-s3.service.js.map +1 -0
- package/dist/core/common/services/core-trust-proxy.initializer.d.ts +16 -0
- package/dist/core/common/services/core-trust-proxy.initializer.js +77 -0
- package/dist/core/common/services/core-trust-proxy.initializer.js.map +1 -0
- package/dist/core/common/services/rate-limit-store.d.ts +61 -0
- package/dist/core/common/services/rate-limit-store.js +235 -0
- package/dist/core/common/services/rate-limit-store.js.map +1 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +19 -3
- package/dist/core/modules/ai/core-ai-mcp.controller.js +148 -18
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai.service.d.ts +7 -2
- package/dist/core/modules/ai/services/core-ai.service.js +24 -20
- package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
- package/dist/core/modules/auth/core-auth.module.js +4 -1
- package/dist/core/modules/auth/core-auth.module.js.map +1 -1
- package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.d.ts +1 -1
- package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js +3 -14
- package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js.map +1 -1
- package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.d.ts +12 -10
- package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js +30 -59
- package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +8 -2
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +118 -45
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.d.ts +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js +2 -11
- package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.d.ts +12 -10
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js +30 -85
- package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +0 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/file/core-file.controller.d.ts +15 -3
- package/dist/core/modules/file/core-file.controller.js +90 -20
- package/dist/core/modules/file/core-file.controller.js.map +1 -1
- package/dist/core/modules/file/core-file.resolver.d.ts +4 -4
- package/dist/core/modules/file/core-file.resolver.js +23 -16
- package/dist/core/modules/file/core-file.resolver.js.map +1 -1
- package/dist/core/modules/file/core-file.service.d.ts +41 -7
- package/dist/core/modules/file/core-file.service.js +227 -7
- package/dist/core/modules/file/core-file.service.js.map +1 -1
- package/dist/core/modules/file/file-metadata.helper.d.ts +16 -0
- package/dist/core/modules/file/file-metadata.helper.js +33 -0
- package/dist/core/modules/file/file-metadata.helper.js.map +1 -0
- package/dist/core/modules/file/file-roles.helper.d.ts +4 -0
- package/dist/core/modules/file/file-roles.helper.js +53 -0
- package/dist/core/modules/file/file-roles.helper.js.map +1 -0
- package/dist/core/modules/file/file-storage.helper.d.ts +13 -0
- package/dist/core/modules/file/file-storage.helper.js +65 -0
- package/dist/core/modules/file/file-storage.helper.js.map +1 -0
- package/dist/core/modules/file/filesystem-file.helper.d.ts +22 -0
- package/dist/core/modules/file/filesystem-file.helper.js +77 -0
- package/dist/core/modules/file/filesystem-file.helper.js.map +1 -0
- package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +1 -0
- package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +7 -1
- package/dist/core/modules/file/s3-file.helper.d.ts +37 -0
- package/dist/core/modules/file/s3-file.helper.js +81 -0
- package/dist/core/modules/file/s3-file.helper.js.map +1 -0
- package/dist/core/modules/hub/core-hub.controller.d.ts +8 -8
- package/dist/core/modules/hub/core-hub.controller.js +24 -22
- package/dist/core/modules/hub/core-hub.controller.js.map +1 -1
- package/dist/core/modules/hub/helpers/hub-mask.helper.js +2 -2
- package/dist/core/modules/hub/helpers/hub-mask.helper.js.map +1 -1
- package/dist/core/modules/hub/hub-buffer.d.ts +23 -0
- package/dist/core/modules/hub/hub-buffer.js +101 -0
- package/dist/core/modules/hub/hub-buffer.js.map +1 -0
- package/dist/core/modules/hub/services/core-hub-mailbox.service.d.ts +7 -5
- package/dist/core/modules/hub/services/core-hub-mailbox.service.js +14 -10
- package/dist/core/modules/hub/services/core-hub-mailbox.service.js.map +1 -1
- package/dist/core/modules/hub/services/hub-log-buffer.service.d.ts +5 -2
- package/dist/core/modules/hub/services/hub-log-buffer.service.js +17 -8
- package/dist/core/modules/hub/services/hub-log-buffer.service.js.map +1 -1
- package/dist/core/modules/hub/services/hub-query-profiler.service.d.ts +4 -2
- package/dist/core/modules/hub/services/hub-query-profiler.service.js +13 -7
- package/dist/core/modules/hub/services/hub-query-profiler.service.js.map +1 -1
- package/dist/core/modules/hub/services/hub-trace-buffer.service.d.ts +4 -2
- package/dist/core/modules/hub/services/hub-trace-buffer.service.js +13 -8
- package/dist/core/modules/hub/services/hub-trace-buffer.service.js.map +1 -1
- package/dist/core/modules/migrate/cli/migrate-cli.d.ts +3 -1
- package/dist/core/modules/migrate/cli/migrate-cli.js +29 -4
- package/dist/core/modules/migrate/cli/migrate-cli.js.map +1 -1
- package/dist/core/modules/migrate/helpers/migration.helper.d.ts +3 -6
- package/dist/core/modules/migrate/helpers/migration.helper.js +55 -8
- package/dist/core/modules/migrate/helpers/migration.helper.js.map +1 -1
- package/dist/core/modules/migrate/migration-runner.d.ts +1 -0
- package/dist/core/modules/migrate/migration-runner.js +4 -0
- package/dist/core/modules/migrate/migration-runner.js.map +1 -1
- package/dist/core/modules/migrate/mongo-state-store.d.ts +1 -0
- package/dist/core/modules/migrate/mongo-state-store.js +98 -9
- package/dist/core/modules/migrate/mongo-state-store.js.map +1 -1
- package/dist/core/modules/system-setup/core-system-setup.service.d.ts +2 -0
- package/dist/core/modules/system-setup/core-system-setup.service.js +31 -1
- package/dist/core/modules/system-setup/core-system-setup.service.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.guard.d.ts +17 -3
- package/dist/core/modules/tenant/core-tenant.guard.js +92 -15
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.module.js +3 -4
- package/dist/core/modules/tenant/core-tenant.module.js.map +1 -1
- package/dist/core/modules/tus/core-tus.controller.d.ts +2 -0
- package/dist/core/modules/tus/core-tus.controller.js +29 -3
- package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
- package/dist/core/modules/tus/core-tus.service.d.ts +24 -1
- package/dist/core/modules/tus/core-tus.service.js +182 -28
- package/dist/core/modules/tus/core-tus.service.js.map +1 -1
- package/dist/core/modules/tus/interfaces/tus-config.interface.js +3 -0
- package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
- package/dist/core/modules/tus/tus-redis-locker.d.ts +25 -0
- package/dist/core/modules/tus/tus-redis-locker.js +87 -0
- package/dist/core/modules/tus/tus-redis-locker.js.map +1 -0
- package/dist/core/modules/tus/tus.module.d.ts +1 -0
- package/dist/core/modules/tus/tus.module.js +30 -4
- package/dist/core/modules/tus/tus.module.js.map +1 -1
- package/dist/core.module.js +18 -1
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/server/modules/file/file.controller.js.map +1 -1
- package/dist/server/modules/file/file.resolver.js +1 -12
- package/dist/server/modules/file/file.resolver.js.map +1 -1
- package/dist/server/modules/file/file.service.d.ts +5 -1
- package/dist/server/modules/file/file.service.js +12 -3
- package/dist/server/modules/file/file.service.js.map +1 -1
- package/dist/server/modules/user/avatar.controller.d.ts +5 -1
- package/dist/server/modules/user/avatar.controller.js +26 -10
- package/dist/server/modules/user/avatar.controller.js.map +1 -1
- package/dist/server/modules/user/user.model.js +3 -2
- package/dist/server/modules/user/user.model.js.map +1 -1
- package/dist/server/modules/user/user.module.js +6 -2
- package/dist/server/modules/user/user.module.js.map +1 -1
- package/dist/server/modules/user/user.service.d.ts +1 -1
- package/dist/server/modules/user/user.service.js +6 -13
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +57 -8
- package/docs/security-overrides.md +30 -5
- package/migration-guides/11.32.3-to-11.32.4.md +335 -0
- package/migration-guides/11.32.x-to-11.33.x.md +1551 -0
- package/package.json +74 -42
- package/src/core/common/helpers/content-disposition.helper.ts +90 -0
- package/src/core/common/helpers/file.helper.ts +201 -20
- package/src/core/common/helpers/graceful-shutdown.helper.ts +116 -0
- package/src/core/common/helpers/project-name.helper.ts +71 -0
- package/src/core/common/interfaces/cron-job-config.interface.ts +16 -0
- package/src/core/common/interfaces/server-options.interface.ts +439 -2
- package/src/core/common/services/core-cron-jobs.initializer.ts +39 -0
- package/src/core/common/services/core-cron-jobs.registry.ts +45 -0
- package/src/core/common/services/core-cron-jobs.service.ts +589 -47
- package/src/core/common/services/core-redis-pubsub.ts +183 -0
- package/src/core/common/services/core-redis.service.ts +249 -0
- package/src/core/common/services/core-s3.service.ts +465 -0
- package/src/core/common/services/core-trust-proxy.initializer.ts +112 -0
- package/src/core/common/services/rate-limit-store.ts +492 -0
- package/src/core/modules/ai/README.md +25 -0
- package/src/core/modules/ai/core-ai-mcp.controller.ts +253 -22
- package/src/core/modules/ai/services/core-ai.service.ts +33 -24
- package/src/core/modules/auth/core-auth.module.ts +7 -1
- package/src/core/modules/auth/guards/legacy-auth-rate-limit.guard.ts +12 -21
- package/src/core/modules/auth/services/legacy-auth-rate-limiter.service.ts +49 -93
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +202 -66
- package/src/core/modules/better-auth/core-better-auth-rate-limit.middleware.ts +11 -18
- package/src/core/modules/better-auth/core-better-auth-rate-limiter.service.ts +51 -132
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +7 -1
- package/src/core/modules/file/INTEGRATION-CHECKLIST.md +108 -0
- package/src/core/modules/file/README.md +324 -41
- package/src/core/modules/file/core-file.controller.ts +267 -19
- package/src/core/modules/file/core-file.resolver.ts +46 -12
- package/src/core/modules/file/core-file.service.ts +482 -16
- package/src/core/modules/file/file-metadata.helper.ts +142 -0
- package/src/core/modules/file/file-roles.helper.ts +110 -0
- package/src/core/modules/file/file-storage.helper.ts +163 -0
- package/src/core/modules/file/filesystem-file.helper.ts +184 -0
- package/src/core/modules/file/interfaces/file-service-options.interface.ts +14 -0
- package/src/core/modules/file/interfaces/file-upload.interface.ts +17 -1
- package/src/core/modules/file/s3-file.helper.ts +209 -0
- package/src/core/modules/hub/README.md +48 -0
- package/src/core/modules/hub/core-hub.controller.ts +18 -16
- package/src/core/modules/hub/helpers/hub-mask.helper.ts +21 -4
- package/src/core/modules/hub/hub-buffer.ts +198 -0
- package/src/core/modules/hub/services/core-hub-mailbox.service.ts +22 -12
- package/src/core/modules/hub/services/hub-log-buffer.service.ts +24 -8
- package/src/core/modules/hub/services/hub-query-profiler.service.ts +9 -6
- package/src/core/modules/hub/services/hub-trace-buffer.service.ts +14 -9
- package/src/core/modules/migrate/README.md +68 -6
- package/src/core/modules/migrate/cli/migrate-cli.ts +69 -6
- package/src/core/modules/migrate/helpers/migration.helper.ts +144 -10
- package/src/core/modules/migrate/migration-runner.ts +10 -1
- package/src/core/modules/migrate/mongo-state-store.ts +179 -11
- package/src/core/modules/system-setup/README.md +7 -2
- package/src/core/modules/system-setup/core-system-setup.service.ts +95 -1
- package/src/core/modules/tenant/README.md +21 -1
- package/src/core/modules/tenant/core-tenant.guard.ts +135 -14
- package/src/core/modules/tenant/core-tenant.module.ts +8 -4
- package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +93 -21
- package/src/core/modules/tus/README.md +35 -2
- package/src/core/modules/tus/core-tus.controller.ts +64 -12
- package/src/core/modules/tus/core-tus.service.ts +358 -34
- package/src/core/modules/tus/interfaces/tus-config.interface.ts +9 -0
- package/src/core/modules/tus/tus-redis-locker.ts +139 -0
- package/src/core/modules/tus/tus.module.ts +78 -6
- package/src/core.module.ts +26 -1
- package/src/index.ts +10 -0
- package/src/main.ts +7 -1
- package/src/server/modules/file/file.controller.ts +7 -3
- package/src/server/modules/file/file.resolver.ts +5 -17
- package/src/server/modules/file/file.service.ts +42 -3
- package/src/server/modules/user/avatar.controller.ts +39 -9
- package/src/server/modules/user/user.model.ts +9 -3
- package/src/server/modules/user/user.module.ts +12 -3
- package/src/server/modules/user/user.service.ts +20 -17
- package/src/test/README.md +7 -2
- package/src/core/common/helpers/logging.helper.spec.ts +0 -61
- package/src/core/common/helpers/process-diagnostics.helper.spec.ts +0 -310
- package/src/core/common/services/brevo.service.spec.ts +0 -266
- package/src/core/modules/hub/core-hub.module.spec.ts +0 -108
- package/src/core/modules/hub/helpers/hub-command-shape.helper.spec.ts +0 -48
- package/src/core/modules/hub/helpers/hub-mask.helper.spec.ts +0 -67
- package/src/core/modules/hub/helpers/hub-mermaid.helper.spec.ts +0 -61
- package/src/core/modules/hub/helpers/hub-shell.helper.spec.ts +0 -106
- package/src/core/modules/hub/hub-config.helper.spec.ts +0 -108
- package/src/core/modules/hub/hub-ring-buffer.spec.ts +0 -95
- package/src/core/modules/hub/services/core-hub-mailbox.service.spec.ts +0 -116
- package/src/core/modules/hub/services/hub-trace-buffer.service.spec.ts +0 -112
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { PATH_METADATA } from '@nestjs/common/constants';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import { CoreHubController } from './core-hub.controller';
|
|
5
|
-
import { CoreHubModule } from './core-hub.module';
|
|
6
|
-
|
|
7
|
-
const ctx = { env: 'local', version: '1.0.0' };
|
|
8
|
-
|
|
9
|
-
describe('CoreHubModule.forRoot', () => {
|
|
10
|
-
it('builds a dynamic module with the hub controller', () => {
|
|
11
|
-
const mod = CoreHubModule.forRoot({ config: {}, ctx });
|
|
12
|
-
expect(mod.module).toBe(CoreHubModule);
|
|
13
|
-
expect(mod.controllers?.length).toBeGreaterThan(0);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('becomes global and provides the email-capture binding when the mailbox is enabled', () => {
|
|
17
|
-
const mod = CoreHubModule.forRoot({ config: { mailbox: { mode: 'capture' } }, ctx });
|
|
18
|
-
expect(mod.global).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('is not global when the mailbox is disabled', () => {
|
|
22
|
-
const mod = CoreHubModule.forRoot({ config: {}, ctx });
|
|
23
|
-
expect(mod.global).toBeFalsy();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('throws when mailbox capture mode is used in production (silent mail suppression guard)', () => {
|
|
27
|
-
expect(() =>
|
|
28
|
-
CoreHubModule.forRoot({ config: { mailbox: { mode: 'capture' } }, ctx: { env: 'production', version: '1' } }),
|
|
29
|
-
).toThrow(/capture.*not permitted/i);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('allows mailbox copy mode in production', () => {
|
|
33
|
-
expect(() =>
|
|
34
|
-
CoreHubModule.forRoot({ config: { mailbox: { mode: 'copy' } }, ctx: { env: 'production', version: '1' } }),
|
|
35
|
-
).not.toThrow();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('public-access guard (roles: false in a reachable env)', () => {
|
|
39
|
-
it('throws when roles:false in production without acknowledgment', () => {
|
|
40
|
-
expect(() =>
|
|
41
|
-
CoreHubModule.forRoot({ config: { roles: false }, ctx: { env: 'production', version: '1' } }),
|
|
42
|
-
).toThrow(/roles: false.*not permitted/i);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('throws when roles:false in staging without acknowledgment', () => {
|
|
46
|
-
expect(() => CoreHubModule.forRoot({ config: { roles: false }, ctx: { env: 'staging', version: '1' } })).toThrow(
|
|
47
|
-
/not permitted/i,
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('throws for CUSTOM reachable env names too (fail-safe — not just production/staging)', () => {
|
|
52
|
-
// Regression guard for the exact-string bypass: prod / live / preprod / production-eu / staging-2
|
|
53
|
-
// must all be treated as reachable.
|
|
54
|
-
for (const env of ['prod', 'live', 'preprod', 'production-eu', 'staging-2']) {
|
|
55
|
-
expect(() => CoreHubModule.forRoot({ config: { roles: false }, ctx: { env, version: '1' } })).toThrow(
|
|
56
|
-
/not permitted/i,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('allows roles:false in production ONLY with explicit allowPublicAccessInProduction', () => {
|
|
62
|
-
expect(() =>
|
|
63
|
-
CoreHubModule.forRoot({
|
|
64
|
-
config: { allowPublicAccessInProduction: true, roles: false },
|
|
65
|
-
ctx: { env: 'production', version: '1' },
|
|
66
|
-
}),
|
|
67
|
-
).not.toThrow();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('allows roles:false in non-reachable envs (local, development)', () => {
|
|
71
|
-
expect(() => CoreHubModule.forRoot({ config: { roles: false }, ctx })).not.toThrow(); // ctx = local
|
|
72
|
-
expect(() =>
|
|
73
|
-
CoreHubModule.forRoot({ config: { roles: false }, ctx: { env: 'development', version: '1' } }),
|
|
74
|
-
).not.toThrow();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('allows the default ADMIN gate in production (no roles set)', () => {
|
|
78
|
-
expect(() => CoreHubModule.forRoot({ config: {}, ctx: { env: 'production', version: '1' } })).not.toThrow();
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('runtime path/roles metadata (shared controller class)', () => {
|
|
83
|
-
it('writes the configured base path onto the controller class', () => {
|
|
84
|
-
CoreHubModule.forRoot({ config: { path: 'cockpit' }, ctx });
|
|
85
|
-
expect(Reflect.getMetadata(PATH_METADATA, CoreHubController)).toBe('cockpit');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('fully overwrites stale metadata on a second forRoot (no cross-instance pollution)', () => {
|
|
89
|
-
// The controller class is shared across app instances in the same process (parallel tests). A
|
|
90
|
-
// second forRoot must overwrite the first, never leak the previous path/roles.
|
|
91
|
-
CoreHubModule.forRoot({ config: { path: 'first', roles: 'admin' }, ctx });
|
|
92
|
-
expect(Reflect.getMetadata(PATH_METADATA, CoreHubController)).toBe('first');
|
|
93
|
-
expect(Reflect.getMetadata('roles', CoreHubController)).toEqual(['admin']);
|
|
94
|
-
|
|
95
|
-
CoreHubModule.forRoot({ config: { path: 'second', roles: ['ops', 'admin'] }, ctx });
|
|
96
|
-
expect(Reflect.getMetadata(PATH_METADATA, CoreHubController)).toBe('second');
|
|
97
|
-
expect(Reflect.getMetadata('roles', CoreHubController)).toEqual(['ops', 'admin']);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('clears the roles metadata when roles: false (public opt-out)', () => {
|
|
101
|
-
CoreHubModule.forRoot({ config: { roles: 'admin' }, ctx });
|
|
102
|
-
expect(Reflect.getMetadata('roles', CoreHubController)).toEqual(['admin']);
|
|
103
|
-
|
|
104
|
-
CoreHubModule.forRoot({ config: { roles: false }, ctx });
|
|
105
|
-
expect(Reflect.getMetadata('roles', CoreHubController)).toBeUndefined();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { normalizeCommandShape } from './hub-command-shape.helper';
|
|
4
|
-
|
|
5
|
-
describe('normalizeCommandShape', () => {
|
|
6
|
-
it('replaces scalar values with a placeholder but keeps keys', () => {
|
|
7
|
-
const out = normalizeCommandShape({ email: 'alice@example.com', age: 30, active: true });
|
|
8
|
-
expect(out).toEqual({ active: '?', age: '?', email: '?' });
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('keeps Mongo operators as keys (they are structure, not data)', () => {
|
|
12
|
-
const out = normalizeCommandShape({ age: { $gte: 18, $lt: 65 } });
|
|
13
|
-
expect(out).toEqual({ age: { $gte: '?', $lt: '?' } });
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('collapses scalar arrays ($in) to a single placeholder', () => {
|
|
17
|
-
const out = normalizeCommandShape({ id: { $in: [1, 2, 3, 4] } });
|
|
18
|
-
expect(out).toEqual({ id: { $in: ['?'] } });
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('keeps per-stage shape for pipeline-like object arrays', () => {
|
|
22
|
-
const out = normalizeCommandShape([{ $match: { x: 1 } }, { $group: { _id: '$y' } }]) as unknown[];
|
|
23
|
-
expect(out).toHaveLength(2);
|
|
24
|
-
expect(out[0]).toEqual({ $match: { x: '?' } });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('does NOT leak the original values (privacy)', () => {
|
|
28
|
-
const secret = 'super-secret@example.com';
|
|
29
|
-
const out = normalizeCommandShape({ filter: { email: secret } });
|
|
30
|
-
expect(JSON.stringify(out)).not.toContain(secret);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('caps recursion depth', () => {
|
|
34
|
-
let deep: any = 'leaf';
|
|
35
|
-
for (let i = 0; i < 20; i++) {
|
|
36
|
-
deep = { nested: deep };
|
|
37
|
-
}
|
|
38
|
-
const out = JSON.stringify(normalizeCommandShape(deep));
|
|
39
|
-
// Must terminate and not blow the stack; the sentinel marks the cut.
|
|
40
|
-
expect(out).toContain('…');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('produces identical output for identical query shapes (the N+1 template key)', () => {
|
|
44
|
-
const a = normalizeCommandShape({ filter: { email: 'a@x.com' }, limit: 1 });
|
|
45
|
-
const b = normalizeCommandShape({ filter: { email: 'b@y.com' }, limit: 999 });
|
|
46
|
-
expect(JSON.stringify(a)).toBe(JSON.stringify(b));
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { maskConfigDeep } from './hub-mask.helper';
|
|
4
|
-
|
|
5
|
-
describe('maskConfigDeep', () => {
|
|
6
|
-
it('masks values whose key matches the secret pattern', () => {
|
|
7
|
-
const out = maskConfigDeep({
|
|
8
|
-
jwt: { secret: 'SENTINEL_JWT', refresh: { secret: 'SENTINEL_REFRESH' } },
|
|
9
|
-
port: 3000,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
expect(out.jwt.secret).toBe('***');
|
|
13
|
-
expect(out.jwt.refresh.secret).toBe('***');
|
|
14
|
-
expect(out.port).toBe(3000);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('masks a broad set of secret-ish key names', () => {
|
|
18
|
-
const out = maskConfigDeep({
|
|
19
|
-
apiKey: 'AK',
|
|
20
|
-
betterAuth: { secret: 'BA' },
|
|
21
|
-
encryptionSecret: 'ENC',
|
|
22
|
-
password: 'PW',
|
|
23
|
-
privateKey: 'PK',
|
|
24
|
-
token: 'TK',
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
for (const value of Object.values(out)) {
|
|
28
|
-
const flat = JSON.stringify(value);
|
|
29
|
-
expect(flat).not.toMatch(/AK|BA|ENC|PW|PK|TK/);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('deep-clones — the input object is never mutated', () => {
|
|
34
|
-
const input = { jwt: { secret: 'SENTINEL' }, nested: { arr: [{ token: 'T' }] } };
|
|
35
|
-
const out = maskConfigDeep(input);
|
|
36
|
-
|
|
37
|
-
expect(input.jwt.secret).toBe('SENTINEL');
|
|
38
|
-
expect(input.nested.arr[0].token).toBe('T');
|
|
39
|
-
expect(out.jwt.secret).toBe('***');
|
|
40
|
-
expect(out.nested.arr[0].token).toBe('***');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('masks credentials embedded in URI-shaped values', () => {
|
|
44
|
-
const out = maskConfigDeep({ mongoose: { uri: 'mongodb://user:secretpw@host:27017/db' } });
|
|
45
|
-
|
|
46
|
-
expect(out.mongoose.uri).not.toContain('secretpw');
|
|
47
|
-
expect(out.mongoose.uri).toContain('mongodb://');
|
|
48
|
-
expect(out.mongoose.uri).toContain('host:27017');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('honors additional explicit secret field names', () => {
|
|
52
|
-
const out = maskConfigDeep({ customField: 'HIDE_ME', other: 'keep' }, ['customField']);
|
|
53
|
-
|
|
54
|
-
expect(out.customField).toBe('***');
|
|
55
|
-
expect(out.other).toBe('keep');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('passes through null, numbers, booleans and dates', () => {
|
|
59
|
-
const date = new Date('2026-01-01T00:00:00.000Z');
|
|
60
|
-
const out = maskConfigDeep({ a: null, b: 42, c: true, d: date });
|
|
61
|
-
|
|
62
|
-
expect(out.a).toBeNull();
|
|
63
|
-
expect(out.b).toBe(42);
|
|
64
|
-
expect(out.c).toBe(true);
|
|
65
|
-
expect(out.d).toBeInstanceOf(Date);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Value and type imports are deliberately split into two statements. The lt CLI's vendor
|
|
4
|
-
// conversion drops an INLINE `type` specifier from a mixed import — `{ buildErDiagram, type
|
|
5
|
-
// HubModelDescriptor }` arrives in a vendored project as `{ buildErDiagram }`, and the file then
|
|
6
|
-
// fails to compile with TS2304. Keeping the two forms separate survives the conversion.
|
|
7
|
-
// (The CLI defect is tracked separately; this keeps src/core/ vendor-safe meanwhile.)
|
|
8
|
-
import type { HubModelDescriptor } from './hub-mermaid.helper';
|
|
9
|
-
|
|
10
|
-
import { buildErDiagram } from './hub-mermaid.helper';
|
|
11
|
-
|
|
12
|
-
describe('buildErDiagram', () => {
|
|
13
|
-
const models: HubModelDescriptor[] = [
|
|
14
|
-
{
|
|
15
|
-
fields: [
|
|
16
|
-
{ name: 'email', type: 'String' },
|
|
17
|
-
{ name: 'age', type: 'Number' },
|
|
18
|
-
{ name: 'company', ref: 'Company', type: 'ObjectId' },
|
|
19
|
-
],
|
|
20
|
-
name: 'User',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
fields: [{ name: 'name', type: 'String' }],
|
|
24
|
-
name: 'Company',
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
it('opens an erDiagram block', () => {
|
|
29
|
-
const out = buildErDiagram(models);
|
|
30
|
-
expect(out.startsWith('erDiagram')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('emits one entity per model with its fields and types', () => {
|
|
34
|
-
const out = buildErDiagram(models);
|
|
35
|
-
expect(out).toContain('User {');
|
|
36
|
-
expect(out).toContain('Company {');
|
|
37
|
-
expect(out).toContain('String email');
|
|
38
|
-
expect(out).toContain('Number age');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('renders a relationship for a ref field', () => {
|
|
42
|
-
const out = buildErDiagram(models);
|
|
43
|
-
expect(out).toMatch(/User \|\|--o\{ Company : company/);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('ignores refs pointing at models that are not present', () => {
|
|
47
|
-
const out = buildErDiagram([{ fields: [{ name: 'ghost', ref: 'Missing', type: 'ObjectId' }], name: 'Solo' }]);
|
|
48
|
-
expect(out).toContain('Solo {');
|
|
49
|
-
expect(out).not.toContain('Missing');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('sanitizes field/entity names so the diagram stays valid', () => {
|
|
53
|
-
const out = buildErDiagram([{ fields: [{ name: 'weird-field.name', type: 'String' }], name: 'Odd Name' }]);
|
|
54
|
-
expect(out).not.toContain('weird-field.name');
|
|
55
|
-
expect(out).not.toContain('Odd Name {');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('handles an empty model list without throwing', () => {
|
|
59
|
-
expect(buildErDiagram([])).toBe('erDiagram');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
buildHubCsp,
|
|
5
|
-
buildHubSecurityHeaders,
|
|
6
|
-
escapeHtml,
|
|
7
|
-
escapeJsString,
|
|
8
|
-
generateNonce,
|
|
9
|
-
injectNonce,
|
|
10
|
-
} from './hub-shell.helper';
|
|
11
|
-
|
|
12
|
-
describe('hub-shell.helper', () => {
|
|
13
|
-
describe('escapeHtml', () => {
|
|
14
|
-
it('escapes all five HTML-significant characters', () => {
|
|
15
|
-
expect(escapeHtml(`<img src="x" onerror='y'>&`)).toBe('<img src="x" onerror='y'>&');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('neutralizes a script-tag XSS payload', () => {
|
|
19
|
-
const out = escapeHtml('<script>alert(1)</script>');
|
|
20
|
-
expect(out).not.toContain('<script>');
|
|
21
|
-
expect(out).toContain('<script>');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('handles empty / non-string-ish input', () => {
|
|
25
|
-
expect(escapeHtml('')).toBe('');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('escapeJsString', () => {
|
|
30
|
-
it('escapes characters that would break out of a JS string / script context', () => {
|
|
31
|
-
const out = escapeJsString(`a'b\\c</script>`);
|
|
32
|
-
expect(out).not.toContain('</script>');
|
|
33
|
-
expect(out).toContain("\\'");
|
|
34
|
-
expect(out).toContain('\\\\');
|
|
35
|
-
// The closing angle bracket must be escaped so `</script>` cannot terminate the tag.
|
|
36
|
-
expect(out).toContain('\\u003c');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('escapes CR / LF into their JS escapes', () => {
|
|
40
|
-
const out = escapeJsString('line1\r\nline2');
|
|
41
|
-
expect(out).toBe('line1\\r\\nline2');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('escapes U+2028 / U+2029, which are line terminators inside a JS string literal', () => {
|
|
45
|
-
// Built via fromCharCode so this test file stays pure ASCII (a literal separator would itself
|
|
46
|
-
// terminate the source line). A raw U+2028 in a `'...'` literal is a syntax error at runtime.
|
|
47
|
-
const lineSep = String.fromCharCode(0x2028);
|
|
48
|
-
const paraSep = String.fromCharCode(0x2029);
|
|
49
|
-
const out = escapeJsString(`before${lineSep}mid${paraSep}after`);
|
|
50
|
-
expect(out).toContain('\\u2028');
|
|
51
|
-
expect(out).toContain('\\u2029');
|
|
52
|
-
expect(out).not.toContain(lineSep);
|
|
53
|
-
expect(out).not.toContain(paraSep);
|
|
54
|
-
// The escaped result must be a valid single-line JS string literal.
|
|
55
|
-
expect(() => new Function(`return '${out}';`)()).not.toThrow();
|
|
56
|
-
expect(new Function(`return '${out}';`)()).toBe(`before${lineSep}mid${paraSep}after`);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('generateNonce', () => {
|
|
61
|
-
it('produces a non-empty base64 nonce that differs each call', () => {
|
|
62
|
-
const a = generateNonce();
|
|
63
|
-
const b = generateNonce();
|
|
64
|
-
expect(a.length).toBeGreaterThan(10);
|
|
65
|
-
expect(a).not.toBe(b);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('injectNonce', () => {
|
|
70
|
-
it('replaces every __CSP_NONCE__ placeholder', () => {
|
|
71
|
-
const html = `<style nonce="__CSP_NONCE__"></style><script nonce="__CSP_NONCE__"></script>`;
|
|
72
|
-
const out = injectNonce(html, 'abc123');
|
|
73
|
-
expect(out).not.toContain('__CSP_NONCE__');
|
|
74
|
-
expect(out).toContain('nonce="abc123"');
|
|
75
|
-
expect((out.match(/abc123/g) ?? []).length).toBe(2);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe('buildHubCsp', () => {
|
|
80
|
-
it('is a strict, self-contained policy pinned to the nonce', () => {
|
|
81
|
-
const csp = buildHubCsp('nonce123');
|
|
82
|
-
expect(csp).toContain("default-src 'none'");
|
|
83
|
-
expect(csp).toContain("script-src 'nonce-nonce123'");
|
|
84
|
-
expect(csp).toContain("frame-ancestors 'none'");
|
|
85
|
-
expect(csp).not.toContain('unsafe-inline');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('buildHubSecurityHeaders', () => {
|
|
90
|
-
it('returns the strict header set with no-store for HTML by default', () => {
|
|
91
|
-
const headers = buildHubSecurityHeaders('n1');
|
|
92
|
-
expect(headers['Content-Security-Policy']).toContain('nonce-n1');
|
|
93
|
-
expect(headers['X-Frame-Options']).toBe('DENY');
|
|
94
|
-
expect(headers['X-Content-Type-Options']).toBe('nosniff');
|
|
95
|
-
expect(headers['Referrer-Policy']).toBe('no-referrer');
|
|
96
|
-
expect(headers['Cache-Control']).toContain('no-store');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('uses a long-lived immutable cache for the cacheable script bundle', () => {
|
|
100
|
-
const headers = buildHubSecurityHeaders('n1', { cacheable: true });
|
|
101
|
-
expect(headers['Cache-Control']).toContain('max-age');
|
|
102
|
-
expect(headers['Cache-Control']).toContain('immutable');
|
|
103
|
-
expect(headers['Cache-Control']).not.toContain('no-store');
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
});
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { isHubEnabled, isHubQueriesEnabled, normalizeHubConfig } from './hub-config.helper';
|
|
4
|
-
|
|
5
|
-
describe('hub-config.helper', () => {
|
|
6
|
-
describe('isHubEnabled', () => {
|
|
7
|
-
it('is disabled by default (undefined)', () => {
|
|
8
|
-
expect(isHubEnabled(undefined)).toBe(false);
|
|
9
|
-
expect(isHubEnabled(false)).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('is enabled for true / {} / a config object', () => {
|
|
13
|
-
expect(isHubEnabled(true)).toBe(true);
|
|
14
|
-
expect(isHubEnabled({})).toBe(true);
|
|
15
|
-
expect(isHubEnabled({ path: 'x' })).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('respects explicit { enabled: false }', () => {
|
|
19
|
-
expect(isHubEnabled({ enabled: false, path: 'x' })).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe('isHubQueriesEnabled', () => {
|
|
24
|
-
it('is off by default even when the hub is enabled', () => {
|
|
25
|
-
expect(isHubQueriesEnabled(true)).toBe(false);
|
|
26
|
-
expect(isHubQueriesEnabled({})).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('is on when queries collector is explicitly enabled', () => {
|
|
30
|
-
expect(isHubQueriesEnabled({ collectors: { queries: true } })).toBe(true);
|
|
31
|
-
expect(isHubQueriesEnabled({ collectors: { queries: { warnMs: 5 } } })).toBe(true);
|
|
32
|
-
expect(isHubQueriesEnabled({ collectors: { queries: { enabled: false } } })).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('is off when the hub itself is disabled', () => {
|
|
36
|
-
expect(isHubQueriesEnabled(false)).toBe(false);
|
|
37
|
-
expect(isHubQueriesEnabled({ collectors: { queries: true }, enabled: false })).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe('normalizeHubConfig', () => {
|
|
42
|
-
it('applies defaults for the boolean-true form', () => {
|
|
43
|
-
const cfg = normalizeHubConfig(true, { env: 'local', version: '1.2.3' });
|
|
44
|
-
|
|
45
|
-
expect(cfg.path).toBe('hub');
|
|
46
|
-
expect(cfg.roles).toEqual(['admin']);
|
|
47
|
-
expect(cfg.actions).toBe(true);
|
|
48
|
-
expect(cfg.pollIntervalMs).toBe(5000);
|
|
49
|
-
expect(cfg.env).toBe('local');
|
|
50
|
-
expect(cfg.version).toBe('1.2.3');
|
|
51
|
-
// Default collectors: logs + traces on, queries off.
|
|
52
|
-
expect(cfg.collectors.logs).not.toBe(false);
|
|
53
|
-
expect(cfg.collectors.traces).not.toBe(false);
|
|
54
|
-
expect(cfg.collectors.queries).toBe(false);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('clamps the poll interval to the minimum', () => {
|
|
58
|
-
const cfg = normalizeHubConfig({ pollIntervalMs: 10 }, { env: 'local', version: '1' });
|
|
59
|
-
expect(cfg.pollIntervalMs).toBe(1000);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('normalizes a single role string to an array', () => {
|
|
63
|
-
const cfg = normalizeHubConfig({ roles: 'S_USER' }, { env: 'local', version: '1' });
|
|
64
|
-
expect(cfg.roles).toEqual(['S_USER']);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('keeps roles: false (opt-out of auth)', () => {
|
|
68
|
-
const cfg = normalizeHubConfig({ roles: false }, { env: 'local', version: '1' });
|
|
69
|
-
expect(cfg.roles).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('resolves the queries collector with thresholds when enabled', () => {
|
|
73
|
-
const cfg = normalizeHubConfig({ collectors: { queries: { warnMs: 5 } } }, { env: 'local', version: '1' });
|
|
74
|
-
expect(cfg.collectors.queries).not.toBe(false);
|
|
75
|
-
if (cfg.collectors.queries) {
|
|
76
|
-
expect(cfg.collectors.queries.warnMs).toBe(5);
|
|
77
|
-
expect(cfg.collectors.queries.criticalMs).toBe(200);
|
|
78
|
-
expect(cfg.collectors.queries.capacity).toBe(500);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('defaults the mailbox to disabled but enables it with capture mode when present', () => {
|
|
83
|
-
expect(normalizeHubConfig({}, { env: 'local', version: '1' }).mailbox).toBe(false);
|
|
84
|
-
|
|
85
|
-
const cfg = normalizeHubConfig({ mailbox: true }, { env: 'local', version: '1' });
|
|
86
|
-
expect(cfg.mailbox).not.toBe(false);
|
|
87
|
-
if (cfg.mailbox) {
|
|
88
|
-
expect(cfg.mailbox.mode).toBe('capture');
|
|
89
|
-
expect(cfg.mailbox.capacity).toBe(100);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('resolves default external links', () => {
|
|
94
|
-
const cfg = normalizeHubConfig(
|
|
95
|
-
{},
|
|
96
|
-
{ env: 'local', graphQlEnabled: true, permissionsPath: 'permissions', version: '1' },
|
|
97
|
-
);
|
|
98
|
-
expect(cfg.links.swagger).toBe('/swagger');
|
|
99
|
-
expect(cfg.links.graphql).toBe('/graphql');
|
|
100
|
-
expect(cfg.links.permissions).toBe('/permissions');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('omits the graphql link when GraphQL is disabled', () => {
|
|
104
|
-
const cfg = normalizeHubConfig({}, { env: 'local', graphQlEnabled: false, version: '1' });
|
|
105
|
-
expect(cfg.links.graphql).toBeUndefined();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { HubRingBuffer } from './hub-ring-buffer';
|
|
4
|
-
|
|
5
|
-
interface TestEntry {
|
|
6
|
-
seq: number;
|
|
7
|
-
timestamp: number;
|
|
8
|
-
value: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe('HubRingBuffer', () => {
|
|
12
|
-
it('assigns monotonic sequence numbers and a timestamp', () => {
|
|
13
|
-
const buffer = new HubRingBuffer<TestEntry>(3);
|
|
14
|
-
const a = buffer.add({ value: 'a' });
|
|
15
|
-
const b = buffer.add({ value: 'b' });
|
|
16
|
-
|
|
17
|
-
expect(a.seq).toBe(0);
|
|
18
|
-
expect(b.seq).toBe(1);
|
|
19
|
-
expect(typeof a.timestamp).toBe('number');
|
|
20
|
-
expect(b.seq).toBeGreaterThan(a.seq);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('honors an explicitly provided timestamp', () => {
|
|
24
|
-
const buffer = new HubRingBuffer<TestEntry>(3);
|
|
25
|
-
const entry = buffer.add({ timestamp: 123456, value: 'a' });
|
|
26
|
-
|
|
27
|
-
expect(entry.timestamp).toBe(123456);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('evicts the oldest entries once capacity is exceeded (fixed memory)', () => {
|
|
31
|
-
const buffer = new HubRingBuffer<TestEntry>(3);
|
|
32
|
-
for (const value of ['a', 'b', 'c', 'd', 'e']) {
|
|
33
|
-
buffer.add({ value });
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const items = buffer.recent();
|
|
37
|
-
expect(items.map((i) => i.value)).toEqual(['c', 'd', 'e']);
|
|
38
|
-
expect(buffer.size).toBe(3);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('recent(limit) returns the newest N entries, oldest→newest', () => {
|
|
42
|
-
const buffer = new HubRingBuffer<TestEntry>(5);
|
|
43
|
-
for (const value of ['a', 'b', 'c', 'd']) {
|
|
44
|
-
buffer.add({ value });
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
expect(buffer.recent(2).map((i) => i.value)).toEqual(['c', 'd']);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('since(seq) returns only entries newer than the cursor', () => {
|
|
51
|
-
const buffer = new HubRingBuffer<TestEntry>(10);
|
|
52
|
-
buffer.add({ value: 'a' }); // seq 0
|
|
53
|
-
const b = buffer.add({ value: 'b' }); // seq 1
|
|
54
|
-
buffer.add({ value: 'c' }); // seq 2
|
|
55
|
-
|
|
56
|
-
expect(buffer.since(b.seq).map((i) => i.value)).toEqual(['c']);
|
|
57
|
-
expect(buffer.since(-1).map((i) => i.value)).toEqual(['a', 'b', 'c']);
|
|
58
|
-
expect(buffer.since(buffer.lastSeq)).toEqual([]);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('since(seq) clamps to what is still retained after eviction', () => {
|
|
62
|
-
const buffer = new HubRingBuffer<TestEntry>(3);
|
|
63
|
-
for (const value of ['a', 'b', 'c', 'd', 'e']) {
|
|
64
|
-
buffer.add({ value }); // seqs 0..4, only 2,3,4 retained
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Asking since an evicted cursor (seq 0) returns only what survives, no crash, no duplicates.
|
|
68
|
-
expect(buffer.since(0).map((i) => i.value)).toEqual(['c', 'd', 'e']);
|
|
69
|
-
expect(buffer.firstRetainedSeq).toBe(2);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('exposes lastSeq (-1 when empty) and firstRetainedSeq', () => {
|
|
73
|
-
const buffer = new HubRingBuffer<TestEntry>(3);
|
|
74
|
-
expect(buffer.lastSeq).toBe(-1);
|
|
75
|
-
expect(buffer.firstRetainedSeq).toBe(-1);
|
|
76
|
-
expect(buffer.size).toBe(0);
|
|
77
|
-
|
|
78
|
-
buffer.add({ value: 'a' });
|
|
79
|
-
expect(buffer.lastSeq).toBe(0);
|
|
80
|
-
expect(buffer.firstRetainedSeq).toBe(0);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('clear() empties the buffer but keeps the sequence counter monotonic', () => {
|
|
84
|
-
const buffer = new HubRingBuffer<TestEntry>(3);
|
|
85
|
-
buffer.add({ value: 'a' });
|
|
86
|
-
buffer.add({ value: 'b' });
|
|
87
|
-
buffer.clear();
|
|
88
|
-
|
|
89
|
-
expect(buffer.size).toBe(0);
|
|
90
|
-
expect(buffer.recent()).toEqual([]);
|
|
91
|
-
// A new entry after clear must not reuse an old seq (cursors stay valid across a clear).
|
|
92
|
-
const c = buffer.add({ value: 'c' });
|
|
93
|
-
expect(c.seq).toBe(2);
|
|
94
|
-
});
|
|
95
|
-
});
|