@volley/recognition-client-sdk
Version:
Recognition Service TypeScript/Node.js Client SDK
312 lines (268 loc) • 8.31 kB
text/typescript
/**
* 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();
});
});
});