@camstack/server 0.1.7 → 0.2.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/package.json +11 -9
- package/src/__tests__/addon-install-e2e.test.ts +0 -1
- package/src/__tests__/addon-pages-e2e.test.ts +40 -18
- package/src/__tests__/addon-settings-router.spec.ts +6 -1
- package/src/__tests__/addon-upload.spec.ts +91 -29
- package/src/__tests__/agent-registry.spec.ts +26 -9
- package/src/__tests__/agent-status-page.spec.ts +1 -3
- package/src/__tests__/auth-session-cookie.test.ts +28 -1
- package/src/__tests__/bulk-update-coordinator.spec.ts +48 -31
- package/src/__tests__/cap-ownership-authority.spec.ts +39 -8
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +206 -0
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +17 -3
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +57 -11
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +292 -0
- package/src/__tests__/cap-providers-bulk-update.spec.ts +27 -7
- package/src/__tests__/cap-route-adapter.spec.ts +28 -15
- package/src/__tests__/cap-routers/_meta.spec.ts +6 -7
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +19 -10
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +177 -0
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +3 -1
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +18 -5
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +137 -0
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +72 -20
- package/src/__tests__/cap-routers/harness.ts +11 -7
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +17 -3
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +5 -7
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +35 -11
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +59 -15
- package/src/__tests__/capability-e2e.test.ts +9 -11
- package/src/__tests__/cli-e2e.test.ts +80 -59
- package/src/__tests__/core-cap-bridge.spec.ts +3 -1
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +12 -2
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +61 -30
- package/src/__tests__/embedded-deps-e2e.test.ts +35 -19
- package/src/__tests__/event-bus-proxy-router.spec.ts +3 -0
- package/src/__tests__/framework-allowlist.spec.ts +5 -4
- package/src/__tests__/https-e2e.test.ts +12 -6
- package/src/__tests__/lifecycle-e2e.test.ts +60 -11
- package/src/__tests__/live-events-subscription.spec.ts +17 -18
- package/src/__tests__/moleculer/uds-readiness.spec.ts +11 -4
- package/src/__tests__/moleculer/uds-topology.spec.ts +39 -11
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +265 -5
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +16 -7
- package/src/__tests__/native-cap-route.spec.ts +42 -19
- package/src/__tests__/oauth2-account-linking.spec.ts +63 -17
- package/src/__tests__/singleton-contention.test.ts +23 -11
- package/src/__tests__/streaming-diagnostic.test.ts +156 -53
- package/src/__tests__/streaming-scale.test.ts +69 -35
- package/src/__tests__/uds-addon-call-wiring.spec.ts +6 -1
- package/src/agent-status-page.ts +4 -3
- package/src/api/__tests__/addons-custom.spec.ts +22 -8
- package/src/api/__tests__/capabilities.router.test.ts +18 -9
- package/src/api/addon-upload.ts +46 -15
- package/src/api/addons-custom.router.ts +7 -6
- package/src/api/auth-whoami.ts +3 -1
- package/src/api/bridge-addons.router.ts +3 -1
- package/src/api/capabilities.router.ts +117 -78
- package/src/api/core/__tests__/auth-router-totp.spec.ts +57 -16
- package/src/api/core/__tests__/integration-markers.spec.ts +10 -0
- package/src/api/core/addon-settings.router.ts +4 -1
- package/src/api/core/agents.router.ts +52 -53
- package/src/api/core/auth.router.ts +55 -36
- package/src/api/core/bulk-update-coordinator.ts +25 -22
- package/src/api/core/cap-providers.ts +459 -166
- package/src/api/core/capabilities.router.ts +30 -23
- package/src/api/core/hwaccel.router.ts +37 -10
- package/src/api/core/live-events.router.ts +16 -9
- package/src/api/core/logs.router.ts +58 -25
- package/src/api/core/notifications.router.ts +2 -1
- package/src/api/core/repl.router.ts +1 -3
- package/src/api/core/settings-backend.router.ts +68 -70
- package/src/api/core/system-events.router.ts +41 -32
- package/src/api/health/health.routes.ts +7 -13
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +12 -2
- package/src/api/oauth2/consent-page.ts +4 -3
- package/src/api/oauth2/oauth2-routes.ts +41 -12
- package/src/api/trpc/__tests__/client-ip.spec.ts +27 -1
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +68 -23
- package/src/api/trpc/__tests__/scope-access.spec.ts +8 -13
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +136 -0
- package/src/api/trpc/cap-mount-helpers.ts +64 -44
- package/src/api/trpc/cap-route-error-formatter.ts +17 -9
- package/src/api/trpc/client-ip.ts +17 -0
- package/src/api/trpc/core-cap-bridge.ts +3 -1
- package/src/api/trpc/generated-cap-mounts.ts +801 -286
- package/src/api/trpc/generated-cap-routers.ts +5723 -719
- package/src/api/trpc/scope-access.ts +7 -7
- package/src/api/trpc/trpc.context.ts +7 -4
- package/src/api/trpc/trpc.middleware.ts +4 -2
- package/src/api/trpc/trpc.router.ts +117 -48
- package/src/auth/session-cookie.ts +10 -0
- package/src/boot/__tests__/integration-id-backfill.spec.ts +131 -0
- package/src/boot/boot-config.ts +103 -122
- package/src/boot/integration-id-backfill.ts +109 -0
- package/src/boot/post-boot.service.ts +5 -3
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +12 -3
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +62 -0
- package/src/core/addon/addon-call-gateway.ts +20 -6
- package/src/core/addon/addon-package.service.ts +183 -89
- package/src/core/addon/addon-registry.service.ts +1212 -1267
- package/src/core/addon/addon-row-manifest.ts +29 -0
- package/src/core/addon/addon-search.service.ts +2 -1
- package/src/core/addon/addon-settings-provider.ts +27 -7
- package/src/core/addon-bridge/addon-bridge.service.ts +11 -6
- package/src/core/addon-pages/addon-pages.service.ts +3 -1
- package/src/core/addon-widgets/addon-widgets.service.ts +5 -2
- package/src/core/agent/agent-registry.service.ts +60 -38
- package/src/core/auth/auth.service.spec.ts +6 -8
- package/src/core/config/config.service.spec.ts +1 -1
- package/src/core/events/event-bus.service.spec.ts +44 -21
- package/src/core/events/event-bus.service.ts +5 -1
- package/src/core/feature/feature.service.spec.ts +4 -1
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +8 -10
- package/src/core/logging/logging.service.spec.ts +61 -21
- package/src/core/logging/logging.service.ts +19 -5
- package/src/core/moleculer/cap-call-fn.spec.ts +17 -10
- package/src/core/moleculer/cap-call-fn.ts +5 -1
- package/src/core/moleculer/cap-route-authority.ts +18 -6
- package/src/core/moleculer/moleculer.service.ts +145 -29
- package/src/core/network/network-quality.service.spec.ts +7 -1
- package/src/core/notification/notification-wrapper.service.ts +1 -3
- package/src/core/notification/toast-wrapper.service.ts +1 -5
- package/src/core/repl/repl-engine.service.spec.ts +66 -39
- package/src/core/repl/repl-engine.service.ts +11 -12
- package/src/core/storage/storage-location-manager.spec.ts +12 -3
- package/src/core/streaming/stream-probe.service.ts +22 -13
- package/src/core/topology/topology-emitter.service.ts +5 -1
- package/src/launcher.ts +14 -9
- package/src/main.ts +658 -495
- package/src/manual-boot.ts +133 -154
- package/tsconfig.json +20 -8
- package/src/core/storage/settings-store.spec.ts +0 -213
- package/src/core/storage/settings-store.ts +0 -2
- package/src/core/storage/sql-schema.spec.ts +0 -140
- package/src/core/storage/sql-schema.ts +0 -3
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* Embedded dependencies E2E — verify ffmpeg/python resolution and download.
|
|
4
3
|
*
|
|
@@ -11,9 +10,19 @@ import * as path from 'node:path'
|
|
|
11
10
|
import * as os from 'node:os'
|
|
12
11
|
// Import directly from source submodules because vitest+swc doesn't resolve
|
|
13
12
|
// `export * from './deps/index.js'` barrel re-exports in the @camstack/core index.
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import {
|
|
14
|
+
findInPath,
|
|
15
|
+
getPlatformInfo,
|
|
16
|
+
buildBinaryPath,
|
|
17
|
+
} from '../../../../packages/core/src/deps/binary-downloader'
|
|
18
|
+
import {
|
|
19
|
+
getFfmpegDownloadUrl,
|
|
20
|
+
ensureFfmpeg,
|
|
21
|
+
} from '../../../../packages/core/src/deps/ffmpeg-downloader'
|
|
22
|
+
import {
|
|
23
|
+
getPythonDownloadUrl,
|
|
24
|
+
ensurePython,
|
|
25
|
+
} from '../../../../packages/core/src/deps/python-downloader'
|
|
17
26
|
import type { IScopedLogger } from '@camstack/types'
|
|
18
27
|
|
|
19
28
|
function createMockLogger(): IScopedLogger {
|
|
@@ -27,7 +36,6 @@ function createMockLogger(): IScopedLogger {
|
|
|
27
36
|
return logger
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
|
|
31
39
|
const mockLogger = createMockLogger()
|
|
32
40
|
|
|
33
41
|
describe('Embedded Dependencies E2E', () => {
|
|
@@ -71,11 +79,15 @@ describe('Embedded Dependencies E2E', () => {
|
|
|
71
79
|
|
|
72
80
|
// Only run download test if explicitly enabled (downloads ~80MB)
|
|
73
81
|
const downloadTest = process.env.CAMSTACK_TEST_DOWNLOAD === 'true' ? it : it.skip
|
|
74
|
-
downloadTest(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
downloadTest(
|
|
83
|
+
'downloads ffmpeg binary',
|
|
84
|
+
async () => {
|
|
85
|
+
const ffmpegPath = await ensureFfmpeg(tmpDir, mockLogger)
|
|
86
|
+
expect(ffmpegPath).toBeTruthy()
|
|
87
|
+
expect(fs.existsSync(ffmpegPath)).toBe(true)
|
|
88
|
+
},
|
|
89
|
+
120000,
|
|
90
|
+
)
|
|
79
91
|
})
|
|
80
92
|
|
|
81
93
|
describe('Python', () => {
|
|
@@ -95,15 +107,19 @@ describe('Embedded Dependencies E2E', () => {
|
|
|
95
107
|
|
|
96
108
|
// Only run download test if explicitly enabled (downloads ~25MB)
|
|
97
109
|
const downloadTest = process.env.CAMSTACK_TEST_DOWNLOAD === 'true' ? it : it.skip
|
|
98
|
-
downloadTest(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
downloadTest(
|
|
111
|
+
'downloads portable Python',
|
|
112
|
+
async () => {
|
|
113
|
+
const pythonPath = await ensurePython(tmpDir, mockLogger)
|
|
114
|
+
expect(pythonPath).toBeTruthy()
|
|
115
|
+
expect(fs.existsSync(pythonPath!)).toBe(true)
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
// Verify it actually runs
|
|
118
|
+
const { execFileSync } = await import('node:child_process')
|
|
119
|
+
const version = execFileSync(pythonPath!, ['--version'], { encoding: 'utf8' }).trim()
|
|
120
|
+
expect(version).toMatch(/Python 3\.12/)
|
|
121
|
+
},
|
|
122
|
+
120000,
|
|
123
|
+
)
|
|
108
124
|
})
|
|
109
125
|
})
|
|
@@ -20,6 +20,7 @@ describe('event-bus-proxy router', () => {
|
|
|
20
20
|
id: 'evt-1',
|
|
21
21
|
timestamp: '2026-01-15T10:00:00.000Z',
|
|
22
22
|
source: { type: 'addon', id: 'my-addon' },
|
|
23
|
+
// biome-ignore lint/plugin: synthetic test-only event category
|
|
23
24
|
category: 'motion',
|
|
24
25
|
data: { zone: 'front' },
|
|
25
26
|
})
|
|
@@ -43,6 +44,7 @@ describe('event-bus-proxy router', () => {
|
|
|
43
44
|
id: 'evt-2',
|
|
44
45
|
timestamp: '2026-01-15T10:00:00.000Z',
|
|
45
46
|
source: { type: 'device', id: 'cam-1' },
|
|
47
|
+
// biome-ignore lint/plugin: synthetic test-only event category
|
|
46
48
|
category: 'alert',
|
|
47
49
|
data: {},
|
|
48
50
|
})
|
|
@@ -61,6 +63,7 @@ describe('event-bus-proxy router', () => {
|
|
|
61
63
|
id: 'evt-3',
|
|
62
64
|
timestamp: isoString,
|
|
63
65
|
source: { type: 'addon', id: 'test' },
|
|
66
|
+
// biome-ignore lint/plugin: synthetic test-only event category
|
|
64
67
|
category: 'info',
|
|
65
68
|
data: {},
|
|
66
69
|
})
|
|
@@ -37,10 +37,11 @@ function readPackageJson(pkgJsonPath: string): PackageJsonView | null {
|
|
|
37
37
|
const name = obj['name']
|
|
38
38
|
if (typeof name !== 'string') return null
|
|
39
39
|
const camstack = obj['camstack']
|
|
40
|
-
const isSystem =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
const isSystem =
|
|
41
|
+
camstack !== null &&
|
|
42
|
+
typeof camstack === 'object' &&
|
|
43
|
+
!Array.isArray(camstack) &&
|
|
44
|
+
(camstack as Record<string, unknown>)['system'] === true
|
|
44
45
|
return { name, system: isSystem, path: pkgJsonPath }
|
|
45
46
|
} catch {
|
|
46
47
|
return null
|
|
@@ -74,7 +74,9 @@ describe('HTTPS E2E', () => {
|
|
|
74
74
|
{ hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
|
|
75
75
|
(res) => {
|
|
76
76
|
let data = ''
|
|
77
|
-
res.on('data', (c) => {
|
|
77
|
+
res.on('data', (c) => {
|
|
78
|
+
data += c
|
|
79
|
+
})
|
|
78
80
|
res.on('end', () => resolve(data))
|
|
79
81
|
},
|
|
80
82
|
)
|
|
@@ -103,11 +105,15 @@ describe('HTTPS E2E', () => {
|
|
|
103
105
|
|
|
104
106
|
try {
|
|
105
107
|
const body = await new Promise<string>((resolve, reject) => {
|
|
106
|
-
http
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
http
|
|
109
|
+
.get(`http://127.0.0.1:${port}/`, (res) => {
|
|
110
|
+
let data = ''
|
|
111
|
+
res.on('data', (c) => {
|
|
112
|
+
data += c
|
|
113
|
+
})
|
|
114
|
+
res.on('end', () => resolve(data))
|
|
115
|
+
})
|
|
116
|
+
.on('error', reject)
|
|
111
117
|
})
|
|
112
118
|
|
|
113
119
|
expect(body).toBe('http-ok')
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import { describe, it, expect, vi } from 'vitest'
|
|
3
2
|
import { CapabilityRegistry, isInfraCapability } from '@camstack/kernel'
|
|
4
3
|
import type { IScopedLogger } from '@camstack/types'
|
|
@@ -36,12 +35,42 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
|
|
|
36
35
|
it('registers singleton and collection capabilities correctly', () => {
|
|
37
36
|
const registry = createRegistry()
|
|
38
37
|
|
|
39
|
-
registry.declareCapability({
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
registry.declareCapability({
|
|
39
|
+
name: 'storage',
|
|
40
|
+
scope: 'system',
|
|
41
|
+
mode: 'singleton',
|
|
42
|
+
methods: {},
|
|
43
|
+
})
|
|
44
|
+
registry.declareCapability({
|
|
45
|
+
name: 'log-destination',
|
|
46
|
+
scope: 'system',
|
|
47
|
+
mode: 'collection',
|
|
48
|
+
methods: {},
|
|
49
|
+
})
|
|
50
|
+
registry.declareCapability({
|
|
51
|
+
name: 'streaming-engine',
|
|
52
|
+
scope: 'system',
|
|
53
|
+
mode: 'singleton',
|
|
54
|
+
methods: {},
|
|
55
|
+
})
|
|
56
|
+
registry.declareCapability({
|
|
57
|
+
name: 'analysis-pipeline',
|
|
58
|
+
scope: 'system',
|
|
59
|
+
mode: 'singleton',
|
|
60
|
+
methods: {},
|
|
61
|
+
})
|
|
62
|
+
registry.declareCapability({
|
|
63
|
+
name: 'device-provider',
|
|
64
|
+
scope: 'system',
|
|
65
|
+
mode: 'collection',
|
|
66
|
+
methods: {},
|
|
67
|
+
})
|
|
68
|
+
registry.declareCapability({
|
|
69
|
+
name: 'admin-ui',
|
|
70
|
+
scope: 'system',
|
|
71
|
+
mode: 'singleton',
|
|
72
|
+
methods: {},
|
|
73
|
+
})
|
|
45
74
|
|
|
46
75
|
const mockStorage = { id: 'sqlite' }
|
|
47
76
|
const mockLogger = { id: 'winston' }
|
|
@@ -79,7 +108,12 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
|
|
|
79
108
|
describe('Singleton swap', () => {
|
|
80
109
|
it('swapping analysis provider changes getSingleton result', async () => {
|
|
81
110
|
const registry = createRegistry()
|
|
82
|
-
registry.declareCapability({
|
|
111
|
+
registry.declareCapability({
|
|
112
|
+
name: 'analysis-pipeline',
|
|
113
|
+
scope: 'system',
|
|
114
|
+
mode: 'singleton',
|
|
115
|
+
methods: {},
|
|
116
|
+
})
|
|
83
117
|
|
|
84
118
|
const analysisA = { id: 'analysis-a', processFrame: vi.fn() }
|
|
85
119
|
const analysisB = { id: 'analysis-b', processFrame: vi.fn() }
|
|
@@ -97,7 +131,12 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
|
|
|
97
131
|
describe('Collection capability add/remove', () => {
|
|
98
132
|
it('adding and removing device providers', () => {
|
|
99
133
|
const registry = createRegistry()
|
|
100
|
-
registry.declareCapability({
|
|
134
|
+
registry.declareCapability({
|
|
135
|
+
name: 'device-provider',
|
|
136
|
+
scope: 'system',
|
|
137
|
+
mode: 'collection',
|
|
138
|
+
methods: {},
|
|
139
|
+
})
|
|
101
140
|
|
|
102
141
|
const frigate = { id: 'frigate', type: 'frigate' }
|
|
103
142
|
const onvif = { id: 'onvif', type: 'onvif' }
|
|
@@ -116,8 +155,18 @@ describe('Full Lifecycle E2E (mock, no external services)', () => {
|
|
|
116
155
|
describe('Capability introspection', () => {
|
|
117
156
|
it('listCapabilities returns full info', () => {
|
|
118
157
|
const registry = createRegistry()
|
|
119
|
-
registry.declareCapability({
|
|
120
|
-
|
|
158
|
+
registry.declareCapability({
|
|
159
|
+
name: 'storage',
|
|
160
|
+
scope: 'system',
|
|
161
|
+
mode: 'singleton',
|
|
162
|
+
methods: {},
|
|
163
|
+
})
|
|
164
|
+
registry.declareCapability({
|
|
165
|
+
name: 'device-provider',
|
|
166
|
+
scope: 'system',
|
|
167
|
+
mode: 'collection',
|
|
168
|
+
methods: {},
|
|
169
|
+
})
|
|
121
170
|
|
|
122
171
|
registry.registerProvider('storage', 'sqlite', { id: 'sqlite' })
|
|
123
172
|
registry.registerProvider('device-provider', 'frigate', { id: 'frigate' })
|
|
@@ -30,10 +30,9 @@ function makeEvent(category: string, data: Record<string, unknown> = {}) {
|
|
|
30
30
|
describe('live events subscription', () => {
|
|
31
31
|
it('delivers events emitted AFTER subscription', async () => {
|
|
32
32
|
const bus = new SystemEventBus() as unknown as EventBusService
|
|
33
|
-
const router = createLiveEventsRouter(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
33
|
+
const router = createLiveEventsRouter(bus, {
|
|
34
|
+
getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
|
|
35
|
+
} as never)
|
|
37
36
|
const caller = router.createCaller(makeCtx('admin'))
|
|
38
37
|
|
|
39
38
|
const iter = await caller.onEvent({ category: 'benchmark.progress' })
|
|
@@ -63,10 +62,9 @@ describe('live events subscription', () => {
|
|
|
63
62
|
|
|
64
63
|
it('filters events that do not match the requested category', async () => {
|
|
65
64
|
const bus = new SystemEventBus() as unknown as EventBusService
|
|
66
|
-
const router = createLiveEventsRouter(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
65
|
+
const router = createLiveEventsRouter(bus, {
|
|
66
|
+
getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
|
|
67
|
+
} as never)
|
|
70
68
|
const caller = router.createCaller(makeCtx('admin'))
|
|
71
69
|
|
|
72
70
|
const iter = await caller.onEvent({ category: 'benchmark.progress' })
|
|
@@ -76,7 +74,10 @@ describe('live events subscription', () => {
|
|
|
76
74
|
const collector = (async () => {
|
|
77
75
|
for await (const ev of iter) {
|
|
78
76
|
received.push(ev.category)
|
|
79
|
-
if (received.length >= 2) {
|
|
77
|
+
if (received.length >= 2) {
|
|
78
|
+
stop()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
80
81
|
}
|
|
81
82
|
})()
|
|
82
83
|
|
|
@@ -97,10 +98,9 @@ describe('live events subscription', () => {
|
|
|
97
98
|
|
|
98
99
|
it('receives recent events via recentSystemEvents query', async () => {
|
|
99
100
|
const bus = new SystemEventBus() as unknown as EventBusService
|
|
100
|
-
const router = createLiveEventsRouter(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
)
|
|
101
|
+
const router = createLiveEventsRouter(bus, {
|
|
102
|
+
getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
|
|
103
|
+
} as never)
|
|
104
104
|
const caller = router.createCaller(makeCtx('admin'))
|
|
105
105
|
|
|
106
106
|
bus.emit(makeEvent('benchmark.progress', { iteration: 1 }))
|
|
@@ -110,7 +110,7 @@ describe('live events subscription', () => {
|
|
|
110
110
|
const recent = await caller.recentSystemEvents({ category: 'benchmark.progress', limit: 10 })
|
|
111
111
|
expect(recent).toHaveLength(2)
|
|
112
112
|
// recentSystemEvents returns newest-first
|
|
113
|
-
const iterations = recent.map((e) => e.data.iteration as number).
|
|
113
|
+
const iterations = recent.map((e) => e.data.iteration as number).toSorted((a, b) => a - b)
|
|
114
114
|
expect(iterations).toEqual([1, 2])
|
|
115
115
|
})
|
|
116
116
|
|
|
@@ -118,10 +118,9 @@ describe('live events subscription', () => {
|
|
|
118
118
|
// Reproduces the benchmark scenario where runBenchmark emits 3 events
|
|
119
119
|
// back-to-back; the subscription should buffer them and deliver all 3.
|
|
120
120
|
const bus = new SystemEventBus() as unknown as EventBusService
|
|
121
|
-
const router = createLiveEventsRouter(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
)
|
|
121
|
+
const router = createLiveEventsRouter(bus, {
|
|
122
|
+
getDeviceRegistry: () => ({ getAll: () => [], getAllForAddon: () => [] }),
|
|
123
|
+
} as never)
|
|
125
124
|
const caller = router.createCaller(makeCtx('admin'))
|
|
126
125
|
|
|
127
126
|
const iter = await caller.onEvent({ category: 'benchmark.progress' })
|
|
@@ -71,7 +71,9 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
|
|
|
71
71
|
|
|
72
72
|
let capturedHandler: (() => readonly IReadinessRegistryRecord[]) | null = null
|
|
73
73
|
const childReg: FakeLocalChildRegistry = {
|
|
74
|
-
onReadinessSnapshotRequest: (h) => {
|
|
74
|
+
onReadinessSnapshotRequest: (h) => {
|
|
75
|
+
capturedHandler = h
|
|
76
|
+
},
|
|
75
77
|
}
|
|
76
78
|
const readinessReg: FakeReadinessRegistry = {
|
|
77
79
|
getSnapshotForTransport: () => records,
|
|
@@ -85,7 +87,8 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
|
|
|
85
87
|
})
|
|
86
88
|
|
|
87
89
|
it('handler calls getSnapshotForTransport on every invocation (live snapshot)', () => {
|
|
88
|
-
const getSnapshot = vi
|
|
90
|
+
const getSnapshot = vi
|
|
91
|
+
.fn<[], readonly IReadinessRegistryRecord[]>()
|
|
89
92
|
.mockReturnValueOnce([])
|
|
90
93
|
.mockReturnValueOnce([
|
|
91
94
|
{
|
|
@@ -101,7 +104,9 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
|
|
|
101
104
|
|
|
102
105
|
let capturedHandler: (() => readonly IReadinessRegistryRecord[]) | null = null
|
|
103
106
|
const childReg: FakeLocalChildRegistry = {
|
|
104
|
-
onReadinessSnapshotRequest: (h) => {
|
|
107
|
+
onReadinessSnapshotRequest: (h) => {
|
|
108
|
+
capturedHandler = h
|
|
109
|
+
},
|
|
105
110
|
}
|
|
106
111
|
const readinessReg: FakeReadinessRegistry = { getSnapshotForTransport: getSnapshot }
|
|
107
112
|
|
|
@@ -120,7 +125,9 @@ describe('D1 — wireReadinessSnapshotHandler', () => {
|
|
|
120
125
|
|
|
121
126
|
let capturedHandler: (() => readonly IReadinessRegistryRecord[]) | null = null
|
|
122
127
|
const childReg: FakeLocalChildRegistry = {
|
|
123
|
-
onReadinessSnapshotRequest: (h) => {
|
|
128
|
+
onReadinessSnapshotRequest: (h) => {
|
|
129
|
+
capturedHandler = h
|
|
130
|
+
},
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
wireReadinessSnapshotHandler(childReg, readinessReg)
|
|
@@ -97,8 +97,12 @@ describe('E1 — wireHubChildManifest', () => {
|
|
|
97
97
|
let capturedGoneHandler: ((childId: string) => void) | null = null
|
|
98
98
|
|
|
99
99
|
const registry: FakeLocalChildRegistry = {
|
|
100
|
-
onChildRegistered: (h) => {
|
|
101
|
-
|
|
100
|
+
onChildRegistered: (h) => {
|
|
101
|
+
capturedRegisteredHandler = h
|
|
102
|
+
},
|
|
103
|
+
onChildGone: (h) => {
|
|
104
|
+
capturedGoneHandler = h
|
|
105
|
+
},
|
|
102
106
|
setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -130,7 +134,9 @@ describe('E1 — wireHubChildManifest', () => {
|
|
|
130
134
|
|
|
131
135
|
const registry: FakeLocalChildRegistry = {
|
|
132
136
|
onChildRegistered: vi.fn(),
|
|
133
|
-
onChildGone: (h) => {
|
|
137
|
+
onChildGone: (h) => {
|
|
138
|
+
capturedGoneHandler = h
|
|
139
|
+
},
|
|
134
140
|
setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
|
|
135
141
|
}
|
|
136
142
|
|
|
@@ -155,8 +161,12 @@ describe('E1 — wireHubChildManifest', () => {
|
|
|
155
161
|
let goneHandler: ((childId: string) => void) | null = null
|
|
156
162
|
|
|
157
163
|
const registry: FakeLocalChildRegistry = {
|
|
158
|
-
onChildRegistered: (h) => {
|
|
159
|
-
|
|
164
|
+
onChildRegistered: (h) => {
|
|
165
|
+
registeredHandler = h
|
|
166
|
+
},
|
|
167
|
+
onChildGone: (h) => {
|
|
168
|
+
goneHandler = h
|
|
169
|
+
},
|
|
160
170
|
setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
|
|
161
171
|
}
|
|
162
172
|
|
|
@@ -249,11 +259,15 @@ describe('E1 — idempotency (double-registration via RPC + UDS)', () => {
|
|
|
249
259
|
// so the second call is a no-op at the CapabilityRegistry level.
|
|
250
260
|
// Here we verify that onChildRegistered fires ONCE per UDS connect.
|
|
251
261
|
let callCount = 0
|
|
252
|
-
const applyChildManifest = vi.fn<[string, readonly ChildCapDescriptor[]], void>(() => {
|
|
262
|
+
const applyChildManifest = vi.fn<[string, readonly ChildCapDescriptor[]], void>(() => {
|
|
263
|
+
callCount++
|
|
264
|
+
})
|
|
253
265
|
|
|
254
266
|
let registeredHandler: ((child: RegisteredChild) => void) | null = null
|
|
255
267
|
const registry: FakeLocalChildRegistry = {
|
|
256
|
-
onChildRegistered: (h) => {
|
|
268
|
+
onChildRegistered: (h) => {
|
|
269
|
+
registeredHandler = h
|
|
270
|
+
},
|
|
257
271
|
onChildGone: vi.fn(),
|
|
258
272
|
setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
|
|
259
273
|
}
|
|
@@ -287,7 +301,9 @@ describe('E1 — idempotency (double-registration via RPC + UDS)', () => {
|
|
|
287
301
|
let registeredHandler: ((child: RegisteredChild) => void) | null = null
|
|
288
302
|
|
|
289
303
|
const registry: FakeLocalChildRegistry = {
|
|
290
|
-
onChildRegistered: (h) => {
|
|
304
|
+
onChildRegistered: (h) => {
|
|
305
|
+
registeredHandler = h
|
|
306
|
+
},
|
|
291
307
|
onChildGone: vi.fn(),
|
|
292
308
|
setChildLogLevel: vi.fn<[string, string], boolean>().mockReturnValue(false),
|
|
293
309
|
}
|
|
@@ -333,7 +349,11 @@ describe('E2 — LocalChildRegistry.setChildLogLevel (real implementation)', ()
|
|
|
333
349
|
const result = registry.setChildLogLevel('unknown-child', 'debug')
|
|
334
350
|
expect(result).toBe(false)
|
|
335
351
|
// Cleanup: close the server without starting (no-op on most implementations).
|
|
336
|
-
try {
|
|
352
|
+
try {
|
|
353
|
+
await registry.close()
|
|
354
|
+
} catch {
|
|
355
|
+
/* server was never started */
|
|
356
|
+
}
|
|
337
357
|
})
|
|
338
358
|
|
|
339
359
|
it('setProcessLogLevel falls back to Moleculer when setChildLogLevel returns false', () => {
|
|
@@ -344,7 +364,11 @@ describe('E2 — LocalChildRegistry.setChildLogLevel (real implementation)', ()
|
|
|
344
364
|
// and the Moleculer fallback is invoked.
|
|
345
365
|
const moleculerFallback = vi.fn<[string, string], void>()
|
|
346
366
|
|
|
347
|
-
const simulateSetProcessLogLevel = (
|
|
367
|
+
const simulateSetProcessLogLevel = (
|
|
368
|
+
nodeId: string,
|
|
369
|
+
level: string,
|
|
370
|
+
udsResult: boolean,
|
|
371
|
+
): void => {
|
|
348
372
|
if (!udsResult) {
|
|
349
373
|
moleculerFallback(nodeId, level)
|
|
350
374
|
}
|
|
@@ -367,7 +391,11 @@ describe('E2 — LocalChildRegistry.setChildLogLevel (real implementation)', ()
|
|
|
367
391
|
// We test the routing logic inline (no full MoleculerService instantiation).
|
|
368
392
|
const hubNodeId = 'hub'
|
|
369
393
|
|
|
370
|
-
const routeSetLogLevel = (
|
|
394
|
+
const routeSetLogLevel = (
|
|
395
|
+
nodeId: string,
|
|
396
|
+
level: string,
|
|
397
|
+
registry: { setChildLogLevel(id: string, lvl: string): boolean } | null,
|
|
398
|
+
): boolean => {
|
|
371
399
|
if (!nodeId.startsWith(`${hubNodeId}/`)) return false
|
|
372
400
|
const childId = nodeId.slice(hubNodeId.length + 1)
|
|
373
401
|
if (registry === null) return false
|