@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,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E2E test for the device-manager aggregator via the generated tRPC cap
|
|
3
|
-
* router. Verifies the wire shape of `getDeviceSettingsAggregate`,
|
|
4
|
-
* `getDeviceLiveInfoAggregate`, and `updateDeviceField` — the three
|
|
5
|
-
* aggregator methods the admin UI consumes.
|
|
6
|
-
*
|
|
7
|
-
* Covers:
|
|
8
|
-
* - input validation at the router boundary
|
|
9
|
-
* - schema shape roundtrip (provider → router → caller)
|
|
10
|
-
* - auth enforcement (getters = protected, updateDeviceField = admin)
|
|
11
|
-
*/
|
|
12
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
13
|
-
import { createCapRouter_deviceManager } from '../../api/trpc/generated-cap-routers.js'
|
|
14
|
-
import type { IDeviceManagerProvider } from '@camstack/types'
|
|
15
|
-
import { makeCtx, invokeProcedure } from './harness.js'
|
|
16
|
-
|
|
17
|
-
function makeProvider(): IDeviceManagerProvider {
|
|
18
|
-
return {
|
|
19
|
-
// Aggregator methods we exercise
|
|
20
|
-
getDeviceSettingsAggregate: vi.fn(async ({ deviceId }) => ({
|
|
21
|
-
sections: [
|
|
22
|
-
{
|
|
23
|
-
id: 'identity',
|
|
24
|
-
title: 'Identity',
|
|
25
|
-
tab: 'general',
|
|
26
|
-
order: 0,
|
|
27
|
-
fields: [
|
|
28
|
-
{
|
|
29
|
-
type: 'text',
|
|
30
|
-
key: '_stableId',
|
|
31
|
-
label: 'Stable ID',
|
|
32
|
-
readonlyField: true,
|
|
33
|
-
value: String(deviceId),
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: 'motion-tuning',
|
|
39
|
-
title: 'Motion Tuning',
|
|
40
|
-
tab: 'detection',
|
|
41
|
-
order: 5,
|
|
42
|
-
fields: [
|
|
43
|
-
{
|
|
44
|
-
type: 'number',
|
|
45
|
-
key: 'threshold',
|
|
46
|
-
label: 'Threshold',
|
|
47
|
-
writerCapName: 'motion-detection',
|
|
48
|
-
writerAddonId: 'motion-wasm',
|
|
49
|
-
source: 'settings',
|
|
50
|
-
value: 20,
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
})),
|
|
56
|
-
getDeviceLiveInfoAggregate: vi.fn(async ({ deviceId }) => ({
|
|
57
|
-
sections: [
|
|
58
|
-
{
|
|
59
|
-
id: 'orchestrator-live',
|
|
60
|
-
title: 'Pipeline Status',
|
|
61
|
-
tab: 'detection',
|
|
62
|
-
order: 100,
|
|
63
|
-
fields: [
|
|
64
|
-
{
|
|
65
|
-
type: 'text',
|
|
66
|
-
key: 'assignedRunner',
|
|
67
|
-
label: 'Assigned Runner',
|
|
68
|
-
readonlyField: true,
|
|
69
|
-
source: 'live',
|
|
70
|
-
value: deviceId === 1 ? 'hub' : '',
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
})),
|
|
76
|
-
updateDeviceField: vi.fn(async () => ({ success: true as const })),
|
|
77
|
-
|
|
78
|
-
// Placeholders for the other device-manager cap methods we don't
|
|
79
|
-
// exercise here — kept as no-ops so the IDeviceManagerProvider
|
|
80
|
-
// interface is fully satisfied.
|
|
81
|
-
registerDevice: vi.fn() as IDeviceManagerProvider['registerDevice'],
|
|
82
|
-
removeDevice: vi.fn() as IDeviceManagerProvider['removeDevice'],
|
|
83
|
-
persistConfig: vi.fn() as IDeviceManagerProvider['persistConfig'],
|
|
84
|
-
loadConfig: vi.fn(async () => ({})) as IDeviceManagerProvider['loadConfig'],
|
|
85
|
-
listPersistedByAddon: vi.fn(async () => []) as IDeviceManagerProvider['listPersistedByAddon'],
|
|
86
|
-
listAll: vi.fn(async () => []) as IDeviceManagerProvider['listAll'],
|
|
87
|
-
getDevice: vi.fn(async () => null) as IDeviceManagerProvider['getDevice'],
|
|
88
|
-
getChildren: vi.fn(async () => []) as IDeviceManagerProvider['getChildren'],
|
|
89
|
-
getStreamSources: vi.fn(async () => []) as IDeviceManagerProvider['getStreamSources'],
|
|
90
|
-
getConfigSchema: vi.fn(async () => []) as IDeviceManagerProvider['getConfigSchema'],
|
|
91
|
-
getSettingsSchema: vi.fn(async () => null) as IDeviceManagerProvider['getSettingsSchema'],
|
|
92
|
-
updateConfig: vi.fn(async () => ({
|
|
93
|
-
success: true as const,
|
|
94
|
-
})) as IDeviceManagerProvider['updateConfig'],
|
|
95
|
-
enable: vi.fn(async () => ({ success: true as const })) as IDeviceManagerProvider['enable'],
|
|
96
|
-
disable: vi.fn(async () => ({ success: true as const })) as IDeviceManagerProvider['disable'],
|
|
97
|
-
remove: vi.fn(async () => ({ success: true as const })) as IDeviceManagerProvider['remove'],
|
|
98
|
-
getStreamProfileMap: vi.fn(async () => ({})) as IDeviceManagerProvider['getStreamProfileMap'],
|
|
99
|
-
setStreamProfileMap: vi.fn(async () => ({
|
|
100
|
-
success: true as const,
|
|
101
|
-
})) as IDeviceManagerProvider['setStreamProfileMap'],
|
|
102
|
-
probeStreams: vi.fn(async () => []) as IDeviceManagerProvider['probeStreams'],
|
|
103
|
-
getBindings: vi.fn(async ({ deviceId }: { deviceId: number }) => ({
|
|
104
|
-
deviceId,
|
|
105
|
-
entries: [],
|
|
106
|
-
})) as IDeviceManagerProvider['getBindings'],
|
|
107
|
-
setWrapperActive: vi.fn() as IDeviceManagerProvider['setWrapperActive'],
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
describe('device-manager aggregator via tRPC router', () => {
|
|
112
|
-
const DEVICE_ID = 1
|
|
113
|
-
|
|
114
|
-
it('getDeviceSettingsAggregate returns the merged shape for a known device', async () => {
|
|
115
|
-
const provider = makeProvider()
|
|
116
|
-
const router = createCapRouter_deviceManager(() => provider)
|
|
117
|
-
const result = await invokeProcedure(router, 'getDeviceSettingsAggregate', makeCtx('admin'), {
|
|
118
|
-
deviceId: DEVICE_ID,
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
expect(result.ok).toBe(true)
|
|
122
|
-
if (!result.ok) return
|
|
123
|
-
const value = result.value as {
|
|
124
|
-
sections: readonly { id: string; tab?: string; fields: readonly unknown[] }[]
|
|
125
|
-
}
|
|
126
|
-
expect(value.sections.map((s) => s.id)).toEqual(['identity', 'motion-tuning'])
|
|
127
|
-
expect(provider.getDeviceSettingsAggregate).toHaveBeenCalledWith({ deviceId: DEVICE_ID })
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('getDeviceLiveInfoAggregate returns live sections', async () => {
|
|
131
|
-
const provider = makeProvider()
|
|
132
|
-
const router = createCapRouter_deviceManager(() => provider)
|
|
133
|
-
const result = await invokeProcedure(router, 'getDeviceLiveInfoAggregate', makeCtx('admin'), {
|
|
134
|
-
deviceId: DEVICE_ID,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
expect(result.ok).toBe(true)
|
|
138
|
-
if (!result.ok) return
|
|
139
|
-
const value = result.value as {
|
|
140
|
-
sections: readonly { fields: readonly { key?: string; value?: unknown }[] }[]
|
|
141
|
-
}
|
|
142
|
-
const runner = value.sections[0]!.fields.find((f) => f.key === 'assignedRunner')
|
|
143
|
-
expect(runner?.value).toBe('hub')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('updateDeviceField forwards the full payload (deviceId, writerCap, writerAddon, key, value)', async () => {
|
|
147
|
-
const provider = makeProvider()
|
|
148
|
-
const router = createCapRouter_deviceManager(() => provider)
|
|
149
|
-
const result = await invokeProcedure(router, 'updateDeviceField', makeCtx('admin'), {
|
|
150
|
-
deviceId: DEVICE_ID,
|
|
151
|
-
writerCapName: 'motion-detection',
|
|
152
|
-
writerAddonId: 'motion-wasm',
|
|
153
|
-
key: 'threshold',
|
|
154
|
-
value: 42,
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
expect(result.ok).toBe(true)
|
|
158
|
-
expect(provider.updateDeviceField).toHaveBeenCalledWith({
|
|
159
|
-
deviceId: DEVICE_ID,
|
|
160
|
-
writerCapName: 'motion-detection',
|
|
161
|
-
writerAddonId: 'motion-wasm',
|
|
162
|
-
key: 'threshold',
|
|
163
|
-
value: 42,
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('updateDeviceField enforces admin auth', async () => {
|
|
168
|
-
const provider = makeProvider()
|
|
169
|
-
const router = createCapRouter_deviceManager(() => provider)
|
|
170
|
-
|
|
171
|
-
const payload = {
|
|
172
|
-
deviceId: DEVICE_ID,
|
|
173
|
-
writerCapName: 'motion-detection',
|
|
174
|
-
writerAddonId: 'motion-wasm',
|
|
175
|
-
key: 'threshold',
|
|
176
|
-
value: 1,
|
|
177
|
-
}
|
|
178
|
-
const viewer = await invokeProcedure(router, 'updateDeviceField', makeCtx('user'), payload)
|
|
179
|
-
expect(viewer.ok).toBe(false)
|
|
180
|
-
if (!viewer.ok) expect(viewer.code).toBe('FORBIDDEN')
|
|
181
|
-
|
|
182
|
-
const admin = await invokeProcedure(router, 'updateDeviceField', makeCtx('admin'), payload)
|
|
183
|
-
expect(admin.ok).toBe(true)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('router returns PRECONDITION_FAILED when the provider is missing', async () => {
|
|
187
|
-
const router = createCapRouter_deviceManager(() => null)
|
|
188
|
-
const result = await invokeProcedure(router, 'getDeviceSettingsAggregate', makeCtx('admin'), {
|
|
189
|
-
deviceId: DEVICE_ID,
|
|
190
|
-
})
|
|
191
|
-
expect(result.ok).toBe(false)
|
|
192
|
-
if (!result.ok) expect(result.code).toBe('PRECONDITION_FAILED')
|
|
193
|
-
})
|
|
194
|
-
})
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test harness for codegen'd capability tRPC routers.
|
|
3
|
-
*
|
|
4
|
-
* Provides helpers to:
|
|
5
|
-
* - Build a minimal `TrpcContext` per auth role (anonymous/user/admin/agent)
|
|
6
|
-
* - Wrap a `createCapRouter_X` with a mock provider and get a typed caller
|
|
7
|
-
* - Assert that a procedure enforces the expected auth level
|
|
8
|
-
* - Drive subscriptions via mock push callbacks
|
|
9
|
-
*
|
|
10
|
-
* This harness is the foundation for per-cap `.router.spec.ts` files. See
|
|
11
|
-
* `system-info.router.spec.ts` for a usage example.
|
|
12
|
-
*/
|
|
13
|
-
import { TRPCError } from '@trpc/server'
|
|
14
|
-
import type { TrpcContext, AuthenticatedAgent } from '../../api/trpc/trpc.context.js'
|
|
15
|
-
|
|
16
|
-
// v2 auth model — there's no role enum, just `isAdmin`. The harness
|
|
17
|
-
// exposes four synthetic identities covering every gate we ship:
|
|
18
|
-
// anonymous : no auth (null user)
|
|
19
|
-
// user : authenticated, isAdmin=false (the only non-admin tier)
|
|
20
|
-
// admin : authenticated, isAdmin=true
|
|
21
|
-
// agent : authenticated, isAdmin=true with `agentId` set
|
|
22
|
-
export type AuthLevel = 'public' | 'protected' | 'admin'
|
|
23
|
-
export type TestRole = 'anonymous' | 'user' | 'admin' | 'agent'
|
|
24
|
-
|
|
25
|
-
/** Build an AuthenticatedAgent stub for a given synthetic identity. */
|
|
26
|
-
export function makeUser(
|
|
27
|
-
role: Exclude<TestRole, 'anonymous'>,
|
|
28
|
-
overrides: Partial<AuthenticatedAgent> = {},
|
|
29
|
-
): AuthenticatedAgent {
|
|
30
|
-
const isAdmin = role === 'admin' || role === 'agent'
|
|
31
|
-
const base: AuthenticatedAgent = {
|
|
32
|
-
id: `user-${role}`,
|
|
33
|
-
username: role,
|
|
34
|
-
isAdmin,
|
|
35
|
-
permissions: { isAdmin, allowedProviders: '*', allowedDevices: {} },
|
|
36
|
-
isApiKey: false,
|
|
37
|
-
}
|
|
38
|
-
if (role === 'agent') {
|
|
39
|
-
return { ...base, agentId: 'agent-test', ...overrides }
|
|
40
|
-
}
|
|
41
|
-
return { ...base, ...overrides }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Build a minimal TrpcContext with the given identity (or `anonymous` for an unauthenticated ctx). */
|
|
45
|
-
export function makeCtx(role: TestRole, overrides: Partial<AuthenticatedAgent> = {}): TrpcContext {
|
|
46
|
-
const user = role === 'anonymous' ? null : makeUser(role, overrides)
|
|
47
|
-
// `req` is only used by subscription/WS plumbing which we don't exercise here.
|
|
48
|
-
// Cast via `unknown` to avoid pulling in a full Fastify stub.
|
|
49
|
-
return { user, req: {} as unknown as TrpcContext['req'] }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Identities that SHOULD pass for each auth level (positive set).
|
|
54
|
-
* Anything outside the allow-list should receive UNAUTHORIZED or FORBIDDEN.
|
|
55
|
-
*/
|
|
56
|
-
export const ALLOWED_ROLES_BY_AUTH: Readonly<Record<AuthLevel, readonly TestRole[]>> = {
|
|
57
|
-
public: ['anonymous', 'user', 'admin', 'agent'],
|
|
58
|
-
protected: ['user', 'admin', 'agent'],
|
|
59
|
-
admin: ['admin', 'agent'],
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export const ALL_ROLES: readonly TestRole[] = ['anonymous', 'user', 'admin', 'agent']
|
|
63
|
-
|
|
64
|
-
/** Determine whether a synthetic identity should be allowed to call a procedure with the given auth level. */
|
|
65
|
-
export function isRoleAllowed(role: TestRole, auth: AuthLevel): boolean {
|
|
66
|
-
return ALLOWED_ROLES_BY_AUTH[auth].includes(role)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Minimal router interface returned by the codegen. Each method is a tRPC procedure;
|
|
71
|
-
* calling `.createCaller(ctx)` returns an object with one async function per method.
|
|
72
|
-
*/
|
|
73
|
-
export interface CreateCallerCapableRouter {
|
|
74
|
-
readonly createCaller: (ctx: TrpcContext) => Record<string, (input?: unknown) => unknown>
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Invoke a procedure and capture the outcome (ok | trpc error code | other throw).
|
|
79
|
-
* Useful for auth matrix assertions without leaking exceptions.
|
|
80
|
-
*/
|
|
81
|
-
export async function invokeProcedure(
|
|
82
|
-
router: CreateCallerCapableRouter,
|
|
83
|
-
procedure: string,
|
|
84
|
-
ctx: TrpcContext,
|
|
85
|
-
input?: unknown,
|
|
86
|
-
): Promise<{ ok: true; value: unknown } | { ok: false; code: string; message: string }> {
|
|
87
|
-
try {
|
|
88
|
-
const caller = router.createCaller(ctx)
|
|
89
|
-
const fn = caller[procedure]
|
|
90
|
-
if (typeof fn !== 'function') {
|
|
91
|
-
return { ok: false, code: 'NOT_FOUND', message: `procedure "${procedure}" missing on caller` }
|
|
92
|
-
}
|
|
93
|
-
const value = await fn(input)
|
|
94
|
-
return { ok: true, value }
|
|
95
|
-
} catch (err) {
|
|
96
|
-
if (err instanceof TRPCError) {
|
|
97
|
-
return { ok: false, code: err.code, message: err.message }
|
|
98
|
-
}
|
|
99
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
100
|
-
return { ok: false, code: 'THROWN', message }
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Assert that a procedure enforces the expected auth level: each identity in `ALL_ROLES`
|
|
106
|
-
* should either succeed (allowed) or fail with UNAUTHORIZED/FORBIDDEN (denied).
|
|
107
|
-
*
|
|
108
|
-
* @returns An array of `{ role, allowed, outcome }` describing what happened — useful
|
|
109
|
-
* for building focused expectations in the calling test.
|
|
110
|
-
*/
|
|
111
|
-
export async function checkAuthMatrix(
|
|
112
|
-
router: CreateCallerCapableRouter,
|
|
113
|
-
procedure: string,
|
|
114
|
-
auth: AuthLevel,
|
|
115
|
-
input?: unknown,
|
|
116
|
-
): Promise<
|
|
117
|
-
ReadonlyArray<{
|
|
118
|
-
readonly role: TestRole
|
|
119
|
-
readonly allowed: boolean
|
|
120
|
-
readonly outcome: Awaited<ReturnType<typeof invokeProcedure>>
|
|
121
|
-
}>
|
|
122
|
-
> {
|
|
123
|
-
const results: Array<{
|
|
124
|
-
role: TestRole
|
|
125
|
-
allowed: boolean
|
|
126
|
-
outcome: Awaited<ReturnType<typeof invokeProcedure>>
|
|
127
|
-
}> = []
|
|
128
|
-
for (const role of ALL_ROLES) {
|
|
129
|
-
const outcome = await invokeProcedure(router, procedure, makeCtx(role), input)
|
|
130
|
-
results.push({ role, allowed: isRoleAllowed(role, auth), outcome })
|
|
131
|
-
}
|
|
132
|
-
return results
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Collect the first N values pushed to a subscription within `timeoutMs`.
|
|
137
|
-
* The codegen wraps subscriptions with `iterableSubscription`, which yields an
|
|
138
|
-
* async iterator — this helper just reads `count` values and returns them.
|
|
139
|
-
*/
|
|
140
|
-
export async function collectSubscription<T = unknown>(
|
|
141
|
-
iter: AsyncIterable<T>,
|
|
142
|
-
count: number,
|
|
143
|
-
timeoutMs = 1000,
|
|
144
|
-
): Promise<readonly T[]> {
|
|
145
|
-
const out: T[] = []
|
|
146
|
-
const iterator = iter[Symbol.asyncIterator]()
|
|
147
|
-
const deadline = Date.now() + timeoutMs
|
|
148
|
-
while (out.length < count) {
|
|
149
|
-
const remaining = deadline - Date.now()
|
|
150
|
-
if (remaining <= 0) break
|
|
151
|
-
const result = await Promise.race([
|
|
152
|
-
iterator.next(),
|
|
153
|
-
new Promise<IteratorResult<T>>((_, reject) =>
|
|
154
|
-
setTimeout(() => reject(new Error('collectSubscription timeout')), remaining),
|
|
155
|
-
),
|
|
156
|
-
])
|
|
157
|
-
if (result.done) break
|
|
158
|
-
out.push(result.value)
|
|
159
|
-
}
|
|
160
|
-
// Best-effort cleanup — async generators from iterableSubscription handle return() in their finally block.
|
|
161
|
-
await iterator.return?.()
|
|
162
|
-
return out
|
|
163
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example spec exercising the codegen'd metrics-provider router end-to-end:
|
|
3
|
-
* - Wires a mock provider into `createCapRouter_metricsProvider`
|
|
4
|
-
* - Verifies happy-path query dispatch + output validation for getCurrent()
|
|
5
|
-
* - Verifies provider-missing → PRECONDITION_FAILED
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, expect } from 'vitest'
|
|
8
|
-
import { createCapRouter_metricsProvider } from '../../api/trpc/generated-cap-routers.js'
|
|
9
|
-
import { type IMetricsProvider } from '@camstack/types'
|
|
10
|
-
import { makeCtx, invokeProcedure } from './harness.js'
|
|
11
|
-
|
|
12
|
-
function makeMockProvider(overrides: Partial<IMetricsProvider> = {}): IMetricsProvider {
|
|
13
|
-
return {
|
|
14
|
-
collectSnapshot: async () => ({
|
|
15
|
-
cpu: { total: 0, user: 0, system: 0, irq: 0, nice: 0, loadAvg: [0, 0, 0], cores: 1 },
|
|
16
|
-
memory: {
|
|
17
|
-
percent: 0,
|
|
18
|
-
totalBytes: 0,
|
|
19
|
-
usedBytes: 0,
|
|
20
|
-
availableBytes: 0,
|
|
21
|
-
swapUsedBytes: 0,
|
|
22
|
-
swapTotalBytes: 0,
|
|
23
|
-
},
|
|
24
|
-
gpu: null,
|
|
25
|
-
network: {
|
|
26
|
-
rxBytes: 0,
|
|
27
|
-
txBytes: 0,
|
|
28
|
-
rxPackets: 0,
|
|
29
|
-
txPackets: 0,
|
|
30
|
-
rxErrors: 0,
|
|
31
|
-
txErrors: 0,
|
|
32
|
-
timestampMs: 0,
|
|
33
|
-
},
|
|
34
|
-
disk: { readBytes: 0, writeBytes: 0, readOps: 0, writeOps: 0, timestampMs: 0 },
|
|
35
|
-
pressure: { cpu: null, memory: null, io: null },
|
|
36
|
-
process: { openFds: 0, threadCount: 0, activeHandles: 0, activeRequests: 0 },
|
|
37
|
-
cpuTemperature: null,
|
|
38
|
-
timestampMs: 0,
|
|
39
|
-
}),
|
|
40
|
-
getCached: async () => null,
|
|
41
|
-
getCurrent: async () => ({
|
|
42
|
-
cpuPercent: 42.5,
|
|
43
|
-
memoryPercent: 68.2,
|
|
44
|
-
memoryUsedMB: 8192,
|
|
45
|
-
memoryTotalMB: 16384,
|
|
46
|
-
diskPercent: 55.1,
|
|
47
|
-
temperature: 48.0,
|
|
48
|
-
}),
|
|
49
|
-
getDiskSpace: async ({ dirPath }) => ({
|
|
50
|
-
path: dirPath,
|
|
51
|
-
totalBytes: 0,
|
|
52
|
-
usedBytes: 0,
|
|
53
|
-
availableBytes: 0,
|
|
54
|
-
percent: 0,
|
|
55
|
-
}),
|
|
56
|
-
getGpuInfo: async () => null,
|
|
57
|
-
getCpuTemperature: async () => null,
|
|
58
|
-
getProcessStats: async () => [],
|
|
59
|
-
...overrides,
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
describe('createCapRouter_metricsProvider', () => {
|
|
64
|
-
describe('happy path', () => {
|
|
65
|
-
it('dispatches getCurrent() to the provider and returns typed metrics', async () => {
|
|
66
|
-
const provider = makeMockProvider()
|
|
67
|
-
const router = createCapRouter_metricsProvider(() => provider)
|
|
68
|
-
|
|
69
|
-
const outcome = await invokeProcedure(router, 'getCurrent', makeCtx('admin'))
|
|
70
|
-
|
|
71
|
-
expect(outcome.ok).toBe(true)
|
|
72
|
-
if (outcome.ok) {
|
|
73
|
-
expect(outcome.value).toMatchObject({
|
|
74
|
-
cpuPercent: 42.5,
|
|
75
|
-
memoryPercent: 68.2,
|
|
76
|
-
memoryUsedMB: 8192,
|
|
77
|
-
memoryTotalMB: 16384,
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('propagates optional fields (temperature, gpuPercent)', async () => {
|
|
83
|
-
const router = createCapRouter_metricsProvider(() =>
|
|
84
|
-
makeMockProvider({
|
|
85
|
-
getCurrent: async () => ({
|
|
86
|
-
cpuPercent: 10,
|
|
87
|
-
memoryPercent: 20,
|
|
88
|
-
memoryUsedMB: 1024,
|
|
89
|
-
memoryTotalMB: 8192,
|
|
90
|
-
gpuPercent: 75,
|
|
91
|
-
gpuMemoryPercent: 60,
|
|
92
|
-
}),
|
|
93
|
-
}),
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
const outcome = await invokeProcedure(router, 'getCurrent', makeCtx('admin'))
|
|
97
|
-
|
|
98
|
-
expect(outcome.ok).toBe(true)
|
|
99
|
-
if (outcome.ok) {
|
|
100
|
-
const value = outcome.value as { gpuPercent?: number; gpuMemoryPercent?: number }
|
|
101
|
-
expect(value.gpuPercent).toBe(75)
|
|
102
|
-
expect(value.gpuMemoryPercent).toBe(60)
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
describe('auth', () => {
|
|
108
|
-
it('rejects anonymous (protected endpoint)', async () => {
|
|
109
|
-
const router = createCapRouter_metricsProvider(() => makeMockProvider())
|
|
110
|
-
const outcome = await invokeProcedure(router, 'getCurrent', makeCtx('anonymous'))
|
|
111
|
-
expect(outcome.ok).toBe(false)
|
|
112
|
-
if (!outcome.ok) expect(outcome.code).toBe('UNAUTHORIZED')
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('allows viewer (protected endpoint)', async () => {
|
|
116
|
-
const router = createCapRouter_metricsProvider(() => makeMockProvider())
|
|
117
|
-
const outcome = await invokeProcedure(router, 'getCurrent', makeCtx('user'))
|
|
118
|
-
expect(outcome.ok).toBe(true)
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
describe('missing provider', () => {
|
|
123
|
-
it('throws PRECONDITION_FAILED when getProvider returns null', async () => {
|
|
124
|
-
const router = createCapRouter_metricsProvider(() => null)
|
|
125
|
-
const outcome = await invokeProcedure(router, 'getCurrent', makeCtx('admin'))
|
|
126
|
-
expect(outcome.ok).toBe(false)
|
|
127
|
-
if (!outcome.ok) {
|
|
128
|
-
expect(outcome.code).toBe('PRECONDITION_FAILED')
|
|
129
|
-
expect(outcome.message).toContain('metrics-provider')
|
|
130
|
-
}
|
|
131
|
-
})
|
|
132
|
-
})
|
|
133
|
-
})
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Comprehensive null-provider spec — verifies that EVERY codegen'd cap
|
|
3
|
-
* router returns PRECONDITION_FAILED when the provider is null.
|
|
4
|
-
*
|
|
5
|
-
* This is a safety net: if a new cap is added and the codegen'd router
|
|
6
|
-
* doesn't guard against a null provider, this test catches it.
|
|
7
|
-
*
|
|
8
|
-
* NOTE: some caps have methods that require non-void input (z.object({...})).
|
|
9
|
-
* For those, Zod input validation may reject our empty input before the
|
|
10
|
-
* null-provider check runs — in which case we expect BAD_REQUEST or
|
|
11
|
-
* PRECONDITION_FAILED (both are acceptable: the point is that the call
|
|
12
|
-
* does NOT succeed and return garbage data).
|
|
13
|
-
*/
|
|
14
|
-
import { describe, it, expect } from 'vitest'
|
|
15
|
-
import * as generatedModule from '../../api/trpc/generated-cap-routers.js'
|
|
16
|
-
import { makeCtx, invokeProcedure } from './harness.js'
|
|
17
|
-
|
|
18
|
-
// Discover all createCapRouter_* functions from the generated module
|
|
19
|
-
const routerFactories: Array<{ name: string; factory: (getProvider: () => null) => unknown }> = []
|
|
20
|
-
for (const [key, value] of Object.entries(generatedModule)) {
|
|
21
|
-
if (key.startsWith('createCapRouter_') && typeof value === 'function') {
|
|
22
|
-
routerFactories.push({
|
|
23
|
-
name: key.replace('createCapRouter_', ''),
|
|
24
|
-
factory: value as (getProvider: () => null) => unknown,
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe('null-provider guard — all capabilities', () => {
|
|
30
|
-
it(`discovers ${routerFactories.length} cap router factories`, () => {
|
|
31
|
-
expect(routerFactories.length).toBeGreaterThanOrEqual(30)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
for (const { name, factory } of routerFactories) {
|
|
35
|
-
it(`${name}: null provider does not return success`, async () => {
|
|
36
|
-
const router = factory(() => null)
|
|
37
|
-
|
|
38
|
-
// Find the first method on the router's caller
|
|
39
|
-
const caller = (
|
|
40
|
-
router as { createCaller: (ctx: unknown) => Record<string, unknown> }
|
|
41
|
-
).createCaller(makeCtx('admin'))
|
|
42
|
-
|
|
43
|
-
const methods = Object.keys(caller).filter(
|
|
44
|
-
(k) => typeof caller[k] === 'function' && !k.startsWith('_'),
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if (methods.length === 0) return // No methods — skip
|
|
48
|
-
|
|
49
|
-
// Try the first method with no input (will work for void-input methods)
|
|
50
|
-
const firstMethod = methods[0]!
|
|
51
|
-
const result = await invokeProcedure(
|
|
52
|
-
router as { createCaller: (ctx: unknown) => Record<string, (input?: unknown) => unknown> },
|
|
53
|
-
firstMethod,
|
|
54
|
-
makeCtx('admin'),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
// The call should NOT succeed — either PRECONDITION_FAILED (null provider
|
|
58
|
-
// caught by codegen guard) or BAD_REQUEST (input validation before guard)
|
|
59
|
-
// or INTERNAL_SERVER_ERROR (provider method call on null).
|
|
60
|
-
// Any of these is acceptable — the important thing is ok !== true.
|
|
61
|
-
expect(result.ok, `${name}.${firstMethod} returned success with null provider!`).toBe(false)
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
})
|