@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,99 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import * as fs from 'node:fs'
|
|
3
|
-
import * as path from 'node:path'
|
|
4
|
-
import * as os from 'node:os'
|
|
5
|
-
import * as yaml from 'js-yaml'
|
|
6
|
-
import { FeatureService } from './feature.service'
|
|
7
|
-
import { ConfigService } from '../config/config.service'
|
|
8
|
-
import type { FeatureManifest } from '@camstack/types'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Test-only ConfigService subclass that overrides the `features` getter
|
|
12
|
-
* to return a fixed manifest. Avoids wiring a full settings-store just to
|
|
13
|
-
* drive a FeatureService. The bootstrap YAML still has to be real so the
|
|
14
|
-
* ConfigManager constructor succeeds.
|
|
15
|
-
*/
|
|
16
|
-
class StaticFeatureConfigService extends ConfigService {
|
|
17
|
-
constructor(
|
|
18
|
-
configPath: string,
|
|
19
|
-
private readonly staticFeatures: FeatureManifest,
|
|
20
|
-
) {
|
|
21
|
-
super(configPath)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
override get features(): FeatureManifest {
|
|
25
|
-
return this.staticFeatures
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe('FeatureService', () => {
|
|
30
|
-
let tmpDir: string
|
|
31
|
-
|
|
32
|
-
beforeEach(() => {
|
|
33
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'camstack-feature-'))
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
const createService = (features: Partial<FeatureManifest> = {}): FeatureService => {
|
|
41
|
-
const fullFeatures: FeatureManifest = {
|
|
42
|
-
streaming: true,
|
|
43
|
-
notifications: true,
|
|
44
|
-
objectDetection: false,
|
|
45
|
-
remoteAccess: true,
|
|
46
|
-
agentCluster: false,
|
|
47
|
-
smartHome: true,
|
|
48
|
-
recordings: true,
|
|
49
|
-
backup: true,
|
|
50
|
-
repl: true,
|
|
51
|
-
...features,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const configPath = path.join(tmpDir, 'config.yaml')
|
|
55
|
-
fs.writeFileSync(
|
|
56
|
-
configPath,
|
|
57
|
-
yaml.dump({
|
|
58
|
-
server: { port: 4443 },
|
|
59
|
-
auth: { adminPassword: 'secret123' },
|
|
60
|
-
}),
|
|
61
|
-
'utf-8',
|
|
62
|
-
)
|
|
63
|
-
const configService = new StaticFeatureConfigService(configPath, fullFeatures)
|
|
64
|
-
return new FeatureService(configService)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
it('returns true for enabled features', () => {
|
|
68
|
-
const service = createService({ streaming: true, notifications: true })
|
|
69
|
-
|
|
70
|
-
expect(service.isEnabled('streaming')).toBe(true)
|
|
71
|
-
expect(service.isEnabled('notifications')).toBe(true)
|
|
72
|
-
expect(service.isEnabled('remoteAccess')).toBe(true)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('returns false for disabled features', () => {
|
|
76
|
-
const service = createService({ objectDetection: false, agentCluster: false })
|
|
77
|
-
|
|
78
|
-
expect(service.isEnabled('objectDetection')).toBe(false)
|
|
79
|
-
expect(service.isEnabled('agentCluster')).toBe(false)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('returns full manifest', () => {
|
|
83
|
-
const customFeatures: Partial<FeatureManifest> = {
|
|
84
|
-
streaming: false,
|
|
85
|
-
objectDetection: true,
|
|
86
|
-
}
|
|
87
|
-
const service = createService(customFeatures)
|
|
88
|
-
|
|
89
|
-
const manifest = service.getManifest()
|
|
90
|
-
|
|
91
|
-
expect(manifest.streaming).toBe(false)
|
|
92
|
-
|
|
93
|
-
expect(manifest.objectDetection).toBe(true)
|
|
94
|
-
|
|
95
|
-
expect(manifest.notifications).toBe(true)
|
|
96
|
-
|
|
97
|
-
expect(manifest.remoteAccess).toBe(true)
|
|
98
|
-
})
|
|
99
|
-
})
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { LifecycleStateMachine } from './lifecycle-state-machine'
|
|
3
|
-
import type { IEventBus, SystemEvent } from '@camstack/types'
|
|
4
|
-
import type { IScopedLogger } from '@camstack/types'
|
|
5
|
-
|
|
6
|
-
const createMockEventBus = (): IEventBus & { emitted: SystemEvent[] } => {
|
|
7
|
-
const emitted: SystemEvent[] = []
|
|
8
|
-
return {
|
|
9
|
-
emitted,
|
|
10
|
-
emit: vi.fn((event: SystemEvent) => emitted.push(event)),
|
|
11
|
-
subscribe: vi.fn(() => () => {}),
|
|
12
|
-
getRecent: vi.fn(() => []),
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const createMockLogger = (): IScopedLogger => ({
|
|
17
|
-
debug: vi.fn(),
|
|
18
|
-
info: vi.fn(),
|
|
19
|
-
warn: vi.fn(),
|
|
20
|
-
error: vi.fn(),
|
|
21
|
-
child: vi.fn(),
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
describe('LifecycleStateMachine', () => {
|
|
25
|
-
let eventBus: ReturnType<typeof createMockEventBus>
|
|
26
|
-
let logger: IScopedLogger
|
|
27
|
-
let machine: LifecycleStateMachine
|
|
28
|
-
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
eventBus = createMockEventBus()
|
|
31
|
-
|
|
32
|
-
logger = createMockLogger()
|
|
33
|
-
machine = new LifecycleStateMachine('test-element', 'device', eventBus, logger)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('starts in stopped state', () => {
|
|
37
|
-
expect(machine.state).toBe('stopped')
|
|
38
|
-
expect(machine.getStatus().state).toBe('stopped')
|
|
39
|
-
expect(machine.getStatus().restartCount).toBe(0)
|
|
40
|
-
expect(machine.getStatus().uptime).toBe(0)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('allows valid transition: stopped → starting → running', () => {
|
|
44
|
-
const result1 = machine.transition('starting')
|
|
45
|
-
expect(result1).toBe(true)
|
|
46
|
-
expect(machine.state).toBe('starting')
|
|
47
|
-
|
|
48
|
-
const result2 = machine.transition('running')
|
|
49
|
-
expect(result2).toBe(true)
|
|
50
|
-
expect(machine.state).toBe('running')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('rejects invalid transition (e.g. stopped → running directly)', () => {
|
|
54
|
-
const result = machine.transition('running')
|
|
55
|
-
expect(result).toBe(false)
|
|
56
|
-
expect(machine.state).toBe('stopped')
|
|
57
|
-
|
|
58
|
-
expect(logger.warn).toHaveBeenCalledWith(
|
|
59
|
-
expect.stringContaining('Invalid state transition'),
|
|
60
|
-
expect.anything(),
|
|
61
|
-
)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('emits event on bus for each valid transition', () => {
|
|
65
|
-
machine.transition('starting')
|
|
66
|
-
machine.transition('running')
|
|
67
|
-
|
|
68
|
-
expect(eventBus.emit).toHaveBeenCalledTimes(2)
|
|
69
|
-
|
|
70
|
-
expect(eventBus.emitted[0]!.category).toBe('device.state.starting')
|
|
71
|
-
|
|
72
|
-
expect(eventBus.emitted[0]!.source).toEqual({ type: 'device', id: 'test-element' })
|
|
73
|
-
|
|
74
|
-
expect(eventBus.emitted[0]!.data).toMatchObject({
|
|
75
|
-
from: 'stopped',
|
|
76
|
-
to: 'starting',
|
|
77
|
-
elementId: 'test-element',
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
expect(eventBus.emitted[1]!.category).toBe('device.state.running')
|
|
81
|
-
|
|
82
|
-
expect(eventBus.emitted[1]!.data).toMatchObject({
|
|
83
|
-
from: 'starting',
|
|
84
|
-
to: 'running',
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('does not emit event on invalid transition', () => {
|
|
89
|
-
machine.transition('running')
|
|
90
|
-
|
|
91
|
-
expect(eventBus.emit).not.toHaveBeenCalled()
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('tracks uptime when running', () => {
|
|
95
|
-
machine.transition('starting')
|
|
96
|
-
machine.transition('running')
|
|
97
|
-
|
|
98
|
-
const status = machine.getStatus()
|
|
99
|
-
expect(status.uptime).toBeGreaterThanOrEqual(0)
|
|
100
|
-
expect(status.startedAt).toBeDefined()
|
|
101
|
-
expect(status.stoppedAt).toBeUndefined()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('records error message in error state', () => {
|
|
105
|
-
machine.transition('starting')
|
|
106
|
-
machine.transition('error', 'connection refused')
|
|
107
|
-
|
|
108
|
-
const status = machine.getStatus()
|
|
109
|
-
expect(status.state).toBe('error')
|
|
110
|
-
expect(status.error).toBe('connection refused')
|
|
111
|
-
expect(status.stoppedAt).toBeDefined()
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('getStatus returns complete info after full lifecycle', () => {
|
|
115
|
-
machine.transition('starting')
|
|
116
|
-
machine.transition('running')
|
|
117
|
-
machine.transition('stopping')
|
|
118
|
-
machine.transition('stopped')
|
|
119
|
-
|
|
120
|
-
const status = machine.getStatus()
|
|
121
|
-
expect(status.state).toBe('stopped')
|
|
122
|
-
expect(status.restartCount).toBe(0)
|
|
123
|
-
expect(status.stoppedAt).toBeDefined()
|
|
124
|
-
expect(status.uptime).toBe(0)
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('increments restart count only on subsequent starts', () => {
|
|
128
|
-
// First start
|
|
129
|
-
machine.transition('starting')
|
|
130
|
-
machine.transition('running')
|
|
131
|
-
expect(machine.getStatus().restartCount).toBe(0)
|
|
132
|
-
|
|
133
|
-
// Stop and restart
|
|
134
|
-
machine.transition('stopping')
|
|
135
|
-
machine.transition('stopped')
|
|
136
|
-
machine.transition('starting')
|
|
137
|
-
machine.transition('running')
|
|
138
|
-
expect(machine.getStatus().restartCount).toBe(1)
|
|
139
|
-
|
|
140
|
-
// Error and restart
|
|
141
|
-
machine.transition('error', 'crash')
|
|
142
|
-
machine.transition('starting')
|
|
143
|
-
machine.transition('running')
|
|
144
|
-
expect(machine.getStatus().restartCount).toBe(2)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('allows disabled state from stopped', () => {
|
|
148
|
-
const result = machine.transition('disabled')
|
|
149
|
-
expect(result).toBe(true)
|
|
150
|
-
expect(machine.state).toBe('disabled')
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('requires going through stopped to leave disabled', () => {
|
|
154
|
-
machine.transition('disabled')
|
|
155
|
-
|
|
156
|
-
expect(machine.transition('starting')).toBe(false)
|
|
157
|
-
expect(machine.transition('stopped')).toBe(true)
|
|
158
|
-
expect(machine.state).toBe('stopped')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('incrementRestartCount manually increments the counter', () => {
|
|
162
|
-
machine.incrementRestartCount()
|
|
163
|
-
machine.incrementRestartCount()
|
|
164
|
-
expect(machine.getStatus().restartCount).toBe(2)
|
|
165
|
-
})
|
|
166
|
-
})
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import * as fs from 'node:fs'
|
|
3
|
-
import * as path from 'node:path'
|
|
4
|
-
import * as os from 'node:os'
|
|
5
|
-
import * as yaml from 'js-yaml'
|
|
6
|
-
import { LogRingBuffer } from './log-ring-buffer'
|
|
7
|
-
import { ScopedLogger } from './scoped-logger'
|
|
8
|
-
import { LoggingService } from './logging.service'
|
|
9
|
-
import { ConfigService } from '../config/config.service'
|
|
10
|
-
import type { ISettingsStore } from '@camstack/kernel'
|
|
11
|
-
import type { LogEntry, ILogDestination } from '@camstack/types'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* In-memory settings store used by the test to drive `ConfigService.get`
|
|
15
|
-
* for the `eventBus.ringBufferSize` key. The bootstrap YAML schema does not
|
|
16
|
-
* declare `eventBus`, so writing it to the YAML file would be discarded by
|
|
17
|
-
* `bootstrapSchema.parse`. Wiring an ISettingsStore is the clean path to
|
|
18
|
-
* feed runtime values into `ConfigService` without casts.
|
|
19
|
-
*/
|
|
20
|
-
class InMemorySettingsStore implements ISettingsStore {
|
|
21
|
-
private readonly system: Record<string, unknown>
|
|
22
|
-
|
|
23
|
-
constructor(seed: Record<string, unknown>) {
|
|
24
|
-
this.system = { ...seed }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getSystem(key: string): unknown {
|
|
28
|
-
return this.system[key]
|
|
29
|
-
}
|
|
30
|
-
setSystem(key: string, value: unknown): void {
|
|
31
|
-
this.system[key] = value
|
|
32
|
-
}
|
|
33
|
-
getAllSystem(): Record<string, unknown> {
|
|
34
|
-
return { ...this.system }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getAllAddon(_addonId: string): Record<string, unknown> {
|
|
38
|
-
return {}
|
|
39
|
-
}
|
|
40
|
-
setAllAddon(_addonId: string, _config: Record<string, unknown>): void {
|
|
41
|
-
/* no-op */
|
|
42
|
-
}
|
|
43
|
-
getAllProvider(_providerId: string): Record<string, unknown> {
|
|
44
|
-
return {}
|
|
45
|
-
}
|
|
46
|
-
setProvider(_providerId: string, _key: string, _value: unknown): void {
|
|
47
|
-
/* no-op */
|
|
48
|
-
}
|
|
49
|
-
getAllDevice(_deviceId: string): Record<string, unknown> {
|
|
50
|
-
return {}
|
|
51
|
-
}
|
|
52
|
-
setDevice(_deviceId: string, _key: string, _value: unknown): void {
|
|
53
|
-
/* no-op */
|
|
54
|
-
}
|
|
55
|
-
getAddonDevice(_addonId: string, _deviceId: string): Record<string, unknown> {
|
|
56
|
-
return {}
|
|
57
|
-
}
|
|
58
|
-
setAddonDevice(_addonId: string, _deviceId: string, _values: Record<string, unknown>): void {
|
|
59
|
-
/* no-op */
|
|
60
|
-
}
|
|
61
|
-
clearAddonDevice(_addonId: string, _deviceId: string): void {
|
|
62
|
-
/* no-op */
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe('ScopedLogger', () => {
|
|
67
|
-
it('emits entries with a single scope string', () => {
|
|
68
|
-
const entries: LogEntry[] = []
|
|
69
|
-
const writeFn = (entry: LogEntry) => entries.push(entry)
|
|
70
|
-
const logger = new ScopedLogger('config', writeFn)
|
|
71
|
-
|
|
72
|
-
logger.info('loaded config')
|
|
73
|
-
|
|
74
|
-
expect(entries).toHaveLength(1)
|
|
75
|
-
expect(entries[0]!.scope).toBe('config')
|
|
76
|
-
expect(entries[0]!.level).toBe('info')
|
|
77
|
-
expect(entries[0]!.message).toBe('loaded config')
|
|
78
|
-
expect(entries[0]!.timestamp).toBeInstanceOf(Date)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('child() replaces the scope (no hierarchy)', () => {
|
|
82
|
-
const entries: LogEntry[] = []
|
|
83
|
-
const writeFn = (entry: LogEntry) => entries.push(entry)
|
|
84
|
-
const logger = new ScopedLogger('core', writeFn)
|
|
85
|
-
const child = logger.child('http')
|
|
86
|
-
|
|
87
|
-
child.warn('timeout', { meta: { ms: 3000 } })
|
|
88
|
-
|
|
89
|
-
expect(entries).toHaveLength(1)
|
|
90
|
-
expect(entries[0]!.scope).toBe('http')
|
|
91
|
-
expect(entries[0]!.level).toBe('warn')
|
|
92
|
-
expect(entries[0]!.meta).toEqual({ ms: 3000 })
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('creates entries for all log levels', () => {
|
|
96
|
-
const entries: LogEntry[] = []
|
|
97
|
-
const writeFn = (entry: LogEntry) => entries.push(entry)
|
|
98
|
-
const logger = new ScopedLogger('test', writeFn)
|
|
99
|
-
|
|
100
|
-
logger.debug('d')
|
|
101
|
-
logger.info('i')
|
|
102
|
-
logger.warn('w')
|
|
103
|
-
logger.error('e')
|
|
104
|
-
|
|
105
|
-
expect(entries.map((e) => e.level)).toEqual(['debug', 'info', 'warn', 'error'])
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it('omits the scope field when the logger was created without one', () => {
|
|
109
|
-
const entries: LogEntry[] = []
|
|
110
|
-
const writeFn = (entry: LogEntry) => entries.push(entry)
|
|
111
|
-
const logger = new ScopedLogger(undefined, writeFn)
|
|
112
|
-
|
|
113
|
-
logger.info('no scope')
|
|
114
|
-
|
|
115
|
-
expect(entries).toHaveLength(1)
|
|
116
|
-
expect(entries[0]!.scope).toBeUndefined()
|
|
117
|
-
})
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
describe('LogRingBuffer', () => {
|
|
121
|
-
it('stores up to capacity and evicts oldest', () => {
|
|
122
|
-
const buffer = new LogRingBuffer(3)
|
|
123
|
-
|
|
124
|
-
const makeEntry = (msg: string): LogEntry => ({
|
|
125
|
-
timestamp: new Date(),
|
|
126
|
-
level: 'info',
|
|
127
|
-
scope: 'test',
|
|
128
|
-
message: msg,
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
buffer.push(makeEntry('a'))
|
|
132
|
-
buffer.push(makeEntry('b'))
|
|
133
|
-
buffer.push(makeEntry('c'))
|
|
134
|
-
buffer.push(makeEntry('d'))
|
|
135
|
-
|
|
136
|
-
const all = buffer.getAll()
|
|
137
|
-
expect(all).toHaveLength(3)
|
|
138
|
-
// newest first
|
|
139
|
-
expect(all[0]!.message).toBe('d')
|
|
140
|
-
expect(all[1]!.message).toBe('c')
|
|
141
|
-
expect(all[2]!.message).toBe('b')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('filters by level', () => {
|
|
145
|
-
const buffer = new LogRingBuffer(100)
|
|
146
|
-
|
|
147
|
-
buffer.push({ timestamp: new Date(), level: 'debug', scope: 'test', message: 'a' })
|
|
148
|
-
buffer.push({ timestamp: new Date(), level: 'error', scope: 'test', message: 'b' })
|
|
149
|
-
buffer.push({ timestamp: new Date(), level: 'error', scope: 'test', message: 'c' })
|
|
150
|
-
|
|
151
|
-
const result = buffer.query({ level: 'error' })
|
|
152
|
-
expect(result).toHaveLength(2)
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
it('filters by since/until', () => {
|
|
156
|
-
const buffer = new LogRingBuffer(100)
|
|
157
|
-
const t1 = new Date('2025-01-01T00:00:00Z')
|
|
158
|
-
const t2 = new Date('2025-01-02T00:00:00Z')
|
|
159
|
-
const t3 = new Date('2025-01-03T00:00:00Z')
|
|
160
|
-
|
|
161
|
-
buffer.push({ timestamp: t1, level: 'info', scope: 'test', message: 'a' })
|
|
162
|
-
buffer.push({ timestamp: t2, level: 'info', scope: 'test', message: 'b' })
|
|
163
|
-
buffer.push({ timestamp: t3, level: 'info', scope: 'test', message: 'c' })
|
|
164
|
-
|
|
165
|
-
const result = buffer.query({ since: t2 })
|
|
166
|
-
expect(result).toHaveLength(2)
|
|
167
|
-
expect(result.map((e) => e.message)).toEqual(['c', 'b'])
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('filters by tags (addonId exact match)', () => {
|
|
171
|
-
const buffer = new LogRingBuffer(100)
|
|
172
|
-
|
|
173
|
-
buffer.push({
|
|
174
|
-
timestamp: new Date(),
|
|
175
|
-
level: 'info',
|
|
176
|
-
message: 'a',
|
|
177
|
-
tags: { addonId: 'stream-broker' },
|
|
178
|
-
})
|
|
179
|
-
buffer.push({
|
|
180
|
-
timestamp: new Date(),
|
|
181
|
-
level: 'info',
|
|
182
|
-
message: 'b',
|
|
183
|
-
tags: { addonId: 'provider-rtsp' },
|
|
184
|
-
})
|
|
185
|
-
buffer.push({
|
|
186
|
-
timestamp: new Date(),
|
|
187
|
-
level: 'info',
|
|
188
|
-
message: 'c',
|
|
189
|
-
tags: { addonId: 'stream-broker' },
|
|
190
|
-
})
|
|
191
|
-
|
|
192
|
-
const result = buffer.query({ tags: { addonId: 'stream-broker' } })
|
|
193
|
-
expect(result).toHaveLength(2)
|
|
194
|
-
expect(result.map((e) => e.message).toSorted()).toEqual(['a', 'c'])
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('respects limit', () => {
|
|
198
|
-
const buffer = new LogRingBuffer(100)
|
|
199
|
-
|
|
200
|
-
for (let i = 0; i < 10; i++) {
|
|
201
|
-
buffer.push({ timestamp: new Date(), level: 'info', scope: 'test', message: `msg-${i}` })
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const result = buffer.query({ limit: 3 })
|
|
205
|
-
expect(result).toHaveLength(3)
|
|
206
|
-
})
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
describe('LoggingService', () => {
|
|
210
|
-
let tmpDir: string
|
|
211
|
-
|
|
212
|
-
beforeEach(() => {
|
|
213
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'camstack-logging-'))
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
afterEach(() => {
|
|
217
|
-
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
const createService = (bufferSize = 100): LoggingService => {
|
|
221
|
-
const configPath = path.join(tmpDir, 'config.yaml')
|
|
222
|
-
fs.writeFileSync(
|
|
223
|
-
configPath,
|
|
224
|
-
yaml.dump({
|
|
225
|
-
server: { port: 4443 },
|
|
226
|
-
auth: { adminPassword: 'secret123' },
|
|
227
|
-
}),
|
|
228
|
-
'utf-8',
|
|
229
|
-
)
|
|
230
|
-
const configService = new ConfigService(configPath)
|
|
231
|
-
configService.setSettingsStore(
|
|
232
|
-
new InMemorySettingsStore({
|
|
233
|
-
'eventBus.ringBufferSize': bufferSize,
|
|
234
|
-
}),
|
|
235
|
-
)
|
|
236
|
-
return new LoggingService(configService)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
it('routes entries to all destinations', () => {
|
|
240
|
-
const service = createService()
|
|
241
|
-
const written: LogEntry[] = []
|
|
242
|
-
const dest: ILogDestination = {
|
|
243
|
-
initialize: async () => {},
|
|
244
|
-
shutdown: async () => {},
|
|
245
|
-
write: (entry) => written.push(entry),
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
service.addDestination(dest)
|
|
249
|
-
const logger = service.createLogger('test')
|
|
250
|
-
logger.info('hello')
|
|
251
|
-
|
|
252
|
-
expect(written).toHaveLength(1)
|
|
253
|
-
expect(written[0]!.message).toBe('hello')
|
|
254
|
-
expect(written[0]!.scope).toBe('test')
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('creates scoped loggers that push to ring buffer', () => {
|
|
258
|
-
const service = createService()
|
|
259
|
-
const logger = service.createLogger('myScope')
|
|
260
|
-
logger.error('boom')
|
|
261
|
-
|
|
262
|
-
const entries = service.query({})
|
|
263
|
-
expect(entries).toHaveLength(1)
|
|
264
|
-
expect(entries[0]!.message).toBe('boom')
|
|
265
|
-
expect(entries[0]!.scope).toBe('myScope')
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
it('removes destinations', () => {
|
|
269
|
-
const service = createService()
|
|
270
|
-
const written: LogEntry[] = []
|
|
271
|
-
const dest: ILogDestination = {
|
|
272
|
-
initialize: async () => {},
|
|
273
|
-
shutdown: async () => {},
|
|
274
|
-
write: (entry) => written.push(entry),
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
service.addDestination(dest)
|
|
278
|
-
service.removeDestination(dest)
|
|
279
|
-
|
|
280
|
-
const logger = service.createLogger('test')
|
|
281
|
-
logger.info('should not reach dest')
|
|
282
|
-
|
|
283
|
-
expect(written).toHaveLength(0)
|
|
284
|
-
// but still in ring buffer
|
|
285
|
-
expect(service.query({})).toHaveLength(1)
|
|
286
|
-
})
|
|
287
|
-
})
|