@sonde/agent 0.0.0
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/dist/cli/packs.d.ts +23 -0
- package/dist/cli/packs.d.ts.map +1 -0
- package/dist/cli/packs.js +172 -0
- package/dist/cli/packs.js.map +1 -0
- package/dist/cli/packs.test.d.ts +2 -0
- package/dist/cli/packs.test.d.ts.map +1 -0
- package/dist/cli/packs.test.js +171 -0
- package/dist/cli/packs.test.js.map +1 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/attestation.d.ts +9 -0
- package/dist/runtime/attestation.d.ts.map +1 -0
- package/dist/runtime/attestation.js +32 -0
- package/dist/runtime/attestation.js.map +1 -0
- package/dist/runtime/attestation.test.d.ts +2 -0
- package/dist/runtime/attestation.test.d.ts.map +1 -0
- package/dist/runtime/attestation.test.js +59 -0
- package/dist/runtime/attestation.test.js.map +1 -0
- package/dist/runtime/audit.d.ts +19 -0
- package/dist/runtime/audit.d.ts.map +1 -0
- package/dist/runtime/audit.js +52 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/audit.test.d.ts +2 -0
- package/dist/runtime/audit.test.d.ts.map +1 -0
- package/dist/runtime/audit.test.js +53 -0
- package/dist/runtime/audit.test.js.map +1 -0
- package/dist/runtime/connection.d.ts +55 -0
- package/dist/runtime/connection.d.ts.map +1 -0
- package/dist/runtime/connection.js +325 -0
- package/dist/runtime/connection.js.map +1 -0
- package/dist/runtime/connection.test.d.ts +2 -0
- package/dist/runtime/connection.test.d.ts.map +1 -0
- package/dist/runtime/connection.test.js +221 -0
- package/dist/runtime/connection.test.js.map +1 -0
- package/dist/runtime/executor.d.ts +21 -0
- package/dist/runtime/executor.d.ts.map +1 -0
- package/dist/runtime/executor.js +89 -0
- package/dist/runtime/executor.js.map +1 -0
- package/dist/runtime/executor.test.d.ts +2 -0
- package/dist/runtime/executor.test.d.ts.map +1 -0
- package/dist/runtime/executor.test.js +88 -0
- package/dist/runtime/executor.test.js.map +1 -0
- package/dist/runtime/privilege.d.ts +9 -0
- package/dist/runtime/privilege.d.ts.map +1 -0
- package/dist/runtime/privilege.js +35 -0
- package/dist/runtime/privilege.js.map +1 -0
- package/dist/runtime/privilege.test.d.ts +2 -0
- package/dist/runtime/privilege.test.d.ts.map +1 -0
- package/dist/runtime/privilege.test.js +22 -0
- package/dist/runtime/privilege.test.js.map +1 -0
- package/dist/runtime/scrubber.d.ts +17 -0
- package/dist/runtime/scrubber.d.ts.map +1 -0
- package/dist/runtime/scrubber.js +84 -0
- package/dist/runtime/scrubber.js.map +1 -0
- package/dist/runtime/scrubber.test.d.ts +2 -0
- package/dist/runtime/scrubber.test.d.ts.map +1 -0
- package/dist/runtime/scrubber.test.js +72 -0
- package/dist/runtime/scrubber.test.js.map +1 -0
- package/dist/system/scanner.d.ts +32 -0
- package/dist/system/scanner.d.ts.map +1 -0
- package/dist/system/scanner.js +90 -0
- package/dist/system/scanner.js.map +1 -0
- package/dist/system/scanner.test.d.ts +2 -0
- package/dist/system/scanner.test.d.ts.map +1 -0
- package/dist/system/scanner.test.js +121 -0
- package/dist/system/scanner.test.js.map +1 -0
- package/dist/tui/installer/InstallerApp.d.ts +11 -0
- package/dist/tui/installer/InstallerApp.d.ts.map +1 -0
- package/dist/tui/installer/InstallerApp.js +32 -0
- package/dist/tui/installer/InstallerApp.js.map +1 -0
- package/dist/tui/installer/StepComplete.d.ts +9 -0
- package/dist/tui/installer/StepComplete.d.ts.map +1 -0
- package/dist/tui/installer/StepComplete.js +46 -0
- package/dist/tui/installer/StepComplete.js.map +1 -0
- package/dist/tui/installer/StepHub.d.ts +8 -0
- package/dist/tui/installer/StepHub.d.ts.map +1 -0
- package/dist/tui/installer/StepHub.js +65 -0
- package/dist/tui/installer/StepHub.js.map +1 -0
- package/dist/tui/installer/StepPacks.d.ts +9 -0
- package/dist/tui/installer/StepPacks.d.ts.map +1 -0
- package/dist/tui/installer/StepPacks.js +35 -0
- package/dist/tui/installer/StepPacks.js.map +1 -0
- package/dist/tui/installer/StepPermissions.d.ts +9 -0
- package/dist/tui/installer/StepPermissions.d.ts.map +1 -0
- package/dist/tui/installer/StepPermissions.js +39 -0
- package/dist/tui/installer/StepPermissions.js.map +1 -0
- package/dist/tui/installer/StepScan.d.ts +7 -0
- package/dist/tui/installer/StepScan.d.ts.map +1 -0
- package/dist/tui/installer/StepScan.js +38 -0
- package/dist/tui/installer/StepScan.js.map +1 -0
- package/dist/tui/manager/ActivityLog.d.ts +7 -0
- package/dist/tui/manager/ActivityLog.d.ts.map +1 -0
- package/dist/tui/manager/ActivityLog.js +25 -0
- package/dist/tui/manager/ActivityLog.js.map +1 -0
- package/dist/tui/manager/AuditView.d.ts +7 -0
- package/dist/tui/manager/AuditView.d.ts.map +1 -0
- package/dist/tui/manager/AuditView.js +32 -0
- package/dist/tui/manager/AuditView.js.map +1 -0
- package/dist/tui/manager/ManagerApp.d.ts +20 -0
- package/dist/tui/manager/ManagerApp.d.ts.map +1 -0
- package/dist/tui/manager/ManagerApp.js +79 -0
- package/dist/tui/manager/ManagerApp.js.map +1 -0
- package/dist/tui/manager/PackManager.d.ts +7 -0
- package/dist/tui/manager/PackManager.d.ts.map +1 -0
- package/dist/tui/manager/PackManager.js +22 -0
- package/dist/tui/manager/PackManager.js.map +1 -0
- package/dist/tui/manager/StatusView.d.ts +15 -0
- package/dist/tui/manager/StatusView.d.ts.map +1 -0
- package/dist/tui/manager/StatusView.js +10 -0
- package/dist/tui/manager/StatusView.js.map +1 -0
- package/package.json +45 -0
- package/scripts/install.sh +11 -0
- package/src/cli/packs.test.ts +213 -0
- package/src/cli/packs.ts +214 -0
- package/src/config.ts +62 -0
- package/src/index.ts +218 -0
- package/src/runtime/attestation.test.ts +69 -0
- package/src/runtime/attestation.ts +36 -0
- package/src/runtime/audit.test.ts +64 -0
- package/src/runtime/audit.ts +70 -0
- package/src/runtime/connection.test.ts +303 -0
- package/src/runtime/connection.ts +389 -0
- package/src/runtime/executor.test.ts +112 -0
- package/src/runtime/executor.ts +107 -0
- package/src/runtime/privilege.test.ts +25 -0
- package/src/runtime/privilege.ts +36 -0
- package/src/runtime/scrubber.test.ts +84 -0
- package/src/runtime/scrubber.ts +96 -0
- package/src/system/scanner.test.ts +154 -0
- package/src/system/scanner.ts +133 -0
- package/src/tui/installer/InstallerApp.tsx +86 -0
- package/src/tui/installer/StepComplete.tsx +94 -0
- package/src/tui/installer/StepHub.tsx +111 -0
- package/src/tui/installer/StepPacks.tsx +73 -0
- package/src/tui/installer/StepPermissions.tsx +104 -0
- package/src/tui/installer/StepScan.tsx +82 -0
- package/src/tui/manager/ActivityLog.tsx +57 -0
- package/src/tui/manager/AuditView.tsx +73 -0
- package/src/tui/manager/ManagerApp.tsx +157 -0
- package/src/tui/manager/PackManager.tsx +71 -0
- package/src/tui/manager/StatusView.tsx +103 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export interface AgentAuditEntry {
|
|
4
|
+
timestamp: string;
|
|
5
|
+
probe: string;
|
|
6
|
+
status: string;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
prevHash: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CAPACITY = 1000;
|
|
12
|
+
|
|
13
|
+
export class AgentAuditLog {
|
|
14
|
+
private entries: AgentAuditEntry[] = [];
|
|
15
|
+
private capacity: number;
|
|
16
|
+
|
|
17
|
+
constructor(capacity = DEFAULT_CAPACITY) {
|
|
18
|
+
this.capacity = capacity;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
log(probe: string, status: string, durationMs: number): void {
|
|
22
|
+
const prevHash =
|
|
23
|
+
this.entries.length > 0
|
|
24
|
+
? crypto
|
|
25
|
+
.createHash('sha256')
|
|
26
|
+
.update(JSON.stringify(this.entries[this.entries.length - 1]))
|
|
27
|
+
.digest('hex')
|
|
28
|
+
: '';
|
|
29
|
+
|
|
30
|
+
const entry: AgentAuditEntry = {
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
probe,
|
|
33
|
+
status,
|
|
34
|
+
durationMs,
|
|
35
|
+
prevHash,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.entries.push(entry);
|
|
39
|
+
|
|
40
|
+
// Ring buffer: drop oldest when over capacity
|
|
41
|
+
if (this.entries.length > this.capacity) {
|
|
42
|
+
this.entries.shift();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getRecent(n?: number): AgentAuditEntry[] {
|
|
47
|
+
if (n === undefined) return [...this.entries];
|
|
48
|
+
return this.entries.slice(-n);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
verifyChain(): { valid: boolean; brokenAt?: number } {
|
|
52
|
+
if (this.entries.length === 0) return { valid: true };
|
|
53
|
+
|
|
54
|
+
const first = this.entries[0];
|
|
55
|
+
if (first && first.prevHash !== '') {
|
|
56
|
+
return { valid: false, brokenAt: 0 };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (let i = 1; i < this.entries.length; i++) {
|
|
60
|
+
const prev = this.entries[i - 1] as AgentAuditEntry;
|
|
61
|
+
const curr = this.entries[i] as AgentAuditEntry;
|
|
62
|
+
const expectedHash = crypto.createHash('sha256').update(JSON.stringify(prev)).digest('hex');
|
|
63
|
+
if (curr.prevHash !== expectedHash) {
|
|
64
|
+
return { valid: false, brokenAt: i };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { valid: true };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import type { ProbeRequest } from '@sonde/shared';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import type { AgentConfig } from '../config.js';
|
|
4
|
+
import { AgentConnection, enrollWithHub } from './connection.js';
|
|
5
|
+
import { ProbeExecutor } from './executor.js';
|
|
6
|
+
|
|
7
|
+
// Mock WebSocket
|
|
8
|
+
const mockWsInstances: Array<{
|
|
9
|
+
url: string;
|
|
10
|
+
handlers: Record<string, (...args: unknown[]) => void>;
|
|
11
|
+
send: ReturnType<typeof vi.fn>;
|
|
12
|
+
close: ReturnType<typeof vi.fn>;
|
|
13
|
+
readyState: number;
|
|
14
|
+
}> = [];
|
|
15
|
+
|
|
16
|
+
vi.mock('ws', () => {
|
|
17
|
+
const OPEN = 1;
|
|
18
|
+
const CLOSED = 3;
|
|
19
|
+
|
|
20
|
+
class MockWebSocket {
|
|
21
|
+
url: string;
|
|
22
|
+
readyState: number;
|
|
23
|
+
send = vi.fn();
|
|
24
|
+
close = vi.fn();
|
|
25
|
+
private handlers: Record<string, (...args: unknown[]) => void> = {};
|
|
26
|
+
|
|
27
|
+
constructor(url: string, _opts?: unknown) {
|
|
28
|
+
this.url = url;
|
|
29
|
+
this.readyState = OPEN;
|
|
30
|
+
mockWsInstances.push({
|
|
31
|
+
url,
|
|
32
|
+
handlers: this.handlers,
|
|
33
|
+
send: this.send,
|
|
34
|
+
close: this.close,
|
|
35
|
+
readyState: this.readyState,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
on(event: string, handler: (...args: unknown[]) => void) {
|
|
40
|
+
this.handlers[event] = handler;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Helper: simulate receiving events from tests
|
|
44
|
+
_emit(event: string, ...args: unknown[]) {
|
|
45
|
+
this.handlers[event]?.(...args);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Attach static constants
|
|
50
|
+
(MockWebSocket as unknown as Record<string, number>).OPEN = OPEN;
|
|
51
|
+
(MockWebSocket as unknown as Record<string, number>).CLOSED = CLOSED;
|
|
52
|
+
|
|
53
|
+
return { default: MockWebSocket, WebSocket: MockWebSocket };
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function createConfig(): AgentConfig {
|
|
57
|
+
return {
|
|
58
|
+
hubUrl: 'http://localhost:3000',
|
|
59
|
+
apiKey: 'test-key',
|
|
60
|
+
agentName: 'test-agent',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createExecutor(): ProbeExecutor {
|
|
65
|
+
return new ProbeExecutor(new Map());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe('AgentConnection', () => {
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
mockWsInstances.length = 0;
|
|
71
|
+
vi.useFakeTimers();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
vi.useRealTimers();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('connects to the correct WebSocket URL', () => {
|
|
79
|
+
const conn = new AgentConnection(createConfig(), createExecutor());
|
|
80
|
+
conn.start();
|
|
81
|
+
|
|
82
|
+
expect(mockWsInstances).toHaveLength(1);
|
|
83
|
+
expect(mockWsInstances[0]?.url).toBe('ws://localhost:3000/ws/agent');
|
|
84
|
+
|
|
85
|
+
conn.stop();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('converts https hub URL to wss', () => {
|
|
89
|
+
const config = createConfig();
|
|
90
|
+
config.hubUrl = 'https://hub.example.com';
|
|
91
|
+
|
|
92
|
+
const conn = new AgentConnection(config, createExecutor());
|
|
93
|
+
conn.start();
|
|
94
|
+
|
|
95
|
+
expect(mockWsInstances[0]?.url).toBe('wss://hub.example.com/ws/agent');
|
|
96
|
+
|
|
97
|
+
conn.stop();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('sends register message on open', () => {
|
|
101
|
+
const conn = new AgentConnection(createConfig(), createExecutor());
|
|
102
|
+
conn.start();
|
|
103
|
+
|
|
104
|
+
// Simulate WebSocket open
|
|
105
|
+
const ws = mockWsInstances[0];
|
|
106
|
+
ws?.handlers.open?.();
|
|
107
|
+
|
|
108
|
+
expect(ws?.send).toHaveBeenCalledOnce();
|
|
109
|
+
const sent = JSON.parse(ws?.send.mock.calls[0]?.[0] as string);
|
|
110
|
+
expect(sent.type).toBe('agent.register');
|
|
111
|
+
expect(sent.payload.name).toBe('test-agent');
|
|
112
|
+
|
|
113
|
+
conn.stop();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('stores agentId from hub.ack', () => {
|
|
117
|
+
const onRegistered = vi.fn();
|
|
118
|
+
const conn = new AgentConnection(createConfig(), createExecutor(), { onRegistered });
|
|
119
|
+
conn.start();
|
|
120
|
+
|
|
121
|
+
const ws = mockWsInstances[0];
|
|
122
|
+
ws?.handlers.open?.();
|
|
123
|
+
|
|
124
|
+
// Simulate hub.ack
|
|
125
|
+
ws?.handlers.message?.(
|
|
126
|
+
JSON.stringify({
|
|
127
|
+
id: '00000000-0000-0000-0000-000000000001',
|
|
128
|
+
type: 'hub.ack',
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
agentId: 'assigned-id',
|
|
131
|
+
signature: '',
|
|
132
|
+
payload: { agentId: 'assigned-id' },
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(onRegistered).toHaveBeenCalledWith('assigned-id');
|
|
137
|
+
expect(conn.getAgentId()).toBe('assigned-id');
|
|
138
|
+
|
|
139
|
+
conn.stop();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('sends heartbeats on interval', () => {
|
|
143
|
+
const conn = new AgentConnection(createConfig(), createExecutor());
|
|
144
|
+
conn.start();
|
|
145
|
+
|
|
146
|
+
const ws = mockWsInstances[0];
|
|
147
|
+
ws?.handlers.open?.();
|
|
148
|
+
|
|
149
|
+
// Simulate hub.ack to set agentId
|
|
150
|
+
ws?.handlers.message?.(
|
|
151
|
+
JSON.stringify({
|
|
152
|
+
id: '00000000-0000-0000-0000-000000000001',
|
|
153
|
+
type: 'hub.ack',
|
|
154
|
+
timestamp: new Date().toISOString(),
|
|
155
|
+
agentId: 'agent-1',
|
|
156
|
+
signature: '',
|
|
157
|
+
payload: { agentId: 'agent-1' },
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Register sends one message, ack handler sends another (onConnected)
|
|
162
|
+
const callsBeforeHeartbeat = ws?.send.mock.calls.length ?? 0;
|
|
163
|
+
|
|
164
|
+
// Advance time by 30 seconds (HEARTBEAT_INTERVAL_MS)
|
|
165
|
+
vi.advanceTimersByTime(30_000);
|
|
166
|
+
|
|
167
|
+
const heartbeatCalls = (ws?.send.mock.calls.length ?? 0) - callsBeforeHeartbeat;
|
|
168
|
+
expect(heartbeatCalls).toBe(1);
|
|
169
|
+
|
|
170
|
+
const lastCall = ws?.send.mock.calls.at(-1)?.[0] as string;
|
|
171
|
+
const msg = JSON.parse(lastCall);
|
|
172
|
+
expect(msg.type).toBe('agent.heartbeat');
|
|
173
|
+
expect(msg.agentId).toBe('agent-1');
|
|
174
|
+
|
|
175
|
+
conn.stop();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('schedules reconnect with exponential backoff on close', () => {
|
|
179
|
+
const onDisconnected = vi.fn();
|
|
180
|
+
const conn = new AgentConnection(createConfig(), createExecutor(), { onDisconnected });
|
|
181
|
+
conn.start();
|
|
182
|
+
|
|
183
|
+
const ws = mockWsInstances[0];
|
|
184
|
+
|
|
185
|
+
// First disconnect
|
|
186
|
+
ws?.handlers.close?.();
|
|
187
|
+
expect(onDisconnected).toHaveBeenCalledOnce();
|
|
188
|
+
expect(mockWsInstances).toHaveLength(1); // Not reconnected yet
|
|
189
|
+
|
|
190
|
+
// Advance past first reconnect delay (1s)
|
|
191
|
+
vi.advanceTimersByTime(1_000);
|
|
192
|
+
expect(mockWsInstances).toHaveLength(2); // Reconnected
|
|
193
|
+
|
|
194
|
+
// Second disconnect
|
|
195
|
+
mockWsInstances[1]?.handlers.close?.();
|
|
196
|
+
|
|
197
|
+
// Advance 1s — should NOT reconnect yet (backoff is 2s)
|
|
198
|
+
vi.advanceTimersByTime(1_000);
|
|
199
|
+
expect(mockWsInstances).toHaveLength(2);
|
|
200
|
+
|
|
201
|
+
// Advance another 1s (total 2s) — should reconnect
|
|
202
|
+
vi.advanceTimersByTime(1_000);
|
|
203
|
+
expect(mockWsInstances).toHaveLength(3);
|
|
204
|
+
|
|
205
|
+
conn.stop();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('does not reconnect after stop()', () => {
|
|
209
|
+
const conn = new AgentConnection(createConfig(), createExecutor());
|
|
210
|
+
conn.start();
|
|
211
|
+
|
|
212
|
+
conn.stop();
|
|
213
|
+
|
|
214
|
+
const ws = mockWsInstances[0];
|
|
215
|
+
ws?.handlers.close?.();
|
|
216
|
+
|
|
217
|
+
vi.advanceTimersByTime(60_000);
|
|
218
|
+
expect(mockWsInstances).toHaveLength(1); // No reconnect
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('enrollWithHub', () => {
|
|
223
|
+
beforeEach(() => {
|
|
224
|
+
mockWsInstances.length = 0;
|
|
225
|
+
vi.useFakeTimers();
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
afterEach(() => {
|
|
229
|
+
vi.useRealTimers();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('connects to correct URL and sends register', () => {
|
|
233
|
+
const promise = enrollWithHub(createConfig(), createExecutor());
|
|
234
|
+
|
|
235
|
+
expect(mockWsInstances).toHaveLength(1);
|
|
236
|
+
expect(mockWsInstances[0]?.url).toBe('ws://localhost:3000/ws/agent');
|
|
237
|
+
|
|
238
|
+
const ws = mockWsInstances[0];
|
|
239
|
+
ws?.handlers.open?.();
|
|
240
|
+
|
|
241
|
+
expect(ws?.send).toHaveBeenCalledOnce();
|
|
242
|
+
const sent = JSON.parse(ws?.send.mock.calls[0]?.[0] as string);
|
|
243
|
+
expect(sent.type).toBe('agent.register');
|
|
244
|
+
expect(sent.payload.name).toBe('test-agent');
|
|
245
|
+
|
|
246
|
+
// Send ack to resolve and avoid dangling promise
|
|
247
|
+
ws?.handlers.message?.(
|
|
248
|
+
JSON.stringify({
|
|
249
|
+
id: '00000000-0000-0000-0000-000000000001',
|
|
250
|
+
type: 'hub.ack',
|
|
251
|
+
timestamp: new Date().toISOString(),
|
|
252
|
+
agentId: 'enrolled-id',
|
|
253
|
+
signature: '',
|
|
254
|
+
payload: { agentId: 'enrolled-id' },
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
return promise;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('returns agentId from hub.ack', async () => {
|
|
262
|
+
const promise = enrollWithHub(createConfig(), createExecutor());
|
|
263
|
+
|
|
264
|
+
const ws = mockWsInstances[0];
|
|
265
|
+
ws?.handlers.open?.();
|
|
266
|
+
|
|
267
|
+
ws?.handlers.message?.(
|
|
268
|
+
JSON.stringify({
|
|
269
|
+
id: '00000000-0000-0000-0000-000000000001',
|
|
270
|
+
type: 'hub.ack',
|
|
271
|
+
timestamp: new Date().toISOString(),
|
|
272
|
+
agentId: 'enrolled-id',
|
|
273
|
+
signature: '',
|
|
274
|
+
payload: { agentId: 'enrolled-id' },
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const result = await promise;
|
|
279
|
+
expect(result.agentId).toBe('enrolled-id');
|
|
280
|
+
expect(result.certIssued).toBe(false);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('rejects on timeout when no ack received', async () => {
|
|
284
|
+
const promise = enrollWithHub(createConfig(), createExecutor());
|
|
285
|
+
|
|
286
|
+
const ws = mockWsInstances[0];
|
|
287
|
+
ws?.handlers.open?.();
|
|
288
|
+
|
|
289
|
+
// Advance past the 10s timeout
|
|
290
|
+
vi.advanceTimersByTime(10_000);
|
|
291
|
+
|
|
292
|
+
await expect(promise).rejects.toThrow('Enrollment timed out');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('rejects on WebSocket error', async () => {
|
|
296
|
+
const promise = enrollWithHub(createConfig(), createExecutor());
|
|
297
|
+
|
|
298
|
+
const ws = mockWsInstances[0];
|
|
299
|
+
ws?.handlers.error?.(new Error('Connection refused'));
|
|
300
|
+
|
|
301
|
+
await expect(promise).rejects.toThrow('Connection refused');
|
|
302
|
+
});
|
|
303
|
+
});
|