@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,110 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { computeTopology } from '../../api/core/cap-providers'
|
|
3
|
-
|
|
4
|
-
// Minimal stubs — only the shapes computeTopology reads.
|
|
5
|
-
function makeAgentRegistry(): { listNodes: () => Promise<unknown[]> } {
|
|
6
|
-
return {
|
|
7
|
-
listNodes: async () => [
|
|
8
|
-
{
|
|
9
|
-
info: {
|
|
10
|
-
id: 'hub',
|
|
11
|
-
name: 'hub',
|
|
12
|
-
hostname: 'hub',
|
|
13
|
-
platform: 'darwin',
|
|
14
|
-
arch: 'arm64',
|
|
15
|
-
cpuModel: 'M2',
|
|
16
|
-
cpuCores: 8,
|
|
17
|
-
memoryMB: 16384,
|
|
18
|
-
pythonRuntimes: [],
|
|
19
|
-
},
|
|
20
|
-
status: { cpuPercent: 12, memoryPercent: 30 },
|
|
21
|
-
isHub: true,
|
|
22
|
-
isOnline: true,
|
|
23
|
-
connectedSince: Date.now() - 60_000,
|
|
24
|
-
subProcesses: [],
|
|
25
|
-
agentAddons: [],
|
|
26
|
-
localIps: [],
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function makeAddonRegistry(): { listAddons: () => readonly unknown[] } {
|
|
33
|
-
return {
|
|
34
|
-
listAddons: () => [
|
|
35
|
-
{
|
|
36
|
-
manifest: { id: 'provider-hikvision' },
|
|
37
|
-
declaration: {
|
|
38
|
-
id: 'provider-hikvision',
|
|
39
|
-
category: 'providers',
|
|
40
|
-
capabilities: [{ name: 'stream-params' }],
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
manifest: { id: 'provider-onvif' },
|
|
45
|
-
declaration: {
|
|
46
|
-
id: 'provider-onvif',
|
|
47
|
-
category: 'providers',
|
|
48
|
-
capabilities: [{ name: 'device-provider' }],
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
manifest: { id: 'stream-broker' },
|
|
53
|
-
declaration: {
|
|
54
|
-
id: 'stream-broker',
|
|
55
|
-
category: 'pipeline',
|
|
56
|
-
capabilities: [{ name: 'stream-broker' }],
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
manifest: { id: 'sqlite-settings' },
|
|
61
|
-
declaration: { id: 'sqlite-settings', capabilities: [{ name: 'settings-store' }] },
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
describe('computeTopology categories[] aggregation', () => {
|
|
68
|
-
it('groups addons by manifest.category, defaults to system, emits totals', async () => {
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
-
const nodes = await computeTopology(makeAgentRegistry() as any, makeAddonRegistry() as any)
|
|
71
|
-
expect(nodes).toHaveLength(1)
|
|
72
|
-
const cats = nodes[0]!.categories
|
|
73
|
-
const byId = new Map(cats.map((c) => [c.category, c]))
|
|
74
|
-
expect(byId.get('providers')?.total).toBe(2)
|
|
75
|
-
expect(byId.get('providers')?.healthy).toBe(2)
|
|
76
|
-
expect(
|
|
77
|
-
byId
|
|
78
|
-
.get('providers')
|
|
79
|
-
?.addons.map((a) => a.id)
|
|
80
|
-
.toSorted(),
|
|
81
|
-
).toEqual(['provider-hikvision', 'provider-onvif'])
|
|
82
|
-
expect(byId.get('pipeline')?.total).toBe(1)
|
|
83
|
-
expect(byId.get('system')?.addons.map((a) => a.id)).toEqual(['sqlite-settings'])
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('counts a non-running addon as not-healthy', async () => {
|
|
87
|
-
const agentReg = makeAgentRegistry()
|
|
88
|
-
// Force the addon registry to return one stopped + one running provider.
|
|
89
|
-
const addonReg = {
|
|
90
|
-
listAddons: () => [
|
|
91
|
-
{
|
|
92
|
-
manifest: { id: 'provider-rtsp' },
|
|
93
|
-
declaration: { id: 'provider-rtsp', category: 'providers', capabilities: [] },
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
manifest: { id: 'provider-hikvision' },
|
|
97
|
-
declaration: { id: 'provider-hikvision', category: 'providers', capabilities: [] },
|
|
98
|
-
status: 'failed',
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
}
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
-
const nodes = await computeTopology(agentReg as any, addonReg as any)
|
|
104
|
-
const providers = nodes[0]!.categories.find((c) => c.category === 'providers')!
|
|
105
|
-
expect(providers.total).toBe(2)
|
|
106
|
-
// The current TopologyNode `addons[].status` is always `'running'` per existing code;
|
|
107
|
-
// failed addons would surface through subProcesses[].state. Document the current contract:
|
|
108
|
-
expect(providers.healthy).toBe(2)
|
|
109
|
-
})
|
|
110
|
-
})
|
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { buildIntegrationsProvider } from '../../api/core/cap-providers'
|
|
3
|
-
import type { CapabilityRegistry } from '@camstack/kernel'
|
|
4
|
-
import type { Integration } from '@camstack/types'
|
|
5
|
-
|
|
6
|
-
// ── Minimal stubs ────────────────────────────────────────────────────────────
|
|
7
|
-
|
|
8
|
-
const INTEGRATION_ID = 'integ-abc-123'
|
|
9
|
-
const ADDON_ID = 'provider-test'
|
|
10
|
-
|
|
11
|
-
function makeIntegration(id = INTEGRATION_ID): Integration {
|
|
12
|
-
return {
|
|
13
|
-
id,
|
|
14
|
-
addonId: ADDON_ID,
|
|
15
|
-
name: 'Test Integration',
|
|
16
|
-
enabled: true,
|
|
17
|
-
info: null,
|
|
18
|
-
settings: null,
|
|
19
|
-
createdAt: new Date().toISOString(),
|
|
20
|
-
updatedAt: new Date().toISOString(),
|
|
21
|
-
} as unknown as Integration
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function makeIntegrationRegistry(integration: Integration | null = makeIntegration()) {
|
|
25
|
-
return {
|
|
26
|
-
getIntegration: vi.fn(async (_id: string) => integration),
|
|
27
|
-
deleteIntegration: vi.fn(async (_id: string) => undefined),
|
|
28
|
-
listIntegrations: vi.fn(async () => (integration ? [integration] : [])),
|
|
29
|
-
createIntegration: vi.fn(),
|
|
30
|
-
updateIntegration: vi.fn(),
|
|
31
|
-
getIntegrationByAddonId: vi.fn(),
|
|
32
|
-
getIntegrationSettings: vi.fn(),
|
|
33
|
-
setIntegrationSettings: vi.fn(),
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function makeAddonRegistry(reg = makeIntegrationRegistry()) {
|
|
38
|
-
return {
|
|
39
|
-
getIntegrationRegistry: vi.fn(() => reg),
|
|
40
|
-
listAddons: vi.fn(() => []),
|
|
41
|
-
restartAddon: vi.fn(),
|
|
42
|
-
getCapabilityRegistry: vi.fn(() => ({
|
|
43
|
-
getProviderByAddon: vi.fn(() => null),
|
|
44
|
-
})),
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function makeEventBus() {
|
|
49
|
-
return { emit: vi.fn() }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function makeLogger() {
|
|
53
|
-
return {
|
|
54
|
-
info: vi.fn(),
|
|
55
|
-
warn: vi.fn(),
|
|
56
|
-
error: vi.fn(),
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function makeLoggingService(log = makeLogger()) {
|
|
61
|
-
return { createLogger: vi.fn(() => log) }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function makeCapabilityRegistry(
|
|
65
|
-
removeByIntegration:
|
|
66
|
-
| ((input: { integrationId: string }) => Promise<{ removed: number }>)
|
|
67
|
-
| null = vi.fn(async () => ({ removed: 2 })),
|
|
68
|
-
): CapabilityRegistry {
|
|
69
|
-
return {
|
|
70
|
-
getSingleton: vi.fn((_cap: string) => (removeByIntegration ? { removeByIntegration } : null)),
|
|
71
|
-
} as unknown as CapabilityRegistry
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
describe('integrations.delete cascade-removes devices via removeByIntegration', () => {
|
|
77
|
-
let integrationReg: ReturnType<typeof makeIntegrationRegistry>
|
|
78
|
-
let addonReg: ReturnType<typeof makeAddonRegistry>
|
|
79
|
-
let eb: ReturnType<typeof makeEventBus>
|
|
80
|
-
let log: ReturnType<typeof makeLogger>
|
|
81
|
-
let loggingService: ReturnType<typeof makeLoggingService>
|
|
82
|
-
|
|
83
|
-
beforeEach(() => {
|
|
84
|
-
integrationReg = makeIntegrationRegistry()
|
|
85
|
-
addonReg = makeAddonRegistry(integrationReg)
|
|
86
|
-
eb = makeEventBus()
|
|
87
|
-
log = makeLogger()
|
|
88
|
-
loggingService = makeLoggingService(log)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('calls removeByIntegration with the integration id before deleteIntegration', async () => {
|
|
92
|
-
const removeByIntegration = vi.fn(async () => ({ removed: 3 }))
|
|
93
|
-
const capReg = makeCapabilityRegistry(removeByIntegration)
|
|
94
|
-
|
|
95
|
-
const provider = buildIntegrationsProvider(
|
|
96
|
-
addonReg as never,
|
|
97
|
-
eb as never,
|
|
98
|
-
loggingService as never,
|
|
99
|
-
capReg,
|
|
100
|
-
)
|
|
101
|
-
await provider.delete({ id: INTEGRATION_ID })
|
|
102
|
-
|
|
103
|
-
expect(removeByIntegration).toHaveBeenCalledOnce()
|
|
104
|
-
expect(removeByIntegration).toHaveBeenCalledWith({ integrationId: INTEGRATION_ID })
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('still deletes the integration record and emits integration.deleted after the cascade', async () => {
|
|
108
|
-
const capReg = makeCapabilityRegistry()
|
|
109
|
-
|
|
110
|
-
const provider = buildIntegrationsProvider(
|
|
111
|
-
addonReg as never,
|
|
112
|
-
eb as never,
|
|
113
|
-
loggingService as never,
|
|
114
|
-
capReg,
|
|
115
|
-
)
|
|
116
|
-
const result = await provider.delete({ id: INTEGRATION_ID })
|
|
117
|
-
|
|
118
|
-
expect(integrationReg.deleteIntegration).toHaveBeenCalledOnce()
|
|
119
|
-
expect(integrationReg.deleteIntegration).toHaveBeenCalledWith(INTEGRATION_ID)
|
|
120
|
-
|
|
121
|
-
expect(eb.emit).toHaveBeenCalledOnce()
|
|
122
|
-
expect(eb.emit).toHaveBeenCalledWith(
|
|
123
|
-
expect.objectContaining({
|
|
124
|
-
category: 'integration.deleted',
|
|
125
|
-
data: expect.objectContaining({ integrationId: INTEGRATION_ID }),
|
|
126
|
-
}),
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
expect(result).toEqual({ success: true, deletedId: INTEGRATION_ID })
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('logs the removed count from removeByIntegration', async () => {
|
|
133
|
-
const removeByIntegration = vi.fn(async () => ({ removed: 5 }))
|
|
134
|
-
const capReg = makeCapabilityRegistry(removeByIntegration)
|
|
135
|
-
|
|
136
|
-
const provider = buildIntegrationsProvider(
|
|
137
|
-
addonReg as never,
|
|
138
|
-
eb as never,
|
|
139
|
-
loggingService as never,
|
|
140
|
-
capReg,
|
|
141
|
-
)
|
|
142
|
-
await provider.delete({ id: INTEGRATION_ID })
|
|
143
|
-
|
|
144
|
-
expect(log.info).toHaveBeenCalledWith(
|
|
145
|
-
'cascade-removed devices',
|
|
146
|
-
expect.objectContaining({ meta: expect.objectContaining({ removed: 5 }) }),
|
|
147
|
-
)
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('does NOT abort the integration delete when removeByIntegration throws (best-effort)', async () => {
|
|
151
|
-
const removeByIntegration = vi.fn(async () => {
|
|
152
|
-
throw new Error('device-manager transient error')
|
|
153
|
-
})
|
|
154
|
-
const capReg = makeCapabilityRegistry(removeByIntegration)
|
|
155
|
-
|
|
156
|
-
const provider = buildIntegrationsProvider(
|
|
157
|
-
addonReg as never,
|
|
158
|
-
eb as never,
|
|
159
|
-
loggingService as never,
|
|
160
|
-
capReg,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
// Should NOT throw
|
|
164
|
-
await expect(provider.delete({ id: INTEGRATION_ID })).resolves.toEqual({
|
|
165
|
-
success: true,
|
|
166
|
-
deletedId: INTEGRATION_ID,
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// Integration record deletion and event still fire
|
|
170
|
-
expect(integrationReg.deleteIntegration).toHaveBeenCalledOnce()
|
|
171
|
-
expect(eb.emit).toHaveBeenCalledOnce()
|
|
172
|
-
|
|
173
|
-
// A warning is logged
|
|
174
|
-
expect(log.warn).toHaveBeenCalledWith(
|
|
175
|
-
'device cascade-remove failed (best-effort — continuing)',
|
|
176
|
-
expect.anything(),
|
|
177
|
-
)
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it('warns and skips cascade when capabilityRegistry is null', async () => {
|
|
181
|
-
const provider = buildIntegrationsProvider(
|
|
182
|
-
addonReg as never,
|
|
183
|
-
eb as never,
|
|
184
|
-
loggingService as never,
|
|
185
|
-
null,
|
|
186
|
-
)
|
|
187
|
-
await provider.delete({ id: INTEGRATION_ID })
|
|
188
|
-
|
|
189
|
-
expect(integrationReg.deleteIntegration).toHaveBeenCalledOnce()
|
|
190
|
-
expect(eb.emit).toHaveBeenCalledOnce()
|
|
191
|
-
expect(log.warn).toHaveBeenCalledWith(
|
|
192
|
-
'device-manager not available — skipping cascade device removal',
|
|
193
|
-
expect.anything(),
|
|
194
|
-
)
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('warns and skips cascade when device-manager singleton is not registered', async () => {
|
|
198
|
-
const capReg = makeCapabilityRegistry(null)
|
|
199
|
-
|
|
200
|
-
const provider = buildIntegrationsProvider(
|
|
201
|
-
addonReg as never,
|
|
202
|
-
eb as never,
|
|
203
|
-
loggingService as never,
|
|
204
|
-
capReg,
|
|
205
|
-
)
|
|
206
|
-
await provider.delete({ id: INTEGRATION_ID })
|
|
207
|
-
|
|
208
|
-
expect(integrationReg.deleteIntegration).toHaveBeenCalledOnce()
|
|
209
|
-
expect(log.warn).toHaveBeenCalledWith(
|
|
210
|
-
'device-manager not available — skipping cascade device removal',
|
|
211
|
-
expect.anything(),
|
|
212
|
-
)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('stamps legacy un-tagged devices of the addon BEFORE cascade so they are removed too', async () => {
|
|
216
|
-
const removeByIntegration = vi.fn(async () => ({ removed: 1 }))
|
|
217
|
-
const setIntegrationId = vi.fn(
|
|
218
|
-
async (_input: { deviceId: number; integrationId: string }) => undefined,
|
|
219
|
-
)
|
|
220
|
-
// One un-tagged top-level device of the integration's addon (claimable),
|
|
221
|
-
// one already-tagged (skip), one child (skip), one other-addon (skip).
|
|
222
|
-
const listAll = vi.fn(async () => [
|
|
223
|
-
{ id: 4, addonId: ADDON_ID, parentDeviceId: null },
|
|
224
|
-
{ id: 5, addonId: ADDON_ID, parentDeviceId: null, integrationId: INTEGRATION_ID },
|
|
225
|
-
{ id: 6, addonId: ADDON_ID, parentDeviceId: 4 },
|
|
226
|
-
{ id: 7, addonId: 'provider-other', parentDeviceId: null },
|
|
227
|
-
])
|
|
228
|
-
const capReg = {
|
|
229
|
-
getSingleton: vi.fn(() => ({ removeByIntegration, listAll, setIntegrationId })),
|
|
230
|
-
} as unknown as CapabilityRegistry
|
|
231
|
-
|
|
232
|
-
const provider = buildIntegrationsProvider(
|
|
233
|
-
addonReg as never,
|
|
234
|
-
eb as never,
|
|
235
|
-
loggingService as never,
|
|
236
|
-
capReg,
|
|
237
|
-
)
|
|
238
|
-
await provider.delete({ id: INTEGRATION_ID })
|
|
239
|
-
|
|
240
|
-
// Only device 4 is claimed (untagged, top-level, this addon's single integration).
|
|
241
|
-
expect(setIntegrationId).toHaveBeenCalledOnce()
|
|
242
|
-
expect(setIntegrationId).toHaveBeenCalledWith({ deviceId: 4, integrationId: INTEGRATION_ID })
|
|
243
|
-
// Cascade still runs after the claim.
|
|
244
|
-
expect(removeByIntegration).toHaveBeenCalledWith({ integrationId: INTEGRATION_ID })
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
it('claim failure does not abort the integration delete (best-effort)', async () => {
|
|
248
|
-
const removeByIntegration = vi.fn(async () => ({ removed: 0 }))
|
|
249
|
-
const setIntegrationId = vi.fn(async () => {
|
|
250
|
-
throw new Error('stamp boom')
|
|
251
|
-
})
|
|
252
|
-
const listAll = vi.fn(async () => [{ id: 4, addonId: ADDON_ID, parentDeviceId: null }])
|
|
253
|
-
const capReg = {
|
|
254
|
-
getSingleton: vi.fn(() => ({ removeByIntegration, listAll, setIntegrationId })),
|
|
255
|
-
} as unknown as CapabilityRegistry
|
|
256
|
-
|
|
257
|
-
const provider = buildIntegrationsProvider(
|
|
258
|
-
addonReg as never,
|
|
259
|
-
eb as never,
|
|
260
|
-
loggingService as never,
|
|
261
|
-
capReg,
|
|
262
|
-
)
|
|
263
|
-
await expect(provider.delete({ id: INTEGRATION_ID })).resolves.toEqual({
|
|
264
|
-
success: true,
|
|
265
|
-
deletedId: INTEGRATION_ID,
|
|
266
|
-
})
|
|
267
|
-
expect(integrationReg.deleteIntegration).toHaveBeenCalledOnce()
|
|
268
|
-
expect(log.warn).toHaveBeenCalledWith(
|
|
269
|
-
'legacy device claim failed (best-effort — continuing)',
|
|
270
|
-
expect.anything(),
|
|
271
|
-
)
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
it('throws when the integration is not found (guard still fires before cascade)', async () => {
|
|
275
|
-
integrationReg = makeIntegrationRegistry(null)
|
|
276
|
-
addonReg = makeAddonRegistry(integrationReg)
|
|
277
|
-
const capReg = makeCapabilityRegistry()
|
|
278
|
-
|
|
279
|
-
const provider = buildIntegrationsProvider(
|
|
280
|
-
addonReg as never,
|
|
281
|
-
eb as never,
|
|
282
|
-
loggingService as never,
|
|
283
|
-
capReg,
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
await expect(provider.delete({ id: 'missing-id' })).rejects.toThrow('not found')
|
|
287
|
-
|
|
288
|
-
// Nothing should have been cleaned up
|
|
289
|
-
expect(integrationReg.deleteIntegration).not.toHaveBeenCalled()
|
|
290
|
-
expect(eb.emit).not.toHaveBeenCalled()
|
|
291
|
-
})
|
|
292
|
-
})
|