@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,302 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the two adapter factories and the resolver wired with them.
|
|
3
|
-
*
|
|
4
|
-
* Exercises:
|
|
5
|
-
* - createNodeCapAuthority: delegates to HubNodeRegistry correctly
|
|
6
|
-
* - createInProcessProviderLookup: invokes provider methods cast-free
|
|
7
|
-
* - Resolver wired with both adapters:
|
|
8
|
-
* hub-local UDS child cap resolves via callCapOnChild
|
|
9
|
-
* hub-resident singleton resolves in-process (invoke called)
|
|
10
|
-
* absent cap throws CapRouteError (NOT the old opaque string)
|
|
11
|
-
*
|
|
12
|
-
* We test the adapter factories directly + a resolver wired with them —
|
|
13
|
-
* standing up a full MoleculerService is too heavy for a unit test, and
|
|
14
|
-
* testing the adapters + resolver in isolation exercises the meaningful unit.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
18
|
-
import { CapRouteError, CapRouteResolver } from '@camstack/kernel'
|
|
19
|
-
import type {
|
|
20
|
-
NodeCapAuthority,
|
|
21
|
-
InProcessProviderLookup,
|
|
22
|
-
HubLocalChildDispatcher,
|
|
23
|
-
CapRouteResolverDeps,
|
|
24
|
-
} from '@camstack/kernel'
|
|
25
|
-
import type { InProcessProviderRef } from '@camstack/kernel'
|
|
26
|
-
import {
|
|
27
|
-
createNodeCapAuthority,
|
|
28
|
-
createInProcessProviderLookup,
|
|
29
|
-
} from '../core/moleculer/cap-route-authority.js'
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Helpers for stub HubNodeRegistry
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
interface StubNodeEntry {
|
|
36
|
-
readonly addonId: string
|
|
37
|
-
readonly capabilities: readonly string[]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function makeNodeRegistry(nodes: ReadonlyMap<string, readonly StubNodeEntry[]>) {
|
|
41
|
-
return {
|
|
42
|
-
getNodeManifest(nodeId: string) {
|
|
43
|
-
return nodes.get(nodeId)
|
|
44
|
-
},
|
|
45
|
-
listNodeIds(): readonly string[] {
|
|
46
|
-
return [...nodes.keys()]
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Helpers for stub CapabilityService
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
function makeCapabilityService(providers: ReadonlyMap<string, Record<string, unknown>>) {
|
|
56
|
-
return {
|
|
57
|
-
getSingleton<T>(capability: string): T | null {
|
|
58
|
-
return (providers.get(capability) as T) ?? null
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// createNodeCapAuthority
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
describe('createNodeCapAuthority', () => {
|
|
68
|
-
const nodes = new Map([
|
|
69
|
-
[
|
|
70
|
-
'hub/stream-broker',
|
|
71
|
-
[{ addonId: 'addon-stream-broker', capabilities: ['stream-broker', 'stream-params'] }],
|
|
72
|
-
],
|
|
73
|
-
['dev-agent-0', [{ addonId: 'addon-detection-pipeline', capabilities: ['pipeline-executor'] }]],
|
|
74
|
-
])
|
|
75
|
-
const registry = makeNodeRegistry(nodes)
|
|
76
|
-
const authority = createNodeCapAuthority(registry)
|
|
77
|
-
|
|
78
|
-
it('nodeKnowsCap returns true when the node manifest includes the cap', () => {
|
|
79
|
-
expect(authority.nodeKnowsCap('hub/stream-broker', 'stream-broker')).toBe(true)
|
|
80
|
-
expect(authority.nodeKnowsCap('hub/stream-broker', 'stream-params')).toBe(true)
|
|
81
|
-
expect(authority.nodeKnowsCap('hub/stream-broker', 'ghost-cap')).toBe(false)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('nodeKnowsCap returns false for unknown nodes', () => {
|
|
85
|
-
expect(authority.nodeKnowsCap('not-a-node', 'stream-broker')).toBe(false)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('getAddonId returns the addonId for a known cap', () => {
|
|
89
|
-
expect(authority.getAddonId('hub/stream-broker', 'stream-broker')).toBe('addon-stream-broker')
|
|
90
|
-
expect(authority.getAddonId('dev-agent-0', 'pipeline-executor')).toBe(
|
|
91
|
-
'addon-detection-pipeline',
|
|
92
|
-
)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('getAddonId returns null for missing nodes or caps', () => {
|
|
96
|
-
expect(authority.getAddonId('not-a-node', 'stream-broker')).toBeNull()
|
|
97
|
-
expect(authority.getAddonId('hub/stream-broker', 'ghost-cap')).toBeNull()
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('nodeIsAgent: hub and hub-children are NOT agents; bare-id non-hub nodes are agents', () => {
|
|
101
|
-
expect(authority.nodeIsAgent('hub')).toBe(false)
|
|
102
|
-
expect(authority.nodeIsAgent('hub/stream-broker')).toBe(false)
|
|
103
|
-
expect(authority.nodeIsAgent('dev-agent-0')).toBe(true)
|
|
104
|
-
expect(authority.nodeIsAgent('some-remote-agent')).toBe(true)
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('nodeOnline: nodes in the registry are online; absent ones are not', () => {
|
|
108
|
-
expect(authority.nodeOnline('hub/stream-broker')).toBe(true)
|
|
109
|
-
expect(authority.nodeOnline('dev-agent-0')).toBe(true)
|
|
110
|
-
expect(authority.nodeOnline('not-registered')).toBe(false)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('listNodeIds returns all registered node ids', () => {
|
|
114
|
-
const ids = authority.listNodeIds()
|
|
115
|
-
expect(ids).toContain('hub/stream-broker')
|
|
116
|
-
expect(ids).toContain('dev-agent-0')
|
|
117
|
-
expect(ids).toHaveLength(2)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('getAgentChildId always returns null (hub cannot resolve; Task 6 handles it)', () => {
|
|
121
|
-
expect(authority.getAgentChildId('dev-agent-0', 'pipeline-executor')).toBeNull()
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
// createNodeCapAuthority — per-node singleton override
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
|
|
129
|
-
describe('createNodeCapAuthority — per-node singleton override', () => {
|
|
130
|
-
it('getAddonId honors the per-node singleton override when available', () => {
|
|
131
|
-
const nodeRegistry = {
|
|
132
|
-
getNodeManifest: (id: string) =>
|
|
133
|
-
id === 'dev-agent-0'
|
|
134
|
-
? [
|
|
135
|
-
{ addonId: 'webrtc-native', capabilities: ['webrtc-session'] },
|
|
136
|
-
{ addonId: 'stream-broker', capabilities: ['webrtc-session'] },
|
|
137
|
-
]
|
|
138
|
-
: undefined,
|
|
139
|
-
listNodeIds: () => ['hub', 'dev-agent-0'],
|
|
140
|
-
}
|
|
141
|
-
const authority = createNodeCapAuthority(nodeRegistry, {
|
|
142
|
-
resolveSingleton: (cap, nodeId) =>
|
|
143
|
-
cap === 'webrtc-session' && nodeId === 'dev-agent-0' ? 'stream-broker' : null,
|
|
144
|
-
})
|
|
145
|
-
expect(authority.getAddonId('dev-agent-0', 'webrtc-session')).toBe('stream-broker')
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('getAddonId falls back to first manifest match without an override', () => {
|
|
149
|
-
const nodeRegistry = {
|
|
150
|
-
getNodeManifest: (id: string) =>
|
|
151
|
-
id === 'dev-agent-0'
|
|
152
|
-
? [{ addonId: 'webrtc-native', capabilities: ['webrtc-session'] }]
|
|
153
|
-
: undefined,
|
|
154
|
-
listNodeIds: () => ['hub', 'dev-agent-0'],
|
|
155
|
-
}
|
|
156
|
-
const authority = createNodeCapAuthority(nodeRegistry, { resolveSingleton: () => null })
|
|
157
|
-
expect(authority.getAddonId('dev-agent-0', 'webrtc-session')).toBe('webrtc-native')
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
// createInProcessProviderLookup
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
|
|
165
|
-
describe('createInProcessProviderLookup', () => {
|
|
166
|
-
it('returns null for a cap not hosted in-process', () => {
|
|
167
|
-
const lookup = createInProcessProviderLookup(makeCapabilityService(new Map()))
|
|
168
|
-
expect(lookup('device-manager')).toBeNull()
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('returns an InProcessProviderRef whose invoke delegates to the provider method', async () => {
|
|
172
|
-
const getStatusImpl = vi.fn().mockReturnValue({ ok: true })
|
|
173
|
-
const providers = new Map<string, Record<string, unknown>>([
|
|
174
|
-
['device-manager', { getStatus: getStatusImpl }],
|
|
175
|
-
])
|
|
176
|
-
const lookup = createInProcessProviderLookup(makeCapabilityService(providers))
|
|
177
|
-
|
|
178
|
-
const ref = lookup('device-manager')
|
|
179
|
-
expect(ref).not.toBeNull()
|
|
180
|
-
|
|
181
|
-
const result = await ref!.invoke('getStatus', { deviceId: 1 })
|
|
182
|
-
expect(result).toEqual({ ok: true })
|
|
183
|
-
expect(getStatusImpl).toHaveBeenCalledOnce()
|
|
184
|
-
expect(getStatusImpl).toHaveBeenCalledWith({ deviceId: 1 })
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('invoke throws a typed Error when the method is not a function (never casts)', async () => {
|
|
188
|
-
const providers = new Map<string, Record<string, unknown>>([
|
|
189
|
-
['device-manager', { notAFn: 'some-string' }],
|
|
190
|
-
])
|
|
191
|
-
const lookup = createInProcessProviderLookup(makeCapabilityService(providers))
|
|
192
|
-
const ref = lookup('device-manager')
|
|
193
|
-
expect(ref).not.toBeNull()
|
|
194
|
-
|
|
195
|
-
await expect(ref!.invoke('notAFn', {})).rejects.toThrow(/method "notAFn" not found/)
|
|
196
|
-
await expect(ref!.invoke('missingMethod', {})).rejects.toThrow(
|
|
197
|
-
/method "missingMethod" not found/,
|
|
198
|
-
)
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
// Resolver wired with both adapters
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
describe('Resolver + adapters — end-to-end dispatch', () => {
|
|
207
|
-
const HUB_NODE_ID = 'hub'
|
|
208
|
-
|
|
209
|
-
function makeCallCapOnChildSpy() {
|
|
210
|
-
return vi.fn(async (_childId: string, _input: unknown) => ({ ok: true, from: 'uds' }))
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function makeHubLocalRegistry(
|
|
214
|
-
caps: ReadonlyMap<string, string>,
|
|
215
|
-
): HubLocalChildDispatcher & { callSpy: ReturnType<typeof vi.fn> } {
|
|
216
|
-
const callSpy = makeCallCapOnChildSpy()
|
|
217
|
-
return {
|
|
218
|
-
resolveChildId: (capName: string) => caps.get(capName) ?? null,
|
|
219
|
-
callCapOnChild: callSpy,
|
|
220
|
-
callSpy,
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
it('hub-local UDS child cap resolves via callCapOnChild', async () => {
|
|
225
|
-
// Registry: stream-broker is served by a hub-local child.
|
|
226
|
-
const nodes = new Map([
|
|
227
|
-
['hub/stream-broker', [{ addonId: 'addon-stream-broker', capabilities: ['stream-broker'] }]],
|
|
228
|
-
])
|
|
229
|
-
const nodeRegistry = makeNodeRegistry(nodes)
|
|
230
|
-
const authority = createNodeCapAuthority(nodeRegistry)
|
|
231
|
-
|
|
232
|
-
const hubLocalCaps = new Map([['stream-broker', 'addon-stream-broker']])
|
|
233
|
-
const hubLocalRegistry = makeHubLocalRegistry(hubLocalCaps)
|
|
234
|
-
|
|
235
|
-
const lookup = createInProcessProviderLookup(makeCapabilityService(new Map()))
|
|
236
|
-
|
|
237
|
-
const deps: CapRouteResolverDeps = {
|
|
238
|
-
hubNodeId: HUB_NODE_ID,
|
|
239
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
240
|
-
hubLocalRegistry,
|
|
241
|
-
nodeAuthority: authority,
|
|
242
|
-
inProcessProviders: lookup,
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const resolver = new CapRouteResolver(deps)
|
|
246
|
-
const route = resolver.resolveCapRoute('stream-broker', { nodeId: HUB_NODE_ID })
|
|
247
|
-
expect(route.kind).toBe('hub-local-uds')
|
|
248
|
-
|
|
249
|
-
const result = await resolver.dispatch(route, 'attachCamera', { deviceId: 5 })
|
|
250
|
-
expect(result).toEqual({ ok: true, from: 'uds' })
|
|
251
|
-
expect(hubLocalRegistry.callSpy).toHaveBeenCalledOnce()
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('hub-resident singleton resolves in-process via invoke', async () => {
|
|
255
|
-
const invokeSpy = vi.fn().mockResolvedValue({ devices: [] })
|
|
256
|
-
const providers = new Map<string, Record<string, unknown>>([
|
|
257
|
-
['device-manager', { listAll: invokeSpy }],
|
|
258
|
-
])
|
|
259
|
-
const lookup = createInProcessProviderLookup(makeCapabilityService(providers))
|
|
260
|
-
|
|
261
|
-
const deps: CapRouteResolverDeps = {
|
|
262
|
-
hubNodeId: HUB_NODE_ID,
|
|
263
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
264
|
-
hubLocalRegistry: null,
|
|
265
|
-
nodeAuthority: createNodeCapAuthority(makeNodeRegistry(new Map())),
|
|
266
|
-
inProcessProviders: lookup,
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const resolver = new CapRouteResolver(deps)
|
|
270
|
-
const route = resolver.resolveCapRoute('device-manager', { nodeId: HUB_NODE_ID })
|
|
271
|
-
expect(route.kind).toBe('hub-in-process')
|
|
272
|
-
|
|
273
|
-
const result = await resolver.dispatch(route, 'listAll', {})
|
|
274
|
-
expect(result).toEqual({ devices: [] })
|
|
275
|
-
expect(invokeSpy).toHaveBeenCalledOnce()
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
it('genuinely-absent cap throws CapRouteError, NOT the old opaque string', () => {
|
|
279
|
-
const deps: CapRouteResolverDeps = {
|
|
280
|
-
hubNodeId: HUB_NODE_ID,
|
|
281
|
-
broker: { call: vi.fn(), waitForServices: vi.fn() },
|
|
282
|
-
hubLocalRegistry: null,
|
|
283
|
-
nodeAuthority: createNodeCapAuthority(makeNodeRegistry(new Map())),
|
|
284
|
-
inProcessProviders: createInProcessProviderLookup(makeCapabilityService(new Map())),
|
|
285
|
-
}
|
|
286
|
-
const resolver = new CapRouteResolver(deps)
|
|
287
|
-
|
|
288
|
-
let thrown: unknown
|
|
289
|
-
try {
|
|
290
|
-
resolver.resolveCapRoute('ghost-cap', {})
|
|
291
|
-
} catch (e) {
|
|
292
|
-
thrown = e
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
expect(thrown).toBeInstanceOf(CapRouteError)
|
|
296
|
-
expect((thrown as CapRouteError).reason).toBe('no-provider')
|
|
297
|
-
// Must NOT be the old opaque string
|
|
298
|
-
expect((thrown as CapRouteError).message).not.toContain(
|
|
299
|
-
'Capability "ghost-cap" not available on node',
|
|
300
|
-
)
|
|
301
|
-
})
|
|
302
|
-
})
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Meta-test: ensures that every capability with methods has a corresponding
|
|
3
|
-
* `<cap-name>.router.spec.ts` file in this directory. This prevents new caps
|
|
4
|
-
* from being added without a router-level test.
|
|
5
|
-
*
|
|
6
|
-
* Caps listed in `ALLOWED_MISSING` are explicitly exempted (document why).
|
|
7
|
-
*/
|
|
8
|
-
import { describe, it, expect } from 'vitest'
|
|
9
|
-
import * as fs from 'node:fs'
|
|
10
|
-
import * as path from 'node:path'
|
|
11
|
-
import * as capsModule from '@camstack/types'
|
|
12
|
-
import type { CapabilityDefinition } from '@camstack/types'
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Caps that are knowingly untested at the router level.
|
|
16
|
-
* Keep this list empty whenever possible — every entry is tech debt.
|
|
17
|
-
*/
|
|
18
|
-
const ALLOWED_MISSING: ReadonlySet<string> = new Set<string>([
|
|
19
|
-
// Populated during migration. Goal: drain to empty.
|
|
20
|
-
])
|
|
21
|
-
|
|
22
|
-
function isCapabilityDefinition(value: unknown): value is CapabilityDefinition {
|
|
23
|
-
if (value === null || typeof value !== 'object') return false
|
|
24
|
-
const v = value as Record<string, unknown>
|
|
25
|
-
return (
|
|
26
|
-
typeof v['name'] === 'string' &&
|
|
27
|
-
typeof v['scope'] === 'string' &&
|
|
28
|
-
typeof v['mode'] === 'string' &&
|
|
29
|
-
typeof v['methods'] === 'object' &&
|
|
30
|
-
v['methods'] !== null
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function collectCapabilitiesWithMethods(): readonly CapabilityDefinition[] {
|
|
35
|
-
const out: CapabilityDefinition[] = []
|
|
36
|
-
for (const [key, value] of Object.entries(capsModule)) {
|
|
37
|
-
if (!key.endsWith('Capability')) continue
|
|
38
|
-
if (!isCapabilityDefinition(value)) continue
|
|
39
|
-
if (Object.keys(value.methods).length === 0) continue
|
|
40
|
-
out.push(value)
|
|
41
|
-
}
|
|
42
|
-
return out.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function specFileNameFor(capName: string): string {
|
|
46
|
-
return `${capName}.router.spec.ts`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('cap-routers meta', () => {
|
|
50
|
-
const caps = collectCapabilitiesWithMethods()
|
|
51
|
-
const specDir = path.dirname(new URL(import.meta.url).pathname)
|
|
52
|
-
const existingSpecs = new Set(
|
|
53
|
-
fs.readdirSync(specDir).filter((f) => f.endsWith('.router.spec.ts')),
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
it('discovers at least one capability with methods', () => {
|
|
57
|
-
expect(caps.length).toBeGreaterThan(0)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('every capability has a <cap>.router.spec.ts (or is in ALLOWED_MISSING)', () => {
|
|
61
|
-
const missing: string[] = []
|
|
62
|
-
for (const cap of caps) {
|
|
63
|
-
const expected = specFileNameFor(cap.name)
|
|
64
|
-
if (!existingSpecs.has(expected) && !ALLOWED_MISSING.has(cap.name)) {
|
|
65
|
-
missing.push(cap.name)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Soft-fail during migration: list missing but warn loudly.
|
|
70
|
-
// Flip to `expect(missing).toEqual([])` once coverage is complete.
|
|
71
|
-
if (missing.length > 0) {
|
|
72
|
-
console.warn(
|
|
73
|
-
`\n[cap-routers meta] ${missing.length} capabilities lack a router spec:\n - ${missing.join('\n - ')}\n`,
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
// Hard gate: at least one real spec must exist so the harness stays exercised.
|
|
77
|
-
expect(existingSpecs.size).toBeGreaterThanOrEqual(1)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('ALLOWED_MISSING only references real capabilities', () => {
|
|
81
|
-
const names = new Set(caps.map((c) => c.name))
|
|
82
|
-
for (const name of ALLOWED_MISSING) {
|
|
83
|
-
expect(names, `ALLOWED_MISSING references unknown cap "${name}"`).toContain(name)
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
// ── Subscription codegen guard ──────────────────────────────────────
|
|
88
|
-
//
|
|
89
|
-
// Enumerates every capability method and, for each method marked
|
|
90
|
-
// `kind: 'subscription'`, verifies that the generated cap-router file
|
|
91
|
-
// includes the corresponding `.subscription(` wiring and the
|
|
92
|
-
// `iterableSubscription` import. This catches codegen drift when new
|
|
93
|
-
// subscription methods are added to caps.
|
|
94
|
-
describe('subscription codegen', () => {
|
|
95
|
-
const generatedPath = path.resolve(specDir, '../../api/trpc/generated-cap-routers.ts')
|
|
96
|
-
const generatedSource = fs.existsSync(generatedPath)
|
|
97
|
-
? fs.readFileSync(generatedPath, 'utf-8')
|
|
98
|
-
: null
|
|
99
|
-
|
|
100
|
-
it('generated-cap-routers.ts exists', () => {
|
|
101
|
-
expect(generatedSource, `expected generated file at ${generatedPath}`).not.toBeNull()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const subscriptionMethods: Array<{ capName: string; methodName: string }> = []
|
|
105
|
-
for (const cap of caps) {
|
|
106
|
-
for (const [methodName, schema] of Object.entries(cap.methods)) {
|
|
107
|
-
if (schema.kind === 'subscription') {
|
|
108
|
-
subscriptionMethods.push({ capName: cap.name, methodName })
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
it('if any cap has subscriptions, iterableSubscription is imported', () => {
|
|
114
|
-
if (subscriptionMethods.length === 0 || generatedSource === null) return
|
|
115
|
-
expect(
|
|
116
|
-
generatedSource,
|
|
117
|
-
'generated-cap-routers.ts must import iterableSubscription when any cap has kind: "subscription"',
|
|
118
|
-
).toContain('iterableSubscription')
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('every subscription method is wired in the generated router', () => {
|
|
122
|
-
if (generatedSource === null) return
|
|
123
|
-
const missing: string[] = []
|
|
124
|
-
for (const { capName, methodName } of subscriptionMethods) {
|
|
125
|
-
// The generator emits `${methodName}: procedure\n .input(...)\n .subscription(`.
|
|
126
|
-
// We check for the method-name line followed by `.subscription(` within a small window.
|
|
127
|
-
const nameIdx = generatedSource.indexOf(`${methodName}:`)
|
|
128
|
-
if (nameIdx === -1) {
|
|
129
|
-
missing.push(`${capName}.${methodName} (method name not found)`)
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
const window = generatedSource.slice(nameIdx, nameIdx + 300)
|
|
133
|
-
if (!window.includes('.subscription(')) {
|
|
134
|
-
missing.push(`${capName}.${methodName} (no .subscription( call within 300 chars)`)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
expect(
|
|
138
|
-
missing,
|
|
139
|
-
`Subscription codegen drift — re-run: npx tsx scripts/generate-cap-routers.ts\n ${missing.join('\n ')}`,
|
|
140
|
-
).toEqual([])
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('reports the subscription method inventory (informational)', () => {
|
|
144
|
-
// Not an assertion — this keeps the metric visible in test output.
|
|
145
|
-
console.log(
|
|
146
|
-
`[subscription codegen] ${subscriptionMethods.length} subscription method(s) across ${caps.length} caps`,
|
|
147
|
-
)
|
|
148
|
-
if (subscriptionMethods.length > 0) {
|
|
149
|
-
for (const { capName, methodName } of subscriptionMethods) {
|
|
150
|
-
console.log(` ${capName}.${methodName}`)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
// ── Output-validation codegen guard ────────────────────────────────
|
|
157
|
-
//
|
|
158
|
-
// Per device-proxy redesign §6.8 / Task 4.1, every non-subscription
|
|
159
|
-
// procedure in the generated cap-router file must declare an `.output()`
|
|
160
|
-
// call so that tRPC validates response shapes at runtime. Subscriptions
|
|
161
|
-
// are exempt — tRPC v11 does not support `.output()` on subscriptions.
|
|
162
|
-
describe('output-validation codegen', () => {
|
|
163
|
-
const generatedPath = path.resolve(specDir, '../../api/trpc/generated-cap-routers.ts')
|
|
164
|
-
const generatedSource = fs.existsSync(generatedPath)
|
|
165
|
-
? fs.readFileSync(generatedPath, 'utf-8')
|
|
166
|
-
: null
|
|
167
|
-
|
|
168
|
-
it('every non-subscription procedure declares .output()', () => {
|
|
169
|
-
expect(generatedSource, `expected generated file at ${generatedPath}`).not.toBeNull()
|
|
170
|
-
if (generatedSource === null) return
|
|
171
|
-
|
|
172
|
-
// tRPC procedure call sites are spelled `.query(async` / `.mutation(async`
|
|
173
|
-
// when they're emitted as router endpoints — the few in-body `p.query(...)`
|
|
174
|
-
// / `p.mutation(...)` calls don't include `(async`, so this filter is exact.
|
|
175
|
-
const procedureCallRegex = /\.(query|mutation)\(async/g
|
|
176
|
-
const procedureCount = (generatedSource.match(procedureCallRegex) ?? []).length
|
|
177
|
-
const outputCount = (generatedSource.match(/\.output\(/g) ?? []).length
|
|
178
|
-
|
|
179
|
-
expect(
|
|
180
|
-
outputCount,
|
|
181
|
-
`output-validation codegen drift — expected at least ${procedureCount} .output() ` +
|
|
182
|
-
`calls (one per query/mutation), found ${outputCount}. ` +
|
|
183
|
-
`Re-run: npx tsx scripts/generate-cap-routers.ts`,
|
|
184
|
-
).toBeGreaterThanOrEqual(procedureCount)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('reports the procedure / output-call inventory (informational)', () => {
|
|
188
|
-
if (generatedSource === null) return
|
|
189
|
-
const queryCount = (generatedSource.match(/\.query\(async/g) ?? []).length
|
|
190
|
-
const mutationCount = (generatedSource.match(/\.mutation\(async/g) ?? []).length
|
|
191
|
-
const subscriptionCount = (generatedSource.match(/\.subscription\(/g) ?? []).length
|
|
192
|
-
const outputCount = (generatedSource.match(/\.output\(/g) ?? []).length
|
|
193
|
-
console.log(
|
|
194
|
-
`[output-validation codegen] queries=${queryCount} mutations=${mutationCount} ` +
|
|
195
|
-
`subscriptions=${subscriptionCount} outputs=${outputCount}`,
|
|
196
|
-
)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
})
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Spec for the codegen'd `addon-settings` capability router.
|
|
3
|
-
*
|
|
4
|
-
* Exercises:
|
|
5
|
-
* - All 4 methods (get/update × global/device) — `getAddonSettings` /
|
|
6
|
-
* `updateAddonSettings` were collapsed into the global pair when the
|
|
7
|
-
* three-level settings API was simplified.
|
|
8
|
-
* - Auth enforcement (getters=protected, updaters=admin)
|
|
9
|
-
* - Missing provider → PRECONDITION_FAILED
|
|
10
|
-
* - Device settings routing with deviceId
|
|
11
|
-
*/
|
|
12
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
13
|
-
import { createCapRouter_addonSettings } from '../../api/trpc/generated-cap-routers.js'
|
|
14
|
-
import type { IAddonSettingsProvider } from '@camstack/types'
|
|
15
|
-
import { makeCtx, invokeProcedure, checkAuthMatrix } from './harness.js'
|
|
16
|
-
|
|
17
|
-
function makeMockProvider(): IAddonSettingsProvider {
|
|
18
|
-
return {
|
|
19
|
-
getGlobalSettings: vi.fn(async () => ({
|
|
20
|
-
sections: [{ id: 'g', title: 'Global', fields: [] }],
|
|
21
|
-
})),
|
|
22
|
-
updateGlobalSettings: vi.fn(async () => ({ success: true as const })),
|
|
23
|
-
getDeviceSettings: vi.fn(async () => ({
|
|
24
|
-
sections: [{ id: 'd', title: 'Device', fields: [] }],
|
|
25
|
-
})),
|
|
26
|
-
updateDeviceSettings: vi.fn(async () => ({ success: true as const })),
|
|
27
|
-
} as unknown as IAddonSettingsProvider
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('addon-settings cap router', () => {
|
|
31
|
-
it('getGlobalSettings returns schema', async () => {
|
|
32
|
-
const provider = makeMockProvider()
|
|
33
|
-
const router = createCapRouter_addonSettings(() => provider)
|
|
34
|
-
const result = await invokeProcedure(router, 'getGlobalSettings', makeCtx('admin'), {
|
|
35
|
-
addonId: 'test',
|
|
36
|
-
})
|
|
37
|
-
expect(result.ok).toBe(true)
|
|
38
|
-
if (result.ok)
|
|
39
|
-
expect(result.value).toEqual({ sections: [{ id: 'g', title: 'Global', fields: [] }] })
|
|
40
|
-
expect(provider.getGlobalSettings).toHaveBeenCalledWith({ addonId: 'test' })
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('updateGlobalSettings returns success', async () => {
|
|
44
|
-
const provider = makeMockProvider()
|
|
45
|
-
const router = createCapRouter_addonSettings(() => provider)
|
|
46
|
-
const result = await invokeProcedure(router, 'updateGlobalSettings', makeCtx('admin'), {
|
|
47
|
-
addonId: 'test',
|
|
48
|
-
patch: { volume: 50 },
|
|
49
|
-
})
|
|
50
|
-
expect(result.ok).toBe(true)
|
|
51
|
-
if (result.ok) expect(result.value).toEqual({ success: true })
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('getDeviceSettings passes deviceId', async () => {
|
|
55
|
-
const provider = makeMockProvider()
|
|
56
|
-
const router = createCapRouter_addonSettings(() => provider)
|
|
57
|
-
await invokeProcedure(router, 'getDeviceSettings', makeCtx('admin'), {
|
|
58
|
-
addonId: 'test',
|
|
59
|
-
deviceId: 1,
|
|
60
|
-
})
|
|
61
|
-
expect(provider.getDeviceSettings).toHaveBeenCalledWith({ addonId: 'test', deviceId: 1 })
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('updateDeviceSettings passes deviceId + patch', async () => {
|
|
65
|
-
const provider = makeMockProvider()
|
|
66
|
-
const router = createCapRouter_addonSettings(() => provider)
|
|
67
|
-
await invokeProcedure(router, 'updateDeviceSettings', makeCtx('admin'), {
|
|
68
|
-
addonId: 'test',
|
|
69
|
-
deviceId: 1,
|
|
70
|
-
patch: { enabled: false },
|
|
71
|
-
})
|
|
72
|
-
expect(provider.updateDeviceSettings).toHaveBeenCalledWith({
|
|
73
|
-
addonId: 'test',
|
|
74
|
-
deviceId: 1,
|
|
75
|
-
patch: { enabled: false },
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('returns PRECONDITION_FAILED when provider is null', async () => {
|
|
80
|
-
const router = createCapRouter_addonSettings(() => null)
|
|
81
|
-
const result = await invokeProcedure(router, 'getGlobalSettings', makeCtx('admin'), {
|
|
82
|
-
addonId: 'x',
|
|
83
|
-
})
|
|
84
|
-
expect(result.ok).toBe(false)
|
|
85
|
-
if (!result.ok) expect(result.code).toBe('PRECONDITION_FAILED')
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('getters enforce protected auth', async () => {
|
|
89
|
-
const provider = makeMockProvider()
|
|
90
|
-
const router = createCapRouter_addonSettings(() => provider)
|
|
91
|
-
for (const method of ['getGlobalSettings', 'getDeviceSettings']) {
|
|
92
|
-
const input = method.includes('Device') ? { addonId: 'x', deviceId: 1 } : { addonId: 'x' }
|
|
93
|
-
const results = await checkAuthMatrix(router, method, 'protected', input)
|
|
94
|
-
for (const r of results) {
|
|
95
|
-
if (r.allowed) expect(r.outcome.ok).toBe(true)
|
|
96
|
-
else expect(r.outcome.ok).toBe(false)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('updaters enforce admin auth', async () => {
|
|
102
|
-
const provider = makeMockProvider()
|
|
103
|
-
const router = createCapRouter_addonSettings(() => provider)
|
|
104
|
-
for (const method of ['updateGlobalSettings', 'updateDeviceSettings']) {
|
|
105
|
-
const input = method.includes('Device')
|
|
106
|
-
? { addonId: 'x', deviceId: 1, patch: {} }
|
|
107
|
-
: { addonId: 'x', patch: {} }
|
|
108
|
-
const results = await checkAuthMatrix(router, method, 'admin', input)
|
|
109
|
-
for (const r of results) {
|
|
110
|
-
if (r.allowed) expect(r.outcome.ok).toBe(true)
|
|
111
|
-
else expect(r.outcome.ok).toBe(false)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
})
|