Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 162 additions & 42 deletions __tests__/pages/assistant.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const mockSocket = {
emit: jest.fn(),
auth: { token: 'mock-token' },
hasListeners: jest.fn(() => false),
connected: true,
};

jest.mock('socket.io-client', () => ({
Expand Down Expand Up @@ -1323,82 +1324,201 @@ describe('EventAssistantRoom', () => {
});
});

describe('Jargon message routing', () => {
it('displays jargon clarification messages inline in the assistant panel when jargonFilterAgentId is set', async () => {
const conversationWithJargon = {
describe('Multi-agent channel subscription and message fetching', () => {
it('subscribes to direct channels for all agents in the conversation', async () => {
(createConversationFromData as jest.Mock).mockResolvedValue({
agents: [
{ id: 'agent-123', agentType: 'eventAssistant' },
{ id: 'jargon-agent-456', agentType: 'jargonFilterAgent' },
{ id: 'future-agent-789', agentType: 'someNewAgent' },
],
type: { name: 'eventAssistant' },
});

(RetrieveData as jest.Mock).mockImplementation((path: string) => {
if (path.startsWith('conversations/')) return Promise.resolve({ agents: [] });
return Promise.resolve([]);
});

await act(async () => {
render(<EventAssistantRoom authType={'guest'} />);
});

await waitFor(() => {
expect(mockSocket.emit).toHaveBeenCalledWith(
'conversation:join',
expect.objectContaining({
channels: expect.arrayContaining([
{ name: 'direct-user-123-agent-123', passcode: null, direct: true },
{ name: 'direct-user-123-jargon-agent-456', passcode: null, direct: true },
{ name: 'direct-user-123-future-agent-789', passcode: null, direct: true },
]),
}),
);
});
});

it('fetches messages from all agent direct channels', async () => {
(createConversationFromData as jest.Mock).mockResolvedValue({
agents: [
{ id: 'agent-123', agentType: 'eventAssistant' },
{ id: 'jargon-agent-456', agentType: 'jargonFilterAgent' },
],
type: { name: 'eventAssistant' },
});

(RetrieveData as jest.Mock).mockImplementation((path: string) => {
if (path.startsWith('conversations/')) return Promise.resolve({ agents: [] });
return Promise.resolve([]);
});

await act(async () => {
render(<EventAssistantRoom authType={'guest'} />);
});

await waitFor(() => {
expect(RetrieveData).toHaveBeenCalledWith(
'messages/test-conversation-id?channel=direct-user-123-agent-123',
'mock-access-token',
);
expect(RetrieveData).toHaveBeenCalledWith(
'messages/test-conversation-id?channel=direct-user-123-jargon-agent-456',
'mock-access-token',
);
});
});

it('displays messages from any agent direct channel in the assistant panel', async () => {
const secondaryAgentMessage = {
id: 'secondary-msg-1',
body: { type: 'jargon_clarification', text: 'An SLO is a reliability target.', sourceText: 'Our SLOs...' },
bodyType: 'json',
fromAgent: true,
channels: ['direct-user-123-jargon-agent-456'],
pseudonym: 'Jargon Filter',
createdAt: '2024-01-01T10:00:00Z',
pause: false,
visible: true,
upVotes: [],
downVotes: [],
};
(createConversationFromData as jest.Mock).mockResolvedValue(conversationWithJargon);

(createConversationFromData as jest.Mock).mockResolvedValue({
agents: [
{ id: 'agent-123', agentType: 'eventAssistant' },
{ id: 'jargon-agent-456', agentType: 'jargonFilterAgent' },
],
type: { name: 'eventAssistant' },
});

(RetrieveData as jest.Mock).mockImplementation((path: string) => {
if (path.startsWith('conversations/')) {
return Promise.resolve(conversationWithJargon);
} else if (path.includes('users/user/') && path.includes('/preferences')) {
return Promise.resolve({ jargonClarification: true });
} else if (path.startsWith('messages/')) {
return Promise.resolve([]);
}
return Promise.resolve({});
if (path.startsWith('conversations/')) return Promise.resolve({ agents: [] });
if (path.includes('jargon-agent-456')) return Promise.resolve([secondaryAgentMessage]);
return Promise.resolve([]);
});

const user = userEvent.setup();

await act(async () => {
render(<EventAssistantRoom authType={'guest'} />);
});

// Wait for jargonFilterAgentId to be set from conversation data
await waitFor(() => expect(screen.getAllByLabelText('Berkie').length).toBeGreaterThan(0));
await user.click(screen.getAllByLabelText('Berkie')[0]);

await waitFor(() => {
expect(mockSocket.on).toHaveBeenCalledWith('message:new', expect.any(Function));
expect(screen.getByText('An SLO is a reliability target.')).toBeInTheDocument();
});
});

it('displays real-time messages from any agent direct channel in the assistant panel', async () => {
(createConversationFromData as jest.Mock).mockResolvedValue({
agents: [
{ id: 'agent-123', agentType: 'eventAssistant' },
{ id: 'jargon-agent-456', agentType: 'jargonFilterAgent' },
],
type: { name: 'eventAssistant' },
});

(RetrieveData as jest.Mock).mockImplementation((path: string) => {
if (path.startsWith('conversations/')) return Promise.resolve({ agents: [] });
return Promise.resolve([]);
});

// Retrieve the most recently registered message:new handler — it has jargonFilterAgentId in its closure
await act(async () => {
render(<EventAssistantRoom authType={'guest'} />);
});

await waitFor(() => expect(mockSocket.on).toHaveBeenCalledWith('message:new', expect.any(Function)));

const messageHandler: Function = mockSocket.on.mock.calls
.filter(([event]: [string]) => event === 'message:new')
.map(([, handler]: [string, Function]) => handler)
.at(-1)!;

expect(messageHandler).toBeDefined();
expect(typeof messageHandler).toBe('function');
const user = userEvent.setup();
await waitFor(() => expect(screen.getAllByLabelText('Berkie').length).toBeGreaterThan(0));
await user.click(screen.getAllByLabelText('Berkie')[0]);

// Simulate a jargon clarification message arriving on the jargon filter's direct channel
const jargonMessage = {
id: 'msg-jargon-1',
body: {
type: 'jargon_clarification',
text: 'An SLO is a reliability target.',
sourceText: 'Our SLOs...',
},
bodyType: 'json',
act(() => {
messageHandler({
id: 'msg-jargon-1',
body: { type: 'jargon_clarification', text: 'An SLO is a reliability target.', sourceText: 'Our SLOs...' },
bodyType: 'json',
fromAgent: true,
channels: ['direct-user-123-jargon-agent-456'],
pseudonym: 'Jargon Filter Agent',
pause: false,
visible: true,
upVotes: [],
downVotes: [],
});
});

await waitFor(() => {
expect(screen.getByText('An SLO is a reliability target.')).toBeInTheDocument();
});
});

it('routes replies to the channel of the parent message, not the primary agent channel', async () => {
const secondaryAgentMessage = {
id: 'jargon-msg-1',
body: 'A clarification from jargon agent',
fromAgent: true,
channels: ['direct-user-123-jargon-agent-456'],
pseudonym: 'Jargon Filter Agent',
pseudonymId: 'jargon-agent-456',
conversation: 'test-conversation-id',
pseudonym: 'Jargon Filter',
createdAt: '2024-01-01T10:00:00Z',
pause: false,
visible: true,
upVotes: [],
downVotes: [],
};

// Switch to assistant tab before receiving the message
await waitFor(() => {
const assistantTabs = screen.queryAllByLabelText(/Berkie|Assistant/i);
expect(assistantTabs.length).toBeGreaterThan(0);
(createConversationFromData as jest.Mock).mockResolvedValue({
agents: [
{ id: 'agent-123', agentType: 'eventAssistant' },
{ id: 'jargon-agent-456', agentType: 'jargonFilterAgent' },
],
type: { name: 'eventAssistant' },
});

(RetrieveData as jest.Mock).mockImplementation((path: string) => {
if (path.startsWith('conversations/')) return Promise.resolve({ agents: [] });
if (path.includes('jargon-agent-456')) return Promise.resolve([secondaryAgentMessage]);
return Promise.resolve([]);
});

const assistantTab = screen.getAllByLabelText(/Berkie|Assistant/i)[0];
await userEvent.click(assistantTab);
const user = userEvent.setup();

act(() => {
messageHandler(jargonMessage);
await act(async () => {
render(<EventAssistantRoom authType={'guest'} />);
});

// Verify the jargon clarification content appears inline in the assistant panel
await waitFor(() => expect(screen.getAllByLabelText('Berkie').length).toBeGreaterThan(0));
await user.click(screen.getAllByLabelText('Berkie')[0]);

await waitFor(() => {
expect(screen.getByText('An SLO is a reliability target.')).toBeInTheDocument();
expect(screen.getByText('A clarification from jargon agent')).toBeInTheDocument();
});
});
});
Expand Down Expand Up @@ -1752,7 +1872,7 @@ describe('EventAssistantRoom', () => {
body: 'Welcome to the event!',
pseudonym: 'Berkie',
fromAgent: true,
channels: [],
channels: ['direct-user-123-agent-123'],
createdAt: '2024-01-01T09:59:00Z',
conversation: 'test-conversation-id',
pause: false,
Expand Down Expand Up @@ -1972,7 +2092,7 @@ describe('EventAssistantRoom', () => {
body: 'Intro message',
pseudonym: 'Berkie',
fromAgent: true,
channels: [],
channels: ['direct-user-123-agent-123'],
createdAt: '2024-01-01T09:59:00Z',
};

Expand Down Expand Up @@ -2080,7 +2200,7 @@ describe('EventAssistantRoom', () => {
body: 'Intro message',
pseudonym: 'Berkie',
fromAgent: true,
channels: [],
channels: ['direct-user-123-agent-123'],
createdAt: '2024-01-01T09:59:00Z',
};

Expand Down
Loading