@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.
Files changed (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -1,384 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- test file, mock typing */
2
- // server/backend/src/__tests__/capability-e2e.test.ts
3
- //
4
- // Server-level E2E tests: exercises the CapabilityRegistry through patterns
5
- // matching the real AddonRegistryService boot flow, using mock addons.
6
- //
7
- import { describe, it, expect, vi, beforeEach } from 'vitest'
8
- import { CapabilityRegistry, INFRA_CAPABILITIES } from '@camstack/kernel'
9
- import type { IScopedLogger, CapabilityDeclaration } from '@camstack/types'
10
- import { MockAnalysisAddonA } from './fixtures/mock-analysis-addon-a'
11
- import { MockAnalysisAddonB } from './fixtures/mock-analysis-addon-b'
12
- import { MockLogAddon } from './fixtures/mock-log-addon'
13
- import { MockStorageAddon } from './fixtures/mock-storage-addon'
14
- import type { ICamstackAddon } from '@camstack/types'
15
-
16
- // --- Helpers ----------------------------------------------------------------
17
-
18
- function createMockLogger(): IScopedLogger {
19
- return {
20
- error: vi.fn(),
21
- warn: vi.fn(),
22
- info: vi.fn(),
23
- debug: vi.fn(),
24
- child: vi.fn().mockReturnThis(),
25
- }
26
- }
27
-
28
- interface AddonEntry {
29
- readonly addon: ICamstackAddon
30
- initialized: boolean
31
- }
32
-
33
- /**
34
- * Lightweight harness that mirrors AddonRegistryService boot sequence
35
- * without requiring NestJS DI.
36
- */
37
- class TestAddonHarness {
38
- readonly registry: CapabilityRegistry
39
- private readonly addonEntries = new Map<string, AddonEntry>()
40
-
41
- constructor(configPrefs: Record<string, string> = {}) {
42
- this.registry = new CapabilityRegistry(createMockLogger())
43
- this.registry.setConfigReader((cap) => configPrefs[cap])
44
- this.registry.ready()
45
- }
46
-
47
- /** Register an addon (mimics AddonRegistryService.registerAddon) */
48
- registerAddon(addon: ICamstackAddon): void {
49
- this.addonEntries.set(addon.manifest.id, { addon, initialized: false })
50
- }
51
-
52
- /** Declare capabilities from an addon manifest.
53
- * Post-session-5-A5: `declareFromManifest` no longer synthesizes a
54
- * minimal CapabilityState — it only attaches metadata (dependsOn,
55
- * autoActivate). We must first call `declareCapability` with a full
56
- * definition for each cap so the state exists in the registry. */
57
- declareCapabilities(addon: ICamstackAddon): void {
58
- const caps = this.getAddonCapabilities(addon)
59
- for (const cap of caps) {
60
- // Create a full definition so the registry has a CapabilityState.
61
- // Mode is inferred from the cap name for test convenience.
62
- const mode = cap.name === 'log-destination' ? ('collection' as const) : ('singleton' as const)
63
- this.registry.declareCapability({ name: cap.name, scope: 'system', mode, methods: {} })
64
- this.registry.declareFromManifest(cap)
65
- }
66
- }
67
-
68
- /** Initialize an addon and wire its capabilities */
69
- async initializeAddon(id: string): Promise<void> {
70
- const entry = this.addonEntries.get(id)
71
- if (!entry) throw new Error(`Addon "${id}" not registered`)
72
- if (entry.initialized) return
73
-
74
- // Simulate minimal context with registerProvider that wires into the registry
75
- const self = this
76
-
77
- const context = {
78
- registerProvider(capName: string, provider: unknown) {
79
- self.registry.registerProvider(capName, id, provider)
80
- },
81
- } as any
82
- const result = await entry.addon.initialize(context)
83
- // Mirror the real addon-registry.service behaviour: process the return
84
- // value which may be ProviderRegistration[] or AddonInitResult.
85
- if (result) {
86
- const regs = Array.isArray(result) ? result : ((result as any).providers ?? [])
87
- for (const reg of regs) {
88
- const capName: string =
89
- typeof reg.capability === 'string'
90
- ? reg.capability
91
- : ((reg.capability as any)?.name ?? String(reg.capability))
92
- self.registry.registerProvider(capName, id, reg.provider)
93
- }
94
- }
95
- entry.initialized = true
96
- }
97
-
98
- /** Shutdown an addon and unregister its capabilities */
99
- async shutdownAddon(id: string): Promise<void> {
100
- const entry = this.addonEntries.get(id)
101
- if (!entry) throw new Error(`Addon "${id}" not registered`)
102
-
103
- const caps = this.getAddonCapabilities(entry.addon)
104
- for (const cap of caps) {
105
- this.registry.unregisterProvider(cap.name, id)
106
- }
107
-
108
- await entry.addon.shutdown()
109
- entry.initialized = false
110
- }
111
-
112
- /** Full boot sequence mimicking AddonRegistryService.onModuleInit */
113
- async boot(enabledIds: string[]): Promise<void> {
114
- // 1. Declare capabilities from all registered addons
115
- for (const [, entry] of this.addonEntries) {
116
- this.declareCapabilities(entry.addon)
117
- }
118
-
119
- // 2. Phase 1: infra addons
120
- for (const infra of INFRA_CAPABILITIES) {
121
- const addonId = this.findAddonForCapability(infra.name, enabledIds)
122
- if (addonId) {
123
- await this.initializeAddon(addonId)
124
- } else if (infra.required) {
125
- throw new Error(`No addon provides required infrastructure capability "${infra.name}"`)
126
- }
127
- }
128
-
129
- // 3. Phase 2: remaining addons (dependency-ordered)
130
- const bootOrder = this.registry.getBootOrder()
131
- const infraNames = new Set(INFRA_CAPABILITIES.map((c) => c.name))
132
-
133
- for (const capName of bootOrder) {
134
- if (infraNames.has(capName)) continue
135
- for (const id of enabledIds) {
136
- const entry = this.addonEntries.get(id)
137
- if (!entry || entry.initialized) continue
138
- const provides = this.getAddonCapabilities(entry.addon)
139
- if (provides.some((c) => c.name === capName)) {
140
- await this.initializeAddon(id)
141
- }
142
- }
143
- }
144
-
145
- // 4. Enable remaining addons
146
- for (const id of enabledIds) {
147
- const entry = this.addonEntries.get(id)
148
- if (entry && !entry.initialized) {
149
- await this.initializeAddon(id)
150
- }
151
- }
152
- }
153
-
154
- private getAddonCapabilities(addon: ICamstackAddon): CapabilityDeclaration[] {
155
- const manifest = addon.manifest as any
156
-
157
- if (!manifest.capabilities) return []
158
-
159
- return manifest.capabilities.map((cap: string | CapabilityDeclaration) => {
160
- if (typeof cap === 'string') {
161
- const decl: CapabilityDeclaration = { name: cap }
162
- return decl
163
- }
164
- return cap
165
- })
166
- }
167
-
168
- private findAddonForCapability(capName: string, enabledIds: string[]): string | null {
169
- for (const id of enabledIds) {
170
- const entry = this.addonEntries.get(id)
171
- if (!entry) continue
172
- const caps = this.getAddonCapabilities(entry.addon)
173
- if (caps.some((c) => c.name === capName)) return id
174
- }
175
- return null
176
- }
177
- }
178
-
179
- // ---------------------------------------------------------------------------
180
- // 1. Full boot lifecycle
181
- // ---------------------------------------------------------------------------
182
- describe('Server E2E: Full boot lifecycle', () => {
183
- it('boots infra addons first, then non-infra, all capabilities listed', async () => {
184
- const harness = new TestAddonHarness()
185
-
186
- const storageAddon = new MockStorageAddon()
187
- const logAddon = new MockLogAddon()
188
- const analysisAddonA = new MockAnalysisAddonA()
189
-
190
- harness.registerAddon(storageAddon)
191
- harness.registerAddon(logAddon)
192
- harness.registerAddon(analysisAddonA)
193
-
194
- await harness.boot(['mock-storage', 'mock-log-addon', 'mock-analysis-a'])
195
-
196
- // Verify infra addons enabled
197
- expect(storageAddon.isInitialized()).toBe(true)
198
- expect(logAddon.isInitialized()).toBe(true)
199
- expect(analysisAddonA.isInitialized()).toBe(true)
200
-
201
- // Verify providers accessible via registry
202
- expect(harness.registry.getSingleton('storage')).toBe(storageAddon.provider)
203
- expect(harness.registry.getCollection('log-destination')).toContain(logAddon.provider)
204
- expect(harness.registry.getSingleton('object-detector')).toBe(analysisAddonA.provider)
205
-
206
- // Verify listCapabilities
207
- const list = harness.registry.listCapabilities()
208
- expect(list.length).toBeGreaterThanOrEqual(3)
209
- expect(list.find((c) => c.name === 'storage')?.activeProvider).toBe('mock-storage')
210
- expect(list.find((c) => c.name === 'log-destination')?.providers).toContain('mock-log-addon')
211
- expect(list.find((c) => c.name === 'object-detector')?.activeProvider).toBe('mock-analysis-a')
212
- })
213
-
214
- it('throws when required infra capability is missing', async () => {
215
- const harness = new TestAddonHarness()
216
-
217
- const logAddon = new MockLogAddon()
218
- harness.registerAddon(logAddon)
219
-
220
- await expect(harness.boot(['mock-log-addon'])).rejects.toThrow(/required.*storage/i)
221
- })
222
- })
223
-
224
- // ---------------------------------------------------------------------------
225
- // 2. Singleton swap via harness
226
- // ---------------------------------------------------------------------------
227
- describe('Server E2E: Singleton swap', () => {
228
- it('swaps analysis provider via setActiveSingleton', async () => {
229
- const harness = new TestAddonHarness()
230
-
231
- const storageAddon = new MockStorageAddon()
232
- const analysisA = new MockAnalysisAddonA()
233
- const analysisB = new MockAnalysisAddonB()
234
-
235
- harness.registerAddon(storageAddon)
236
- harness.registerAddon(analysisA)
237
- harness.registerAddon(analysisB)
238
-
239
- await harness.boot(['mock-storage', 'mock-analysis-a', 'mock-analysis-b'])
240
-
241
- // A is default (first registered)
242
- expect(harness.registry.getSingleton('object-detector')).toBe(analysisA.provider)
243
-
244
- // Swap to B
245
- await harness.registry.setActiveSingleton('object-detector', 'mock-analysis-b', true)
246
- expect(harness.registry.getSingleton('object-detector')).toBe(analysisB.provider)
247
- })
248
- })
249
-
250
- // ---------------------------------------------------------------------------
251
- // 3. Collection addon add/remove
252
- // ---------------------------------------------------------------------------
253
- describe('Server E2E: Collection addon add/remove', () => {
254
- it('adds and removes log destinations dynamically', async () => {
255
- const harness = new TestAddonHarness()
256
-
257
- const storageAddon = new MockStorageAddon()
258
- const logAddon = new MockLogAddon()
259
-
260
- harness.registerAddon(storageAddon)
261
- harness.registerAddon(logAddon)
262
-
263
- await harness.boot(['mock-storage', 'mock-log-addon'])
264
-
265
- expect(harness.registry.getCollection('log-destination')).toHaveLength(1)
266
-
267
- // Dynamically add a second log destination
268
- const logAddon2 = new MockLogAddon()
269
-
270
- ;(logAddon2 as any).manifest = {
271
- ...logAddon2.manifest,
272
- id: 'mock-log-addon-2',
273
- name: 'Mock Log Destination 2',
274
- }
275
- harness.registerAddon(logAddon2)
276
- harness.declareCapabilities(logAddon2)
277
- await harness.initializeAddon('mock-log-addon-2')
278
-
279
- expect(harness.registry.getCollection('log-destination')).toHaveLength(2)
280
-
281
- // Disable the second
282
- await harness.shutdownAddon('mock-log-addon-2')
283
- expect(harness.registry.getCollection('log-destination')).toHaveLength(1)
284
- })
285
- })
286
-
287
- // ---------------------------------------------------------------------------
288
- // 4. Addon disable/re-enable
289
- // ---------------------------------------------------------------------------
290
- describe('Server E2E: Addon disable/re-enable', () => {
291
- it('disabling an addon removes its provider, re-enabling restores it', async () => {
292
- const harness = new TestAddonHarness()
293
-
294
- const storageAddon = new MockStorageAddon()
295
- const analysisA = new MockAnalysisAddonA()
296
-
297
- harness.registerAddon(storageAddon)
298
- harness.registerAddon(analysisA)
299
-
300
- await harness.boot(['mock-storage', 'mock-analysis-a'])
301
-
302
- expect(harness.registry.getSingleton('object-detector')).toBe(analysisA.provider)
303
-
304
- // Disable analysis addon
305
- await harness.shutdownAddon('mock-analysis-a')
306
- expect(harness.registry.getSingleton('object-detector')).toBeNull()
307
-
308
- // Re-enable
309
- await harness.initializeAddon('mock-analysis-a')
310
- expect(harness.registry.getSingleton('object-detector')).toBe(analysisA.provider)
311
- })
312
- })
313
-
314
- // ---------------------------------------------------------------------------
315
- // 5. tRPC capabilities endpoints (logic-level, no HTTP)
316
- // ---------------------------------------------------------------------------
317
- describe('Server E2E: tRPC capabilities endpoints (logic-level)', () => {
318
- let harness: TestAddonHarness
319
-
320
- beforeEach(async () => {
321
- harness = new TestAddonHarness()
322
-
323
- const storageAddon = new MockStorageAddon()
324
- const analysisA = new MockAnalysisAddonA()
325
- const analysisB = new MockAnalysisAddonB()
326
- const logAddon = new MockLogAddon()
327
-
328
- harness.registerAddon(storageAddon)
329
- harness.registerAddon(analysisA)
330
- harness.registerAddon(analysisB)
331
- harness.registerAddon(logAddon)
332
-
333
- await harness.boot(['mock-storage', 'mock-analysis-a', 'mock-analysis-b', 'mock-log-addon'])
334
- })
335
-
336
- it('listCapabilities returns all declared capabilities', () => {
337
- const list = harness.registry.listCapabilities()
338
- const names = list.map((c) => c.name)
339
-
340
- expect(names).toContain('storage')
341
- expect(names).toContain('object-detector')
342
- expect(names).toContain('log-destination')
343
- })
344
-
345
- it('getCapability for storage returns CapabilityInfo with provider', () => {
346
- const list = harness.registry.listCapabilities()
347
- const storage = list.find((c) => c.name === 'storage')
348
-
349
- expect(storage).toBeDefined()
350
- expect(storage!.mode).toBe('singleton')
351
- expect(storage!.providers).toContain('mock-storage')
352
- expect(storage!.activeProvider).toBe('mock-storage')
353
- })
354
-
355
- it('setActiveSingleton swaps the active analysis provider', async () => {
356
- const listBefore = harness.registry.listCapabilities()
357
- const analysisBefore = listBefore.find((c) => c.name === 'object-detector')!
358
- expect(analysisBefore.activeProvider).toBe('mock-analysis-a')
359
-
360
- await harness.registry.setActiveSingleton('object-detector', 'mock-analysis-b', true)
361
-
362
- const listAfter = harness.registry.listCapabilities()
363
- const analysisAfter = listAfter.find((c) => c.name === 'object-detector')!
364
- expect(analysisAfter.activeProvider).toBe('mock-analysis-b')
365
- })
366
-
367
- it('setActiveSingleton throws for unknown capability', async () => {
368
- await expect(
369
- harness.registry.setActiveSingleton('nonexistent', 'some-addon', true),
370
- ).rejects.toThrow(/[Uu]nknown/)
371
- })
372
-
373
- it('setActiveSingleton throws for collection capability', async () => {
374
- await expect(
375
- harness.registry.setActiveSingleton('log-destination', 'mock-log-addon', true),
376
- ).rejects.toThrow(/singleton/)
377
- })
378
-
379
- it('setActiveSingleton throws for unregistered provider', async () => {
380
- await expect(
381
- harness.registry.setActiveSingleton('storage', 'nonexistent-addon', true),
382
- ).rejects.toThrow(/[Nn]o provider/)
383
- })
384
- })
@@ -1,150 +0,0 @@
1
- /**
2
- * CLI E2E tests — verify `camstack serve` and `camstack agent` boot correctly.
3
- *
4
- * These tests spawn real processes using the CLI entry point and verify:
5
- * - Server starts and responds on the configured port
6
- * - Agent starts and attempts hub connection
7
- * - Info command prints version and platform
8
- *
9
- * Uses random ports to avoid conflicts with running servers.
10
- *
11
- * The serve/agent tests require the full server build (@camstack/server/main.js)
12
- * and are gated behind CAMSTACK_TEST_CLI_FULL=true.
13
- */
14
- import { describe, it, expect, afterEach } from 'vitest'
15
- import { fork, type ChildProcess } from 'node:child_process'
16
- import { resolve } from 'node:path'
17
- import * as https from 'node:https'
18
- import * as fs from 'node:fs'
19
-
20
- const CLI_PATH = resolve(__dirname, '../../../../packages/cli/dist/cli.js')
21
- const TEST_DATA_DIR = resolve(__dirname, '../../../../.test-data-cli-e2e')
22
-
23
- const CLI_EXISTS = fs.existsSync(CLI_PATH)
24
-
25
- // Random port in 10000-60000 range
26
- function randomPort(): number {
27
- return 10000 + Math.floor(Math.random() * 50000)
28
- }
29
-
30
- function waitForPort(port: number, timeoutMs: number = 15000): Promise<void> {
31
- return new Promise((resolve, reject) => {
32
- const deadline = Date.now() + timeoutMs
33
- const check = () => {
34
- const req = https.request(
35
- { hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
36
- (res) => {
37
- res.resume()
38
- resolve()
39
- },
40
- )
41
- req.on('error', () => {
42
- if (Date.now() > deadline) {
43
- reject(new Error(`Port ${port} not responding after ${timeoutMs}ms`))
44
- } else {
45
- setTimeout(check, 500)
46
- }
47
- })
48
- req.end()
49
- }
50
- check()
51
- })
52
- }
53
-
54
- describe('CLI E2E', () => {
55
- const processes: ChildProcess[] = []
56
-
57
- afterEach(async () => {
58
- for (const proc of processes) {
59
- proc.kill('SIGTERM')
60
- }
61
- processes.length = 0
62
- // Small delay for cleanup
63
- await new Promise((r) => setTimeout(r, 500))
64
- })
65
-
66
- // Full server boot tests require the entire build chain
67
- const fullTest = process.env.CAMSTACK_TEST_CLI_FULL === 'true' && CLI_EXISTS ? it : it.skip
68
-
69
- fullTest(
70
- 'camstack serve boots and responds on HTTPS',
71
- async () => {
72
- const port = randomPort()
73
- const proc = fork(CLI_PATH, ['serve', '--port', String(port), '--data', TEST_DATA_DIR], {
74
- stdio: 'pipe',
75
- env: { ...process.env, CAMSTACK_TLS_ENABLED: 'true' },
76
- })
77
- processes.push(proc)
78
-
79
- await waitForPort(port)
80
-
81
- // Verify HTTPS response
82
- const body = await new Promise<string>((resolve, reject) => {
83
- const req = https.request(
84
- { hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
85
- (res) => {
86
- let data = ''
87
- res.on('data', (chunk) => {
88
- data += chunk
89
- })
90
- res.on('end', () => resolve(data))
91
- },
92
- )
93
- req.on('error', reject)
94
- req.end()
95
- })
96
-
97
- // Should return something (admin UI or health endpoint)
98
- expect(body.length).toBeGreaterThan(0)
99
- },
100
- 30000,
101
- )
102
-
103
- fullTest(
104
- 'camstack agent boots and stays alive',
105
- async () => {
106
- const proc = fork(
107
- CLI_PATH,
108
- ['agent', '--hub', '127.0.0.1', '--token', 'test_token', '--port', String(randomPort())],
109
- {
110
- stdio: 'pipe',
111
- env: {
112
- ...process.env,
113
- CAMSTACK_AGENT_NAME: 'test-agent',
114
- CAMSTACK_DATA: TEST_DATA_DIR,
115
- },
116
- },
117
- )
118
- processes.push(proc)
119
-
120
- // Agent should start even if hub is unreachable (retries in background)
121
- await new Promise((r) => setTimeout(r, 5000))
122
-
123
- // Process should still be alive (not crashed)
124
- expect(proc.exitCode).toBeNull()
125
- },
126
- 15000,
127
- )
128
-
129
- it.skipIf(!CLI_EXISTS)(
130
- 'camstack info prints version and platform',
131
- async () => {
132
- const output = await new Promise<string>((resolve, reject) => {
133
- let stdout = ''
134
- const proc = fork(CLI_PATH, ['info'], { stdio: 'pipe' })
135
- proc.stdout?.on('data', (chunk) => {
136
- stdout += chunk
137
- })
138
- proc.on('exit', (code) => {
139
- if (code === 0) resolve(stdout)
140
- else reject(new Error(`Exit code ${code}`))
141
- })
142
- })
143
-
144
- expect(output).toContain('camstack v')
145
- expect(output).toContain('Platform:')
146
- expect(output).toContain('Node.js:')
147
- },
148
- 10000,
149
- )
150
- })
@@ -1,91 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest'
2
- import { ServiceBroker } from 'moleculer'
3
- import { z } from 'zod'
4
- import { CORE_CAP_SERVICE_NAME } from '@camstack/kernel'
5
- import { trpcRouter, publicProcedure, protectedProcedure } from '../api/trpc/trpc.middleware.js'
6
- import { buildCoreCapService } from '../api/trpc/core-cap-bridge.js'
7
- import type { AppRouter } from '../api/trpc/trpc.router.js'
8
-
9
- /**
10
- * Synthetic router covering every branch of `buildCoreCapService`:
11
- * - a core namespace with a query + a mutation
12
- * - a camelCase core namespace (kebab-case action name)
13
- * - a core namespace with a subscription (must be skipped)
14
- * - a non-core namespace (must be excluded)
15
- * - the deliberately-excluded `auth` namespace
16
- * - a `protectedProcedure` (must pass under the trusted mesh ctx)
17
- */
18
- function makeRouter() {
19
- return trpcRouter({
20
- system: trpcRouter({
21
- info: publicProcedure.query(() => ({ version: '9.9.9' })),
22
- setRetentionConfig: protectedProcedure
23
- .input(z.object({ days: z.number() }))
24
- .mutation(({ input }) => ({ days: input.days })),
25
- }),
26
- streamProbe: trpcRouter({
27
- probe: publicProcedure.query(() => ({ ok: true })),
28
- }),
29
- toast: trpcRouter({
30
- onToast: publicProcedure.subscription(async function* () {
31
- yield 1
32
- }),
33
- }),
34
- deviceManager: trpcRouter({
35
- listAll: publicProcedure.query(() => []),
36
- }),
37
- auth: trpcRouter({
38
- login: publicProcedure.query(() => 'token'),
39
- }),
40
- })
41
- }
42
-
43
- function asAppRouter(router: ReturnType<typeof makeRouter>): AppRouter {
44
- // The synthetic router is structurally a tRPC router; the cast lets
45
- // the test exercise `buildCoreCapService` without booting every
46
- // backend service the real appRouter depends on.
47
- return router as unknown as AppRouter
48
- }
49
-
50
- describe('buildCoreCapService', () => {
51
- let broker: ServiceBroker
52
-
53
- afterEach(async () => {
54
- if (broker) await broker.stop()
55
- })
56
-
57
- it('exposes core query + mutation procedures as kebab-cased actions', () => {
58
- const schema = buildCoreCapService(asAppRouter(makeRouter()))
59
- expect(schema.name).toBe(CORE_CAP_SERVICE_NAME)
60
- expect(schema.actions?.['system.info']).toBeDefined()
61
- expect(schema.actions?.['system.setRetentionConfig']).toBeDefined()
62
- expect(schema.actions?.['stream-probe.probe']).toBeDefined()
63
- })
64
-
65
- it('excludes non-core namespaces, the auth router, and subscriptions', () => {
66
- const schema = buildCoreCapService(asAppRouter(makeRouter()))
67
- expect(schema.actions?.['device-manager.listAll']).toBeUndefined()
68
- expect(schema.actions?.['auth.login']).toBeUndefined()
69
- expect(schema.actions?.['toast.onToast']).toBeUndefined()
70
- })
71
-
72
- it('routes a bridged query through the trusted mesh caller', async () => {
73
- broker = new ServiceBroker({ nodeID: 'test', transporter: 'Fake', logger: false })
74
- broker.createService(buildCoreCapService(asAppRouter(makeRouter())))
75
- await broker.start()
76
-
77
- const result = await broker.call(`${CORE_CAP_SERVICE_NAME}.system.info`)
78
- expect(result).toEqual({ version: '9.9.9' })
79
- })
80
-
81
- it('passes protectedProcedure under the synthetic admin mesh identity', async () => {
82
- broker = new ServiceBroker({ nodeID: 'test', transporter: 'Fake', logger: false })
83
- broker.createService(buildCoreCapService(asAppRouter(makeRouter())))
84
- await broker.start()
85
-
86
- const result = await broker.call(`${CORE_CAP_SERVICE_NAME}.system.setRetentionConfig`, {
87
- days: 30,
88
- })
89
- expect(result).toEqual({ days: 30 })
90
- })
91
- })
@@ -1,40 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { existsSync, readdirSync } from 'node:fs'
3
- import { join } from 'node:path'
4
-
5
- describe('dev bootstrap — shm-ring prebuild availability', () => {
6
- it('has at least one .node prebuild for the current platform-arch', () => {
7
- const platform = process.platform
8
- const arch = process.arch
9
- const triple = `${platform}-${arch}`
10
- const prebuildsDir = join(
11
- __dirname,
12
- '..',
13
- '..',
14
- '..',
15
- '..',
16
- 'packages',
17
- 'shm-ring',
18
- 'prebuilds',
19
- triple,
20
- )
21
- expect(existsSync(prebuildsDir)).toBe(true)
22
- const files = readdirSync(prebuildsDir)
23
- const nodeBinaries = files.filter((f) => f.endsWith('.node'))
24
- expect(nodeBinaries.length).toBeGreaterThan(0)
25
- })
26
-
27
- it('@camstack/shm-ring resolves and loads (smoke test)', () => {
28
- // If this require() throws "No native build was found...", the
29
- // dev bootstrap is broken — the regression Bug-2 is back.
30
- let mod: unknown
31
- expect(() => {
32
- mod = require('@camstack/shm-ring')
33
- }).not.toThrow()
34
- expect(mod).toBeDefined()
35
- // Should expose at least FrameRingReader/Writer (per the published API)
36
- const m = mod as Record<string, unknown>
37
- expect(typeof m['FrameRingReader']).toBe('function')
38
- expect(typeof m['FrameRingWriter']).toBe('function')
39
- })
40
- })