@camstack/server 1.0.0 → 1.0.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/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
- package/dist/api/addon-upload.js +441 -0
- package/dist/api/addons-custom.router.js +91 -0
- package/dist/api/auth-whoami.js +55 -0
- package/dist/api/bridge-addons.router.js +109 -0
- package/dist/api/capabilities.router.js +229 -0
- package/dist/api/core/addon-settings.router.js +117 -0
- package/dist/api/core/agents.router.js +73 -0
- package/dist/api/core/auth.router.js +286 -0
- package/dist/api/core/bulk-update-coordinator.js +229 -0
- package/dist/api/core/cap-providers.js +1124 -0
- package/dist/api/core/capabilities.router.js +138 -0
- package/dist/api/core/collection-preference.js +17 -0
- package/dist/api/core/event-bus-proxy.router.js +45 -0
- package/dist/api/core/hwaccel.router.js +91 -0
- package/dist/api/core/live-events.router.js +61 -0
- package/dist/api/core/logs.router.js +172 -0
- package/dist/api/core/notifications.router.js +67 -0
- package/dist/api/core/repl.router.js +35 -0
- package/dist/api/core/settings-backend.router.js +121 -0
- package/dist/api/core/stream-probe.router.js +58 -0
- package/dist/api/core/system-events.router.js +100 -0
- package/dist/api/health/health.routes.js +68 -0
- package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
- package/dist/api/oauth2/oauth2-routes.js +219 -0
- package/dist/api/trpc/cap-mount-helpers.js +194 -0
- package/dist/api/trpc/cap-route-error-formatter.js +133 -0
- package/dist/api/trpc/client-ip.js +147 -0
- package/dist/api/trpc/core-cap-bridge.js +115 -0
- package/dist/api/trpc/generated-cap-mounts.js +388 -0
- package/dist/api/trpc/generated-cap-routers.js +7635 -0
- package/dist/api/trpc/scope-access.js +93 -0
- package/dist/api/trpc/trpc.context.js +184 -0
- package/dist/api/trpc/trpc.middleware.js +139 -0
- package/dist/api/trpc/trpc.router.js +188 -0
- package/dist/auth/session-cookie.js +47 -0
- package/dist/boot/boot-config.js +241 -0
- package/dist/boot/integration-id-backfill.js +76 -0
- package/dist/boot/post-boot.service.js +85 -0
- package/dist/core/addon/addon-call-gateway.js +99 -0
- package/dist/core/addon/addon-package.service.js +1560 -0
- package/dist/core/addon/addon-registry.service.js +2739 -0
- package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
- package/dist/core/addon/addon-search.service.js +62 -0
- package/dist/core/addon/addon-settings-provider.js +102 -0
- package/dist/core/addon/addon.tokens.js +5 -0
- package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
- package/dist/core/addon-pages/addon-pages.service.js +107 -0
- package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
- package/dist/core/agent/agent-registry.service.js +477 -0
- package/dist/core/auth/auth.service.js +10 -0
- package/dist/core/capability/capability.service.js +58 -0
- package/dist/core/config/config.schema.js +7 -0
- package/dist/core/config/config.service.js +10 -0
- package/dist/core/events/event-bus.service.js +83 -0
- package/dist/core/feature/feature.service.js +10 -0
- package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
- package/dist/core/logging/log-ring-buffer.js +6 -0
- package/dist/core/logging/logging.service.js +130 -0
- package/dist/core/logging/scoped-logger.js +6 -0
- package/dist/core/moleculer/cap-call-fn.js +50 -0
- package/dist/core/moleculer/cap-route-authority.js +122 -0
- package/dist/core/moleculer/moleculer.service.js +898 -0
- package/dist/core/network/network-quality.service.js +7 -0
- package/dist/core/notification/notification-wrapper.service.js +33 -0
- package/dist/core/notification/toast-wrapper.service.js +25 -0
- package/dist/core/provider/provider.tokens.js +4 -0
- package/dist/core/repl/repl-engine.service.js +140 -0
- package/dist/core/storage/fs-storage-backend.js +6 -0
- package/dist/core/storage/storage-location-manager.js +6 -0
- package/dist/core/storage/storage.service.js +7 -0
- package/dist/core/streaming/stream-probe.service.js +209 -0
- package/dist/core/topology/topology-emitter.service.js +106 -0
- package/dist/launcher.js +325 -0
- package/dist/main.js +1098 -0
- package/dist/manual-boot.js +227 -0
- package/package.json +5 -1
- package/src/__tests__/addon-install-e2e.test.ts +0 -74
- package/src/__tests__/addon-pages-e2e.test.ts +0 -200
- package/src/__tests__/addon-route-session.test.ts +0 -17
- package/src/__tests__/addon-settings-router.spec.ts +0 -67
- package/src/__tests__/addon-upload.spec.ts +0 -475
- package/src/__tests__/agent-registry.spec.ts +0 -179
- package/src/__tests__/agent-status-page.spec.ts +0 -82
- package/src/__tests__/auth-session-cookie.test.ts +0 -48
- package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
- package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
- package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
- package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
- package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
- package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
- package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
- package/src/__tests__/cap-route-adapter.spec.ts +0 -302
- package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
- package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
- package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
- package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
- package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
- package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
- package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
- package/src/__tests__/cap-routers/harness.ts +0 -163
- package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
- package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
- package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
- package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
- package/src/__tests__/capability-e2e.test.ts +0 -384
- package/src/__tests__/cli-e2e.test.ts +0 -150
- package/src/__tests__/core-cap-bridge.spec.ts +0 -91
- package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
- package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
- package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
- package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
- package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
- package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
- package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
- package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
- package/src/__tests__/framework-allowlist.spec.ts +0 -96
- package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
- package/src/__tests__/https-e2e.test.ts +0 -124
- package/src/__tests__/lifecycle-e2e.test.ts +0 -189
- package/src/__tests__/live-events-subscription.spec.ts +0 -149
- package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
- package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
- package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
- package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
- package/src/__tests__/native-cap-route.spec.ts +0 -427
- package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
- package/src/__tests__/post-boot-restart.spec.ts +0 -161
- package/src/__tests__/singleton-contention.test.ts +0 -499
- package/src/__tests__/streaming-diagnostic.test.ts +0 -615
- package/src/__tests__/streaming-scale.test.ts +0 -314
- package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
- package/src/__tests__/uds-log-ingest.spec.ts +0 -183
- package/src/api/__tests__/addons-custom.spec.ts +0 -148
- package/src/api/__tests__/capabilities.router.test.ts +0 -56
- package/src/api/addon-upload.ts +0 -529
- package/src/api/addons-custom.router.ts +0 -101
- package/src/api/auth-whoami.ts +0 -101
- package/src/api/bridge-addons.router.ts +0 -122
- package/src/api/capabilities.router.ts +0 -265
- package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
- package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
- package/src/api/core/addon-settings.router.ts +0 -127
- package/src/api/core/agents.router.ts +0 -86
- package/src/api/core/auth.router.ts +0 -322
- package/src/api/core/bulk-update-coordinator.ts +0 -305
- package/src/api/core/cap-providers.ts +0 -1339
- package/src/api/core/capabilities.router.ts +0 -149
- package/src/api/core/collection-preference.ts +0 -40
- package/src/api/core/event-bus-proxy.router.ts +0 -45
- package/src/api/core/hwaccel.router.ts +0 -108
- package/src/api/core/live-events.router.ts +0 -67
- package/src/api/core/logs.router.ts +0 -195
- package/src/api/core/notifications.router.ts +0 -66
- package/src/api/core/repl.router.ts +0 -39
- package/src/api/core/settings-backend.router.ts +0 -140
- package/src/api/core/stream-probe.router.ts +0 -57
- package/src/api/core/system-events.router.ts +0 -125
- package/src/api/health/health.routes.ts +0 -117
- package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
- package/src/api/oauth2/oauth2-routes.ts +0 -281
- package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
- package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
- package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
- package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
- package/src/api/trpc/cap-mount-helpers.ts +0 -245
- package/src/api/trpc/cap-route-error-formatter.ts +0 -171
- package/src/api/trpc/client-ip.ts +0 -147
- package/src/api/trpc/core-cap-bridge.ts +0 -154
- package/src/api/trpc/generated-cap-mounts.ts +0 -1240
- package/src/api/trpc/generated-cap-routers.ts +0 -11523
- package/src/api/trpc/scope-access.ts +0 -110
- package/src/api/trpc/trpc.context.ts +0 -258
- package/src/api/trpc/trpc.middleware.ts +0 -146
- package/src/api/trpc/trpc.router.ts +0 -389
- package/src/auth/session-cookie.ts +0 -54
- package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
- package/src/boot/boot-config.ts +0 -259
- package/src/boot/integration-id-backfill.ts +0 -109
- package/src/boot/post-boot.service.ts +0 -105
- package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
- package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
- package/src/core/addon/addon-call-gateway.ts +0 -171
- package/src/core/addon/addon-package.service.ts +0 -1787
- package/src/core/addon/addon-registry.service.ts +0 -3130
- package/src/core/addon/addon-search.service.ts +0 -91
- package/src/core/addon/addon-settings-provider.ts +0 -220
- package/src/core/addon/addon.tokens.ts +0 -2
- package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
- package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
- package/src/core/addon-pages/addon-pages.service.ts +0 -82
- package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
- package/src/core/agent/agent-registry.service.ts +0 -529
- package/src/core/auth/auth.service.spec.ts +0 -86
- package/src/core/auth/auth.service.ts +0 -8
- package/src/core/capability/capability.service.ts +0 -66
- package/src/core/config/config.schema.ts +0 -3
- package/src/core/config/config.service.spec.ts +0 -175
- package/src/core/config/config.service.ts +0 -7
- package/src/core/events/event-bus.service.spec.ts +0 -235
- package/src/core/events/event-bus.service.ts +0 -89
- package/src/core/feature/feature.service.spec.ts +0 -99
- package/src/core/feature/feature.service.ts +0 -8
- package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
- package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
- package/src/core/logging/log-ring-buffer.ts +0 -3
- package/src/core/logging/logging.service.spec.ts +0 -287
- package/src/core/logging/logging.service.ts +0 -143
- package/src/core/logging/scoped-logger.ts +0 -3
- package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
- package/src/core/moleculer/cap-call-fn.ts +0 -107
- package/src/core/moleculer/cap-route-authority.ts +0 -194
- package/src/core/moleculer/moleculer.service.ts +0 -1072
- package/src/core/network/network-quality.service.spec.ts +0 -53
- package/src/core/network/network-quality.service.ts +0 -5
- package/src/core/notification/notification-wrapper.service.ts +0 -34
- package/src/core/notification/toast-wrapper.service.ts +0 -27
- package/src/core/provider/provider.tokens.ts +0 -1
- package/src/core/repl/repl-engine.service.spec.ts +0 -444
- package/src/core/repl/repl-engine.service.ts +0 -155
- package/src/core/storage/fs-storage-backend.spec.ts +0 -70
- package/src/core/storage/fs-storage-backend.ts +0 -3
- package/src/core/storage/storage-location-manager.spec.ts +0 -130
- package/src/core/storage/storage-location-manager.ts +0 -3
- package/src/core/storage/storage.service.spec.ts +0 -73
- package/src/core/storage/storage.service.ts +0 -3
- package/src/core/streaming/stream-probe.service.ts +0 -221
- package/src/core/topology/topology-emitter.service.ts +0 -105
- package/src/launcher.ts +0 -314
- package/src/main.ts +0 -1245
- package/src/manual-boot.ts +0 -301
- package/tsconfig.build.json +0 -8
- package/tsconfig.json +0 -33
- package/vitest.config.ts +0 -26
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Task 5 — TDD tests: device-scoped native cap routing through CapRouteResolver.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* 1. A device-scoped native cap on a HUB-LOCAL child resolves to hub-local-uds
|
|
6
|
-
* and dispatches via callCapOnChild with {capName, method, args:{...deviceId}, deviceId}.
|
|
7
|
-
* 2. d9ba709 regression guard: a singleton cap on BOTH a hub-local child AND a remote
|
|
8
|
-
* node, resolved WITHOUT explicit nodeId, classifies to hub-local (not remote).
|
|
9
|
-
* 3. A remote device-scoped native cap builds the ${addonId}.native-provider.${cap}.${method}
|
|
10
|
-
* action (NATIVE_PROVIDER_SERVICE_INFIX).
|
|
11
|
-
* 4. nodeKnowsCap returns true for native caps (not just manifest caps).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
15
|
-
import { CapRouteResolver, NATIVE_PROVIDER_SERVICE_INFIX } from '@camstack/kernel'
|
|
16
|
-
import type {
|
|
17
|
-
NodeCapAuthority,
|
|
18
|
-
HubLocalChildDispatcher,
|
|
19
|
-
CapRouteResolverDeps,
|
|
20
|
-
InProcessProviderRef,
|
|
21
|
-
} from '@camstack/kernel'
|
|
22
|
-
import { CapRouteError } from '@camstack/kernel'
|
|
23
|
-
|
|
24
|
-
const HUB_NODE_ID = 'hub'
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// Fake HubLocalChildDispatcher with deviceId-aware resolveChildId
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
interface FakeHubLocalRegistry extends HubLocalChildDispatcher {
|
|
31
|
-
readonly callCapOnChildSpy: ReturnType<typeof vi.fn>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Build a fake registry that supports device-scoped cap resolution.
|
|
36
|
-
* The caps map is {capName → {deviceId? → childId}} or {capName → childId} for singletons.
|
|
37
|
-
*/
|
|
38
|
-
function makeDeviceAwareHubLocalRegistry(
|
|
39
|
-
caps: ReadonlyMap<string, ReadonlyMap<number | 'singleton', string>>,
|
|
40
|
-
): FakeHubLocalRegistry {
|
|
41
|
-
const callCapOnChildSpy = vi.fn(async (_childId: string, _input: unknown) => ({
|
|
42
|
-
ok: true,
|
|
43
|
-
from: 'uds',
|
|
44
|
-
}))
|
|
45
|
-
return {
|
|
46
|
-
resolveChildId: (capName: string, deviceId?: number): string | null => {
|
|
47
|
-
const capMap = caps.get(capName)
|
|
48
|
-
if (capMap === undefined) return null
|
|
49
|
-
if (deviceId !== undefined) {
|
|
50
|
-
const deviceSpecific = capMap.get(deviceId)
|
|
51
|
-
if (deviceSpecific !== undefined) return deviceSpecific
|
|
52
|
-
}
|
|
53
|
-
return capMap.get('singleton') ?? null
|
|
54
|
-
},
|
|
55
|
-
callCapOnChild: callCapOnChildSpy,
|
|
56
|
-
callCapOnChildSpy,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Fake NodeCapAuthority with native-cap awareness
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
interface NativeCapSpec {
|
|
65
|
-
readonly nodeId: string
|
|
66
|
-
readonly addonId: string
|
|
67
|
-
readonly capName: string
|
|
68
|
-
readonly deviceId: number
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function makeNativeAwareNodeAuthority(
|
|
72
|
-
systemCaps: ReadonlyMap<string, { addonId: string; nodeId: string }>,
|
|
73
|
-
onlineNodes: ReadonlySet<string>,
|
|
74
|
-
nativeCaps: readonly NativeCapSpec[],
|
|
75
|
-
): NodeCapAuthority {
|
|
76
|
-
return {
|
|
77
|
-
nodeKnowsCap: (nodeId: string, capName: string): boolean => {
|
|
78
|
-
// Check system (manifest) caps
|
|
79
|
-
const sys = systemCaps.get(capName)
|
|
80
|
-
if (sys !== undefined && sys.nodeId === nodeId) return true
|
|
81
|
-
// Check native caps (any deviceId for this node+capName)
|
|
82
|
-
return nativeCaps.some((n) => n.nodeId === nodeId && n.capName === capName)
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
nodeIsAgent: (nodeId: string): boolean => nodeId !== HUB_NODE_ID && !nodeId.includes('/'),
|
|
86
|
-
|
|
87
|
-
nodeOnline: (nodeId: string): boolean => onlineNodes.has(nodeId),
|
|
88
|
-
|
|
89
|
-
listNodeIds: (): readonly string[] => {
|
|
90
|
-
const ids = new Set<string>()
|
|
91
|
-
for (const spec of systemCaps.values()) ids.add(spec.nodeId)
|
|
92
|
-
for (const n of nativeCaps) ids.add(n.nodeId)
|
|
93
|
-
return [...ids]
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
getAddonId: (nodeId: string, capName: string): string | null => {
|
|
97
|
-
// Check system (manifest) caps
|
|
98
|
-
const sys = systemCaps.get(capName)
|
|
99
|
-
if (sys !== undefined && sys.nodeId === nodeId) return sys.addonId
|
|
100
|
-
// Check native caps
|
|
101
|
-
const nat = nativeCaps.find((n) => n.nodeId === nodeId && n.capName === capName)
|
|
102
|
-
return nat?.addonId ?? null
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
getAgentChildId: (_agentNodeId: string, _capName: string): string | null => null,
|
|
106
|
-
|
|
107
|
-
isNativeCap: (nodeId: string, capName: string, deviceId?: number): boolean => {
|
|
108
|
-
if (deviceId !== undefined) {
|
|
109
|
-
return nativeCaps.some(
|
|
110
|
-
(n) => n.nodeId === nodeId && n.capName === capName && n.deviceId === deviceId,
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
return nativeCaps.some((n) => n.nodeId === nodeId && n.capName === capName)
|
|
114
|
-
},
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Test 1: device-scoped native cap on hub-local child → hub-local-uds dispatch
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
describe('Task5 – device-scoped native cap on hub-local child', () => {
|
|
123
|
-
it('resolves to hub-local-uds and calls callCapOnChild with {capName, method, args+deviceId, deviceId}', async () => {
|
|
124
|
-
// ptz is a device-scoped native cap owned by hub child 'provider-reolink'
|
|
125
|
-
const hubLocalCaps = makeDeviceAwareHubLocalRegistry(
|
|
126
|
-
new Map([['ptz', new Map([[7, 'provider-reolink']])]]),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
const nativeCaps: NativeCapSpec[] = [
|
|
130
|
-
{
|
|
131
|
-
nodeId: 'hub/provider-reolink',
|
|
132
|
-
addonId: 'addon-provider-reolink',
|
|
133
|
-
capName: 'ptz',
|
|
134
|
-
deviceId: 7,
|
|
135
|
-
},
|
|
136
|
-
]
|
|
137
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(
|
|
138
|
-
new Map(),
|
|
139
|
-
new Set(['hub/provider-reolink']),
|
|
140
|
-
nativeCaps,
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
const deps: CapRouteResolverDeps = {
|
|
144
|
-
hubNodeId: HUB_NODE_ID,
|
|
145
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
146
|
-
hubLocalRegistry: hubLocalCaps,
|
|
147
|
-
nodeAuthority,
|
|
148
|
-
inProcessProviders: () => null,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const resolver = new CapRouteResolver(deps)
|
|
152
|
-
|
|
153
|
-
// Resolve with deviceId = 7 so the snapshot uses the device-aware accessor
|
|
154
|
-
const route = resolver.resolveCapRoute('ptz', { nodeId: HUB_NODE_ID, deviceId: 7 })
|
|
155
|
-
expect(route.kind).toBe('hub-local-uds')
|
|
156
|
-
if (route.kind !== 'hub-local-uds') return
|
|
157
|
-
expect(route.childId).toBe('provider-reolink')
|
|
158
|
-
|
|
159
|
-
const result = await resolver.dispatch(route, 'move', { pan: 10, deviceId: 7 })
|
|
160
|
-
expect(result).toEqual({ ok: true, from: 'uds' })
|
|
161
|
-
|
|
162
|
-
expect(hubLocalCaps.callCapOnChildSpy).toHaveBeenCalledOnce()
|
|
163
|
-
const [calledChildId, calledInput] = hubLocalCaps.callCapOnChildSpy.mock.calls[0] as [
|
|
164
|
-
string,
|
|
165
|
-
unknown,
|
|
166
|
-
]
|
|
167
|
-
expect(calledChildId).toBe('provider-reolink')
|
|
168
|
-
expect(calledInput).toMatchObject({
|
|
169
|
-
capName: 'ptz',
|
|
170
|
-
method: 'move',
|
|
171
|
-
args: { pan: 10, deviceId: 7 },
|
|
172
|
-
deviceId: 7,
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('resolving ptz WITHOUT deviceId does NOT match the device-scoped child (no spurious route)', () => {
|
|
177
|
-
// When no deviceId hint is given, the singleton fallback fires — no deviceId-less descriptor exists
|
|
178
|
-
const hubLocalCaps = makeDeviceAwareHubLocalRegistry(
|
|
179
|
-
new Map([
|
|
180
|
-
['ptz', new Map([[7, 'provider-reolink']])],
|
|
181
|
-
// No 'singleton' entry for ptz — ptz is strictly device-scoped
|
|
182
|
-
]),
|
|
183
|
-
)
|
|
184
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(new Map(), new Set(), [])
|
|
185
|
-
|
|
186
|
-
const deps: CapRouteResolverDeps = {
|
|
187
|
-
hubNodeId: HUB_NODE_ID,
|
|
188
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
189
|
-
hubLocalRegistry: hubLocalCaps,
|
|
190
|
-
nodeAuthority,
|
|
191
|
-
inProcessProviders: () => null,
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const resolver = new CapRouteResolver(deps)
|
|
195
|
-
// Without deviceId, no hub-local-uds route → CapRouteError
|
|
196
|
-
expect(() => resolver.resolveCapRoute('ptz', { nodeId: HUB_NODE_ID })).toThrow(CapRouteError)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
// Test 2: d9ba709 regression guard — hub-local singleton beats remote
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
|
|
204
|
-
describe('Task5 – d9ba709 regression: singleton prefers hub-local over remote', () => {
|
|
205
|
-
it('singleton cap provided by both hub-local child AND remote node classifies to hub-local-uds (no nodeId given)', () => {
|
|
206
|
-
// 'pipeline-executor' is on BOTH local child addon-detection-pipeline AND remote-agent-0
|
|
207
|
-
const hubLocalCaps = makeDeviceAwareHubLocalRegistry(
|
|
208
|
-
new Map([['pipeline-executor', new Map([['singleton', 'addon-detection-pipeline']])]]),
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
const systemCaps = new Map([
|
|
212
|
-
['pipeline-executor', { addonId: 'addon-detection-pipeline', nodeId: 'remote-agent-0' }],
|
|
213
|
-
])
|
|
214
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(
|
|
215
|
-
systemCaps,
|
|
216
|
-
new Set(['hub/addon-detection-pipeline', 'remote-agent-0']),
|
|
217
|
-
[],
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
const deps: CapRouteResolverDeps = {
|
|
221
|
-
hubNodeId: HUB_NODE_ID,
|
|
222
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
223
|
-
hubLocalRegistry: hubLocalCaps,
|
|
224
|
-
nodeAuthority,
|
|
225
|
-
inProcessProviders: () => null,
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const resolver = new CapRouteResolver(deps)
|
|
229
|
-
// Singleton resolution (no nodeId) — must prefer hub-local-uds
|
|
230
|
-
const route = resolver.resolveCapRoute('pipeline-executor', {})
|
|
231
|
-
expect(route.kind).toBe('hub-local-uds')
|
|
232
|
-
if (route.kind === 'hub-local-uds') {
|
|
233
|
-
expect(route.childId).toBe('addon-detection-pipeline')
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
|
-
// Test 3: remote device-scoped native cap uses NATIVE_PROVIDER_SERVICE_INFIX
|
|
240
|
-
// (Remote-moleculer path: non-agent remote node hosting a native device cap,
|
|
241
|
-
// e.g. a secondary hub or a directly-wired remote runner)
|
|
242
|
-
// ---------------------------------------------------------------------------
|
|
243
|
-
|
|
244
|
-
describe('Task5 – remote native cap uses NATIVE_PROVIDER_SERVICE_INFIX in action name', () => {
|
|
245
|
-
it('dispatches remote native cap (on non-agent remote node) with action ${addonId}.native-provider.${cap}.${method}', async () => {
|
|
246
|
-
const callSpy = vi.fn(async () => ({ ok: true }))
|
|
247
|
-
const broker = { call: callSpy, waitForServices: vi.fn(async () => {}) }
|
|
248
|
-
|
|
249
|
-
// ptz is provided by a REMOTE non-agent node (contains '/' so nodeIsAgent=false)
|
|
250
|
-
const remoteNodeId = 'hub-secondary/provider-reolink'
|
|
251
|
-
const nativeCaps: NativeCapSpec[] = [
|
|
252
|
-
{ nodeId: remoteNodeId, addonId: 'addon-provider-reolink', capName: 'ptz', deviceId: 7 },
|
|
253
|
-
]
|
|
254
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(
|
|
255
|
-
new Map(),
|
|
256
|
-
new Set([remoteNodeId]),
|
|
257
|
-
nativeCaps,
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
const deps: CapRouteResolverDeps = {
|
|
261
|
-
hubNodeId: HUB_NODE_ID,
|
|
262
|
-
broker,
|
|
263
|
-
hubLocalRegistry: null,
|
|
264
|
-
nodeAuthority,
|
|
265
|
-
inProcessProviders: () => null,
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const resolver = new CapRouteResolver(deps)
|
|
269
|
-
const route = resolver.resolveCapRoute('ptz', { nodeId: remoteNodeId, deviceId: 7 })
|
|
270
|
-
expect(route.kind).toBe('remote-moleculer')
|
|
271
|
-
|
|
272
|
-
await resolver.dispatch(route, 'move', { pan: 10, deviceId: 7 })
|
|
273
|
-
|
|
274
|
-
expect(callSpy).toHaveBeenCalledOnce()
|
|
275
|
-
const [action, , opts] = callSpy.mock.calls[0] as [string, unknown, { nodeID?: string }]
|
|
276
|
-
expect(action).toBe(`addon-provider-reolink${NATIVE_PROVIDER_SERVICE_INFIX}.ptz.move`)
|
|
277
|
-
expect((opts as { nodeID?: string }).nodeID).toBe(remoteNodeId)
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
it('non-native remote cap still uses plain ${addonId}.${cap}.${method} action', async () => {
|
|
281
|
-
const callSpy = vi.fn(async () => ({ ok: true }))
|
|
282
|
-
const broker = { call: callSpy, waitForServices: vi.fn(async () => {}) }
|
|
283
|
-
|
|
284
|
-
const remoteNodeId = 'hub-secondary/addon-analytics-suite'
|
|
285
|
-
const systemCaps = new Map([
|
|
286
|
-
['analytics', { addonId: 'addon-analytics-suite', nodeId: remoteNodeId }],
|
|
287
|
-
])
|
|
288
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(
|
|
289
|
-
systemCaps,
|
|
290
|
-
new Set([remoteNodeId]),
|
|
291
|
-
[], // no native caps
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
const deps: CapRouteResolverDeps = {
|
|
295
|
-
hubNodeId: HUB_NODE_ID,
|
|
296
|
-
broker,
|
|
297
|
-
hubLocalRegistry: null,
|
|
298
|
-
nodeAuthority,
|
|
299
|
-
inProcessProviders: () => null,
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const resolver = new CapRouteResolver(deps)
|
|
303
|
-
const route = resolver.resolveCapRoute('analytics', { nodeId: remoteNodeId })
|
|
304
|
-
expect(route.kind).toBe('remote-moleculer')
|
|
305
|
-
|
|
306
|
-
await resolver.dispatch(route, 'getReport', { period: 'day' })
|
|
307
|
-
|
|
308
|
-
const [action] = callSpy.mock.calls[0] as [string]
|
|
309
|
-
expect(action).toBe('addon-analytics-suite.analytics.getReport')
|
|
310
|
-
// Must NOT have the native infix
|
|
311
|
-
expect(action).not.toContain(NATIVE_PROVIDER_SERVICE_INFIX)
|
|
312
|
-
})
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
// Test 4: nodeKnowsCap returns true for native caps (not just manifest)
|
|
317
|
-
// ---------------------------------------------------------------------------
|
|
318
|
-
|
|
319
|
-
describe('Task5 – nodeKnowsCap includes native caps', () => {
|
|
320
|
-
it('nodeKnowsCap returns true when the node has a native cap entry (even with no manifest cap)', () => {
|
|
321
|
-
const nativeCaps: NativeCapSpec[] = [
|
|
322
|
-
{
|
|
323
|
-
nodeId: 'hub/provider-reolink',
|
|
324
|
-
addonId: 'addon-provider-reolink',
|
|
325
|
-
capName: 'ptz',
|
|
326
|
-
deviceId: 7,
|
|
327
|
-
},
|
|
328
|
-
]
|
|
329
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(
|
|
330
|
-
new Map(), // no system caps
|
|
331
|
-
new Set(['hub/provider-reolink']),
|
|
332
|
-
nativeCaps,
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
expect(nodeAuthority.nodeKnowsCap('hub/provider-reolink', 'ptz')).toBe(true)
|
|
336
|
-
expect(nodeAuthority.nodeKnowsCap('hub/provider-reolink', 'stream-broker')).toBe(false)
|
|
337
|
-
expect(nodeAuthority.nodeKnowsCap('other-node', 'ptz')).toBe(false)
|
|
338
|
-
})
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
// ---------------------------------------------------------------------------
|
|
342
|
-
// Test 5: native-fallback regression — an in-hub WRAPPER must not shadow the
|
|
343
|
-
// hub-local-uds NATIVE child for the same cap name.
|
|
344
|
-
//
|
|
345
|
-
// Reproduces the snapshot bug: the `snapshot` cap has an in-hub wrapper
|
|
346
|
-
// (SnapshotAddon, registered as a system singleton, so inProcessProviders
|
|
347
|
-
// returns a ref) AND a per-device native provider in a forked vendor child
|
|
348
|
-
// (provider-reolink). `resolveCapRoute` gives Priority 1 to hub-in-process,
|
|
349
|
-
// so it returns the WRAPPER route — never reaching the hub-local-uds native.
|
|
350
|
-
// The setNativeFallback resolver wants the NATIVE child specifically; it must
|
|
351
|
-
// use `resolveHubLocalUdsRoute`, which skips the in-process branch.
|
|
352
|
-
// ---------------------------------------------------------------------------
|
|
353
|
-
|
|
354
|
-
describe('native-fallback – wrapper must not shadow the hub-local native child', () => {
|
|
355
|
-
// snapshot has an in-hub wrapper AND a forked native child for device 7.
|
|
356
|
-
const wrapperRef: InProcessProviderRef = { invoke: vi.fn(async () => ({ from: 'wrapper' })) }
|
|
357
|
-
|
|
358
|
-
function makeSnapshotResolver(): {
|
|
359
|
-
resolver: CapRouteResolver
|
|
360
|
-
hubLocalCaps: FakeHubLocalRegistry
|
|
361
|
-
} {
|
|
362
|
-
const hubLocalCaps = makeDeviceAwareHubLocalRegistry(
|
|
363
|
-
new Map([['snapshot', new Map([[7, 'provider-reolink']])]]),
|
|
364
|
-
)
|
|
365
|
-
const nativeCaps: NativeCapSpec[] = [
|
|
366
|
-
{
|
|
367
|
-
nodeId: 'hub/provider-reolink',
|
|
368
|
-
addonId: 'addon-provider-reolink',
|
|
369
|
-
capName: 'snapshot',
|
|
370
|
-
deviceId: 7,
|
|
371
|
-
},
|
|
372
|
-
]
|
|
373
|
-
const nodeAuthority = makeNativeAwareNodeAuthority(
|
|
374
|
-
new Map(),
|
|
375
|
-
new Set(['hub/provider-reolink']),
|
|
376
|
-
nativeCaps,
|
|
377
|
-
)
|
|
378
|
-
const deps: CapRouteResolverDeps = {
|
|
379
|
-
hubNodeId: HUB_NODE_ID,
|
|
380
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
381
|
-
hubLocalRegistry: hubLocalCaps,
|
|
382
|
-
nodeAuthority,
|
|
383
|
-
// The wrapper occupies the in-hub slot for 'snapshot'.
|
|
384
|
-
inProcessProviders: (cap: string): InProcessProviderRef | null =>
|
|
385
|
-
cap === 'snapshot' ? wrapperRef : null,
|
|
386
|
-
}
|
|
387
|
-
return { resolver: new CapRouteResolver(deps), hubLocalCaps }
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
it('documents the shadow: resolveCapRoute picks the in-hub wrapper (hub-in-process)', () => {
|
|
391
|
-
const { resolver } = makeSnapshotResolver()
|
|
392
|
-
const route = resolver.resolveCapRoute('snapshot', { nodeId: HUB_NODE_ID, deviceId: 7 })
|
|
393
|
-
// The wrapper shadows the native — this is the existing (correct for the
|
|
394
|
-
// generic dispatch path) behaviour the native fallback must NOT rely on.
|
|
395
|
-
expect(route.kind).toBe('hub-in-process')
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
it('resolveHubLocalUdsRoute returns the native child route, bypassing the wrapper shadow', () => {
|
|
399
|
-
const { resolver } = makeSnapshotResolver()
|
|
400
|
-
const route = resolver.resolveHubLocalUdsRoute('snapshot', 7)
|
|
401
|
-
expect(route).not.toBeNull()
|
|
402
|
-
expect(route?.kind).toBe('hub-local-uds')
|
|
403
|
-
expect(route?.childId).toBe('provider-reolink')
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
it('resolveHubLocalUdsRoute returns null when no hub-local child owns the cap (lets fallback try remote)', () => {
|
|
407
|
-
const { resolver } = makeSnapshotResolver()
|
|
408
|
-
// device 999 has no hub-local native child
|
|
409
|
-
expect(resolver.resolveHubLocalUdsRoute('snapshot', 999)).toBeNull()
|
|
410
|
-
// a cap with neither wrapper nor hub-local child
|
|
411
|
-
expect(resolver.resolveHubLocalUdsRoute('nonexistent', 7)).toBeNull()
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
it('dispatching the resolved hub-local-uds route reaches the native child (not the wrapper)', async () => {
|
|
415
|
-
const { resolver, hubLocalCaps } = makeSnapshotResolver()
|
|
416
|
-
const route = resolver.resolveHubLocalUdsRoute('snapshot', 7)
|
|
417
|
-
expect(route).not.toBeNull()
|
|
418
|
-
if (route === null) return
|
|
419
|
-
const result = await resolver.dispatch(route, 'getSnapshot', { deviceId: 7 })
|
|
420
|
-
expect(result).toEqual({ ok: true, from: 'uds' })
|
|
421
|
-
expect(hubLocalCaps.callCapOnChildSpy).toHaveBeenCalledOnce()
|
|
422
|
-
const [childId] = hubLocalCaps.callCapOnChildSpy.mock.calls[0] as [string]
|
|
423
|
-
expect(childId).toBe('provider-reolink')
|
|
424
|
-
// The wrapper's invoke must NOT have been called.
|
|
425
|
-
expect(wrapperRef.invoke).not.toHaveBeenCalled()
|
|
426
|
-
})
|
|
427
|
-
})
|