mirror of https://github.com/buster-so/buster.git
334 lines
8.9 KiB
TypeScript
334 lines
8.9 KiB
TypeScript
import type { CoreMessage } from 'ai';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import type { MessageContext } from '../types';
|
|
import {
|
|
buildConversationHistory,
|
|
buildWorkflowInput,
|
|
concatenateDatasets,
|
|
formatPreviousMessages,
|
|
} from './data-transformers';
|
|
|
|
// Mock console.error to avoid noise in tests
|
|
beforeEach(() => {
|
|
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
});
|
|
|
|
describe('data-transformers', () => {
|
|
describe('buildConversationHistory', () => {
|
|
it('should combine multiple message arrays correctly', () => {
|
|
const messages = [
|
|
{
|
|
id: '1',
|
|
rawLlmMessages: [
|
|
{ role: 'user', content: 'Hello' } as CoreMessage,
|
|
{ role: 'assistant', content: 'Hi there' } as CoreMessage,
|
|
],
|
|
createdAt: new Date('2024-01-01T00:00:00Z'),
|
|
},
|
|
{
|
|
id: '2',
|
|
rawLlmMessages: [
|
|
{ role: 'user', content: 'How are you?' } as CoreMessage,
|
|
{ role: 'assistant', content: 'I am fine' } as CoreMessage,
|
|
],
|
|
createdAt: new Date('2024-01-01T00:01:00Z'),
|
|
},
|
|
];
|
|
|
|
const result = buildConversationHistory(messages);
|
|
|
|
expect(result).toHaveLength(4);
|
|
expect(result?.[0]).toEqual({ role: 'user', content: 'Hello' });
|
|
expect(result?.[3]).toEqual({ role: 'assistant', content: 'I am fine' });
|
|
});
|
|
|
|
it('should handle empty messages array', () => {
|
|
const result = buildConversationHistory([]);
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('should skip messages with null rawLlmMessages', () => {
|
|
const messages = [
|
|
{
|
|
id: '1',
|
|
rawLlmMessages: null as any,
|
|
createdAt: new Date(),
|
|
},
|
|
{
|
|
id: '2',
|
|
rawLlmMessages: [{ role: 'user', content: 'Test' } as CoreMessage],
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const result = buildConversationHistory(messages);
|
|
expect(result).toHaveLength(1);
|
|
expect(result?.[0]).toEqual({ role: 'user', content: 'Test' });
|
|
});
|
|
|
|
it('should handle non-array rawLlmMessages gracefully', () => {
|
|
const messages = [
|
|
{
|
|
id: '1',
|
|
rawLlmMessages: 'invalid data' as any,
|
|
createdAt: new Date(),
|
|
},
|
|
{
|
|
id: '2',
|
|
rawLlmMessages: [{ role: 'user', content: 'Valid message' } as CoreMessage],
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const result = buildConversationHistory(messages);
|
|
expect(result).toHaveLength(1);
|
|
expect(result?.[0]).toEqual({ role: 'user', content: 'Valid message' });
|
|
});
|
|
});
|
|
|
|
describe('formatPreviousMessages', () => {
|
|
it('should extract string representation correctly', () => {
|
|
const results = [
|
|
{
|
|
postProcessingMessage: { assumptions: ['Test assumption'] },
|
|
createdAt: new Date(),
|
|
},
|
|
{
|
|
postProcessingMessage: { message: 'Direct string message' },
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const formatted = formatPreviousMessages(results);
|
|
|
|
expect(formatted).toHaveLength(2);
|
|
expect(formatted[0]).toContain('assumptions');
|
|
expect(formatted[1]).toContain('Direct string message');
|
|
});
|
|
|
|
it('should handle complex nested objects', () => {
|
|
const results = [
|
|
{
|
|
postProcessingMessage: {
|
|
initial: {
|
|
assumptions: ['Complex assumption'],
|
|
flagForReview: true,
|
|
nested: {
|
|
deep: 'value',
|
|
},
|
|
},
|
|
},
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const formatted = formatPreviousMessages(results);
|
|
expect(formatted[0]).toContain('Complex assumption');
|
|
expect(formatted[0]).toContain('flagForReview');
|
|
expect(formatted[0]).toContain('deep');
|
|
});
|
|
|
|
it('should return empty array for no messages', () => {
|
|
const formatted = formatPreviousMessages([]);
|
|
expect(formatted).toEqual([]);
|
|
});
|
|
|
|
it('should filter out empty strings from errors', () => {
|
|
const results = [
|
|
{
|
|
postProcessingMessage: {}, // This will cause an error/empty result
|
|
createdAt: new Date(),
|
|
},
|
|
{
|
|
postProcessingMessage: { message: 'Valid message' },
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const formatted = formatPreviousMessages(results);
|
|
expect(formatted).toHaveLength(2);
|
|
expect(formatted[1]).toContain('Valid message');
|
|
});
|
|
});
|
|
|
|
describe('concatenateDatasets', () => {
|
|
it('should join with correct separator', () => {
|
|
const datasets = [
|
|
{
|
|
id: '1',
|
|
name: 'Dataset 1',
|
|
ymlFile: 'content1',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
deletedAt: null,
|
|
dataSourceId: 'ds1',
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Dataset 2',
|
|
ymlFile: 'content2',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
deletedAt: null,
|
|
dataSourceId: 'ds2',
|
|
},
|
|
];
|
|
|
|
const result = concatenateDatasets(datasets);
|
|
expect(result).toBe('content1\n---\ncontent2');
|
|
});
|
|
|
|
it('should filter null ymlFile entries', () => {
|
|
const datasets = [
|
|
{
|
|
id: '1',
|
|
name: 'Dataset 1',
|
|
ymlFile: 'content1',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
deletedAt: null,
|
|
dataSourceId: 'ds1',
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'Dataset 2',
|
|
ymlFile: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
deletedAt: null,
|
|
dataSourceId: 'ds2',
|
|
},
|
|
];
|
|
|
|
const result = concatenateDatasets(datasets);
|
|
expect(result).toBe('content1');
|
|
});
|
|
|
|
it('should return empty string for no datasets', () => {
|
|
const result = concatenateDatasets([]);
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('should return empty string if all datasets have null ymlFile', () => {
|
|
const datasets = [
|
|
{
|
|
id: '1',
|
|
name: 'Dataset 1',
|
|
ymlFile: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
deletedAt: null,
|
|
dataSourceId: 'ds1',
|
|
},
|
|
];
|
|
|
|
const result = concatenateDatasets(datasets);
|
|
expect(result).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('buildWorkflowInput', () => {
|
|
const baseMessageContext: MessageContext = {
|
|
id: 'msg-123',
|
|
chatId: 'chat-123',
|
|
createdBy: 'user-123',
|
|
createdAt: new Date(),
|
|
userName: 'John Doe',
|
|
organizationId: 'org-123',
|
|
};
|
|
|
|
const baseConversationMessages = [
|
|
{
|
|
id: '1',
|
|
rawLlmMessages: [{ role: 'user', content: 'Hello' }] as any,
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const basePreviousResults: any[] = [];
|
|
|
|
const baseDatasets = [
|
|
{
|
|
id: '1',
|
|
name: 'Dataset 1',
|
|
ymlFile: 'yaml content',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
deletedAt: null,
|
|
dataSourceId: 'ds1',
|
|
},
|
|
];
|
|
|
|
it('should build complete workflow input for initial message', () => {
|
|
const result = buildWorkflowInput(
|
|
baseMessageContext,
|
|
baseConversationMessages,
|
|
basePreviousResults,
|
|
baseDatasets,
|
|
false
|
|
);
|
|
|
|
expect(result).toEqual({
|
|
conversationHistory: [{ role: 'user', content: 'Hello' }],
|
|
userName: 'John Doe',
|
|
messageId: 'msg-123',
|
|
userId: 'user-123',
|
|
chatId: 'chat-123',
|
|
isFollowUp: false,
|
|
isSlackFollowUp: false,
|
|
previousMessages: [],
|
|
datasets: 'yaml content',
|
|
});
|
|
});
|
|
|
|
it('should build workflow input for follow-up message', () => {
|
|
const previousResults = [
|
|
{
|
|
postProcessingMessage: { assumptions: ['Previous assumption'] },
|
|
createdAt: new Date(),
|
|
},
|
|
];
|
|
|
|
const result = buildWorkflowInput(
|
|
baseMessageContext,
|
|
baseConversationMessages,
|
|
previousResults,
|
|
baseDatasets,
|
|
true
|
|
);
|
|
|
|
expect(result.isFollowUp).toBe(true);
|
|
expect(result.isSlackFollowUp).toBe(true);
|
|
expect(result.previousMessages).toHaveLength(1);
|
|
expect(result.previousMessages[0]).toContain('Previous assumption');
|
|
});
|
|
|
|
it('should handle null userName', () => {
|
|
const messageContextWithNullUser = {
|
|
...baseMessageContext,
|
|
userName: 'Unknown User',
|
|
};
|
|
|
|
const result = buildWorkflowInput(
|
|
messageContextWithNullUser,
|
|
baseConversationMessages,
|
|
basePreviousResults,
|
|
baseDatasets,
|
|
false
|
|
);
|
|
expect(result.userName).toBe('Unknown User');
|
|
});
|
|
|
|
it('should handle empty conversation history', () => {
|
|
const result = buildWorkflowInput(
|
|
baseMessageContext,
|
|
[],
|
|
basePreviousResults,
|
|
baseDatasets,
|
|
false
|
|
);
|
|
expect(result.conversationHistory).toBeUndefined();
|
|
});
|
|
});
|
|
});
|