@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,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Framework package allow-list coherence.
|
|
3
|
-
*
|
|
4
|
-
* Spec: docs/superpowers/specs/2026-05-14-framework-live-update-design.md
|
|
5
|
-
*
|
|
6
|
-
* Invariants:
|
|
7
|
-
* - Every entry in `FRAMEWORK_PACKAGE_ALLOWLIST` resolves to a workspace
|
|
8
|
-
* package whose `package.json` declares `camstack.system: true`.
|
|
9
|
-
* - Every workspace package with `camstack.system: true` appears in the
|
|
10
|
-
* allow-list — drift in either direction breaks live-update.
|
|
11
|
-
* - `isFrameworkPackage` accepts framework names and rejects everything
|
|
12
|
-
* else (sanity check on the gate the cap method uses).
|
|
13
|
-
*/
|
|
14
|
-
import { describe, it, expect } from 'vitest'
|
|
15
|
-
import * as fs from 'node:fs'
|
|
16
|
-
import * as path from 'node:path'
|
|
17
|
-
import {
|
|
18
|
-
FRAMEWORK_PACKAGE_ALLOWLIST,
|
|
19
|
-
isFrameworkPackage,
|
|
20
|
-
} from '../core/addon/addon-package.service.js'
|
|
21
|
-
|
|
22
|
-
function repoRoot(): string {
|
|
23
|
-
return path.resolve(__dirname, '..', '..', '..', '..')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface PackageJsonView {
|
|
27
|
-
readonly name: string
|
|
28
|
-
readonly system: boolean
|
|
29
|
-
readonly path: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readPackageJson(pkgJsonPath: string): PackageJsonView | null {
|
|
33
|
-
try {
|
|
34
|
-
const raw: unknown = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'))
|
|
35
|
-
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return null
|
|
36
|
-
const obj = raw as Record<string, unknown>
|
|
37
|
-
const name = obj['name']
|
|
38
|
-
if (typeof name !== 'string') return null
|
|
39
|
-
const camstack = obj['camstack']
|
|
40
|
-
const isSystem =
|
|
41
|
-
camstack !== null &&
|
|
42
|
-
typeof camstack === 'object' &&
|
|
43
|
-
!Array.isArray(camstack) &&
|
|
44
|
-
(camstack as Record<string, unknown>)['system'] === true
|
|
45
|
-
return { name, system: isSystem, path: pkgJsonPath }
|
|
46
|
-
} catch {
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function discoverWorkspacePackages(): readonly PackageJsonView[] {
|
|
52
|
-
const packagesDir = path.join(repoRoot(), 'packages')
|
|
53
|
-
if (!fs.existsSync(packagesDir)) return []
|
|
54
|
-
const results: PackageJsonView[] = []
|
|
55
|
-
for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
56
|
-
if (!entry.isDirectory()) continue
|
|
57
|
-
const pkgJson = path.join(packagesDir, entry.name, 'package.json')
|
|
58
|
-
if (!fs.existsSync(pkgJson)) continue
|
|
59
|
-
const view = readPackageJson(pkgJson)
|
|
60
|
-
if (view !== null) results.push(view)
|
|
61
|
-
}
|
|
62
|
-
return results
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
describe('framework package allow-list — manifest parity', () => {
|
|
66
|
-
it('every allow-listed package exists in `packages/` with camstack.system: true', () => {
|
|
67
|
-
const pkgs = discoverWorkspacePackages()
|
|
68
|
-
for (const name of FRAMEWORK_PACKAGE_ALLOWLIST) {
|
|
69
|
-
const match = pkgs.find((p) => p.name === name)
|
|
70
|
-
expect(match, `expected ${name} in packages/`).toBeDefined()
|
|
71
|
-
expect(match?.system, `${name} must declare camstack.system: true`).toBe(true)
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('every workspace package with camstack.system: true is in the allow-list', () => {
|
|
76
|
-
const systemPkgs = discoverWorkspacePackages().filter((p) => p.system)
|
|
77
|
-
const allowSet = new Set(FRAMEWORK_PACKAGE_ALLOWLIST)
|
|
78
|
-
const orphans = systemPkgs.filter((p) => !allowSet.has(p.name))
|
|
79
|
-
expect(orphans.map((p) => p.name)).toEqual([])
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
describe('isFrameworkPackage', () => {
|
|
84
|
-
it('returns true for every allow-listed package', () => {
|
|
85
|
-
for (const name of FRAMEWORK_PACKAGE_ALLOWLIST) {
|
|
86
|
-
expect(isFrameworkPackage(name)).toBe(true)
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('returns false for addons and unknown packages', () => {
|
|
91
|
-
expect(isFrameworkPackage('@camstack/addon-stream-broker')).toBe(false)
|
|
92
|
-
expect(isFrameworkPackage('left-pad')).toBe(false)
|
|
93
|
-
expect(isFrameworkPackage('')).toBe(false)
|
|
94
|
-
expect(isFrameworkPackage('@camstack')).toBe(false)
|
|
95
|
-
})
|
|
96
|
-
})
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* deferRestart flag on AddonPackageService.updateFrameworkPackage
|
|
3
|
-
*
|
|
4
|
-
* Spec: docs/superpowers/specs/2026-05-21-addons-bulk-update-progress-design.md
|
|
5
|
-
*
|
|
6
|
-
* Invariants:
|
|
7
|
-
* - deferRestart: true → npm install runs normally; marker write AND
|
|
8
|
-
* scheduleSelfRestart are SKIPPED; restartingAt is 0 (sentinel).
|
|
9
|
-
* - deferRestart omitted (default) → marker write AND scheduleSelfRestart
|
|
10
|
-
* both fire; restartingAt > 0.
|
|
11
|
-
*/
|
|
12
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
13
|
-
import * as os from 'node:os'
|
|
14
|
-
import * as path from 'node:path'
|
|
15
|
-
import * as fs from 'node:fs'
|
|
16
|
-
import type { IScopedLogger } from '@camstack/types'
|
|
17
|
-
|
|
18
|
-
// ── Module-level mocks ──────────────────────────────────────────────────────
|
|
19
|
-
// Must be hoisted before any import that loads the module under test.
|
|
20
|
-
// vi.mock is statically hoisted by vitest's transform, so the factories
|
|
21
|
-
// run before the module graph is resolved.
|
|
22
|
-
|
|
23
|
-
vi.mock('@camstack/kernel', async (importOriginal) => {
|
|
24
|
-
const real = await importOriginal<typeof import('@camstack/kernel')>()
|
|
25
|
-
return {
|
|
26
|
-
...real,
|
|
27
|
-
scheduleSelfRestart: vi.fn(),
|
|
28
|
-
writePendingRestart: vi.fn(),
|
|
29
|
-
// resolveNpmVersion and resolveFrameworkPackageAppRoot are private module
|
|
30
|
-
// functions inside addon-package.service.ts — they are NOT exported from
|
|
31
|
-
// @camstack/kernel, so we don't need to stub them here.
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// Mock node:child_process so no actual `npm install` / `npm view` is executed.
|
|
36
|
-
vi.mock('node:child_process', async (importOriginal) => {
|
|
37
|
-
const real = await importOriginal<typeof import('node:child_process')>()
|
|
38
|
-
return {
|
|
39
|
-
...real,
|
|
40
|
-
execFile: vi.fn(
|
|
41
|
-
(
|
|
42
|
-
_cmd: string,
|
|
43
|
-
_args: string[],
|
|
44
|
-
_opts: unknown,
|
|
45
|
-
callback: (err: null, result: { stdout: string; stderr: string }) => void,
|
|
46
|
-
) => {
|
|
47
|
-
// Simulate `npm view @camstack/types@0.1.40 version` → '0.1.40'
|
|
48
|
-
callback(null, { stdout: '0.1.40\n', stderr: '' })
|
|
49
|
-
return { kill: () => undefined }
|
|
50
|
-
},
|
|
51
|
-
),
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
// ── Imports that depend on the mocked modules ───────────────────────────────
|
|
56
|
-
import { scheduleSelfRestart, writePendingRestart } from '@camstack/kernel'
|
|
57
|
-
import { AddonPackageService } from '../core/addon/addon-package.service.js'
|
|
58
|
-
import { EventCategory } from '@camstack/types'
|
|
59
|
-
|
|
60
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
function makeLogger(): IScopedLogger {
|
|
63
|
-
const logger: IScopedLogger = {
|
|
64
|
-
info: vi.fn(),
|
|
65
|
-
warn: vi.fn(),
|
|
66
|
-
error: vi.fn(),
|
|
67
|
-
debug: vi.fn(),
|
|
68
|
-
trace: vi.fn(),
|
|
69
|
-
fatal: vi.fn(),
|
|
70
|
-
child: (() => logger) as IScopedLogger['child'],
|
|
71
|
-
} as unknown as IScopedLogger
|
|
72
|
-
return logger
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface ServiceEnv {
|
|
76
|
-
readonly service: AddonPackageService
|
|
77
|
-
readonly tmpDir: string
|
|
78
|
-
readonly scheduleSelfRestartMock: ReturnType<typeof vi.fn>
|
|
79
|
-
readonly writePendingRestartMock: ReturnType<typeof vi.fn>
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function createEnv(): ServiceEnv {
|
|
83
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'defer-restart-spec-'))
|
|
84
|
-
|
|
85
|
-
const logger = makeLogger()
|
|
86
|
-
|
|
87
|
-
const loggingService = {
|
|
88
|
-
createLogger: () => logger,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const eventBusService = {
|
|
92
|
-
emit: vi.fn(),
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const configService = {}
|
|
96
|
-
const addonRegistry = {}
|
|
97
|
-
const notificationService = {}
|
|
98
|
-
const toastService = {}
|
|
99
|
-
|
|
100
|
-
const service = new AddonPackageService(
|
|
101
|
-
loggingService as never,
|
|
102
|
-
eventBusService as never,
|
|
103
|
-
configService as never,
|
|
104
|
-
addonRegistry as never,
|
|
105
|
-
notificationService as never,
|
|
106
|
-
toastService as never,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
// Point the framework-package app-root resolver at our tmp dir so
|
|
110
|
-
// `npm install --prefix <appRoot>` (which is mocked anyway) has a
|
|
111
|
-
// valid-looking path without requiring the real workspace layout.
|
|
112
|
-
process.env['CAMSTACK_FRAMEWORK_APP_ROOT_OVERRIDE'] = tmpDir
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
service,
|
|
116
|
-
tmpDir,
|
|
117
|
-
scheduleSelfRestartMock: scheduleSelfRestart as ReturnType<typeof vi.fn>,
|
|
118
|
-
writePendingRestartMock: writePendingRestart as ReturnType<typeof vi.fn>,
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
123
|
-
|
|
124
|
-
describe('AddonPackageService.updateFrameworkPackage — deferRestart flag', () => {
|
|
125
|
-
let env: ServiceEnv
|
|
126
|
-
|
|
127
|
-
beforeEach(() => {
|
|
128
|
-
vi.clearAllMocks()
|
|
129
|
-
env = createEnv()
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
afterEach(() => {
|
|
133
|
-
fs.rmSync(env.tmpDir, { recursive: true, force: true })
|
|
134
|
-
delete process.env['CAMSTACK_FRAMEWORK_APP_ROOT_OVERRIDE']
|
|
135
|
-
vi.restoreAllMocks()
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('skips restart seam (writePendingRestart + scheduleSelfRestart) when deferRestart is true', async () => {
|
|
139
|
-
const result = await env.service.updateFrameworkPackage({
|
|
140
|
-
packageName: '@camstack/types',
|
|
141
|
-
version: '0.1.40',
|
|
142
|
-
deferRestart: true,
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
expect(env.writePendingRestartMock).not.toHaveBeenCalled()
|
|
146
|
-
expect(env.scheduleSelfRestartMock).not.toHaveBeenCalled()
|
|
147
|
-
expect(result.packageName).toBe('@camstack/types')
|
|
148
|
-
expect(result.toVersion).toBe('0.1.40')
|
|
149
|
-
// Sentinel: 0 signals "no restart scheduled"
|
|
150
|
-
expect(result.restartingAt).toBe(0)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('fires writePendingRestart + scheduleSelfRestart when deferRestart is omitted', async () => {
|
|
154
|
-
const result = await env.service.updateFrameworkPackage({
|
|
155
|
-
packageName: '@camstack/types',
|
|
156
|
-
version: '0.1.40',
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
expect(env.writePendingRestartMock).toHaveBeenCalledOnce()
|
|
160
|
-
expect(env.scheduleSelfRestartMock).toHaveBeenCalledOnce()
|
|
161
|
-
expect(result.packageName).toBe('@camstack/types')
|
|
162
|
-
expect(result.toVersion).toBe('0.1.40')
|
|
163
|
-
expect(result.restartingAt).toBeGreaterThan(0)
|
|
164
|
-
})
|
|
165
|
-
})
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTPS E2E tests — verify self-signed cert generation, HTTPS serving, and WSS agent connection.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
5
|
-
import * as fs from 'node:fs'
|
|
6
|
-
import * as path from 'node:path'
|
|
7
|
-
import * as os from 'node:os'
|
|
8
|
-
import * as https from 'node:https'
|
|
9
|
-
import { X509Certificate } from 'node:crypto'
|
|
10
|
-
// Import directly from source submodule because vitest+swc doesn't resolve
|
|
11
|
-
// `export * from './tls/index.js'` barrel re-exports in the @camstack/core index.
|
|
12
|
-
import { ensureTlsCert, loadTlsCert } from '../../../../packages/core/src/tls/cert-manager'
|
|
13
|
-
|
|
14
|
-
describe('HTTPS E2E', () => {
|
|
15
|
-
let tmpDir: string
|
|
16
|
-
|
|
17
|
-
beforeAll(() => {
|
|
18
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'camstack-https-e2e-'))
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
afterAll(() => {
|
|
22
|
-
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('generates a valid self-signed cert on first call', async () => {
|
|
26
|
-
const result = await ensureTlsCert(tmpDir)
|
|
27
|
-
expect(result.generated).toBe(true)
|
|
28
|
-
|
|
29
|
-
// Verify cert file is valid PEM
|
|
30
|
-
const certPem = fs.readFileSync(result.certPath, 'utf-8')
|
|
31
|
-
expect(certPem).toContain('-----BEGIN CERTIFICATE-----')
|
|
32
|
-
|
|
33
|
-
// Parse and validate
|
|
34
|
-
const x509 = new X509Certificate(certPem)
|
|
35
|
-
expect(x509.subject).toContain('CN=camstack.local')
|
|
36
|
-
|
|
37
|
-
// Check SAN includes localhost
|
|
38
|
-
const san = x509.subjectAltName ?? ''
|
|
39
|
-
expect(san).toContain('DNS:localhost')
|
|
40
|
-
expect(san).toContain('IP Address:127.0.0.1')
|
|
41
|
-
|
|
42
|
-
// Check validity (at least 1 year)
|
|
43
|
-
const validTo = new Date(x509.validTo)
|
|
44
|
-
const oneYear = new Date()
|
|
45
|
-
oneYear.setFullYear(oneYear.getFullYear() + 1)
|
|
46
|
-
expect(validTo.getTime()).toBeGreaterThan(oneYear.getTime() - 86400000)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('reuses existing cert on subsequent calls', async () => {
|
|
50
|
-
const first = await ensureTlsCert(tmpDir)
|
|
51
|
-
const second = await ensureTlsCert(tmpDir)
|
|
52
|
-
expect(second.generated).toBe(false)
|
|
53
|
-
|
|
54
|
-
const cert1 = fs.readFileSync(first.certPath, 'utf-8')
|
|
55
|
-
const cert2 = fs.readFileSync(second.certPath, 'utf-8')
|
|
56
|
-
expect(cert1).toBe(cert2)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('serves HTTPS with the generated cert', async () => {
|
|
60
|
-
const { certPath, keyPath } = await ensureTlsCert(tmpDir)
|
|
61
|
-
const { cert, key } = loadTlsCert(certPath, keyPath)
|
|
62
|
-
|
|
63
|
-
const server = https.createServer({ cert, key }, (_req, res) => {
|
|
64
|
-
res.writeHead(200, { 'Content-Type': 'text/plain' })
|
|
65
|
-
res.end('camstack-ok')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const port = 10000 + Math.floor(Math.random() * 50000)
|
|
69
|
-
await new Promise<void>((resolve) => server.listen(port, '127.0.0.1', resolve))
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const body = await new Promise<string>((resolve, reject) => {
|
|
73
|
-
const req = https.request(
|
|
74
|
-
{ hostname: '127.0.0.1', port, path: '/', method: 'GET', rejectUnauthorized: false },
|
|
75
|
-
(res) => {
|
|
76
|
-
let data = ''
|
|
77
|
-
res.on('data', (c) => {
|
|
78
|
-
data += c
|
|
79
|
-
})
|
|
80
|
-
res.on('end', () => resolve(data))
|
|
81
|
-
},
|
|
82
|
-
)
|
|
83
|
-
req.on('error', reject)
|
|
84
|
-
req.end()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
expect(body).toBe('camstack-ok')
|
|
88
|
-
} finally {
|
|
89
|
-
server.close()
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('TLS disabled falls back to HTTP', async () => {
|
|
94
|
-
// When tls.enabled = false, server should work over plain HTTP
|
|
95
|
-
// This is a config-level test, verified by checking that FastifyAdapter
|
|
96
|
-
// receives no https options when tls is disabled
|
|
97
|
-
const http = await import('node:http')
|
|
98
|
-
const server = http.createServer((_req, res) => {
|
|
99
|
-
res.writeHead(200)
|
|
100
|
-
res.end('http-ok')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
const port = 10000 + Math.floor(Math.random() * 50000)
|
|
104
|
-
await new Promise<void>((resolve) => server.listen(port, '127.0.0.1', resolve))
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const body = await new Promise<string>((resolve, reject) => {
|
|
108
|
-
http
|
|
109
|
-
.get(`http://127.0.0.1:${port}/`, (res) => {
|
|
110
|
-
let data = ''
|
|
111
|
-
res.on('data', (c) => {
|
|
112
|
-
data += c
|
|
113
|
-
})
|
|
114
|
-
res.on('end', () => resolve(data))
|
|
115
|
-
})
|
|
116
|
-
.on('error', reject)
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
expect(body).toBe('http-ok')
|
|
120
|
-
} finally {
|
|
121
|
-
server.close()
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
})
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { CapabilityRegistry, isInfraCapability } from '@camstack/kernel'
|
|
3
|
-
import type { IScopedLogger } from '@camstack/types'
|
|
4
|
-
|
|
5
|
-
// ─── Mock helpers ────────────────────────────────────────────────────
|
|
6
|
-
//
|
|
7
|
-
// PipelineOrchestrator phase-transition + result-flow tests used to live
|
|
8
|
-
// in this file (importing from the now-deleted server/backend/src/core/
|
|
9
|
-
// orchestrator/ directory). They have been migrated into the runner package
|
|
10
|
-
// (`packages/addon-pipeline-runner/src/__tests__/runner.spec.ts`) which
|
|
11
|
-
// is the canonical home of the scheduler core.
|
|
12
|
-
|
|
13
|
-
function createMockLogger(): IScopedLogger {
|
|
14
|
-
return {
|
|
15
|
-
info: vi.fn(),
|
|
16
|
-
warn: vi.fn(),
|
|
17
|
-
error: vi.fn(),
|
|
18
|
-
debug: vi.fn(),
|
|
19
|
-
} as unknown as IScopedLogger
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function createRegistry(configReader?: (cap: string) => string | undefined): CapabilityRegistry {
|
|
23
|
-
const registry = new CapabilityRegistry(createMockLogger())
|
|
24
|
-
if (configReader) {
|
|
25
|
-
registry.setConfigReader(configReader)
|
|
26
|
-
}
|
|
27
|
-
registry.ready()
|
|
28
|
-
return registry
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ─── Tests ────────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
describe('Full Lifecycle E2E (mock, no external services)', () => {
|
|
34
|
-
describe('CapabilityRegistry boot sequence', () => {
|
|
35
|
-
it('registers singleton and collection capabilities correctly', () => {
|
|
36
|
-
const registry = createRegistry()
|
|
37
|
-
|
|
38
|
-
registry.declareCapability({
|
|
39
|
-
name: 'storage',
|
|
40
|
-
scope: 'system',
|
|
41
|
-
mode: 'singleton',
|
|
42
|
-
methods: {},
|
|
43
|
-
})
|
|
44
|
-
registry.declareCapability({
|
|
45
|
-
name: 'log-destination',
|
|
46
|
-
scope: 'system',
|
|
47
|
-
mode: 'collection',
|
|
48
|
-
methods: {},
|
|
49
|
-
})
|
|
50
|
-
registry.declareCapability({
|
|
51
|
-
name: 'streaming-engine',
|
|
52
|
-
scope: 'system',
|
|
53
|
-
mode: 'singleton',
|
|
54
|
-
methods: {},
|
|
55
|
-
})
|
|
56
|
-
registry.declareCapability({
|
|
57
|
-
name: 'analysis-pipeline',
|
|
58
|
-
scope: 'system',
|
|
59
|
-
mode: 'singleton',
|
|
60
|
-
methods: {},
|
|
61
|
-
})
|
|
62
|
-
registry.declareCapability({
|
|
63
|
-
name: 'device-provider',
|
|
64
|
-
scope: 'system',
|
|
65
|
-
mode: 'collection',
|
|
66
|
-
methods: {},
|
|
67
|
-
})
|
|
68
|
-
registry.declareCapability({
|
|
69
|
-
name: 'admin-ui',
|
|
70
|
-
scope: 'system',
|
|
71
|
-
mode: 'singleton',
|
|
72
|
-
methods: {},
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const mockStorage = { id: 'sqlite' }
|
|
76
|
-
const mockLogger = { id: 'winston' }
|
|
77
|
-
const mockStreaming = { id: 'go2rtc' }
|
|
78
|
-
const mockAnalysis = { id: 'pipeline-analysis' }
|
|
79
|
-
const mockProvider = { id: 'frigate' }
|
|
80
|
-
|
|
81
|
-
registry.registerProvider('storage', 'sqlite-storage', mockStorage)
|
|
82
|
-
registry.registerProvider('log-destination', 'winston-logging', mockLogger)
|
|
83
|
-
registry.registerProvider('streaming-engine', 'go2rtc', mockStreaming)
|
|
84
|
-
registry.registerProvider('analysis-pipeline', 'pipeline-analysis', mockAnalysis)
|
|
85
|
-
registry.registerProvider('device-provider', 'provider-frigate', mockProvider)
|
|
86
|
-
|
|
87
|
-
expect(registry.getSingleton('storage')).toBe(mockStorage)
|
|
88
|
-
expect(registry.getCollection('log-destination')).toEqual([mockLogger])
|
|
89
|
-
expect(registry.getSingleton('streaming-engine')).toBe(mockStreaming)
|
|
90
|
-
expect(registry.getSingleton('analysis-pipeline')).toBe(mockAnalysis)
|
|
91
|
-
expect(registry.getCollection('device-provider')).toEqual([mockProvider])
|
|
92
|
-
|
|
93
|
-
const caps = registry.listCapabilities()
|
|
94
|
-
expect(caps.length).toBeGreaterThanOrEqual(5)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('infra capabilities are identified correctly', () => {
|
|
98
|
-
expect(isInfraCapability('storage')).toBe(true)
|
|
99
|
-
expect(isInfraCapability('log-destination')).toBe(true)
|
|
100
|
-
expect(isInfraCapability('streaming-engine')).toBe(false)
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// Note: PipelineOrchestrator phase transition + detection result flow tests
|
|
105
|
-
// moved to packages/addon-pipeline-runner/src/__tests__/runner.spec.ts
|
|
106
|
-
// when the scheduler was extracted into the addon-pipeline-runner package.
|
|
107
|
-
|
|
108
|
-
describe('Singleton swap', () => {
|
|
109
|
-
it('swapping analysis provider changes getSingleton result', async () => {
|
|
110
|
-
const registry = createRegistry()
|
|
111
|
-
registry.declareCapability({
|
|
112
|
-
name: 'analysis-pipeline',
|
|
113
|
-
scope: 'system',
|
|
114
|
-
mode: 'singleton',
|
|
115
|
-
methods: {},
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const analysisA = { id: 'analysis-a', processFrame: vi.fn() }
|
|
119
|
-
const analysisB = { id: 'analysis-b', processFrame: vi.fn() }
|
|
120
|
-
|
|
121
|
-
registry.registerProvider('analysis-pipeline', 'addon-a', analysisA)
|
|
122
|
-
expect(registry.getSingleton('analysis-pipeline')).toBe(analysisA)
|
|
123
|
-
|
|
124
|
-
registry.registerProvider('analysis-pipeline', 'addon-b', analysisB)
|
|
125
|
-
|
|
126
|
-
await registry.setActiveSingleton('analysis-pipeline', 'addon-b', true)
|
|
127
|
-
expect(registry.getSingleton('analysis-pipeline')).toBe(analysisB)
|
|
128
|
-
})
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
describe('Collection capability add/remove', () => {
|
|
132
|
-
it('adding and removing device providers', () => {
|
|
133
|
-
const registry = createRegistry()
|
|
134
|
-
registry.declareCapability({
|
|
135
|
-
name: 'device-provider',
|
|
136
|
-
scope: 'system',
|
|
137
|
-
mode: 'collection',
|
|
138
|
-
methods: {},
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const frigate = { id: 'frigate', type: 'frigate' }
|
|
142
|
-
const onvif = { id: 'onvif', type: 'onvif' }
|
|
143
|
-
|
|
144
|
-
registry.registerProvider('device-provider', 'addon-frigate', frigate)
|
|
145
|
-
expect(registry.getCollection('device-provider')).toHaveLength(1)
|
|
146
|
-
|
|
147
|
-
registry.registerProvider('device-provider', 'addon-onvif', onvif)
|
|
148
|
-
expect(registry.getCollection('device-provider')).toHaveLength(2)
|
|
149
|
-
|
|
150
|
-
registry.unregisterProvider('device-provider', 'addon-frigate')
|
|
151
|
-
expect(registry.getCollection('device-provider')).toHaveLength(1)
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
describe('Capability introspection', () => {
|
|
156
|
-
it('listCapabilities returns full info', () => {
|
|
157
|
-
const registry = createRegistry()
|
|
158
|
-
registry.declareCapability({
|
|
159
|
-
name: 'storage',
|
|
160
|
-
scope: 'system',
|
|
161
|
-
mode: 'singleton',
|
|
162
|
-
methods: {},
|
|
163
|
-
})
|
|
164
|
-
registry.declareCapability({
|
|
165
|
-
name: 'device-provider',
|
|
166
|
-
scope: 'system',
|
|
167
|
-
mode: 'collection',
|
|
168
|
-
methods: {},
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
registry.registerProvider('storage', 'sqlite', { id: 'sqlite' })
|
|
172
|
-
registry.registerProvider('device-provider', 'frigate', { id: 'frigate' })
|
|
173
|
-
registry.registerProvider('device-provider', 'onvif', { id: 'onvif' })
|
|
174
|
-
|
|
175
|
-
const caps = registry.listCapabilities()
|
|
176
|
-
|
|
177
|
-
const storage = caps.find((c) => c.name === 'storage')
|
|
178
|
-
expect(storage).toBeDefined()
|
|
179
|
-
expect(storage!.mode).toBe('singleton')
|
|
180
|
-
expect(storage!.providers).toContain('sqlite')
|
|
181
|
-
expect(storage!.activeProvider).toBe('sqlite')
|
|
182
|
-
|
|
183
|
-
const providers = caps.find((c) => c.name === 'device-provider')
|
|
184
|
-
expect(providers).toBeDefined()
|
|
185
|
-
expect(providers!.mode).toBe('collection')
|
|
186
|
-
expect(providers!.providers).toHaveLength(2)
|
|
187
|
-
})
|
|
188
|
-
})
|
|
189
|
-
})
|