@volley/recognition-client-sdk
Version:
Recognition Service TypeScript/Node.js Client SDK
253 lines (227 loc) • 8.17 kB
text/typescript
/**
* Unit tests for URL Builder
*/
import { RecognitionContextTypeV1, STAGES } from '@recog/shared-types';
// Mock the shared-config module BEFORE importing the module under test
const mockGetRecognitionServiceBase = jest.fn();
jest.mock('@recog/shared-config', () => ({
getRecognitionServiceBase: mockGetRecognitionServiceBase
}));
import { buildWebSocketUrl, UrlBuilderConfig } from './url-builder.js';
describe('buildWebSocketUrl', () => {
const baseConfig: UrlBuilderConfig = {
audioUtteranceId: 'test-utterance-123'
};
beforeEach(() => {
// Clear and reset mock before each test
mockGetRecognitionServiceBase.mockClear();
mockGetRecognitionServiceBase.mockReturnValue({
wsBase: 'wss://recognition.volley.com'
});
});
it('should build URL with only audioUtteranceId', () => {
const url = buildWebSocketUrl(baseConfig);
expect(url).toContain('audioUtteranceId=test-utterance-123');
});
it('should use default production URL if neither url nor stage provided', () => {
const url = buildWebSocketUrl(baseConfig);
expect(url).toContain('wss://recognition.volley.com/ws/v1/recognize');
expect(mockGetRecognitionServiceBase).toHaveBeenCalledWith('production');
});
it('should use stage parameter to build URL', () => {
mockGetRecognitionServiceBase.mockReturnValue({
wsBase: 'wss://recognition-staging.volley.com'
});
const config = {
...baseConfig,
stage: 'staging'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('wss://recognition-staging.volley.com/ws/v1/recognize');
expect(mockGetRecognitionServiceBase).toHaveBeenCalledWith('staging');
});
it('should accept Stage enum as stage parameter', () => {
mockGetRecognitionServiceBase.mockReturnValue({
wsBase: 'wss://recognition-dev.volley.com'
});
const config = {
...baseConfig,
stage: STAGES.DEV
};
const url = buildWebSocketUrl(config);
expect(url).toContain('wss://recognition-dev.volley.com/ws/v1/recognize');
expect(mockGetRecognitionServiceBase).toHaveBeenCalledWith(STAGES.DEV);
});
it('should prioritize url over stage when both provided', () => {
const config = {
...baseConfig,
url: 'ws://localhost:3101/ws/v1/recognize',
stage: 'staging'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('ws://localhost:3101/ws/v1/recognize');
// Mock should not be called when explicit URL is provided
expect(mockGetRecognitionServiceBase).not.toHaveBeenCalled();
});
it('should use custom URL if provided (backward compatibility)', () => {
const config = {
...baseConfig,
url: 'ws://localhost:3101/ws/v1/recognize'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('ws://localhost:3101/ws/v1/recognize');
// Mock should not be called when explicit URL is provided
expect(mockGetRecognitionServiceBase).not.toHaveBeenCalled();
});
it('should add userId to query parameters', () => {
const config = {
...baseConfig,
userId: 'user-123'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('userId=user-123');
});
it('should add gameSessionId to query parameters', () => {
const config = {
...baseConfig,
gameSessionId: 'session-456'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('gameSessionId=session-456');
});
it('should add deviceId to query parameters', () => {
const config = {
...baseConfig,
deviceId: 'device-789'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('deviceId=device-789');
});
it('should add accountId to query parameters', () => {
const config = {
...baseConfig,
accountId: 'account-abc'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('accountId=account-abc');
});
it('should add questionAnswerId to query parameters', () => {
const config = {
...baseConfig,
questionAnswerId: 'qa-xyz'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('questionAnswerId=qa-xyz');
});
it('should add platform to query parameters', () => {
const config = {
...baseConfig,
platform: 'ios'
};
const url = buildWebSocketUrl(config);
expect(url).toContain('platform=ios');
});
it('should add gameId and gamePhase from gameContext', () => {
const config = {
...baseConfig,
gameContext: {
type: RecognitionContextTypeV1.GAME_CONTEXT as const,
gameId: 'test-game',
gamePhase: 'test-phase'
}
};
const url = buildWebSocketUrl(config);
expect(url).toContain('gameId=test-game');
expect(url).toContain('gamePhase=test-phase');
});
it('should serialize callbackUrls as JSON', () => {
const callbackUrls = [
{ url: 'https://example.com/callback', messageTypes: ['transcript'] },
{ url: 'https://example.com/metadata', messageTypes: ['metadata'] }
];
const config = {
...baseConfig,
callbackUrls
};
const url = buildWebSocketUrl(config);
expect(url).toContain('callbackUrls=');
// Decode and verify JSON structure
const urlObj = new URL(url);
const callbackUrlsParam = urlObj.searchParams.get('callbackUrls');
expect(callbackUrlsParam).toBeDefined();
expect(JSON.parse(callbackUrlsParam!)).toEqual(callbackUrls);
});
it('should not add callbackUrls if empty array', () => {
const config = {
...baseConfig,
callbackUrls: []
};
const url = buildWebSocketUrl(config);
expect(url).not.toContain('callbackUrls=');
});
it('should not add optional parameters if not provided', () => {
const url = buildWebSocketUrl(baseConfig);
expect(url).not.toContain('userId=');
expect(url).not.toContain('gameSessionId=');
expect(url).not.toContain('deviceId=');
expect(url).not.toContain('accountId=');
expect(url).not.toContain('questionAnswerId=');
expect(url).not.toContain('platform=');
expect(url).not.toContain('gameId=');
expect(url).not.toContain('gamePhase=');
});
it('should handle all parameters together', () => {
const config: UrlBuilderConfig = {
url: 'ws://localhost:3101/ws/v1/recognize',
audioUtteranceId: 'test-utterance-123',
userId: 'user-123',
gameSessionId: 'session-456',
deviceId: 'device-789',
accountId: 'account-abc',
questionAnswerId: 'qa-xyz',
platform: 'ios',
gameContext: {
type: RecognitionContextTypeV1.GAME_CONTEXT as const,
gameId: 'test-game',
gamePhase: 'test-phase'
},
callbackUrls: [
{ url: 'https://example.com/callback', messageTypes: ['transcript'] }
]
};
const url = buildWebSocketUrl(config);
expect(url).toContain('ws://localhost:3101/ws/v1/recognize');
expect(url).toContain('audioUtteranceId=test-utterance-123');
expect(url).toContain('userId=user-123');
expect(url).toContain('gameSessionId=session-456');
expect(url).toContain('deviceId=device-789');
expect(url).toContain('accountId=account-abc');
expect(url).toContain('questionAnswerId=qa-xyz');
expect(url).toContain('platform=ios');
expect(url).toContain('gameId=test-game');
expect(url).toContain('gamePhase=test-phase');
expect(url).toContain('callbackUrls=');
});
it('should properly encode special characters in parameters', () => {
const config = {
...baseConfig,
userId: 'user@example.com',
platform: 'iOS 17.0'
};
const url = buildWebSocketUrl(config);
// URL should be properly encoded
expect(url).toBeDefined();
const urlObj = new URL(url);
expect(urlObj.searchParams.get('userId')).toBe('user@example.com');
expect(urlObj.searchParams.get('platform')).toBe('iOS 17.0');
});
it('should create valid URL object', () => {
const config = {
...baseConfig,
userId: 'user-123'
};
const url = buildWebSocketUrl(config);
// Should not throw
expect(() => new URL(url)).not.toThrow();
});
});