UNPKG

@volley/recognition-client-sdk

Version:

Recognition Service TypeScript/Node.js Client SDK

312 lines (268 loc) 8.31 kB
/** * Unit tests for MessageHandler */ import { MessageHandler, MessageHandlerCallbacks } from './message-handler.js'; import { RecognitionResultTypeV1, ClientControlActionV1 } from '@recog/shared-types'; describe('MessageHandler', () => { let callbacks: MessageHandlerCallbacks; let handler: MessageHandler; let mockLogger: jest.Mock; beforeEach(() => { mockLogger = jest.fn(); callbacks = { onTranscript: jest.fn(), onFunctionCall: jest.fn(), onMetadata: jest.fn(), onError: jest.fn(), onControlMessage: jest.fn(), logger: mockLogger }; handler = new MessageHandler(callbacks); }); describe('handleMessage', () => { it('should handle transcription message', () => { const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'hello world', isFinal: true } }; handler.handleMessage(msg); expect(callbacks.onTranscript).toHaveBeenCalledWith(msg.data); }); it('should handle function call message', () => { const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.FUNCTION_CALL, functionName: 'testFunction', arguments: { arg1: 'value1' } } }; handler.handleMessage(msg); expect(callbacks.onFunctionCall).toHaveBeenCalledWith(msg.data); }); it('should handle metadata message', () => { const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.METADATA, metadata: { key: 'value' } } }; handler.handleMessage(msg); expect(callbacks.onMetadata).toHaveBeenCalledWith(msg.data); }); it('should handle error message', () => { const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.ERROR, error: 'test error', code: 'TEST_ERROR' } }; handler.handleMessage(msg); expect(callbacks.onError).toHaveBeenCalledWith(msg.data); }); it('should handle client control message', () => { const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.CLIENT_CONTROL_MESSAGE, action: ClientControlActionV1.STOP_RECORDING, audioUtteranceId: 'test-utterance' } }; handler.handleMessage(msg); expect(callbacks.onControlMessage).toHaveBeenCalledWith(msg.data); }); it('should handle unknown message type', () => { const msg = { v: 1, type: 'unknown_type', data: { type: 'unknown', content: 'test' } }; handler.handleMessage(msg); expect(mockLogger).toHaveBeenCalledWith( 'debug', 'Unknown message type', expect.objectContaining({ type: 'unknown' }) ); }); it('should log all incoming messages', () => { const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'test' } }; handler.handleMessage(msg); expect(mockLogger).toHaveBeenCalledWith( 'debug', 'Received WebSocket message', expect.objectContaining({ msgType: 'recognition_result', msgDataType: RecognitionResultTypeV1.TRANSCRIPTION }) ); }); it('should handle primitive msg.data', () => { const msg = { v: 1, type: 'recognition_result', data: 'primitive string' }; handler.handleMessage(msg); expect(mockLogger).toHaveBeenCalledWith( 'error', 'Received primitive msg.data from server', expect.objectContaining({ dataType: 'string', data: 'primitive string' }) ); }); it('should handle message without data field', () => { const msg = { v: 1, type: RecognitionResultTypeV1.METADATA, data: { type: RecognitionResultTypeV1.METADATA, metadata: { key: 'value' } } }; handler.handleMessage(msg); expect(callbacks.onMetadata).toHaveBeenCalled(); }); it('should work without logger', () => { const callbacksNoLogger = { onTranscript: jest.fn(), onFunctionCall: jest.fn(), onMetadata: jest.fn(), onError: jest.fn(), onControlMessage: jest.fn() }; const handlerNoLogger = new MessageHandler(callbacksNoLogger); const msg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'test' } }; expect(() => handlerNoLogger.handleMessage(msg)).not.toThrow(); expect(callbacksNoLogger.onTranscript).toHaveBeenCalled(); }); }); describe('setSessionStartTime', () => { it('should set session start time', () => { const startTime = Date.now(); handler.setSessionStartTime(startTime); const metrics = handler.getMetrics(); expect(metrics.sessionStartTime).toBe(startTime); }); }); describe('getMetrics', () => { it('should return initial metrics', () => { const metrics = handler.getMetrics(); expect(metrics).toEqual({ sessionStartTime: null, firstTranscriptTime: null, timeToFirstTranscript: null }); }); it('should track time to first transcript', () => { const startTime = Date.now(); handler.setSessionStartTime(startTime); // Simulate delay before first transcript const transcriptMsg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'first transcript', isFinal: false } }; handler.handleMessage(transcriptMsg); const metrics = handler.getMetrics(); expect(metrics.sessionStartTime).toBe(startTime); expect(metrics.firstTranscriptTime).toBeGreaterThanOrEqual(startTime); expect(metrics.timeToFirstTranscript).toBeGreaterThanOrEqual(0); }); it('should log time to first transcript', () => { const startTime = Date.now(); handler.setSessionStartTime(startTime); const transcriptMsg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'first transcript' } }; handler.handleMessage(transcriptMsg); expect(mockLogger).toHaveBeenCalledWith( 'debug', 'First transcript received', expect.objectContaining({ timeToFirstTranscriptMs: expect.any(Number) }) ); }); it('should only track first transcript time once', () => { const startTime = Date.now(); handler.setSessionStartTime(startTime); // First transcript handler.handleMessage({ v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'first' } }); const firstMetrics = handler.getMetrics(); const firstTranscriptTime = firstMetrics.firstTranscriptTime; // Second transcript handler.handleMessage({ v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'second' } }); const secondMetrics = handler.getMetrics(); expect(secondMetrics.firstTranscriptTime).toBe(firstTranscriptTime); }); it('should not track transcript time without session start', () => { const transcriptMsg = { v: 1, type: 'recognition_result', data: { type: RecognitionResultTypeV1.TRANSCRIPTION, transcript: 'test' } }; handler.handleMessage(transcriptMsg); const metrics = handler.getMetrics(); expect(metrics.firstTranscriptTime).toBeNull(); expect(metrics.timeToFirstTranscript).toBeNull(); }); }); });