@volley/recognition-client-sdk 0.1.200
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 +168 -0
- package/dist/browser-CDQ_TzeH.d.ts +1039 -0
- package/dist/index.d.ts +461 -0
- package/dist/index.js +2332 -0
- package/dist/index.js.map +1 -0
- package/dist/recog-client-sdk.browser.d.ts +2 -0
- package/dist/recog-client-sdk.browser.js +1843 -0
- package/dist/recog-client-sdk.browser.js.map +1 -0
- package/package.json +73 -0
- package/src/browser.ts +24 -0
- package/src/config-builder.ts +213 -0
- package/src/factory.ts +43 -0
- package/src/index.ts +86 -0
- package/src/recognition-client.spec.ts +551 -0
- package/src/recognition-client.ts +595 -0
- package/src/recognition-client.types.ts +260 -0
- package/src/simplified-vgf-recognition-client.spec.ts +671 -0
- package/src/simplified-vgf-recognition-client.ts +339 -0
- package/src/utils/audio-ring-buffer.ts +170 -0
- package/src/utils/message-handler.ts +131 -0
- package/src/utils/url-builder.ts +70 -0
- package/src/vgf-recognition-mapper.ts +225 -0
- package/src/vgf-recognition-state.ts +89 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for RealTimeTwoWayWebSocketRecognitionClient
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RealTimeTwoWayWebSocketRecognitionClient } from './recognition-client';
|
|
6
|
+
import { ClientState } from './recognition-client.types';
|
|
7
|
+
import { WebSocket as MockWebSocket } from 'ws';
|
|
8
|
+
|
|
9
|
+
// Mock WebSocket
|
|
10
|
+
jest.mock('ws');
|
|
11
|
+
|
|
12
|
+
describe('RealTimeTwoWayWebSocketRecognitionClient', () => {
|
|
13
|
+
let client: RealTimeTwoWayWebSocketRecognitionClient;
|
|
14
|
+
let mockWs: any;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Reset mocks
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
|
|
20
|
+
// Create mock WebSocket
|
|
21
|
+
mockWs = {
|
|
22
|
+
readyState: MockWebSocket.CONNECTING,
|
|
23
|
+
send: jest.fn(),
|
|
24
|
+
close: jest.fn(),
|
|
25
|
+
on: jest.fn(),
|
|
26
|
+
removeAllListeners: jest.fn(),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Mock WebSocket constructor
|
|
30
|
+
(MockWebSocket as any).mockImplementation(() => mockWs);
|
|
31
|
+
|
|
32
|
+
// Create client
|
|
33
|
+
client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
34
|
+
url: 'ws://test.example.com/recognize',
|
|
35
|
+
asrRequestConfig: {
|
|
36
|
+
provider: 'deepgram',
|
|
37
|
+
language: 'en',
|
|
38
|
+
sampleRate: 16000,
|
|
39
|
+
encoding: 'linear16'
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
// Clean up
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Constructor', () => {
|
|
49
|
+
it('should initialize with correct default values', () => {
|
|
50
|
+
expect(client.getState()).toBe(ClientState.INITIAL);
|
|
51
|
+
expect(client.isConnected()).toBe(false);
|
|
52
|
+
expect(client.isBufferOverflowing()).toBe(false);
|
|
53
|
+
expect(client.getAudioUtteranceId()).toBeDefined();
|
|
54
|
+
expect(typeof client.getAudioUtteranceId()).toBe('string');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should have immutable audioUtteranceId', () => {
|
|
58
|
+
const originalId = client.getAudioUtteranceId();
|
|
59
|
+
// audioUtteranceId should not change
|
|
60
|
+
expect(client.getAudioUtteranceId()).toBe(originalId);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should initialize stats correctly', () => {
|
|
64
|
+
const stats = client.getStats();
|
|
65
|
+
expect(stats.audioBytesSent).toBe(0);
|
|
66
|
+
expect(stats.audioChunksSent).toBe(0);
|
|
67
|
+
expect(stats.audioChunksBuffered).toBe(0);
|
|
68
|
+
expect(stats.bufferOverflowCount).toBe(0);
|
|
69
|
+
expect(stats.currentBufferedChunks).toBe(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe.skip('State Management', () => {
|
|
74
|
+
it('should transition from INITIAL to CONNECTING on connect()', async () => {
|
|
75
|
+
expect(client.getState()).toBe(ClientState.INITIAL);
|
|
76
|
+
|
|
77
|
+
// Simulate successful connection
|
|
78
|
+
const connectPromise = client.connect();
|
|
79
|
+
expect(client.getState()).toBe(ClientState.CONNECTING);
|
|
80
|
+
|
|
81
|
+
// Simulate WebSocket open event
|
|
82
|
+
mockWs.readyState = MockWebSocket.OPEN;
|
|
83
|
+
const openHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'open')[1];
|
|
84
|
+
openHandler();
|
|
85
|
+
|
|
86
|
+
await connectPromise;
|
|
87
|
+
expect(client.getState()).toBe(ClientState.CONNECTED);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should transition to READY when server sends ready message', async () => {
|
|
91
|
+
// Connect first
|
|
92
|
+
const connectPromise = client.connect();
|
|
93
|
+
mockWs.readyState = MockWebSocket.OPEN;
|
|
94
|
+
const openHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'open')[1];
|
|
95
|
+
openHandler();
|
|
96
|
+
await connectPromise;
|
|
97
|
+
|
|
98
|
+
// Simulate ready message
|
|
99
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
100
|
+
const readyMessage = JSON.stringify({
|
|
101
|
+
v: 1,
|
|
102
|
+
type: 'message',
|
|
103
|
+
data: {
|
|
104
|
+
type: 'ClientControlMessage',
|
|
105
|
+
action: 'ready_for_uploading_recording',
|
|
106
|
+
audioUtteranceId: 'test-utterance-id'
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
messageHandler(readyMessage);
|
|
110
|
+
|
|
111
|
+
expect(client.getState()).toBe(ClientState.READY);
|
|
112
|
+
expect(client.isConnected()).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should transition to STOPPING when stopRecording() is called', async () => {
|
|
116
|
+
// Setup: Connect and become ready
|
|
117
|
+
await setupConnectedClient();
|
|
118
|
+
|
|
119
|
+
// Call stopRecording
|
|
120
|
+
const stopPromise = client.stopRecording();
|
|
121
|
+
expect(client.getState()).toBe(ClientState.STOPPING);
|
|
122
|
+
|
|
123
|
+
// Simulate final transcript
|
|
124
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
125
|
+
const finalMessage = JSON.stringify({
|
|
126
|
+
v: 1,
|
|
127
|
+
type: 'message',
|
|
128
|
+
data: {
|
|
129
|
+
type: 'Transcription',
|
|
130
|
+
finalTranscript: 'test',
|
|
131
|
+
is_finished: true
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
messageHandler(finalMessage);
|
|
135
|
+
|
|
136
|
+
await stopPromise;
|
|
137
|
+
expect(client.getState()).toBe(ClientState.STOPPED);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should transition to FAILED on connection error', async () => {
|
|
141
|
+
const connectPromise = client.connect();
|
|
142
|
+
|
|
143
|
+
// Simulate error
|
|
144
|
+
const errorHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'error')[1];
|
|
145
|
+
errorHandler(new Error('Connection failed'));
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
await connectPromise;
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// Expected
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
expect(client.getState()).toBe(ClientState.FAILED);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe.skip('Connection Handling', () => {
|
|
158
|
+
it('should handle duplicate connect() calls', async () => {
|
|
159
|
+
// Call connect twice
|
|
160
|
+
const promise1 = client.connect();
|
|
161
|
+
const promise2 = client.connect();
|
|
162
|
+
|
|
163
|
+
// Should be the same promise
|
|
164
|
+
expect(promise1).toBe(promise2);
|
|
165
|
+
|
|
166
|
+
// Simulate successful connection
|
|
167
|
+
mockWs.readyState = MockWebSocket.OPEN;
|
|
168
|
+
const openHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'open')[1];
|
|
169
|
+
openHandler();
|
|
170
|
+
|
|
171
|
+
await Promise.all([promise1, promise2]);
|
|
172
|
+
expect(client.getState()).toBe(ClientState.CONNECTED);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should not reconnect if already connected', async () => {
|
|
176
|
+
// First connection
|
|
177
|
+
await setupConnectedClient();
|
|
178
|
+
const firstWs = mockWs;
|
|
179
|
+
|
|
180
|
+
// Try to connect again
|
|
181
|
+
await client.connect();
|
|
182
|
+
|
|
183
|
+
// Should not create new WebSocket
|
|
184
|
+
expect(MockWebSocket).toHaveBeenCalledTimes(1);
|
|
185
|
+
expect(client.isConnected()).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe.skip('Audio Handling', () => {
|
|
190
|
+
it('should buffer audio when not ready', () => {
|
|
191
|
+
const audioData = Buffer.from([1, 2, 3, 4]);
|
|
192
|
+
client.sendAudio(audioData);
|
|
193
|
+
|
|
194
|
+
const stats = client.getStats();
|
|
195
|
+
expect(stats.audioBytesSent).toBe(0);
|
|
196
|
+
expect(stats.audioChunksBuffered).toBe(1);
|
|
197
|
+
expect(stats.currentBufferedChunks).toBe(1);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should send audio immediately when ready', async () => {
|
|
201
|
+
await setupReadyClient();
|
|
202
|
+
|
|
203
|
+
const audioData = Buffer.from([1, 2, 3, 4]);
|
|
204
|
+
client.sendAudio(audioData);
|
|
205
|
+
|
|
206
|
+
// Should have sent the audio
|
|
207
|
+
expect(mockWs.send).toHaveBeenCalled();
|
|
208
|
+
const stats = client.getStats();
|
|
209
|
+
expect(stats.audioBytesSent).toBe(4);
|
|
210
|
+
expect(stats.audioChunksSent).toBe(1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should flush buffered audio when becoming ready', async () => {
|
|
214
|
+
// Buffer some audio while disconnected
|
|
215
|
+
const audioData1 = Buffer.from([1, 2, 3, 4]);
|
|
216
|
+
const audioData2 = Buffer.from([5, 6, 7, 8]);
|
|
217
|
+
client.sendAudio(audioData1);
|
|
218
|
+
client.sendAudio(audioData2);
|
|
219
|
+
|
|
220
|
+
// Verify buffered
|
|
221
|
+
let stats = client.getStats();
|
|
222
|
+
expect(stats.currentBufferedChunks).toBe(2);
|
|
223
|
+
expect(stats.audioBytesSent).toBe(0);
|
|
224
|
+
|
|
225
|
+
// Connect and become ready
|
|
226
|
+
await setupReadyClient();
|
|
227
|
+
|
|
228
|
+
// Should have flushed the buffer
|
|
229
|
+
stats = client.getStats();
|
|
230
|
+
expect(stats.audioBytesSent).toBe(8);
|
|
231
|
+
expect(stats.audioChunksSent).toBe(2);
|
|
232
|
+
expect(stats.currentBufferedChunks).toBe(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should detect buffer overflow', () => {
|
|
236
|
+
// Fill buffer to capacity (simulate overflow)
|
|
237
|
+
const chunkSize = 1024;
|
|
238
|
+
const maxChunks = 6000; // Default buffer size
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i <= maxChunks; i++) {
|
|
241
|
+
client.sendAudio(Buffer.alloc(chunkSize));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
expect(client.isBufferOverflowing()).toBe(true);
|
|
245
|
+
const stats = client.getStats();
|
|
246
|
+
expect(stats.bufferOverflowCount).toBeGreaterThan(0);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe.skip('Helper Methods', () => {
|
|
251
|
+
it('should report correct connection status', async () => {
|
|
252
|
+
expect(client.isConnected()).toBe(false);
|
|
253
|
+
|
|
254
|
+
await setupConnectedClient();
|
|
255
|
+
expect(client.isConnected()).toBe(true);
|
|
256
|
+
|
|
257
|
+
await setupReadyClient();
|
|
258
|
+
expect(client.isConnected()).toBe(true);
|
|
259
|
+
|
|
260
|
+
// Disconnect
|
|
261
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'close')[1];
|
|
262
|
+
closeHandler(1000, 'Normal closure');
|
|
263
|
+
expect(client.isConnected()).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should track statistics correctly', async () => {
|
|
267
|
+
await setupReadyClient();
|
|
268
|
+
|
|
269
|
+
// Send some audio
|
|
270
|
+
client.sendAudio(Buffer.alloc(100));
|
|
271
|
+
client.sendAudio(Buffer.alloc(200));
|
|
272
|
+
client.sendAudio(Buffer.alloc(300));
|
|
273
|
+
|
|
274
|
+
const stats = client.getStats();
|
|
275
|
+
expect(stats.audioBytesSent).toBe(600);
|
|
276
|
+
expect(stats.audioChunksSent).toBe(3);
|
|
277
|
+
expect(stats.audioChunksBuffered).toBe(3);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe.skip('Memory Management', () => {
|
|
282
|
+
it('should clear ring buffer on disconnect', async () => {
|
|
283
|
+
// Buffer some audio
|
|
284
|
+
client.sendAudio(Buffer.alloc(100));
|
|
285
|
+
client.sendAudio(Buffer.alloc(200));
|
|
286
|
+
|
|
287
|
+
let stats = client.getStats();
|
|
288
|
+
expect(stats.currentBufferedChunks).toBe(2);
|
|
289
|
+
|
|
290
|
+
// Connect then disconnect
|
|
291
|
+
await setupConnectedClient();
|
|
292
|
+
const closeHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'close')[1];
|
|
293
|
+
closeHandler(1000, 'Normal closure');
|
|
294
|
+
|
|
295
|
+
// Buffer should be cleared
|
|
296
|
+
stats = client.getStats();
|
|
297
|
+
expect(stats.currentBufferedChunks).toBe(0);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should cleanup WebSocket listeners on close', async () => {
|
|
301
|
+
await setupConnectedClient();
|
|
302
|
+
|
|
303
|
+
await client.stopRecording();
|
|
304
|
+
|
|
305
|
+
expect(mockWs.removeAllListeners).toHaveBeenCalled();
|
|
306
|
+
expect(mockWs.close).toHaveBeenCalled();
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe.skip('Message Handling', () => {
|
|
311
|
+
it('should handle transcription messages', async () => {
|
|
312
|
+
const onTranscript = jest.fn();
|
|
313
|
+
client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
314
|
+
url: 'ws://test.example.com/recognize',
|
|
315
|
+
onTranscript,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await setupConnectedClient();
|
|
319
|
+
|
|
320
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
321
|
+
const transcriptMessage = JSON.stringify({
|
|
322
|
+
v: 1,
|
|
323
|
+
type: 'message',
|
|
324
|
+
data: {
|
|
325
|
+
type: 'Transcription',
|
|
326
|
+
finalTranscript: 'Hello world',
|
|
327
|
+
finalTranscriptConfidence: 0.95,
|
|
328
|
+
is_finished: false
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
messageHandler(transcriptMessage);
|
|
333
|
+
|
|
334
|
+
expect(onTranscript).toHaveBeenCalledWith(expect.objectContaining({
|
|
335
|
+
finalTranscript: 'Hello world',
|
|
336
|
+
finalTranscriptConfidence: 0.95,
|
|
337
|
+
is_finished: false
|
|
338
|
+
}));
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should handle function call messages', async () => {
|
|
342
|
+
const onFunctionCall = jest.fn();
|
|
343
|
+
client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
344
|
+
url: 'ws://test.example.com/recognize',
|
|
345
|
+
onFunctionCall,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await setupConnectedClient();
|
|
349
|
+
|
|
350
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
351
|
+
const functionCallMessage = JSON.stringify({
|
|
352
|
+
v: 1,
|
|
353
|
+
type: 'message',
|
|
354
|
+
data: {
|
|
355
|
+
type: 'FunctionCall',
|
|
356
|
+
audioUtteranceId: 'test-id',
|
|
357
|
+
functionName: 'playMusic',
|
|
358
|
+
functionArgJson: '{"song": "Bohemian Rhapsody"}'
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
messageHandler(functionCallMessage);
|
|
363
|
+
|
|
364
|
+
expect(onFunctionCall).toHaveBeenCalledWith(expect.objectContaining({
|
|
365
|
+
type: 'FunctionCall',
|
|
366
|
+
audioUtteranceId: 'test-id',
|
|
367
|
+
functionName: 'playMusic',
|
|
368
|
+
functionArgJson: '{"song": "Bohemian Rhapsody"}'
|
|
369
|
+
}));
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should handle metadata messages', async () => {
|
|
373
|
+
const onMetadata = jest.fn();
|
|
374
|
+
client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
375
|
+
url: 'ws://test.example.com/recognize',
|
|
376
|
+
onMetadata,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
await setupConnectedClient();
|
|
380
|
+
|
|
381
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
382
|
+
const metadataMessage = JSON.stringify({
|
|
383
|
+
v: 1,
|
|
384
|
+
type: 'message',
|
|
385
|
+
data: {
|
|
386
|
+
type: 'Metadata',
|
|
387
|
+
audioUtteranceId: 'test-id',
|
|
388
|
+
duration: 1000
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
messageHandler(metadataMessage);
|
|
393
|
+
|
|
394
|
+
expect(onMetadata).toHaveBeenCalledWith(expect.objectContaining({
|
|
395
|
+
audioUtteranceId: 'test-id',
|
|
396
|
+
duration: 1000
|
|
397
|
+
}));
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should handle error messages', async () => {
|
|
401
|
+
const onError = jest.fn();
|
|
402
|
+
client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
403
|
+
url: 'ws://test.example.com/recognize',
|
|
404
|
+
onError,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
await setupConnectedClient();
|
|
408
|
+
|
|
409
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
410
|
+
const errorMessage = JSON.stringify({
|
|
411
|
+
v: 1,
|
|
412
|
+
type: 'message',
|
|
413
|
+
data: {
|
|
414
|
+
type: 'Error',
|
|
415
|
+
message: 'Something went wrong'
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
messageHandler(errorMessage);
|
|
420
|
+
|
|
421
|
+
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
422
|
+
expect(onError.mock.calls[0][0].message).toContain('Something went wrong');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should handle primitive message data without crashing', async () => {
|
|
426
|
+
const onError = jest.fn();
|
|
427
|
+
client = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
428
|
+
url: 'ws://test.example.com/recognize',
|
|
429
|
+
onError,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await setupConnectedClient();
|
|
433
|
+
|
|
434
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
435
|
+
|
|
436
|
+
// Test with various primitive values
|
|
437
|
+
const primitiveMessages = [
|
|
438
|
+
JSON.stringify({ v: 1, type: 'test', data: 'string' }),
|
|
439
|
+
JSON.stringify({ v: 1, type: 'test', data: 123 }),
|
|
440
|
+
JSON.stringify({ v: 1, type: 'test', data: true }),
|
|
441
|
+
JSON.stringify({ v: 1, type: 'test', data: null }),
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
// Should not throw
|
|
445
|
+
expect(() => {
|
|
446
|
+
primitiveMessages.forEach(msg => messageHandler(msg));
|
|
447
|
+
}).not.toThrow();
|
|
448
|
+
|
|
449
|
+
// Should log errors for primitives
|
|
450
|
+
expect(onError).toHaveBeenCalled();
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
describe('Debug Logging', () => {
|
|
455
|
+
it('should not log debug messages when debug logging is disabled (default)', () => {
|
|
456
|
+
const mockLogger = jest.fn();
|
|
457
|
+
const testClient = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
458
|
+
url: 'ws://test.example.com/recognize',
|
|
459
|
+
asrRequestConfig: {
|
|
460
|
+
provider: 'deepgram',
|
|
461
|
+
language: 'en',
|
|
462
|
+
sampleRate: 16000,
|
|
463
|
+
encoding: 'linear16'
|
|
464
|
+
},
|
|
465
|
+
logger: mockLogger
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Trigger some actions that would normally log debug messages
|
|
469
|
+
expect(testClient.getState()).toBe(ClientState.INITIAL);
|
|
470
|
+
|
|
471
|
+
// Debug logs should not be called
|
|
472
|
+
const debugCalls = mockLogger.mock.calls.filter(call => call[0] === 'debug');
|
|
473
|
+
expect(debugCalls.length).toBe(0);
|
|
474
|
+
|
|
475
|
+
// But other log levels should work
|
|
476
|
+
const nonDebugCalls = mockLogger.mock.calls.filter(call => call[0] !== 'debug');
|
|
477
|
+
// May or may not have non-debug logs, just checking we can track them
|
|
478
|
+
expect(nonDebugCalls.length).toBeGreaterThanOrEqual(0);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('should log debug messages when enableDebugLog is true in debugCommand', () => {
|
|
482
|
+
const mockLogger = jest.fn();
|
|
483
|
+
const testClient = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
484
|
+
url: 'ws://test.example.com/recognize',
|
|
485
|
+
asrRequestConfig: {
|
|
486
|
+
provider: 'deepgram',
|
|
487
|
+
language: 'en',
|
|
488
|
+
sampleRate: 16000,
|
|
489
|
+
encoding: 'linear16',
|
|
490
|
+
debugCommand: {
|
|
491
|
+
enableDebugLog: true
|
|
492
|
+
}
|
|
493
|
+
} as any, // Using 'as any' to bypass type checking for the new field
|
|
494
|
+
logger: mockLogger
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Note: Debug logging is enabled in onConnected() when ASR request is sent
|
|
498
|
+
// So we need to test after connection, but for now we verify the config is accepted
|
|
499
|
+
expect(testClient.getAudioUtteranceId()).toBeDefined();
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should respect debugCommand.enableDebugLog flag', () => {
|
|
503
|
+
const mockLogger = jest.fn();
|
|
504
|
+
|
|
505
|
+
// Client with debug logging explicitly disabled
|
|
506
|
+
const clientNoDebug = new RealTimeTwoWayWebSocketRecognitionClient({
|
|
507
|
+
url: 'ws://test.example.com/recognize',
|
|
508
|
+
asrRequestConfig: {
|
|
509
|
+
provider: 'deepgram',
|
|
510
|
+
language: 'en',
|
|
511
|
+
sampleRate: 16000,
|
|
512
|
+
encoding: 'linear16',
|
|
513
|
+
debugCommand: {
|
|
514
|
+
enableDebugLog: false
|
|
515
|
+
}
|
|
516
|
+
} as any,
|
|
517
|
+
logger: mockLogger
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
expect(clientNoDebug.getAudioUtteranceId()).toBeDefined();
|
|
521
|
+
|
|
522
|
+
// Debug logs should not be called
|
|
523
|
+
const debugCalls = mockLogger.mock.calls.filter(call => call[0] === 'debug');
|
|
524
|
+
expect(debugCalls.length).toBe(0);
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Helper functions
|
|
529
|
+
async function setupConnectedClient() {
|
|
530
|
+
const connectPromise = client.connect();
|
|
531
|
+
mockWs.readyState = MockWebSocket.OPEN;
|
|
532
|
+
const openHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'open')[1];
|
|
533
|
+
openHandler();
|
|
534
|
+
await connectPromise;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function setupReadyClient() {
|
|
538
|
+
await setupConnectedClient();
|
|
539
|
+
const messageHandler = mockWs.on.mock.calls.find((call: any[]) => call[0] === 'message')[1];
|
|
540
|
+
const readyMessage = JSON.stringify({
|
|
541
|
+
v: 1,
|
|
542
|
+
type: 'message',
|
|
543
|
+
data: {
|
|
544
|
+
type: 'ClientControlMessage',
|
|
545
|
+
action: 'ready_for_uploading_recording',
|
|
546
|
+
audioUtteranceId: 'test-utterance-id'
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
messageHandler(readyMessage);
|
|
550
|
+
}
|
|
551
|
+
});
|