Skip to content

Commit c7b341b

Browse files
committed
fix: improving testcases for sdk
1 parent 4836c94 commit c7b341b

File tree

8 files changed

+1215
-31
lines changed

8 files changed

+1215
-31
lines changed

src/index.ts

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -263,30 +263,7 @@ export {
263263
GetAllAssistantKnowledgeResponse,
264264
} from "@/rapida/clients/protos/assistant-knowledge_pb";
265265
export {
266-
BedrockService,
267-
OpenAiService,
268-
AzureService,
269-
ReplicateService,
270-
AnthropicService,
271-
CohereService,
272-
HuggingfaceService,
273-
MistralService,
274-
StabilityAiService,
275-
TogetherAiService,
276-
DeepInfraService,
277-
VoyageAiService,
278-
BedrockServiceClient,
279-
OpenAiServiceClient,
280-
AzureServiceClient,
281-
ReplicateServiceClient,
282-
AnthropicServiceClient,
283-
CohereServiceClient,
284-
HuggingfaceServiceClient,
285-
MistralServiceClient,
286-
StabilityAiServiceClient,
287-
TogetherAiServiceClient,
288-
DeepInfraServiceClient,
289-
VoyageAiServiceClient,
266+
UnifiedProviderService
290267
} from "@/rapida/clients/protos/integration-api_pb_service";
291268
export {
292269
AuditLog,

tests/agents/agent.test.ts

Lines changed: 265 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,91 @@ jest.mock('@/rapida/clients/protos/talk-api_pb', () => ({
2424
ACTION: 5,
2525
},
2626
},
27-
CreateConversationMetricRequest: jest.fn(),
28-
CreateMessageMetricRequest: jest.fn(),
27+
CreateConversationMetricRequest: jest.fn().mockImplementation(() => ({
28+
setAssistantid: jest.fn(),
29+
setAssistantconversationid: jest.fn(),
30+
addMetrics: jest.fn(),
31+
})),
32+
CreateMessageMetricRequest: jest.fn().mockImplementation(() => ({
33+
setAssistantid: jest.fn(),
34+
setAssistantconversationid: jest.fn(),
35+
setMessageid: jest.fn(),
36+
addMetrics: jest.fn(),
37+
})),
38+
StreamMode: {
39+
STREAM_MODE_AUDIO: 1,
40+
STREAM_MODE_TEXT: 2,
41+
STREAM_MODE_BOTH: 3,
42+
},
43+
ConversationInitialization: jest.fn().mockImplementation(() => {
44+
const options = new Map();
45+
const metadata = new Map();
46+
const args = new Map();
47+
return {
48+
setAssistantconversationid: jest.fn(),
49+
setAssistant: jest.fn(),
50+
getOptionsMap: jest.fn(() => options),
51+
getMetadataMap: jest.fn(() => metadata),
52+
getArgsMap: jest.fn(() => args),
53+
setStreammode: jest.fn(),
54+
setWeb: jest.fn(),
55+
};
56+
}),
57+
ConversationConfiguration: jest.fn().mockImplementation(() => ({
58+
setStreammode: jest.fn(),
59+
})),
60+
WebIdentity: jest.fn().mockImplementation(() => ({
61+
setUserid: jest.fn(),
62+
})),
63+
}));
64+
65+
jest.mock('@/rapida/clients/protos/webrtc_pb', () => ({
66+
WebTalkRequest: jest.fn().mockImplementation(() => ({
67+
hasInitialization: jest.fn().mockReturnValue(false),
68+
hasConfiguration: jest.fn().mockReturnValue(false),
69+
hasMessage: jest.fn().mockReturnValue(false),
70+
hasSignaling: jest.fn().mockReturnValue(false),
71+
getMessage: jest.fn(),
72+
setInitialization: jest.fn(),
73+
setConfiguration: jest.fn(),
74+
})),
75+
WebTalkResponse: {
76+
DataCase: {
77+
USER: 2,
78+
ASSISTANT: 3,
79+
},
80+
},
81+
}));
82+
83+
jest.mock('@/rapida/clients/protos/assistant-api_pb', () => ({
84+
GetAssistantRequest: jest.fn().mockImplementation(() => ({
85+
setAssistantdefinition: jest.fn(),
86+
})),
87+
GetAssistantResponse: jest.fn(),
2988
}));
3089

