mirror of https://github.com/buster-so/buster.git
227 lines
8.5 KiB
TypeScript
227 lines
8.5 KiB
TypeScript
import { db, eq, messages } from '@buster/database';
|
|
import { createTestChat, createTestMessage } from '@buster/test-utils';
|
|
import { runs, tasks } from '@trigger.dev/sdk';
|
|
import { initLogger, wrapTraced } from 'braintrust';
|
|
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
|
|
import type { analystAgentTask } from '../../src/tasks/analyst-agent-task';
|
|
|
|
/**
|
|
* Integration Tests for Analyst Agent Task
|
|
*
|
|
* PREREQUISITES (MUST BE RUNNING):
|
|
* 1. Local Trigger.dev server: `npm run trigger:dev` (in trigger directory)
|
|
* 2. Environment variables:
|
|
* - TRIGGER_API_KEY=tr_dev_your_key_here
|
|
* - DATABASE_URL (for test database operations)
|
|
* - BRAINTRUST_KEY (for observability)
|
|
*
|
|
* SETUP INSTRUCTIONS:
|
|
* 1. Get your Trigger.dev API key from https://cloud.trigger.dev/
|
|
* 2. Add TRIGGER_API_KEY to your .env file
|
|
* 3. Start trigger server: npm run trigger:dev
|
|
* 4. Run this test: npm run test:integration
|
|
*
|
|
* If you get connection errors, ensure:
|
|
* - Trigger server is running on localhost:3000 (check terminal output)
|
|
* - TRIGGER_API_KEY is valid and in .env
|
|
* - Database connection is working
|
|
*/
|
|
|
|
describe('Analyst Agent Task Integration Tests', () => {
|
|
// Use same constants as AI workflow test for consistency
|
|
const TEST_USER_ID = 'c2dd64cd-f7f3-4884-bc91-d46ae431901e';
|
|
const TEST_ORG_ID = 'bf58d19a-8bb9-4f1d-a257-2d2105e7f1ce';
|
|
const TEST_MESSAGE_CONTENT = 'who is our top customer';
|
|
|
|
async function triggerAndPollAnalystAgent(
|
|
payload: { message_id: string },
|
|
pollIntervalMs = 2000,
|
|
timeoutMs = 30 * 60 * 1000 // align with 30 min test timeout
|
|
) {
|
|
const handle = await tasks.trigger<typeof analystAgentTask>('analyst-agent-task', payload);
|
|
|
|
const start = Date.now();
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
const run = await runs.retrieve(handle.id);
|
|
if (run.status === 'COMPLETED' || run.status === 'FAILED' || run.status === 'CANCELED') {
|
|
return run;
|
|
}
|
|
|
|
if (Date.now() - start > timeoutMs) {
|
|
return run;
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
}
|
|
}
|
|
|
|
beforeAll(() => {
|
|
if (!process.env.BRAINTRUST_KEY) {
|
|
throw new Error('BRAINTRUST_KEY is required for observability');
|
|
}
|
|
|
|
// Initialize Braintrust logging for observability
|
|
initLogger({
|
|
apiKey: process.env.BRAINTRUST_KEY,
|
|
projectName: process.env.ENVIRONMENT || 'development',
|
|
});
|
|
|
|
// Verify required environment variables
|
|
if (!process.env.TRIGGER_SECRET_KEY) {
|
|
throw new Error(
|
|
'TRIGGER_SECRET_KEY is required. Add it to your .env file.\n' +
|
|
'Get your key from: https://cloud.trigger.dev/ → Project Settings → API Keys'
|
|
);
|
|
}
|
|
|
|
if (!process.env.DATABASE_URL) {
|
|
throw new Error('DATABASE_URL is required for test database operations');
|
|
}
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Allow time for async operations to complete
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
});
|
|
|
|
test('should successfully trigger and complete analyst agent task with test chat', async () => {
|
|
let chatId: string;
|
|
let messageId: string;
|
|
|
|
try {
|
|
// Create test chat and message with the same user/org as AI workflow tests
|
|
console.log('Creating test chat and message...');
|
|
const chatResult = await createTestChat(TEST_ORG_ID, TEST_USER_ID);
|
|
chatId = chatResult.chatId;
|
|
|
|
messageId = await createTestMessage(chatId, TEST_USER_ID, {
|
|
requestMessage: TEST_MESSAGE_CONTENT,
|
|
});
|
|
|
|
console.log(`Created test message: ${messageId} with content: "${TEST_MESSAGE_CONTENT}"`);
|
|
|
|
// Trigger the analyst agent task using Trigger.dev SDK
|
|
console.log('Triggering analyst agent task...');
|
|
|
|
const tracedTaskTrigger = wrapTraced(
|
|
async () => await triggerAndPollAnalystAgent({ message_id: messageId }, 5000),
|
|
{
|
|
name: 'Trigger Analyst Agent Task',
|
|
}
|
|
);
|
|
|
|
console.log('Waiting for task completion...');
|
|
const result = await tracedTaskTrigger();
|
|
|
|
// Verify task completed successfully
|
|
expect(result).toBeDefined();
|
|
expect(result.status).toBe('COMPLETED');
|
|
expect(result.output).toBeDefined();
|
|
|
|
if (result.status === 'COMPLETED' && result.output) {
|
|
console.log('Task completed successfully');
|
|
console.log('Task output:', JSON.stringify(result.output, null, 2));
|
|
|
|
// Verify the output structure matches expected schema
|
|
expect(result.output.success).toBe(true);
|
|
expect(result.output.messageId).toBe(messageId);
|
|
expect(result.output.result).toBeDefined();
|
|
expect(result.output.result?.workflowCompleted).toBe(true);
|
|
|
|
// Verify the message was updated in the database
|
|
console.log('Verifying database updates...');
|
|
const updatedMessage = await db.select().from(messages).where(eq(messages.id, messageId));
|
|
|
|
expect(updatedMessage).toHaveLength(1);
|
|
expect(updatedMessage[0]?.id).toBe(messageId);
|
|
|
|
// Check if conversation history was saved
|
|
if (updatedMessage[0]?.rawLlmMessages) {
|
|
expect(Array.isArray(updatedMessage[0].rawLlmMessages)).toBe(true);
|
|
}
|
|
|
|
console.log('Integration test completed successfully!');
|
|
} else {
|
|
console.error('Task failed with status:', result.status);
|
|
console.error('Task error:', result.error);
|
|
throw new Error(
|
|
`Task execution failed with status: ${result.status}, error: ${result.error?.message || 'Unknown error'}`
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error('Integration test failed:', error);
|
|
|
|
// Provide helpful error messages for common issues
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('ECONNREFUSED')) {
|
|
console.error('\n🚨 CONNECTION REFUSED - Is the trigger server running?');
|
|
console.error('Start it with: npm run trigger:dev');
|
|
console.error('Wait for "✓ Dev server running" message before running tests\n');
|
|
} else if (error.message.includes('401') || error.message.includes('Unauthorized')) {
|
|
console.error('\n🚨 AUTHENTICATION ERROR - Check your TRIGGER_API_KEY');
|
|
console.error('Get your key from: https://cloud.trigger.dev/');
|
|
console.error('Add to .env: TRIGGER_API_KEY=tr_dev_your_key_here\n');
|
|
} else if (error.message.includes('404') || error.message.includes('Not Found')) {
|
|
console.error('\n🚨 TASK NOT FOUND - Is the task deployed to trigger server?');
|
|
console.error('Ensure analyst-agent-task is properly exported and registered\n');
|
|
}
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}, 1800000); // 30 minute timeout to match task maxDuration
|
|
|
|
test('should handle invalid message ID gracefully', async () => {
|
|
const invalidMessageId = '00000000-0000-0000-0000-000000000000';
|
|
|
|
try {
|
|
console.log('Testing error handling with invalid message ID...');
|
|
|
|
const result = await triggerAndPollAnalystAgent({ message_id: invalidMessageId }, 2000);
|
|
|
|
// Task should complete but with error result
|
|
expect(result).toBeDefined();
|
|
|
|
if (result.status === 'COMPLETED' && result.output) {
|
|
// If task completed "successfully", it should report the error in output
|
|
expect(result.output.success).toBe(false);
|
|
expect(result.output.error).toBeDefined();
|
|
expect(result.output.error?.code).toBe('MESSAGE_NOT_FOUND');
|
|
} else {
|
|
// If task failed at Trigger.dev level, that's also acceptable for this test
|
|
expect(result.error).toBeDefined();
|
|
}
|
|
|
|
console.log('Error handling test completed successfully');
|
|
} catch (error) {
|
|
// Expected behavior - task should handle this gracefully
|
|
console.log('Caught expected error for invalid message ID:', error);
|
|
}
|
|
}, 300000); // 5 minute timeout for error case
|
|
|
|
test('should validate input schema correctly', async () => {
|
|
try {
|
|
console.log('Testing input validation...');
|
|
|
|
// Test with invalid UUID format
|
|
await expect(
|
|
triggerAndPollAnalystAgent(
|
|
// Intentionally invalid input to test validation
|
|
{ message_id: 'not-a-uuid' } as { message_id: string },
|
|
1000
|
|
)
|
|
).rejects.toThrow();
|
|
|
|
console.log('Input validation test completed successfully');
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message.includes('uuid')) {
|
|
// This is expected - Zod should reject invalid UUIDs
|
|
console.log('Input validation working correctly:', error.message);
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}, 30000); // 30 second timeout for validation test
|
|
});
|