@principal-ai/principal-view-core 0.5.6
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/README.md +126 -0
- package/dist/ConfigurationLoader.d.ts +76 -0
- package/dist/ConfigurationLoader.d.ts.map +1 -0
- package/dist/ConfigurationLoader.js +144 -0
- package/dist/ConfigurationLoader.js.map +1 -0
- package/dist/ConfigurationValidator.d.ts +31 -0
- package/dist/ConfigurationValidator.d.ts.map +1 -0
- package/dist/ConfigurationValidator.js +242 -0
- package/dist/ConfigurationValidator.js.map +1 -0
- package/dist/EventProcessor.d.ts +49 -0
- package/dist/EventProcessor.d.ts.map +1 -0
- package/dist/EventProcessor.js +215 -0
- package/dist/EventProcessor.js.map +1 -0
- package/dist/EventRecorderService.d.ts +305 -0
- package/dist/EventRecorderService.d.ts.map +1 -0
- package/dist/EventRecorderService.js +463 -0
- package/dist/EventRecorderService.js.map +1 -0
- package/dist/LibraryLoader.d.ts +63 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +188 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PathBasedEventProcessor.d.ts +90 -0
- package/dist/PathBasedEventProcessor.d.ts.map +1 -0
- package/dist/PathBasedEventProcessor.js +239 -0
- package/dist/PathBasedEventProcessor.js.map +1 -0
- package/dist/SessionManager.d.ts +194 -0
- package/dist/SessionManager.d.ts.map +1 -0
- package/dist/SessionManager.js +299 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/ValidationEngine.d.ts +31 -0
- package/dist/ValidationEngine.d.ts.map +1 -0
- package/dist/ValidationEngine.js +158 -0
- package/dist/ValidationEngine.js.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.js +248 -0
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/config.d.ts +57 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +382 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/engine.d.ts +70 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +252 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/implementations/connection-type-references.d.ts +7 -0
- package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
- package/dist/rules/implementations/connection-type-references.js +104 -0
- package/dist/rules/implementations/connection-type-references.js.map +1 -0
- package/dist/rules/implementations/dead-end-states.d.ts +17 -0
- package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
- package/dist/rules/implementations/dead-end-states.js +72 -0
- package/dist/rules/implementations/dead-end-states.js.map +1 -0
- package/dist/rules/implementations/index.d.ts +24 -0
- package/dist/rules/implementations/index.d.ts.map +1 -0
- package/dist/rules/implementations/index.js +62 -0
- package/dist/rules/implementations/index.js.map +1 -0
- package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
- package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
- package/dist/rules/implementations/library-node-type-match.js +123 -0
- package/dist/rules/implementations/library-node-type-match.js.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.js +54 -0
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.js +211 -0
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.js +47 -0
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.js +50 -0
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
- package/dist/rules/implementations/required-metadata.d.ts +7 -0
- package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
- package/dist/rules/implementations/required-metadata.js +57 -0
- package/dist/rules/implementations/required-metadata.js.map +1 -0
- package/dist/rules/implementations/state-transition-references.d.ts +7 -0
- package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
- package/dist/rules/implementations/state-transition-references.js +135 -0
- package/dist/rules/implementations/state-transition-references.js.map +1 -0
- package/dist/rules/implementations/unreachable-states.d.ts +7 -0
- package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
- package/dist/rules/implementations/unreachable-states.js +80 -0
- package/dist/rules/implementations/unreachable-states.js.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.js +109 -0
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
- package/dist/rules/implementations/valid-color-format.d.ts +7 -0
- package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
- package/dist/rules/implementations/valid-color-format.js +91 -0
- package/dist/rules/implementations/valid-color-format.js.map +1 -0
- package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
- package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-edge-types.js +244 -0
- package/dist/rules/implementations/valid-edge-types.js.map +1 -0
- package/dist/rules/implementations/valid-node-types.d.ts +7 -0
- package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-node-types.js +175 -0
- package/dist/rules/implementations/valid-node-types.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +45 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +309 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +35 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/types/canvas.d.ts +409 -0
- package/dist/types/canvas.d.ts.map +1 -0
- package/dist/types/canvas.js +70 -0
- package/dist/types/canvas.js.map +1 -0
- package/dist/types/index.d.ts +311 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/library.d.ts +185 -0
- package/dist/types/library.d.ts.map +1 -0
- package/dist/types/library.js +15 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/path-based-config.d.ts +230 -0
- package/dist/types/path-based-config.d.ts.map +1 -0
- package/dist/types/path-based-config.js +9 -0
- package/dist/types/path-based-config.js.map +1 -0
- package/dist/utils/CanvasConverter.d.ts +118 -0
- package/dist/utils/CanvasConverter.d.ts.map +1 -0
- package/dist/utils/CanvasConverter.js +315 -0
- package/dist/utils/CanvasConverter.js.map +1 -0
- package/dist/utils/GraphConverter.d.ts +18 -0
- package/dist/utils/GraphConverter.d.ts.map +1 -0
- package/dist/utils/GraphConverter.js +61 -0
- package/dist/utils/GraphConverter.js.map +1 -0
- package/dist/utils/LibraryConverter.d.ts +113 -0
- package/dist/utils/LibraryConverter.d.ts.map +1 -0
- package/dist/utils/LibraryConverter.js +166 -0
- package/dist/utils/LibraryConverter.js.map +1 -0
- package/dist/utils/PathMatcher.d.ts +55 -0
- package/dist/utils/PathMatcher.d.ts.map +1 -0
- package/dist/utils/PathMatcher.js +172 -0
- package/dist/utils/PathMatcher.js.map +1 -0
- package/dist/utils/YamlParser.d.ts +36 -0
- package/dist/utils/YamlParser.d.ts.map +1 -0
- package/dist/utils/YamlParser.js +63 -0
- package/dist/utils/YamlParser.js.map +1 -0
- package/package.json +47 -0
- package/src/ConfigurationLoader.test.ts +490 -0
- package/src/ConfigurationLoader.ts +185 -0
- package/src/ConfigurationValidator.test.ts +200 -0
- package/src/ConfigurationValidator.ts +283 -0
- package/src/EventProcessor.test.ts +405 -0
- package/src/EventProcessor.ts +250 -0
- package/src/EventRecorderService.test.ts +541 -0
- package/src/EventRecorderService.ts +744 -0
- package/src/LibraryLoader.ts +215 -0
- package/src/PathBasedEventProcessor.test.ts +567 -0
- package/src/PathBasedEventProcessor.ts +332 -0
- package/src/SessionManager.test.ts +424 -0
- package/src/SessionManager.ts +470 -0
- package/src/ValidationEngine.test.ts +371 -0
- package/src/ValidationEngine.ts +196 -0
- package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
- package/src/helpers/GraphInstrumentationHelper.ts +326 -0
- package/src/index.ts +85 -0
- package/src/rules/config.test.ts +278 -0
- package/src/rules/config.ts +459 -0
- package/src/rules/engine.test.ts +332 -0
- package/src/rules/engine.ts +318 -0
- package/src/rules/implementations/connection-type-references.ts +117 -0
- package/src/rules/implementations/dead-end-states.ts +101 -0
- package/src/rules/implementations/index.ts +73 -0
- package/src/rules/implementations/library-node-type-match.ts +148 -0
- package/src/rules/implementations/minimum-node-sources.ts +82 -0
- package/src/rules/implementations/no-unknown-fields.ts +342 -0
- package/src/rules/implementations/orphaned-edge-types.ts +55 -0
- package/src/rules/implementations/orphaned-node-types.ts +58 -0
- package/src/rules/implementations/required-metadata.ts +64 -0
- package/src/rules/implementations/state-transition-references.ts +151 -0
- package/src/rules/implementations/unreachable-states.ts +94 -0
- package/src/rules/implementations/valid-action-patterns.ts +136 -0
- package/src/rules/implementations/valid-color-format.ts +140 -0
- package/src/rules/implementations/valid-edge-types.ts +258 -0
- package/src/rules/implementations/valid-node-types.ts +189 -0
- package/src/rules/index.ts +95 -0
- package/src/rules/types.ts +426 -0
- package/src/types/canvas.ts +496 -0
- package/src/types/index.ts +382 -0
- package/src/types/library.ts +233 -0
- package/src/types/path-based-config.ts +281 -0
- package/src/utils/CanvasConverter.ts +431 -0
- package/src/utils/GraphConverter.test.ts +195 -0
- package/src/utils/GraphConverter.ts +71 -0
- package/src/utils/LibraryConverter.ts +245 -0
- package/src/utils/PathMatcher.test.ts +148 -0
- package/src/utils/PathMatcher.ts +183 -0
- package/src/utils/YamlParser.ts +75 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { EventRecorderService } from './EventRecorderService';
|
|
3
|
+
import type {
|
|
4
|
+
SessionStartMessage,
|
|
5
|
+
SessionEndMessage,
|
|
6
|
+
LogMessage,
|
|
7
|
+
LogBatchMessage,
|
|
8
|
+
PingMessage,
|
|
9
|
+
AckMessage,
|
|
10
|
+
ErrorMessage,
|
|
11
|
+
} from './EventRecorderService';
|
|
12
|
+
import type { PathBasedGraphConfiguration } from './types/path-based-config';
|
|
13
|
+
import type { LogEntry } from './PathBasedEventProcessor';
|
|
14
|
+
|
|
15
|
+
describe('EventRecorderService', () => {
|
|
16
|
+
// Sample configuration
|
|
17
|
+
const sampleConfig: PathBasedGraphConfiguration = {
|
|
18
|
+
metadata: {
|
|
19
|
+
name: 'Test System',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
},
|
|
22
|
+
nodeTypes: {
|
|
23
|
+
'lock-manager': {
|
|
24
|
+
shape: 'rectangle',
|
|
25
|
+
icon: 'lock',
|
|
26
|
+
color: '#3b82f6',
|
|
27
|
+
dataSchema: {},
|
|
28
|
+
sources: ['lib/lock-manager.ts'],
|
|
29
|
+
},
|
|
30
|
+
'github-api': {
|
|
31
|
+
shape: 'hexagon',
|
|
32
|
+
icon: 'github',
|
|
33
|
+
color: '#22c55e',
|
|
34
|
+
dataSchema: {},
|
|
35
|
+
sources: ['services/github/*.ts'],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
edgeTypes: {},
|
|
39
|
+
allowedConnections: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let service: EventRecorderService;
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
service = new EventRecorderService({
|
|
46
|
+
graphConfig: sampleConfig,
|
|
47
|
+
recordingMode: 'manual',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
service.dispose();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Helper to create a log entry
|
|
56
|
+
const createLogEntry = (
|
|
57
|
+
message: string,
|
|
58
|
+
file: string,
|
|
59
|
+
level: 'debug' | 'info' | 'warn' | 'error' = 'info'
|
|
60
|
+
): LogEntry => ({
|
|
61
|
+
message,
|
|
62
|
+
metadata: {
|
|
63
|
+
timestamp: Date.now(),
|
|
64
|
+
level,
|
|
65
|
+
source: { file, line: 1 },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('Direct API', () => {
|
|
70
|
+
describe('Session management', () => {
|
|
71
|
+
it('should start a session', () => {
|
|
72
|
+
const session = service.startSession({ name: 'Test Session' });
|
|
73
|
+
|
|
74
|
+
expect(session.id).toMatch(/^session-/);
|
|
75
|
+
expect(session.name).toBe('Test Session');
|
|
76
|
+
expect(session.status).toBe('recording');
|
|
77
|
+
expect(service.recording).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should end a session', () => {
|
|
81
|
+
const session = service.startSession({ name: 'Test Session' });
|
|
82
|
+
service.endSession(session.id, { result: 'pass' });
|
|
83
|
+
|
|
84
|
+
const updated = service.getSession(session.id);
|
|
85
|
+
expect(updated?.status).toBe('completed');
|
|
86
|
+
expect(updated?.metadata.result).toBe('pass');
|
|
87
|
+
expect(service.recording).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should end the active session', () => {
|
|
91
|
+
const session = service.startSession({ name: 'Test Session' });
|
|
92
|
+
service.endActiveSession({ result: 'fail', error: 'Test failed' });
|
|
93
|
+
|
|
94
|
+
const updated = service.getSession(session.id);
|
|
95
|
+
expect(updated?.status).toBe('completed');
|
|
96
|
+
expect(updated?.metadata.result).toBe('fail');
|
|
97
|
+
expect(updated?.metadata.error).toBe('Test failed');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should list all sessions', () => {
|
|
101
|
+
service.startSession({ name: 'First' });
|
|
102
|
+
service.endActiveSession();
|
|
103
|
+
service.startSession({ name: 'Second' });
|
|
104
|
+
service.endActiveSession();
|
|
105
|
+
|
|
106
|
+
const sessions = service.listSessions();
|
|
107
|
+
expect(sessions).toHaveLength(2);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('Log processing', () => {
|
|
112
|
+
it('should not process logs when not recording', () => {
|
|
113
|
+
const log = createLogEntry('Test log', 'lib/lock-manager.ts');
|
|
114
|
+
const events = service.processLog(log);
|
|
115
|
+
|
|
116
|
+
expect(events).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should process logs when recording', () => {
|
|
120
|
+
service.startSession({ name: 'Test Session' });
|
|
121
|
+
|
|
122
|
+
const log = createLogEntry('Lock acquired', 'lib/lock-manager.ts');
|
|
123
|
+
const events = service.processLog(log);
|
|
124
|
+
|
|
125
|
+
expect(events).toHaveLength(1);
|
|
126
|
+
expect(events[0].type).toBe('component-activity');
|
|
127
|
+
expect((events[0] as any).componentId).toBe('lock-manager');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should add processed events to active session', () => {
|
|
131
|
+
const session = service.startSession({ name: 'Test Session' });
|
|
132
|
+
|
|
133
|
+
service.processLog(createLogEntry('Log 1', 'lib/lock-manager.ts'));
|
|
134
|
+
service.processLog(createLogEntry('Log 2', 'lib/lock-manager.ts'));
|
|
135
|
+
|
|
136
|
+
const updated = service.getSession(session.id);
|
|
137
|
+
expect(updated?.events).toHaveLength(2);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should process multiple logs in batch', () => {
|
|
141
|
+
service.startSession({ name: 'Test Session' });
|
|
142
|
+
|
|
143
|
+
const logs = [
|
|
144
|
+
createLogEntry('Log 1', 'lib/lock-manager.ts'),
|
|
145
|
+
createLogEntry('Log 2', 'services/github/client.ts'),
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
const events = service.processLogs(logs);
|
|
149
|
+
expect(events).toHaveLength(2);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should emit events to listeners', () => {
|
|
153
|
+
service.startSession({ name: 'Test Session' });
|
|
154
|
+
|
|
155
|
+
const receivedEvents: any[] = [];
|
|
156
|
+
service.onEvent((event) => {
|
|
157
|
+
receivedEvents.push(event);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
service.processLog(createLogEntry('Test log', 'lib/lock-manager.ts'));
|
|
161
|
+
|
|
162
|
+
expect(receivedEvents).toHaveLength(1);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should unsubscribe event listener', () => {
|
|
166
|
+
service.startSession({ name: 'Test Session' });
|
|
167
|
+
|
|
168
|
+
const receivedEvents: any[] = [];
|
|
169
|
+
const unsubscribe = service.onEvent((event) => {
|
|
170
|
+
receivedEvents.push(event);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
service.processLog(createLogEntry('Log 1', 'lib/lock-manager.ts'));
|
|
174
|
+
expect(receivedEvents).toHaveLength(1);
|
|
175
|
+
|
|
176
|
+
unsubscribe();
|
|
177
|
+
|
|
178
|
+
service.processLog(createLogEntry('Log 2', 'lib/lock-manager.ts'));
|
|
179
|
+
expect(receivedEvents).toHaveLength(1); // No new events
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('Recording control', () => {
|
|
184
|
+
it('should start and stop recording manually', () => {
|
|
185
|
+
expect(service.recording).toBe(false);
|
|
186
|
+
|
|
187
|
+
service.startRecording();
|
|
188
|
+
expect(service.recording).toBe(true);
|
|
189
|
+
|
|
190
|
+
service.stopRecording();
|
|
191
|
+
expect(service.recording).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('WebSocket Protocol', () => {
|
|
197
|
+
beforeEach(() => {
|
|
198
|
+
service.registerConnection('conn-1');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
afterEach(() => {
|
|
202
|
+
service.unregisterConnection('conn-1');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('session_start', () => {
|
|
206
|
+
it('should start a session and return ack', () => {
|
|
207
|
+
const message: SessionStartMessage = {
|
|
208
|
+
type: 'session_start',
|
|
209
|
+
timestamp: Date.now(),
|
|
210
|
+
requestId: 'req-1',
|
|
211
|
+
payload: {
|
|
212
|
+
name: 'Test Session',
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const response = service.processMessage(message, 'conn-1') as AckMessage;
|
|
217
|
+
|
|
218
|
+
expect(response.type).toBe('ack');
|
|
219
|
+
expect(response.payload.requestId).toBe('req-1');
|
|
220
|
+
expect(response.payload.sessionId).toMatch(/^session-/);
|
|
221
|
+
expect(service.getActiveSession()).toBeDefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should start a session with custom ID', () => {
|
|
225
|
+
const message: SessionStartMessage = {
|
|
226
|
+
type: 'session_start',
|
|
227
|
+
timestamp: Date.now(),
|
|
228
|
+
payload: {
|
|
229
|
+
name: 'Test Session',
|
|
230
|
+
id: 'my-session',
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const response = service.processMessage(message, 'conn-1') as AckMessage;
|
|
235
|
+
|
|
236
|
+
expect(response.payload.sessionId).toBe('my-session');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should return error for duplicate session ID', () => {
|
|
240
|
+
const message: SessionStartMessage = {
|
|
241
|
+
type: 'session_start',
|
|
242
|
+
timestamp: Date.now(),
|
|
243
|
+
requestId: 'req-1',
|
|
244
|
+
payload: {
|
|
245
|
+
name: 'Test Session',
|
|
246
|
+
id: 'duplicate',
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
service.processMessage(message, 'conn-1');
|
|
251
|
+
const response = service.processMessage(message, 'conn-1') as ErrorMessage;
|
|
252
|
+
|
|
253
|
+
expect(response.type).toBe('error');
|
|
254
|
+
expect(response.payload.code).toBe('SESSION_START_FAILED');
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('session_end', () => {
|
|
259
|
+
it('should end a session and return ack', () => {
|
|
260
|
+
// Start session first
|
|
261
|
+
const startMessage: SessionStartMessage = {
|
|
262
|
+
type: 'session_start',
|
|
263
|
+
timestamp: Date.now(),
|
|
264
|
+
payload: { name: 'Test Session', id: 'test-session' },
|
|
265
|
+
};
|
|
266
|
+
service.processMessage(startMessage, 'conn-1');
|
|
267
|
+
|
|
268
|
+
// End session
|
|
269
|
+
const endMessage: SessionEndMessage = {
|
|
270
|
+
type: 'session_end',
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
requestId: 'req-2',
|
|
273
|
+
payload: {
|
|
274
|
+
sessionId: 'test-session',
|
|
275
|
+
result: 'pass',
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const response = service.processMessage(endMessage, 'conn-1') as AckMessage;
|
|
280
|
+
|
|
281
|
+
expect(response.type).toBe('ack');
|
|
282
|
+
expect(response.payload.requestId).toBe('req-2');
|
|
283
|
+
|
|
284
|
+
const session = service.getSession('test-session');
|
|
285
|
+
expect(session?.status).toBe('completed');
|
|
286
|
+
expect(session?.metadata.result).toBe('pass');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should return error for non-existent session', () => {
|
|
290
|
+
const message: SessionEndMessage = {
|
|
291
|
+
type: 'session_end',
|
|
292
|
+
timestamp: Date.now(),
|
|
293
|
+
payload: {
|
|
294
|
+
sessionId: 'non-existent',
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const response = service.processMessage(message, 'conn-1') as ErrorMessage;
|
|
299
|
+
|
|
300
|
+
expect(response.type).toBe('error');
|
|
301
|
+
expect(response.payload.code).toBe('SESSION_END_FAILED');
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe('log', () => {
|
|
306
|
+
beforeEach(() => {
|
|
307
|
+
const startMessage: SessionStartMessage = {
|
|
308
|
+
type: 'session_start',
|
|
309
|
+
timestamp: Date.now(),
|
|
310
|
+
payload: { name: 'Test Session' },
|
|
311
|
+
};
|
|
312
|
+
service.processMessage(startMessage, 'conn-1');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should process a log message', () => {
|
|
316
|
+
const message: LogMessage = {
|
|
317
|
+
type: 'log',
|
|
318
|
+
timestamp: Date.now(),
|
|
319
|
+
payload: createLogEntry('Lock acquired', 'lib/lock-manager.ts'),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const receivedEvents: any[] = [];
|
|
323
|
+
service.onEvent((event) => receivedEvents.push(event));
|
|
324
|
+
|
|
325
|
+
service.processMessage(message, 'conn-1');
|
|
326
|
+
|
|
327
|
+
expect(receivedEvents).toHaveLength(1);
|
|
328
|
+
expect(receivedEvents[0].type).toBe('component-activity');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should return ack when requestId is provided', () => {
|
|
332
|
+
const message: LogMessage = {
|
|
333
|
+
type: 'log',
|
|
334
|
+
timestamp: Date.now(),
|
|
335
|
+
requestId: 'req-1',
|
|
336
|
+
payload: createLogEntry('Lock acquired', 'lib/lock-manager.ts'),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const response = service.processMessage(message, 'conn-1') as AckMessage;
|
|
340
|
+
|
|
341
|
+
expect(response.type).toBe('ack');
|
|
342
|
+
expect(response.payload.requestId).toBe('req-1');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should return null when no requestId', () => {
|
|
346
|
+
const message: LogMessage = {
|
|
347
|
+
type: 'log',
|
|
348
|
+
timestamp: Date.now(),
|
|
349
|
+
payload: createLogEntry('Lock acquired', 'lib/lock-manager.ts'),
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const response = service.processMessage(message, 'conn-1');
|
|
353
|
+
expect(response).toBeNull();
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('log_batch', () => {
|
|
358
|
+
beforeEach(() => {
|
|
359
|
+
const startMessage: SessionStartMessage = {
|
|
360
|
+
type: 'session_start',
|
|
361
|
+
timestamp: Date.now(),
|
|
362
|
+
payload: { name: 'Test Session' },
|
|
363
|
+
};
|
|
364
|
+
service.processMessage(startMessage, 'conn-1');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should process a batch of logs', () => {
|
|
368
|
+
const message: LogBatchMessage = {
|
|
369
|
+
type: 'log_batch',
|
|
370
|
+
timestamp: Date.now(),
|
|
371
|
+
payload: {
|
|
372
|
+
logs: [
|
|
373
|
+
createLogEntry('Log 1', 'lib/lock-manager.ts'),
|
|
374
|
+
createLogEntry('Log 2', 'lib/lock-manager.ts'),
|
|
375
|
+
createLogEntry('Log 3', 'services/github/client.ts'),
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const receivedEvents: any[] = [];
|
|
381
|
+
service.onEvent((event) => receivedEvents.push(event));
|
|
382
|
+
|
|
383
|
+
service.processMessage(message, 'conn-1');
|
|
384
|
+
|
|
385
|
+
expect(receivedEvents).toHaveLength(3);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('ping', () => {
|
|
390
|
+
it('should respond with pong', () => {
|
|
391
|
+
const message: PingMessage = {
|
|
392
|
+
type: 'ping',
|
|
393
|
+
timestamp: Date.now(),
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const response = service.processMessage(message, 'conn-1');
|
|
397
|
+
|
|
398
|
+
expect(response?.type).toBe('pong');
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe('Connection management', () => {
|
|
404
|
+
it('should register and unregister connections', () => {
|
|
405
|
+
service.registerConnection('conn-1');
|
|
406
|
+
expect(service.getConnection('conn-1')).toBeDefined();
|
|
407
|
+
|
|
408
|
+
service.unregisterConnection('conn-1');
|
|
409
|
+
expect(service.getConnection('conn-1')).toBeUndefined();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('should track connection state', () => {
|
|
413
|
+
service.registerConnection('conn-1');
|
|
414
|
+
const connection = service.getConnection('conn-1');
|
|
415
|
+
|
|
416
|
+
expect(connection?.id).toBe('conn-1');
|
|
417
|
+
expect(connection?.connectedAt).toBeLessThanOrEqual(Date.now());
|
|
418
|
+
expect(connection?.activeSessionId).toBeNull();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should associate session with connection', () => {
|
|
422
|
+
service.registerConnection('conn-1');
|
|
423
|
+
|
|
424
|
+
const message: SessionStartMessage = {
|
|
425
|
+
type: 'session_start',
|
|
426
|
+
timestamp: Date.now(),
|
|
427
|
+
payload: { name: 'Test Session', id: 'test-session' },
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
service.processMessage(message, 'conn-1');
|
|
431
|
+
|
|
432
|
+
const connection = service.getConnection('conn-1');
|
|
433
|
+
expect(connection?.activeSessionId).toBe('test-session');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should error session when connection closes unexpectedly', () => {
|
|
437
|
+
service.registerConnection('conn-1');
|
|
438
|
+
|
|
439
|
+
const message: SessionStartMessage = {
|
|
440
|
+
type: 'session_start',
|
|
441
|
+
timestamp: Date.now(),
|
|
442
|
+
payload: { name: 'Test Session', id: 'test-session' },
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
service.processMessage(message, 'conn-1');
|
|
446
|
+
service.unregisterConnection('conn-1');
|
|
447
|
+
|
|
448
|
+
const session = service.getSession('test-session');
|
|
449
|
+
expect(session?.status).toBe('error');
|
|
450
|
+
expect(session?.metadata.error).toContain('Connection closed');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('should list all connections', () => {
|
|
454
|
+
service.registerConnection('conn-1');
|
|
455
|
+
service.registerConnection('conn-2');
|
|
456
|
+
service.registerConnection('conn-3');
|
|
457
|
+
|
|
458
|
+
const connections = service.getConnections();
|
|
459
|
+
expect(connections).toHaveLength(3);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe('Recording modes', () => {
|
|
464
|
+
it('should start recording immediately in continuous mode', () => {
|
|
465
|
+
const continuousService = new EventRecorderService({
|
|
466
|
+
graphConfig: sampleConfig,
|
|
467
|
+
recordingMode: 'continuous',
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
expect(continuousService.recording).toBe(true);
|
|
471
|
+
continuousService.dispose();
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('should not start recording automatically in manual mode', () => {
|
|
475
|
+
expect(service.recording).toBe(false);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should continue recording after session end in continuous mode', () => {
|
|
479
|
+
const continuousService = new EventRecorderService({
|
|
480
|
+
graphConfig: sampleConfig,
|
|
481
|
+
recordingMode: 'continuous',
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const session = continuousService.startSession({ name: 'Test' });
|
|
485
|
+
continuousService.endSession(session.id);
|
|
486
|
+
|
|
487
|
+
expect(continuousService.recording).toBe(true);
|
|
488
|
+
continuousService.dispose();
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('Statistics', () => {
|
|
493
|
+
it('should return correct stats', () => {
|
|
494
|
+
service.registerConnection('conn-1');
|
|
495
|
+
service.startSession({ name: 'Test Session' });
|
|
496
|
+
service.processLog(createLogEntry('Log 1', 'lib/lock-manager.ts'));
|
|
497
|
+
service.processLog(createLogEntry('Log 2', 'lib/lock-manager.ts'));
|
|
498
|
+
|
|
499
|
+
const stats = service.getStats();
|
|
500
|
+
|
|
501
|
+
expect(stats.isRecording).toBe(true);
|
|
502
|
+
expect(stats.recordingMode).toBe('manual');
|
|
503
|
+
expect(stats.activeConnections).toBe(1);
|
|
504
|
+
expect(stats.sessions.total).toBe(1);
|
|
505
|
+
expect(stats.sessions.active).toBe(1);
|
|
506
|
+
expect(stats.sessions.totalEvents).toBe(2);
|
|
507
|
+
expect(stats.processor.totalComponents).toBe(2);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('Batch event emission', () => {
|
|
512
|
+
it('should batch events when emitImmediately is false', async () => {
|
|
513
|
+
const batchService = new EventRecorderService({
|
|
514
|
+
graphConfig: sampleConfig,
|
|
515
|
+
recordingMode: 'manual',
|
|
516
|
+
emitImmediately: false,
|
|
517
|
+
batchIntervalMs: 50,
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
batchService.startSession({ name: 'Test Session' });
|
|
521
|
+
|
|
522
|
+
const receivedBatches: any[][] = [];
|
|
523
|
+
batchService.onEventBatch((events) => {
|
|
524
|
+
receivedBatches.push(events);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// Process multiple logs
|
|
528
|
+
batchService.processLog(createLogEntry('Log 1', 'lib/lock-manager.ts'));
|
|
529
|
+
batchService.processLog(createLogEntry('Log 2', 'lib/lock-manager.ts'));
|
|
530
|
+
batchService.processLog(createLogEntry('Log 3', 'lib/lock-manager.ts'));
|
|
531
|
+
|
|
532
|
+
// Wait for batch interval
|
|
533
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
534
|
+
|
|
535
|
+
expect(receivedBatches.length).toBeGreaterThan(0);
|
|
536
|
+
expect(receivedBatches[0]).toHaveLength(3);
|
|
537
|
+
|
|
538
|
+
batchService.dispose();
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
});
|