3190
jest.mock('@/rapida/clients/protos/common_pb', () => ({
32-
AssistantDefinition: jest.fn(),
91+
AssistantDefinition: jest.fn().mockImplementation(() => {
92+
let assistantId = '';
93+
let version = '';
94+
return {
95+
setAssistantid: jest.fn((v: string) => { assistantId = v; }),
96+
getAssistantid: jest.fn(() => assistantId),
97+
setVersion: jest.fn((v: string) => { version = v; }),
98+
getVersion: jest.fn(() => version),
99+
};
100+
}),
33101
AudioConfig: {
34102
AudioFormat: { LINEAR16: 1 },
35103
},
36104
StreamConfig: jest.fn().mockImplementation(() => ({
37105
setAudio: jest.fn(),
38106
})),
39-
Metric: jest.fn(),
107+
Metric: jest.fn().mockImplementation(() => ({
108+
setName: jest.fn(),
109+
setValue: jest.fn(),
110+
setDescription: jest.fn(),
111+
})),
40112
AssistantConversationConfiguration: jest.fn(),
41113
AssistantConversationMessageTextContent: jest.fn().mockImplementation(() => ({
42114
setContent: jest.fn(),
@@ -69,10 +141,21 @@ jest.mock('@/rapida/types/feedback', () => ({
69141
getFeedback: jest.fn(),
70142
}));
71143

72-
import { Agent } from '@/rapida/agents/';
144+
import {
145+
Agent,
146+
buildConfigurationRequest,
147+
buildInitializationRequest,
148+
describeRequest,
149+
describeResponse,
150+
resolveStreamMode,
151+
} from '@/rapida/agents/';
73152
import { ConnectionState } from '@/rapida/types/connection-state';
74153
import { AgentEvent } from '@/rapida/types/agent-event';
75154
import { MessageRole, MessageStatus } from '@/rapida/types/message';
155+
import { CreateConversationMetric, CreateMessageMetric } from '@/rapida/clients/talk';
156+
import { GetAssistant } from '@/rapida/clients/assistant';
157+
import { getFeedback } from '@/rapida/types/feedback';
158+
import { Channel } from '@/rapida/types/channel';
76159

77160
// Create a concrete implementation for testing since Agent may be abstract
78161
class TestAgent extends Agent {
@@ -88,13 +171,22 @@ class TestAgent extends Agent {
88171
public getAgentConfig() {
89172
return this.agentConfig;
90173
}
174+
175+
public setConversation(id: string) {
176+
this.changeConversation(id);
177+
}
178+
179+
public disconnectBase() {
180+
return this.disconnectAgent();
181+
}
91182
}
92183

93184
describe('Agent', () => {
94185
let agent: TestAgent;
95186

96187
const mockConnectionConfig = {
97188
endpoint: 'https://api.test.com',
189+
onConnectionChange: jest.fn(),
98190
};
99191

100192
const mockAgentConfig = {
@@ -107,6 +199,10 @@ describe('Agent', () => {
107199
channel: 'audio',
108200
playerOption: { sampleRate: 24000, format: 'pcm' },
109201
},
202+
definition: {
203+
getAssistantid: () => 'test-agent',
204+
},
205+
version: 'v1',
110206
};
111207

112208
beforeEach(() => {
@@ -259,4 +355,168 @@ describe('Agent', () => {
259355
expect(agent.agentMessages[0].status).toBe(MessageStatus.Pending);
260356
});
261357
});
358+
359+
describe('advanced agent behavior', () => {
360+
it('throws on base switchAgent()', async () => {
361+
await expect(agent.switchAgent(mockAgentConfig as any)).rejects.toThrow(
362+
'switchAgent must be implemented by subclass',
363+
);
364+
});
365+
366+
it('registers callback', () => {
367+
const cb = { onUserMessage: jest.fn() };
368+
agent.registerCallback(cb as any);
369+
expect((agent as any).agentCallbacks).toContain(cb);
370+
});
371+
372+
it('disconnectAgent() is idempotent when already disconnected', async () => {
373+
await agent.disconnectBase();
374+
expect(mockConnectionConfig.onConnectionChange).not.toHaveBeenCalled();
375+
});
376+
377+
it('createMessageMetric throws without active conversation', () => {
378+
expect(() =>
379+
agent.createMessageMetric('m-1', [{ name: 'n', description: 'd', value: 'good' }]),
380+
).toThrow('Cannot create message metric: no active conversation');
381+
});
382+
383+
it('createConversationMetric throws without active conversation', () => {
384+
expect(() =>
385+
agent.createConversationMetric([{ name: 'n', description: 'd', value: 'good' }]),
386+
).toThrow('Cannot create conversation metric: no active conversation');
387+
});
388+
389+
it('creates message metric and emits feedback', async () => {
390+
(getFeedback as jest.Mock).mockReturnValue('POSITIVE');
391+
const feedbackListener = jest.fn();
392+
agent.on(AgentEvent.FeedbackEvent, feedbackListener);
393+
agent.setConversation('conv-1');
394+
agent.agentMessages = [
395+
{
396+
id: 'msg-1',
397+
role: MessageRole.System,
398+
messages: ['hello'],
399+
time: new Date(),
400+
status: MessageStatus.Complete,
401+
} as any,
402+
];
403+
404+
agent.createMessageMetric('msg-1', [
405+
{ name: 'quality', description: 'score', value: 'good' },
406+
]);
407+
408+
await Promise.resolve();
409+
410+
expect(CreateMessageMetric).toHaveBeenCalled();
411+
expect(feedbackListener).toHaveBeenCalledWith('message', 'POSITIVE');
412+
expect((agent.agentMessages[0] as any).feedback).toBe('POSITIVE');
413+
});
414+
415+
it('handles empty message metrics safely', async () => {
416+
agent.setConversation('conv-1');
417+
agent.createMessageMetric('msg-1', []);
418+
await Promise.resolve();
419+
expect(CreateMessageMetric).toHaveBeenCalled();
420+
});
421+
422+
it('creates conversation metric when conversation exists', () => {
423+
agent.setConversation('conv-2');
424+
agent.createConversationMetric([
425+
{ name: 'latency', description: 'ms', value: '123' },
426+
]);
427+
expect(CreateConversationMetric).toHaveBeenCalled();
428+
});
429+
430+
it('returns assistant from getAssistant()', async () => {
431+
const result = await agent.getAssistant();
432+
expect(GetAssistant).toHaveBeenCalled();
433+
expect(result).toBeDefined();
434+
});
435+
436+
it('changeConversation sets conversation once', () => {
437+
agent.setConversation('conv-1');
438+
agent.setConversation('conv-2');
439+
expect(agent.conversationId).toBe('conv-1');
440+
});
441+
});
442+
443+
describe('request/response helpers', () => {
444+
it('describeRequest formats known request types', () => {
445+
expect(
446+
describeRequest({
447+
hasInitialization: () => true,
448+
hasConfiguration: () => false,
449+
hasMessage: () => false,
450+
hasSignaling: () => false,
451+
} as any),
452+
).toBe('Initialization');
453+
454+
expect(
455+
describeRequest({
456+
hasInitialization: () => false,
457+
hasConfiguration: () => false,
458+
hasMessage: () => true,
459+
getMessage: () => ({ getText: () => 'hello world' }),
460+
hasSignaling: () => false,
461+
} as any),
462+
).toContain('Text(');
463+
});
464+
465+
it('describeResponse formats empty and mixed responses', () => {
466+
expect(
467+
describeResponse({
468+
hasInitialization: () => false,
469+
hasConfiguration: () => false,
470+
hasAssistant: () => false,
471+
hasUser: () => false,
472+
hasInterruption: () => false,
473+
hasDirective: () => false,
474+
hasSignaling: () => false,
475+
} as any),
476+
).toBe('Empty');
477+
478+
const mixed = describeResponse({
479+
hasInitialization: () => true,
480+
hasConfiguration: () => true,
481+
hasAssistant: () => true,
482+
getAssistant: () => ({ getText: () => 'assistant text' }),
483+
hasUser: () => true,
484+
getUser: () => ({ getText: () => 'user text' }),
485+
hasInterruption: () => true,
486+
hasDirective: () => true,
487+
hasSignaling: () => true,
488+
} as any);
489+
490+
expect(mixed).toContain('Initialization');
491+
expect(mixed).toContain('Configuration');
492+
expect(mixed).toContain('Assistant(');
493+
expect(mixed).toContain('User(');
494+
expect(mixed).toContain('Interruption');
495+
expect(mixed).toContain('Directive');
496+
expect(mixed).toContain('Signaling');
497+
});
498+
499+
it('resolveStreamMode maps channels', () => {
500+
expect(resolveStreamMode(Channel.Audio)).toBeDefined();
501+
expect(resolveStreamMode(Channel.Text)).toBeDefined();
502+
expect(resolveStreamMode('unknown' as any)).toBeDefined();
503+
});
504+
505+
it('builds initialization/configuration requests', () => {
506+
const config = {
507+
definition: {},
508+
options: new Map([['k1', { x: 1 }]]),
509+
metadata: new Map([['k2', { y: 2 }]]),
510+
arguments: new Map([['k3', { z: 3 }]]),
511+
inputOptions: { channel: Channel.Text },
512+
userIdentifier: { id: 'user-1' },
513+
} as any;
514+
515+
const initReq = buildInitializationRequest(config, 'conv-10');
516+
expect(initReq).toBeDefined();
517+
518+
const confReq = buildConfigurationRequest(Channel.Audio);
519+
expect(confReq).toBeDefined();
520+
});
521+
});
262522
});

0 commit comments

Comments
 (0)