@camstack/server 0.2.2 → 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,314 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unsafe-argument -- test file, mock typing */
|
|
2
|
-
/**
|
|
3
|
-
* Scale test — how many sub-streams can we decode for motion analysis?
|
|
4
|
-
* Run: FRIGATE_HOST=192.168.1.128 npx vitest run server/backend/src/__tests__/streaming-scale.test.ts --reporter verbose
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect, afterAll } from 'vitest'
|
|
7
|
-
import http from 'node:http'
|
|
8
|
-
import { StreamBrokerManager, FfmpegDecoderProvider } from '@camstack/addon-pipeline/stream-broker'
|
|
9
|
-
import type { FrameHandle, IStreamBroker } from '@camstack/types'
|
|
10
|
-
import type { IScopedLogger } from '@camstack/types'
|
|
11
|
-
|
|
12
|
-
const FRIGATE = process.env.FRIGATE_HOST ?? ''
|
|
13
|
-
|
|
14
|
-
const mockLogger: IScopedLogger = {
|
|
15
|
-
debug: () => {},
|
|
16
|
-
info: () => {},
|
|
17
|
-
warn: console.warn,
|
|
18
|
-
error: console.error,
|
|
19
|
-
child: () => mockLogger,
|
|
20
|
-
}
|
|
21
|
-
const mockLoggingService = { createLogger: () => mockLogger }
|
|
22
|
-
|
|
23
|
-
function wait(ms: number) {
|
|
24
|
-
return new Promise((r) => setTimeout(r, ms))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Poll-based replacement for the removed `IStreamBroker.onDecodedFrame`
|
|
29
|
-
* callback (Phase 5 / D9 — decoded frames now travel as shm `FrameHandle`s).
|
|
30
|
-
* Opens a frame-handle subscription, drains `pullFrameHandles` on a 50ms
|
|
31
|
-
* timer, and invokes `onHandle` once per decoded frame. The returned
|
|
32
|
-
* function tears the subscription down. `byteLength` stands in for the old
|
|
33
|
-
* `DecodedFrame.data.length`.
|
|
34
|
-
*/
|
|
35
|
-
async function subscribeDecodedFrames(
|
|
36
|
-
broker: IStreamBroker,
|
|
37
|
-
maxFps: number,
|
|
38
|
-
onHandle: (handle: FrameHandle) => void,
|
|
39
|
-
): Promise<() => Promise<void>> {
|
|
40
|
-
const { subscriptionId } = await broker.subscribeFrameHandles({ format: 'rgb', maxFps })
|
|
41
|
-
const iv = setInterval(() => {
|
|
42
|
-
for (const handle of broker.pullFrameHandles(subscriptionId, 8)) {
|
|
43
|
-
onHandle(handle)
|
|
44
|
-
}
|
|
45
|
-
}, 50)
|
|
46
|
-
return async () => {
|
|
47
|
-
clearInterval(iv)
|
|
48
|
-
await broker.unsubscribeFrameHandles(subscriptionId)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Fetch detect sub-stream URLs from Frigate config */
|
|
53
|
-
async function discoverSubStreams(): Promise<Array<{ name: string; url: string }>> {
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
const req = http.get(`http://${FRIGATE}:5000/api/config`, (res) => {
|
|
56
|
-
let data = ''
|
|
57
|
-
res.on('data', (chunk: Buffer) => {
|
|
58
|
-
data += chunk
|
|
59
|
-
})
|
|
60
|
-
res.on('end', () => {
|
|
61
|
-
try {
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment --
|
|
63
|
-
const config = JSON.parse(data)
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access --
|
|
65
|
-
const cameras = config.cameras || {}
|
|
66
|
-
const streams: Array<{ name: string; url: string }> = []
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any --
|
|
68
|
-
for (const [name, cam] of Object.entries(cameras) as [string, any][]) {
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access --
|
|
70
|
-
if (!cam.enabled && cam.enabled !== undefined) continue
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access --
|
|
72
|
-
const inputs = cam?.ffmpeg?.inputs || []
|
|
73
|
-
for (const inp of inputs) {
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
75
|
-
if (inp.roles?.includes('detect') && inp.path) {
|
|
76
|
-
let streamName: string
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
78
|
-
if (inp.path.includes('8554/')) {
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
80
|
-
streamName = inp.path.split('8554/').pop()!
|
|
81
|
-
} else {
|
|
82
|
-
streamName = `${name}_sub`
|
|
83
|
-
}
|
|
84
|
-
streams.push({ name, url: `rtsp://${FRIGATE}:8554/${streamName}` })
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
resolve(streams)
|
|
89
|
-
} catch (e) {
|
|
90
|
-
reject(e)
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
req.on('error', reject)
|
|
95
|
-
req.setTimeout(5000, () => {
|
|
96
|
-
req.destroy()
|
|
97
|
-
reject(new Error('Timeout'))
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
describe.skipIf(!FRIGATE)('Streaming Scale Test (requires Frigate)', () => {
|
|
103
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call -- @camstack/addon-stream-broker types resolve as any in this test context
|
|
104
|
-
const manager = new StreamBrokerManager([new FfmpegDecoderProvider()], mockLogger)
|
|
105
|
-
|
|
106
|
-
afterAll(async () => {
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
108
|
-
await manager.destroyAll()
|
|
109
|
-
// Wait for ffmpeg processes to exit
|
|
110
|
-
await wait(2000)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('progressive scale: 5 → 10 → 20 → 30 sub-streams @ 2fps for motion', async () => {
|
|
114
|
-
// Discover real streams, then duplicate to simulate more cameras
|
|
115
|
-
const realStreams = await discoverSubStreams()
|
|
116
|
-
|
|
117
|
-
// Test which streams actually respond (filter out 404s)
|
|
118
|
-
const workingStreams: typeof realStreams = []
|
|
119
|
-
for (const s of realStreams.slice(0, 5)) {
|
|
120
|
-
try {
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
122
|
-
const broker = await manager.createBroker(`probe-${s.name}`, {
|
|
123
|
-
type: 'rtsp',
|
|
124
|
-
url: s.url,
|
|
125
|
-
videoCodec: 'h264',
|
|
126
|
-
})
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
128
|
-
await (broker as any).start({ type: 'rtsp', url: s.url, videoCodec: 'h264' })
|
|
129
|
-
const ok = await new Promise<boolean>((resolve) => {
|
|
130
|
-
const timeout = setTimeout(() => resolve(false), 5000)
|
|
131
|
-
let unsub: (() => Promise<void>) | undefined
|
|
132
|
-
subscribeDecodedFrames(broker, 2, () => {
|
|
133
|
-
clearTimeout(timeout)
|
|
134
|
-
void unsub?.()
|
|
135
|
-
resolve(true)
|
|
136
|
-
})
|
|
137
|
-
.then((fn) => {
|
|
138
|
-
unsub = fn
|
|
139
|
-
})
|
|
140
|
-
.catch(() => resolve(false))
|
|
141
|
-
})
|
|
142
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
143
|
-
await broker.stop()
|
|
144
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
145
|
-
await manager.destroyBroker(`probe-${s.name}`)
|
|
146
|
-
if (ok) workingStreams.push(s)
|
|
147
|
-
} catch {
|
|
148
|
-
/* skip */
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(
|
|
153
|
-
`\nDiscovered ${realStreams.length} sub-streams, ${workingStreams.length} responding`,
|
|
154
|
-
)
|
|
155
|
-
if (workingStreams.length === 0) {
|
|
156
|
-
console.log(' No working sub-streams, skipping')
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Generate virtual streams by duplicating working ones (same RTSP URL, different broker ID)
|
|
161
|
-
function generateStreams(count: number) {
|
|
162
|
-
const streams: Array<{ name: string; url: string }> = []
|
|
163
|
-
for (let i = 0; i < count; i++) {
|
|
164
|
-
const real = workingStreams[i % workingStreams.length]!
|
|
165
|
-
streams.push({ name: `cam-${String(i).padStart(2, '0')}-${real.name}`, url: real.url })
|
|
166
|
-
}
|
|
167
|
-
return streams
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const scaleLevels = [5, 10, 20, 30]
|
|
171
|
-
|
|
172
|
-
for (const targetCount of scaleLevels) {
|
|
173
|
-
console.log(`\n=== Scale: ${targetCount} sub-streams @ 2fps ===`)
|
|
174
|
-
|
|
175
|
-
const memBefore = process.memoryUsage()
|
|
176
|
-
const cpuBefore = process.cpuUsage()
|
|
177
|
-
const brokerIds: string[] = []
|
|
178
|
-
const unsubs: Array<() => Promise<void>> = []
|
|
179
|
-
const perCamera: Record<string, { frames: number; totalBytes: number }> = {}
|
|
180
|
-
let connectFailures = 0
|
|
181
|
-
|
|
182
|
-
// Start N brokers IN PARALLEL (using duplicated working streams)
|
|
183
|
-
const streams = generateStreams(targetCount)
|
|
184
|
-
const startTime = Date.now()
|
|
185
|
-
|
|
186
|
-
// Launch all brokers concurrently — don't wait for keyframe sequentially
|
|
187
|
-
const connectPromises = streams.map(async (s) => {
|
|
188
|
-
const brokerId = `scale-${s.name}`
|
|
189
|
-
brokerIds.push(brokerId)
|
|
190
|
-
perCamera[s.name] = { frames: 0, totalBytes: 0 }
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
194
|
-
const broker = await manager.createBroker(brokerId, {
|
|
195
|
-
type: 'rtsp',
|
|
196
|
-
url: s.url,
|
|
197
|
-
videoCodec: 'h264',
|
|
198
|
-
})
|
|
199
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
200
|
-
await (broker as any).start({ type: 'rtsp', url: s.url, videoCodec: 'h264' })
|
|
201
|
-
|
|
202
|
-
// Wait for first frame with tight timeout
|
|
203
|
-
await new Promise<void>((resolve) => {
|
|
204
|
-
const timeout = setTimeout(() => {
|
|
205
|
-
connectFailures++
|
|
206
|
-
resolve()
|
|
207
|
-
}, 10000)
|
|
208
|
-
let unsub: (() => Promise<void>) | undefined
|
|
209
|
-
subscribeDecodedFrames(broker, 2, () => {
|
|
210
|
-
clearTimeout(timeout)
|
|
211
|
-
void unsub?.()
|
|
212
|
-
resolve()
|
|
213
|
-
})
|
|
214
|
-
.then((fn) => {
|
|
215
|
-
unsub = fn
|
|
216
|
-
})
|
|
217
|
-
.catch(() => resolve())
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
// Subscribe for motion analysis
|
|
221
|
-
const unsub = await subscribeDecodedFrames(broker, 2, (handle: FrameHandle) => {
|
|
222
|
-
perCamera[s.name]!.frames++
|
|
223
|
-
perCamera[s.name]!.totalBytes += handle.byteLength
|
|
224
|
-
})
|
|
225
|
-
unsubs.push(unsub)
|
|
226
|
-
} catch (err) {
|
|
227
|
-
connectFailures++
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
await Promise.all(connectPromises)
|
|
232
|
-
|
|
233
|
-
const connectTime = Date.now() - startTime
|
|
234
|
-
console.log(
|
|
235
|
-
` Connected ${targetCount - connectFailures}/${targetCount} in ${(connectTime / 1000).toFixed(1)}s`,
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
// Run for 5 seconds of actual streaming
|
|
239
|
-
const measureStart = Date.now()
|
|
240
|
-
const cpuMeasureStart = process.cpuUsage()
|
|
241
|
-
await wait(5000)
|
|
242
|
-
const cpuMeasure = process.cpuUsage(cpuMeasureStart)
|
|
243
|
-
const measureDuration = (Date.now() - measureStart) / 1000
|
|
244
|
-
|
|
245
|
-
// Unsubscribe
|
|
246
|
-
for (const unsub of unsubs) await unsub()
|
|
247
|
-
|
|
248
|
-
// Calculate metrics
|
|
249
|
-
const cpuAfter = process.cpuUsage(cpuBefore)
|
|
250
|
-
const memAfter = process.memoryUsage()
|
|
251
|
-
|
|
252
|
-
let totalFrames = 0
|
|
253
|
-
let totalBytes = 0
|
|
254
|
-
let activeCameras = 0
|
|
255
|
-
let starvingCameras = 0
|
|
256
|
-
|
|
257
|
-
for (const [name, data] of Object.entries(perCamera)) {
|
|
258
|
-
totalFrames += data.frames
|
|
259
|
-
totalBytes += data.totalBytes
|
|
260
|
-
if (data.frames > 0) activeCameras++
|
|
261
|
-
if (data.frames < 3) starvingCameras++ // less than 3 frames in 5s = starving
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const cpuPct = (
|
|
265
|
-
((cpuMeasure.user + cpuMeasure.system) / (measureDuration * 1_000_000)) *
|
|
266
|
-
100
|
|
267
|
-
).toFixed(1)
|
|
268
|
-
const rssMB = (memAfter.rss / 1024 / 1024).toFixed(0)
|
|
269
|
-
const heapMB = (memAfter.heapUsed / 1024 / 1024).toFixed(0)
|
|
270
|
-
const combinedFps = (totalFrames / measureDuration).toFixed(1)
|
|
271
|
-
const avgFrameKB = totalFrames > 0 ? (totalBytes / totalFrames / 1024).toFixed(0) : '0'
|
|
272
|
-
const bwMBs = (totalBytes / 1024 / 1024 / measureDuration).toFixed(1)
|
|
273
|
-
|
|
274
|
-
console.log(` Active: ${activeCameras}/${targetCount} cameras producing frames`)
|
|
275
|
-
console.log(` Starving: ${starvingCameras} cameras (<3 frames in 5s)`)
|
|
276
|
-
console.log(` Total frames: ${totalFrames} (${combinedFps} fps combined)`)
|
|
277
|
-
console.log(` Avg frame: ${avgFrameKB} KB`)
|
|
278
|
-
console.log(` Decode BW: ${bwMBs} MB/s`)
|
|
279
|
-
console.log(` CPU: ${cpuPct}%`)
|
|
280
|
-
console.log(` RSS: ${rssMB} MB | Heap: ${heapMB} MB`)
|
|
281
|
-
|
|
282
|
-
// Per-camera breakdown
|
|
283
|
-
console.log(` ---`)
|
|
284
|
-
const sorted = Object.entries(perCamera).toSorted((a, b) => b[1].frames - a[1].frames)
|
|
285
|
-
for (const [name, data] of sorted) {
|
|
286
|
-
const fps = (data.frames / measureDuration).toFixed(1)
|
|
287
|
-
const avgKB = data.frames > 0 ? (data.totalBytes / data.frames / 1024).toFixed(0) : '-'
|
|
288
|
-
const status = data.frames >= 3 ? '✓' : data.frames > 0 ? '⚠' : '✗'
|
|
289
|
-
console.log(
|
|
290
|
-
` ${status} ${name.padEnd(35)} ${fps.padStart(4)} fps ${avgKB.padStart(5)} KB/f (${data.frames} frames)`,
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Cleanup for next scale level
|
|
295
|
-
for (const id of brokerIds) {
|
|
296
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
297
|
-
const b = manager.getBroker(id)
|
|
298
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
299
|
-
if (b) await b.stop()
|
|
300
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access --
|
|
301
|
-
await manager.destroyBroker(id)
|
|
302
|
-
}
|
|
303
|
-
brokerIds.length = 0
|
|
304
|
-
unsubs.length = 0
|
|
305
|
-
Object.keys(perCamera).forEach((k) => delete perCamera[k])
|
|
306
|
-
|
|
307
|
-
// Expect at least 80% of cameras producing
|
|
308
|
-
expect(activeCameras).toBeGreaterThanOrEqual(Math.floor(targetCount * 0.8))
|
|
309
|
-
|
|
310
|
-
// Pause between levels for cleanup
|
|
311
|
-
await wait(2000)
|
|
312
|
-
}
|
|
313
|
-
}, 300000) // 5 min timeout for the full progressive test
|
|
314
|
-
})
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* F3 backend wiring — forked-addon routes + custom actions over UDS.
|
|
3
|
-
*
|
|
4
|
-
* Verifies the production wiring `AddonRegistryService` builds, end-to-end over
|
|
5
|
-
* a REAL LocalChildRegistry + LocalChildClient pair, with the child running the
|
|
6
|
-
* REAL addon-runner-side dispatcher (`createChildAddonCallDispatch`):
|
|
7
|
-
*
|
|
8
|
-
* - Custom actions: a `CustomActionRegistry` whose dispatcher is the F3
|
|
9
|
-
* `callAddonOnChild({target:'custom', action, args})` reaches the child's
|
|
10
|
-
* real custom-action handler and returns its result (the path
|
|
11
|
-
* `registerForkedAddonCustomActions` builds via `dispatchForkedCustomAction`).
|
|
12
|
-
* - Routes: `callAddonOnChild({target:'routes'})` returns handler-stripped
|
|
13
|
-
* descriptors; the per-route bridge handler dispatches through the
|
|
14
|
-
* `addon-routes` `invoke()` cap method (the path `mountForkedAddonRoutes`
|
|
15
|
-
* builds), and the captured envelope round-trips.
|
|
16
|
-
*
|
|
17
|
-
* The child-side `onAddonCall` handler is a faithful inline of the addon-runner
|
|
18
|
-
* dispatcher (resolve route provider / custom registry; strip route handlers).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { describe, it, expect, afterEach } from 'vitest'
|
|
22
|
-
import { z } from 'zod'
|
|
23
|
-
import {
|
|
24
|
-
LocalChildRegistry,
|
|
25
|
-
LocalChildClient,
|
|
26
|
-
CustomActionRegistry,
|
|
27
|
-
createLocalTransport,
|
|
28
|
-
} from '@camstack/kernel'
|
|
29
|
-
import type { AddonCallInput } from '@camstack/kernel'
|
|
30
|
-
import { buildAddonRouteProvider, customAction, defineCustomActions } from '@camstack/types'
|
|
31
|
-
import type { IAddonHttpRoute, CustomActionsSpec } from '@camstack/types'
|
|
32
|
-
|
|
33
|
-
const nid = (): string => `f3-wiring-${process.pid}-${Math.random().toString(36).slice(2)}`
|
|
34
|
-
|
|
35
|
-
describe('F3 — forked-addon routes + custom actions over UDS (backend wiring)', () => {
|
|
36
|
-
let registry: LocalChildRegistry | null = null
|
|
37
|
-
const clients: LocalChildClient[] = []
|
|
38
|
-
|
|
39
|
-
afterEach(async () => {
|
|
40
|
-
for (const c of clients) await c.close().catch(() => {})
|
|
41
|
-
clients.length = 0
|
|
42
|
-
await registry?.close().catch(() => {})
|
|
43
|
-
registry = null
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
/** Stand up a hub-side registry + a child running the real addon-call dispatcher. */
|
|
47
|
-
async function standUp(opts: {
|
|
48
|
-
addonId: string
|
|
49
|
-
routes?: readonly IAddonHttpRoute[]
|
|
50
|
-
catalog?: CustomActionsSpec
|
|
51
|
-
handlers?: Record<string, (input: unknown) => Promise<unknown>>
|
|
52
|
-
}): Promise<{ childCustomActions: CustomActionRegistry }> {
|
|
53
|
-
const nodeId = nid()
|
|
54
|
-
registry = new LocalChildRegistry(createLocalTransport().createServer(nodeId))
|
|
55
|
-
await registry.start()
|
|
56
|
-
|
|
57
|
-
// The runner-side custom-action registry — populated as the addon-runner does.
|
|
58
|
-
const childCustomActions = new CustomActionRegistry()
|
|
59
|
-
if (opts.catalog && opts.handlers) {
|
|
60
|
-
const handlers = opts.handlers
|
|
61
|
-
childCustomActions.registerAddon(opts.addonId, opts.catalog, (action, input) => {
|
|
62
|
-
const fn = handlers[action]
|
|
63
|
-
if (!fn) throw new Error(`no handler for ${action}`)
|
|
64
|
-
return fn(input)
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const routeProvider = opts.routes ? buildAddonRouteProvider(opts.addonId, opts.routes) : null
|
|
69
|
-
|
|
70
|
-
const child = new LocalChildClient({
|
|
71
|
-
nodeId,
|
|
72
|
-
childId: opts.addonId,
|
|
73
|
-
caps: [{ capName: 'addon-routes', mode: 'collection' }],
|
|
74
|
-
// Cap dispatch — resolve the `addon-routes` cap's `invoke`/`getRoutes`
|
|
75
|
-
// method on the live route provider (mirrors the runner's
|
|
76
|
-
// createChildCapDispatch resolving allSingletonProviders['addon-routes']).
|
|
77
|
-
dispatch: async (call) => {
|
|
78
|
-
if (call.capName === 'addon-routes' && routeProvider !== null) {
|
|
79
|
-
const fn = Reflect.get(routeProvider, call.method)
|
|
80
|
-
if (typeof fn === 'function') return fn.call(routeProvider, call.args)
|
|
81
|
-
}
|
|
82
|
-
return null
|
|
83
|
-
},
|
|
84
|
-
})
|
|
85
|
-
// Inline addon-runner dispatcher: routes → handler-stripped descriptors,
|
|
86
|
-
// custom → the child custom-action registry. Mirrors
|
|
87
|
-
// `createChildAddonCallDispatch` in the kernel.
|
|
88
|
-
child.onAddonCall(async (call: AddonCallInput) => {
|
|
89
|
-
if (call.target === 'routes') {
|
|
90
|
-
if (routeProvider === null) throw new Error(`addon "${call.addonId}" has no routes`)
|
|
91
|
-
const live = routeProvider.getRoutes()
|
|
92
|
-
return live.map((r) => ({
|
|
93
|
-
method: r.method,
|
|
94
|
-
path: r.path,
|
|
95
|
-
...(r.access !== undefined ? { access: r.access } : {}),
|
|
96
|
-
...(r.description !== undefined ? { description: r.description } : {}),
|
|
97
|
-
}))
|
|
98
|
-
}
|
|
99
|
-
const action = call.action
|
|
100
|
-
if (typeof action !== 'string') throw new Error('missing action')
|
|
101
|
-
const entry = childCustomActions.resolve(call.addonId, action)
|
|
102
|
-
if (entry === null) throw new Error(`no custom action "${action}"`)
|
|
103
|
-
return entry.handler(call.args)
|
|
104
|
-
})
|
|
105
|
-
clients.push(child)
|
|
106
|
-
await child.start()
|
|
107
|
-
|
|
108
|
-
return { childCustomActions }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
it('custom action: the registerForkedAddonCustomActions dispatch path reaches the child handler', async () => {
|
|
112
|
-
const catalog = defineCustomActions({
|
|
113
|
-
runBenchmark: customAction(
|
|
114
|
-
z.object({ iterations: z.number() }),
|
|
115
|
-
z.object({ ran: z.number() }),
|
|
116
|
-
{ kind: 'mutation' },
|
|
117
|
-
),
|
|
118
|
-
})
|
|
119
|
-
await standUp({
|
|
120
|
-
addonId: 'benchmark',
|
|
121
|
-
catalog,
|
|
122
|
-
handlers: {
|
|
123
|
-
runBenchmark: async (input) => {
|
|
124
|
-
const args = input as { iterations: number }
|
|
125
|
-
return { ran: args.iterations }
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
// Hub-side registry — the dispatcher mirrors `dispatchForkedCustomAction`.
|
|
131
|
-
const hubRegistry = new CustomActionRegistry()
|
|
132
|
-
hubRegistry.registerAddon('benchmark', catalog, (action, input) =>
|
|
133
|
-
registry!.callAddonOnChild('benchmark', {
|
|
134
|
-
addonId: 'benchmark',
|
|
135
|
-
target: 'custom',
|
|
136
|
-
action,
|
|
137
|
-
args: input,
|
|
138
|
-
}),
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
const entry = hubRegistry.resolve('benchmark', 'runBenchmark')
|
|
142
|
-
expect(entry).not.toBeNull()
|
|
143
|
-
const result = await entry!.handler({ iterations: 9 })
|
|
144
|
-
expect(result).toEqual({ ran: 9 })
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('routes: callAddonOnChild(routes) returns stripped descriptors and invoke() round-trips', async () => {
|
|
148
|
-
const routes: IAddonHttpRoute[] = [
|
|
149
|
-
{
|
|
150
|
-
method: 'GET',
|
|
151
|
-
path: '/hello/:name',
|
|
152
|
-
access: 'public',
|
|
153
|
-
handler: async (req, reply) => {
|
|
154
|
-
reply.code(200)
|
|
155
|
-
reply.send({ greeting: `hi ${req.params.name}` })
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
]
|
|
159
|
-
await standUp({ addonId: 'export-alexa', routes })
|
|
160
|
-
|
|
161
|
-
// (a) routes come back handler-stripped (the `mountForkedAddonRoutes` fetch).
|
|
162
|
-
const rawRoutes = await registry!.callAddonOnChild('export-alexa', {
|
|
163
|
-
addonId: 'export-alexa',
|
|
164
|
-
target: 'routes',
|
|
165
|
-
})
|
|
166
|
-
expect(rawRoutes).toEqual([{ method: 'GET', path: '/hello/:name', access: 'public' }])
|
|
167
|
-
|
|
168
|
-
// (b) dispatch through the addon-routes cap proxy's `invoke` cap method —
|
|
169
|
-
// the bridge handler `mountForkedAddonRoutes` synthesizes calls this.
|
|
170
|
-
const routesCapProxy = {
|
|
171
|
-
invoke: (input: unknown) =>
|
|
172
|
-
registry!.callCapOnChild('export-alexa', {
|
|
173
|
-
capName: 'addon-routes',
|
|
174
|
-
method: 'invoke',
|
|
175
|
-
args: input,
|
|
176
|
-
}),
|
|
177
|
-
}
|
|
178
|
-
const envelope = await routesCapProxy.invoke({
|
|
179
|
-
method: 'GET',
|
|
180
|
-
path: '/hello/world',
|
|
181
|
-
params: { name: 'world' },
|
|
182
|
-
query: {},
|
|
183
|
-
body: undefined,
|
|
184
|
-
headers: {},
|
|
185
|
-
})
|
|
186
|
-
expect(envelope).toMatchObject({ status: 200, body: { greeting: 'hi world' } })
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('auth-oidc shape: a forked addon with a SYNC array getRoutes + async handlers mounts via the strip path without serializing a function', async () => {
|
|
190
|
-
// Reproduces the regression: `auth-oidc` declares a manual route provider
|
|
191
|
-
// `{ id, getRoutes: () => routes }` (NO `buildAddonRouteProvider`, NO
|
|
192
|
-
// `invoke`). It forks like every non-core addon, so the hub resolves an
|
|
193
|
-
// async UDS cap proxy whose `getRoutes()` returns a Promise. The mount path
|
|
194
|
-
// MUST NOT register that proxy directly (→ `getRoutes(...).map is not a
|
|
195
|
-
// function`) nor dispatch its `getRoutes` cap method over UDS (the child's
|
|
196
|
-
// `getRoutes()` returns the live array WITH async `handler` functions →
|
|
197
|
-
// MsgPack "Unrecognized object: [object AsyncFunction]"). It uses the
|
|
198
|
-
// handler-stripped `callAddonOnChild(target:'routes')` bridge instead.
|
|
199
|
-
const routes: IAddonHttpRoute[] = [
|
|
200
|
-
{
|
|
201
|
-
method: 'GET',
|
|
202
|
-
path: '/:providerId/start',
|
|
203
|
-
access: 'public',
|
|
204
|
-
description: 'Begin OIDC redirect login flow',
|
|
205
|
-
handler: async (req, reply) => {
|
|
206
|
-
reply.code(302)
|
|
207
|
-
reply.send('')
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
method: 'GET',
|
|
212
|
-
path: '/:providerId/callback',
|
|
213
|
-
access: 'public',
|
|
214
|
-
handler: async (req, reply) => {
|
|
215
|
-
reply.code(200)
|
|
216
|
-
reply.send('ok')
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
]
|
|
220
|
-
await standUp({ addonId: 'auth-oidc', routes })
|
|
221
|
-
|
|
222
|
-
// The handler-stripped descriptors cross the real UDS pair. If the strip
|
|
223
|
-
// path leaked a `handler` function, MsgPack `encodeFrame` would throw
|
|
224
|
-
// "Unrecognized object: [object AsyncFunction]" before this resolves.
|
|
225
|
-
const rawRoutes = await registry!.callAddonOnChild('auth-oidc', {
|
|
226
|
-
addonId: 'auth-oidc',
|
|
227
|
-
target: 'routes',
|
|
228
|
-
})
|
|
229
|
-
expect(rawRoutes).toEqual([
|
|
230
|
-
{
|
|
231
|
-
method: 'GET',
|
|
232
|
-
path: '/:providerId/start',
|
|
233
|
-
access: 'public',
|
|
234
|
-
description: 'Begin OIDC redirect login flow',
|
|
235
|
-
},
|
|
236
|
-
{ method: 'GET', path: '/:providerId/callback', access: 'public' },
|
|
237
|
-
])
|
|
238
|
-
// Coarse reachability gate the route-mount fallback uses: the child is
|
|
239
|
-
// connected even though we only checked the addon-call surface.
|
|
240
|
-
expect(registry!.isChildKnown('auth-oidc')).toBe(true)
|
|
241
|
-
})
|
|
242
|
-
})
|