Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
437
vendor/ruvector/npm/packages/ruvbot/tests/e2e/conversations/conversation-flow.test.ts
vendored
Normal file
437
vendor/ruvector/npm/packages/ruvbot/tests/e2e/conversations/conversation-flow.test.ts
vendored
Normal file
@@ -0,0 +1,437 @@
|
||||
/**
|
||||
* Conversation Flow - E2E Tests
|
||||
*
|
||||
* End-to-end tests for complete agent conversation flows
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { createSession, createAgent, createTenant } from '../../factories';
|
||||
import { createMockSlackApp, type MockSlackBoltApp } from '../../mocks/slack.mock';
|
||||
import { createMockPool, type MockPool } from '../../mocks/postgres.mock';
|
||||
import { createMockRuVectorBindings } from '../../mocks/wasm.mock';
|
||||
|
||||
// Mock RuvBot for E2E testing
|
||||
class MockRuvBot {
|
||||
private app: MockSlackBoltApp;
|
||||
private pool: MockPool;
|
||||
private ruvector: ReturnType<typeof createMockRuVectorBindings>;
|
||||
private sessions: Map<string, any> = new Map();
|
||||
private agents: Map<string, any> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.app = createMockSlackApp();
|
||||
this.pool = createMockPool();
|
||||
this.ruvector = createMockRuVectorBindings();
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.pool.connect();
|
||||
await this.app.start(3000);
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
await this.app.stop();
|
||||
await this.pool.end();
|
||||
}
|
||||
|
||||
getApp(): MockSlackBoltApp {
|
||||
return this.app;
|
||||
}
|
||||
|
||||
getPool(): MockPool {
|
||||
return this.pool;
|
||||
}
|
||||
|
||||
getSession(key: string): any {
|
||||
return this.sessions.get(key);
|
||||
}
|
||||
|
||||
async processMessage(message: {
|
||||
text: string;
|
||||
channel: string;
|
||||
user: string;
|
||||
ts: string;
|
||||
thread_ts?: string;
|
||||
}): Promise<void> {
|
||||
await this.app.processMessage(message);
|
||||
}
|
||||
|
||||
private setupHandlers(): void {
|
||||
// Handle greetings
|
||||
this.app.message(/^(hi|hello|hey)/i, async ({ message, say }) => {
|
||||
const sessionKey = `${(message as any).channel}:${(message as any).thread_ts || (message as any).ts}`;
|
||||
|
||||
// Create or get session
|
||||
if (!this.sessions.has(sessionKey)) {
|
||||
this.sessions.set(sessionKey, {
|
||||
id: `session-${Date.now()}`,
|
||||
channelId: (message as any).channel,
|
||||
threadTs: (message as any).thread_ts || (message as any).ts,
|
||||
userId: (message as any).user,
|
||||
messages: [],
|
||||
startedAt: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
const session = this.sessions.get(sessionKey);
|
||||
session.messages.push({ role: 'user', content: (message as any).text, timestamp: new Date() });
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'Hello! I\'m RuvBot. How can I help you today?',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
session.messages.push({ role: 'assistant', content: 'Hello! I\'m RuvBot. How can I help you today?', timestamp: new Date() });
|
||||
});
|
||||
|
||||
// Handle code generation requests
|
||||
this.app.message(/generate.*code|write.*function/i, async ({ message, say }) => {
|
||||
const sessionKey = `${(message as any).channel}:${(message as any).thread_ts || (message as any).ts}`;
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'I\'ll generate that code for you. Give me a moment...',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
// Simulate code generation
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: '```javascript\nfunction example() {\n console.log("Generated code");\n}\n```',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
const session = this.sessions.get(sessionKey);
|
||||
if (session) {
|
||||
session.messages.push({
|
||||
role: 'user',
|
||||
content: (message as any).text,
|
||||
timestamp: new Date()
|
||||
});
|
||||
session.messages.push({
|
||||
role: 'assistant',
|
||||
content: 'Code generated',
|
||||
artifact: { type: 'code', language: 'javascript' },
|
||||
timestamp: new Date()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle help requests
|
||||
this.app.message(/help|what can you do/i, async ({ message, say }) => {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'I can help you with:\n- Code generation\n- Code review\n- Testing\n- Documentation\n\nJust ask me what you need!',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
});
|
||||
|
||||
// Handle thank you
|
||||
this.app.message(/thanks|thank you/i, async ({ message, say }) => {
|
||||
const sessionKey = `${(message as any).channel}:${(message as any).thread_ts || (message as any).ts}`;
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'You\'re welcome! Let me know if you need anything else.',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
// Mark session as potentially complete
|
||||
const session = this.sessions.get(sessionKey);
|
||||
if (session) {
|
||||
session.status = 'satisfied';
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search requests
|
||||
this.app.message(/search|find|look up/i, async ({ message, say }) => {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'Searching through the knowledge base...',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
// Simulate vector search
|
||||
const results = await this.ruvector.search((message as any).text, 3);
|
||||
|
||||
if (results.length > 0) {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Found ${results.length} relevant results.`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
} else {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'No relevant results found.',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('E2E: Conversation Flow', () => {
|
||||
let bot: MockRuvBot;
|
||||
|
||||
beforeEach(async () => {
|
||||
bot = new MockRuvBot();
|
||||
await bot.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await bot.stop();
|
||||
});
|
||||
|
||||
describe('Basic Conversation', () => {
|
||||
it('should handle greeting and establish session', async () => {
|
||||
const channel = 'C12345678';
|
||||
const ts = '1234567890.123456';
|
||||
|
||||
await bot.processMessage({
|
||||
text: 'Hello!',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages).toHaveLength(1);
|
||||
expect(messages[0].text).toContain('RuvBot');
|
||||
|
||||
const session = bot.getSession(`${channel}:${ts}`);
|
||||
expect(session).toBeDefined();
|
||||
expect(session.messages).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should maintain conversation context in thread', async () => {
|
||||
const channel = 'C12345678';
|
||||
const parentTs = '1234567890.111111';
|
||||
|
||||
// Start conversation
|
||||
await bot.processMessage({
|
||||
text: 'Hi there',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: parentTs
|
||||
});
|
||||
|
||||
// Continue in thread
|
||||
await bot.processMessage({
|
||||
text: 'Help me generate code',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.222222',
|
||||
thread_ts: parentTs
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Code Generation Flow', () => {
|
||||
it('should generate code on request', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Generate code for a hello world function',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Should have progress message and code block
|
||||
expect(messages.some(m => m.text?.includes('generating') || m.text?.includes('moment'))).toBe(true);
|
||||
expect(messages.some(m => m.text?.includes('```'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle follow-up questions about generated code', async () => {
|
||||
const channel = 'C12345678';
|
||||
const parentTs = '1234567890.111111';
|
||||
|
||||
// Request code
|
||||
await bot.processMessage({
|
||||
text: 'Write a function to sort an array',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: parentTs
|
||||
});
|
||||
|
||||
// Ask for help about the code
|
||||
await bot.processMessage({
|
||||
text: 'Help me understand this',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.222222',
|
||||
thread_ts: parentTs
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Help Flow', () => {
|
||||
it('should provide help information', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'What can you do?',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages).toHaveLength(1);
|
||||
expect(messages[0].text).toContain('Code generation');
|
||||
expect(messages[0].text).toContain('Code review');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multi-turn Conversation', () => {
|
||||
it('should handle complete conversation lifecycle', async () => {
|
||||
const channel = 'C12345678';
|
||||
const parentTs = '1234567890.000001';
|
||||
|
||||
// 1. Greeting
|
||||
await bot.processMessage({
|
||||
text: 'Hey',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: parentTs
|
||||
});
|
||||
|
||||
// 2. Request
|
||||
await bot.processMessage({
|
||||
text: 'Generate code for a calculator',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.000002',
|
||||
thread_ts: parentTs
|
||||
});
|
||||
|
||||
// 3. Thank you
|
||||
await bot.processMessage({
|
||||
text: 'Thank you!',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.000003',
|
||||
thread_ts: parentTs
|
||||
});
|
||||
|
||||
const session = bot.getSession(`${channel}:${parentTs}`);
|
||||
expect(session).toBeDefined();
|
||||
expect(session.messages.length).toBeGreaterThan(2);
|
||||
expect(session.status).toBe('satisfied');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Recovery', () => {
|
||||
it('should handle unknown requests gracefully', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'asdfghjkl random gibberish',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
// Should not crash
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Multi-user Conversations', () => {
|
||||
let bot: MockRuvBot;
|
||||
|
||||
beforeEach(async () => {
|
||||
bot = new MockRuvBot();
|
||||
await bot.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await bot.stop();
|
||||
});
|
||||
|
||||
it('should handle multiple concurrent users', async () => {
|
||||
const users = ['U11111111', 'U22222222', 'U33333333'];
|
||||
const channel = 'C12345678';
|
||||
|
||||
// All users send messages
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
await bot.processMessage({
|
||||
text: 'Hello',
|
||||
channel,
|
||||
user: users[i],
|
||||
ts: `${Date.now()}.${i}`
|
||||
});
|
||||
}
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages).toHaveLength(3); // One response per user
|
||||
});
|
||||
|
||||
it('should maintain separate sessions per thread', async () => {
|
||||
const channel = 'C12345678';
|
||||
|
||||
// User 1 starts thread
|
||||
await bot.processMessage({
|
||||
text: 'Hi',
|
||||
channel,
|
||||
user: 'U11111111',
|
||||
ts: '1234567890.111111'
|
||||
});
|
||||
|
||||
// User 2 starts different thread
|
||||
await bot.processMessage({
|
||||
text: 'Hello',
|
||||
channel,
|
||||
user: 'U22222222',
|
||||
ts: '1234567890.222222'
|
||||
});
|
||||
|
||||
const session1 = bot.getSession(`${channel}:1234567890.111111`);
|
||||
const session2 = bot.getSession(`${channel}:1234567890.222222`);
|
||||
|
||||
expect(session1.userId).toBe('U11111111');
|
||||
expect(session2.userId).toBe('U22222222');
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Cross-channel Conversations', () => {
|
||||
let bot: MockRuvBot;
|
||||
|
||||
beforeEach(async () => {
|
||||
bot = new MockRuvBot();
|
||||
await bot.start();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await bot.stop();
|
||||
});
|
||||
|
||||
it('should handle messages from different channels', async () => {
|
||||
const channels = ['C11111111', 'C22222222', 'C33333333'];
|
||||
|
||||
for (const channel of channels) {
|
||||
await bot.processMessage({
|
||||
text: 'Hello',
|
||||
channel,
|
||||
user: 'U12345678',
|
||||
ts: `${Date.now()}.000000`
|
||||
});
|
||||
}
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages).toHaveLength(3);
|
||||
|
||||
// Each response should be in the correct channel
|
||||
const responseChannels = new Set(messages.map(m => m.channel));
|
||||
expect(responseChannels.size).toBe(3);
|
||||
});
|
||||
});
|
||||
545
vendor/ruvector/npm/packages/ruvbot/tests/e2e/skills/skill-execution.test.ts
vendored
Normal file
545
vendor/ruvector/npm/packages/ruvbot/tests/e2e/skills/skill-execution.test.ts
vendored
Normal file
@@ -0,0 +1,545 @@
|
||||
/**
|
||||
* Skill Execution - E2E Tests
|
||||
*
|
||||
* End-to-end tests for skill execution flows
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { createSkill } from '../../factories';
|
||||
import { createMockSlackApp, type MockSlackBoltApp } from '../../mocks/slack.mock';
|
||||
import { createMockRuVectorBindings } from '../../mocks/wasm.mock';
|
||||
|
||||
// Skill execution types
|
||||
interface SkillInput {
|
||||
skill: string;
|
||||
params: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface SkillOutput {
|
||||
success: boolean;
|
||||
result: unknown;
|
||||
error?: string;
|
||||
executionTime: number;
|
||||
}
|
||||
|
||||
// Mock Skill Executor
|
||||
class MockSkillExecutor {
|
||||
private skills: Map<string, {
|
||||
handler: (params: Record<string, unknown>) => Promise<unknown>;
|
||||
timeout: number;
|
||||
}> = new Map();
|
||||
|
||||
registerSkill(
|
||||
name: string,
|
||||
handler: (params: Record<string, unknown>) => Promise<unknown>,
|
||||
timeout: number = 30000
|
||||
): void {
|
||||
this.skills.set(name, { handler, timeout });
|
||||
}
|
||||
|
||||
async execute(input: SkillInput): Promise<SkillOutput> {
|
||||
const skill = this.skills.get(input.skill);
|
||||
if (!skill) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: `Skill '${input.skill}' not found`,
|
||||
executionTime: 0
|
||||
};
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const result = await Promise.race([
|
||||
skill.handler(input.params),
|
||||
this.createTimeout(skill.timeout)
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result,
|
||||
executionTime: Date.now() - startTime
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
result: null,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
executionTime: Date.now() - startTime
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private createTimeout(ms: number): Promise<never> {
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Skill execution timed out')), ms);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Mock Skill-enabled Bot
|
||||
class MockSkillBot {
|
||||
private app: MockSlackBoltApp;
|
||||
private executor: MockSkillExecutor;
|
||||
private ruvector: ReturnType<typeof createMockRuVectorBindings>;
|
||||
|
||||
constructor() {
|
||||
this.app = createMockSlackApp();
|
||||
this.executor = new MockSkillExecutor();
|
||||
this.ruvector = createMockRuVectorBindings();
|
||||
this.registerSkills();
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
getApp(): MockSlackBoltApp {
|
||||
return this.app;
|
||||
}
|
||||
|
||||
getExecutor(): MockSkillExecutor {
|
||||
return this.executor;
|
||||
}
|
||||
|
||||
async processMessage(message: {
|
||||
text: string;
|
||||
channel: string;
|
||||
user: string;
|
||||
ts: string;
|
||||
thread_ts?: string;
|
||||
}): Promise<void> {
|
||||
await this.app.processMessage(message);
|
||||
}
|
||||
|
||||
private registerSkills(): void {
|
||||
// Code generation skill
|
||||
this.executor.registerSkill('code-generation', async (params) => {
|
||||
const { language, description } = params;
|
||||
await new Promise(resolve => setTimeout(resolve, 50)); // Simulate processing
|
||||
|
||||
const templates: Record<string, string> = {
|
||||
javascript: `// ${description}\nfunction example() {\n // Implementation\n}`,
|
||||
python: `# ${description}\ndef example():\n # Implementation\n pass`,
|
||||
typescript: `// ${description}\nfunction example(): void {\n // Implementation\n}`
|
||||
};
|
||||
|
||||
return {
|
||||
code: templates[language as string] || templates.javascript,
|
||||
language
|
||||
};
|
||||
});
|
||||
|
||||
// Test generation skill
|
||||
this.executor.registerSkill('test-generation', async (params) => {
|
||||
const { code, framework } = params;
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
|
||||
return {
|
||||
tests: `describe('Generated Tests', () => {\n it('should work', () => {\n expect(true).toBe(true);\n });\n});`,
|
||||
framework: framework || 'jest',
|
||||
coverage: 85
|
||||
};
|
||||
});
|
||||
|
||||
// Vector search skill
|
||||
this.executor.registerSkill('vector-search', async (params) => {
|
||||
const { query, topK } = params;
|
||||
const results = await this.ruvector.search(query as string, topK as number || 5);
|
||||
|
||||
return {
|
||||
results,
|
||||
query,
|
||||
count: results.length
|
||||
};
|
||||
});
|
||||
|
||||
// Code review skill
|
||||
this.executor.registerSkill('code-review', async (params) => {
|
||||
const { code } = params;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
return {
|
||||
issues: [
|
||||
{ type: 'warning', message: 'Consider adding error handling', line: 5 },
|
||||
{ type: 'suggestion', message: 'Variable could be const', line: 2 }
|
||||
],
|
||||
score: 85,
|
||||
summary: 'Code looks good with minor improvements suggested'
|
||||
};
|
||||
});
|
||||
|
||||
// Documentation skill
|
||||
this.executor.registerSkill('generate-docs', async (params) => {
|
||||
const { code, format } = params;
|
||||
await new Promise(resolve => setTimeout(resolve, 75));
|
||||
|
||||
return {
|
||||
documentation: `## Function Documentation\n\nThis function does something useful.\n\n### Parameters\n- param1: Description`,
|
||||
format: format || 'markdown'
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private setupHandlers(): void {
|
||||
// Handle code generation
|
||||
this.app.message(/generate.*code.*in\s+(\w+)/i, async ({ message, say }) => {
|
||||
const languageMatch = (message as any).text.match(/in\s+(\w+)/i);
|
||||
const language = languageMatch ? languageMatch[1].toLowerCase() : 'javascript';
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Generating ${language} code...`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
const result = await this.executor.execute({
|
||||
skill: 'code-generation',
|
||||
params: {
|
||||
language,
|
||||
description: (message as any).text
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
const output = result.result as { code: string };
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `\`\`\`${language}\n${output.code}\n\`\`\``,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
} else {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Error: ${result.error}`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle test generation
|
||||
this.app.message(/generate.*tests?|write.*tests?/i, async ({ message, say }) => {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'Generating tests...',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
const result = await this.executor.execute({
|
||||
skill: 'test-generation',
|
||||
params: {
|
||||
code: 'function example() {}',
|
||||
framework: 'vitest'
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
const output = result.result as { tests: string; coverage: number };
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `\`\`\`typescript\n${output.tests}\n\`\`\`\nEstimated coverage: ${output.coverage}%`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle code review
|
||||
this.app.message(/review.*code|check.*code/i, async ({ message, say }) => {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'Reviewing code...',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
const result = await this.executor.execute({
|
||||
skill: 'code-review',
|
||||
params: {
|
||||
code: '// Sample code for review'
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
const output = result.result as { summary: string; score: number; issues: unknown[] };
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Code Review Results:\n- Score: ${output.score}/100\n- Issues: ${output.issues.length}\n\n${output.summary}`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search
|
||||
this.app.message(/search.*for|find.*about/i, async ({ message, say }) => {
|
||||
const result = await this.executor.execute({
|
||||
skill: 'vector-search',
|
||||
params: {
|
||||
query: (message as any).text,
|
||||
topK: 5
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
const output = result.result as { count: number };
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Found ${output.count} results`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle documentation
|
||||
this.app.message(/generate.*docs|document.*this/i, async ({ message, say }) => {
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: 'Generating documentation...',
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
const result = await this.executor.execute({
|
||||
skill: 'generate-docs',
|
||||
params: {
|
||||
code: 'function example() {}',
|
||||
format: 'markdown'
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
const output = result.result as { documentation: string };
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: output.documentation,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('E2E: Skill Execution', () => {
|
||||
let bot: MockSkillBot;
|
||||
|
||||
beforeEach(() => {
|
||||
bot = new MockSkillBot();
|
||||
});
|
||||
|
||||
describe('Code Generation Skill', () => {
|
||||
it('should generate JavaScript code', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Generate code in JavaScript for a hello world function',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('Generating'))).toBe(true);
|
||||
expect(messages.some(m => m.text?.includes('```javascript'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate Python code', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Generate code in Python for data processing',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('```python'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate TypeScript code', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Generate code in TypeScript for a type-safe function',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('```typescript'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test Generation Skill', () => {
|
||||
it('should generate tests', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Generate tests for this function',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('describe'))).toBe(true);
|
||||
expect(messages.some(m => m.text?.includes('coverage'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Code Review Skill', () => {
|
||||
it('should review code and provide feedback', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Review this code for me',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('Review Results'))).toBe(true);
|
||||
expect(messages.some(m => m.text?.includes('Score'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vector Search Skill', () => {
|
||||
it('should search and return results', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Search for React patterns',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('results'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Documentation Skill', () => {
|
||||
it('should generate documentation', async () => {
|
||||
await bot.processMessage({
|
||||
text: 'Generate docs for this function',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
const messages = bot.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('Documentation'))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Skill Chaining', () => {
|
||||
let executor: MockSkillExecutor;
|
||||
|
||||
beforeEach(() => {
|
||||
executor = new MockSkillExecutor();
|
||||
|
||||
// Register skills for chaining
|
||||
executor.registerSkill('analyze', async (params) => {
|
||||
return { analyzed: true, data: params.input };
|
||||
});
|
||||
|
||||
executor.registerSkill('transform', async (params) => {
|
||||
return { transformed: true, original: params.data };
|
||||
});
|
||||
|
||||
executor.registerSkill('output', async (params) => {
|
||||
return { result: `Processed: ${JSON.stringify(params.transformed)}` };
|
||||
});
|
||||
});
|
||||
|
||||
it('should chain multiple skills together', async () => {
|
||||
// Step 1: Analyze
|
||||
const step1 = await executor.execute({
|
||||
skill: 'analyze',
|
||||
params: { input: 'raw data' }
|
||||
});
|
||||
expect(step1.success).toBe(true);
|
||||
|
||||
// Step 2: Transform
|
||||
const step2 = await executor.execute({
|
||||
skill: 'transform',
|
||||
params: { data: step1.result }
|
||||
});
|
||||
expect(step2.success).toBe(true);
|
||||
|
||||
// Step 3: Output
|
||||
const step3 = await executor.execute({
|
||||
skill: 'output',
|
||||
params: { transformed: step2.result }
|
||||
});
|
||||
expect(step3.success).toBe(true);
|
||||
expect((step3.result as any).result).toContain('Processed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Skill Error Handling', () => {
|
||||
let executor: MockSkillExecutor;
|
||||
|
||||
beforeEach(() => {
|
||||
executor = new MockSkillExecutor();
|
||||
|
||||
executor.registerSkill('failing-skill', async () => {
|
||||
throw new Error('Skill failed intentionally');
|
||||
});
|
||||
|
||||
executor.registerSkill('slow-skill', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
return { result: 'Should not reach' };
|
||||
}, 100); // 100ms timeout
|
||||
});
|
||||
|
||||
it('should handle skill errors gracefully', async () => {
|
||||
const result = await executor.execute({
|
||||
skill: 'failing-skill',
|
||||
params: {}
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Skill failed intentionally');
|
||||
});
|
||||
|
||||
it('should handle skill timeout', async () => {
|
||||
const result = await executor.execute({
|
||||
skill: 'slow-skill',
|
||||
params: {}
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('timed out');
|
||||
});
|
||||
|
||||
it('should handle non-existent skill', async () => {
|
||||
const result = await executor.execute({
|
||||
skill: 'non-existent',
|
||||
params: {}
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Skill Execution Metrics', () => {
|
||||
let executor: MockSkillExecutor;
|
||||
|
||||
beforeEach(() => {
|
||||
executor = new MockSkillExecutor();
|
||||
|
||||
executor.registerSkill('timed-skill', async (params) => {
|
||||
const delay = (params.delay as number) || 50;
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
return { executed: true };
|
||||
});
|
||||
});
|
||||
|
||||
it('should track execution time', async () => {
|
||||
const result = await executor.execute({
|
||||
skill: 'timed-skill',
|
||||
params: { delay: 100 }
|
||||
});
|
||||
|
||||
expect(result.executionTime).toBeGreaterThanOrEqual(100);
|
||||
expect(result.executionTime).toBeLessThan(200);
|
||||
});
|
||||
|
||||
it('should report zero execution time for immediate failures', async () => {
|
||||
const result = await executor.execute({
|
||||
skill: 'non-existent',
|
||||
params: {}
|
||||
});
|
||||
|
||||
expect(result.executionTime).toBe(0);
|
||||
});
|
||||
});
|
||||
464
vendor/ruvector/npm/packages/ruvbot/tests/e2e/tasks/long-running-tasks.test.ts
vendored
Normal file
464
vendor/ruvector/npm/packages/ruvbot/tests/e2e/tasks/long-running-tasks.test.ts
vendored
Normal file
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* Long-running Tasks - E2E Tests
|
||||
*
|
||||
* End-to-end tests for long-running task completion
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { createAgent, createSession } from '../../factories';
|
||||
import { createMockSlackApp, type MockSlackBoltApp } from '../../mocks/slack.mock';
|
||||
|
||||
// Task types
|
||||
interface Task {
|
||||
id: string;
|
||||
type: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
progress: number;
|
||||
result?: unknown;
|
||||
error?: string;
|
||||
startedAt?: Date;
|
||||
completedAt?: Date;
|
||||
}
|
||||
|
||||
// Mock Task Manager
|
||||
class MockTaskManager {
|
||||
private tasks: Map<string, Task> = new Map();
|
||||
private eventHandlers: Map<string, Array<(task: Task) => void>> = new Map();
|
||||
|
||||
async createTask(type: string, payload: unknown): Promise<Task> {
|
||||
const task: Task = {
|
||||
id: `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
type,
|
||||
status: 'pending',
|
||||
progress: 0
|
||||
};
|
||||
|
||||
this.tasks.set(task.id, task);
|
||||
this.emit('created', task);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
async startTask(taskId: string): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) throw new Error(`Task ${taskId} not found`);
|
||||
|
||||
task.status = 'running';
|
||||
task.startedAt = new Date();
|
||||
this.emit('started', task);
|
||||
}
|
||||
|
||||
async updateProgress(taskId: string, progress: number): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) throw new Error(`Task ${taskId} not found`);
|
||||
|
||||
task.progress = progress;
|
||||
this.emit('progress', task);
|
||||
}
|
||||
|
||||
async completeTask(taskId: string, result: unknown): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) throw new Error(`Task ${taskId} not found`);
|
||||
|
||||
task.status = 'completed';
|
||||
task.progress = 100;
|
||||
task.result = result;
|
||||
task.completedAt = new Date();
|
||||
this.emit('completed', task);
|
||||
}
|
||||
|
||||
async failTask(taskId: string, error: string): Promise<void> {
|
||||
const task = this.tasks.get(taskId);
|
||||
if (!task) throw new Error(`Task ${taskId} not found`);
|
||||
|
||||
task.status = 'failed';
|
||||
task.error = error;
|
||||
task.completedAt = new Date();
|
||||
this.emit('failed', task);
|
||||
}
|
||||
|
||||
getTask(taskId: string): Task | undefined {
|
||||
return this.tasks.get(taskId);
|
||||
}
|
||||
|
||||
on(event: string, handler: (task: Task) => void): void {
|
||||
const handlers = this.eventHandlers.get(event) || [];
|
||||
handlers.push(handler);
|
||||
this.eventHandlers.set(event, handlers);
|
||||
}
|
||||
|
||||
private emit(event: string, task: Task): void {
|
||||
const handlers = this.eventHandlers.get(event) || [];
|
||||
handlers.forEach(h => h(task));
|
||||
}
|
||||
|
||||
// Simulate long-running task execution
|
||||
async executeTask(taskId: string, duration: number, steps: number): Promise<void> {
|
||||
await this.startTask(taskId);
|
||||
|
||||
const stepDuration = duration / steps;
|
||||
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, stepDuration));
|
||||
await this.updateProgress(taskId, (i / steps) * 100);
|
||||
}
|
||||
|
||||
await this.completeTask(taskId, { message: 'Task completed successfully' });
|
||||
}
|
||||
}
|
||||
|
||||
// Mock Orchestrator for E2E testing
|
||||
class MockTaskOrchestrator {
|
||||
private app: MockSlackBoltApp;
|
||||
private taskManager: MockTaskManager;
|
||||
private activeTasks: Map<string, { channel: string; threadTs: string }> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.app = createMockSlackApp();
|
||||
this.taskManager = new MockTaskManager();
|
||||
this.setupHandlers();
|
||||
this.setupTaskEvents();
|
||||
}
|
||||
|
||||
getApp(): MockSlackBoltApp {
|
||||
return this.app;
|
||||
}
|
||||
|
||||
getTaskManager(): MockTaskManager {
|
||||
return this.taskManager;
|
||||
}
|
||||
|
||||
async processMessage(message: {
|
||||
text: string;
|
||||
channel: string;
|
||||
user: string;
|
||||
ts: string;
|
||||
thread_ts?: string;
|
||||
}): Promise<void> {
|
||||
await this.app.processMessage(message);
|
||||
}
|
||||
|
||||
private setupHandlers(): void {
|
||||
// Handle long task requests
|
||||
this.app.message(/run.*long.*task|execute.*batch/i, async ({ message, say }) => {
|
||||
const task = await this.taskManager.createTask('long-running', {
|
||||
request: (message as any).text
|
||||
});
|
||||
|
||||
this.activeTasks.set(task.id, {
|
||||
channel: (message as any).channel,
|
||||
threadTs: (message as any).ts
|
||||
});
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Starting task ${task.id}. I'll update you on progress...`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
// Execute task in background
|
||||
this.taskManager.executeTask(task.id, 500, 5);
|
||||
});
|
||||
|
||||
// Handle analysis requests
|
||||
this.app.message(/analyze|process.*data/i, async ({ message, say }) => {
|
||||
const task = await this.taskManager.createTask('analysis', {
|
||||
request: (message as any).text
|
||||
});
|
||||
|
||||
this.activeTasks.set(task.id, {
|
||||
channel: (message as any).channel,
|
||||
threadTs: (message as any).ts
|
||||
});
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Beginning analysis (Task: ${task.id})...`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
// Execute analysis task
|
||||
this.taskManager.executeTask(task.id, 300, 3);
|
||||
});
|
||||
|
||||
// Handle code refactoring requests
|
||||
this.app.message(/refactor.*code|rewrite/i, async ({ message, say }) => {
|
||||
const task = await this.taskManager.createTask('refactoring', {
|
||||
request: (message as any).text
|
||||
});
|
||||
|
||||
this.activeTasks.set(task.id, {
|
||||
channel: (message as any).channel,
|
||||
threadTs: (message as any).ts
|
||||
});
|
||||
|
||||
await say({
|
||||
channel: (message as any).channel,
|
||||
text: `Starting code refactoring (Task: ${task.id}). This may take a while...`,
|
||||
thread_ts: (message as any).ts
|
||||
});
|
||||
|
||||
// Execute refactoring task
|
||||
this.taskManager.executeTask(task.id, 800, 8);
|
||||
});
|
||||
}
|
||||
|
||||
private setupTaskEvents(): void {
|
||||
this.taskManager.on('progress', async (task) => {
|
||||
const context = this.activeTasks.get(task.id);
|
||||
if (!context) return;
|
||||
|
||||
// Only send updates at 25%, 50%, 75%
|
||||
if ([25, 50, 75].includes(task.progress)) {
|
||||
await this.app.client.chat.postMessage({
|
||||
channel: context.channel,
|
||||
text: `Task ${task.id} progress: ${task.progress}%`,
|
||||
thread_ts: context.threadTs
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.taskManager.on('completed', async (task) => {
|
||||
const context = this.activeTasks.get(task.id);
|
||||
if (!context) return;
|
||||
|
||||
const duration = task.completedAt!.getTime() - task.startedAt!.getTime();
|
||||
|
||||
await this.app.client.chat.postMessage({
|
||||
channel: context.channel,
|
||||
text: `Task ${task.id} completed successfully in ${duration}ms!`,
|
||||
thread_ts: context.threadTs
|
||||
});
|
||||
|
||||
this.activeTasks.delete(task.id);
|
||||
});
|
||||
|
||||
this.taskManager.on('failed', async (task) => {
|
||||
const context = this.activeTasks.get(task.id);
|
||||
if (!context) return;
|
||||
|
||||
await this.app.client.chat.postMessage({
|
||||
channel: context.channel,
|
||||
text: `Task ${task.id} failed: ${task.error}`,
|
||||
thread_ts: context.threadTs
|
||||
});
|
||||
|
||||
this.activeTasks.delete(task.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
describe('E2E: Long-running Tasks', () => {
|
||||
let orchestrator: MockTaskOrchestrator;
|
||||
|
||||
beforeEach(() => {
|
||||
orchestrator = new MockTaskOrchestrator();
|
||||
});
|
||||
|
||||
describe('Task Execution', () => {
|
||||
it('should start and complete long-running task', async () => {
|
||||
await orchestrator.processMessage({
|
||||
text: 'Run a long task for me',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
// Wait for task to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
|
||||
// Should have start message, progress updates, and completion
|
||||
expect(messages.some(m => m.text?.includes('Starting task'))).toBe(true);
|
||||
expect(messages.some(m => m.text?.includes('completed'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should send progress updates', async () => {
|
||||
await orchestrator.processMessage({
|
||||
text: 'Run a long task',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
// Wait for task to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
const progressMessages = messages.filter(m => m.text?.includes('progress'));
|
||||
|
||||
// Should have multiple progress updates
|
||||
expect(progressMessages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should report completion time', async () => {
|
||||
await orchestrator.processMessage({
|
||||
text: 'Execute batch process',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 600));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
const completionMessage = messages.find(m => m.text?.includes('completed'));
|
||||
|
||||
expect(completionMessage?.text).toMatch(/\d+ms/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Concurrent Tasks', () => {
|
||||
it('should handle multiple tasks concurrently', async () => {
|
||||
// Start multiple tasks
|
||||
await orchestrator.processMessage({
|
||||
text: 'Run a long task',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.111111'
|
||||
});
|
||||
|
||||
await orchestrator.processMessage({
|
||||
text: 'Analyze this data',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.222222'
|
||||
});
|
||||
|
||||
// Wait for both to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 700));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
const completedMessages = messages.filter(m => m.text?.includes('completed'));
|
||||
|
||||
expect(completedMessages.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should track tasks independently', async () => {
|
||||
await orchestrator.processMessage({
|
||||
text: 'Run a long task',
|
||||
channel: 'C11111111',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.111111'
|
||||
});
|
||||
|
||||
await orchestrator.processMessage({
|
||||
text: 'Process data',
|
||||
channel: 'C22222222',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.222222'
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 700));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
|
||||
// Each channel should have its own completion message
|
||||
const channel1Completed = messages.some(
|
||||
m => m.channel === 'C11111111' && m.text?.includes('completed')
|
||||
);
|
||||
const channel2Completed = messages.some(
|
||||
m => m.channel === 'C22222222' && m.text?.includes('completed')
|
||||
);
|
||||
|
||||
expect(channel1Completed).toBe(true);
|
||||
expect(channel2Completed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task Types', () => {
|
||||
it('should handle analysis tasks', async () => {
|
||||
await orchestrator.processMessage({
|
||||
text: 'Analyze the codebase',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 400));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('analysis'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle refactoring tasks', async () => {
|
||||
await orchestrator.processMessage({
|
||||
text: 'Refactor the code in src/main.ts',
|
||||
channel: 'C12345678',
|
||||
user: 'U12345678',
|
||||
ts: '1234567890.123456'
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 900));
|
||||
|
||||
const messages = orchestrator.getApp().client.getMessageLog();
|
||||
expect(messages.some(m => m.text?.includes('refactoring'))).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Task Manager', () => {
|
||||
let taskManager: MockTaskManager;
|
||||
|
||||
beforeEach(() => {
|
||||
taskManager = new MockTaskManager();
|
||||
});
|
||||
|
||||
describe('Task Lifecycle', () => {
|
||||
it('should create task in pending state', async () => {
|
||||
const task = await taskManager.createTask('test', {});
|
||||
|
||||
expect(task.status).toBe('pending');
|
||||
expect(task.progress).toBe(0);
|
||||
});
|
||||
|
||||
it('should transition through states correctly', async () => {
|
||||
const states: string[] = [];
|
||||
|
||||
taskManager.on('created', (t) => states.push(t.status));
|
||||
taskManager.on('started', (t) => states.push(t.status));
|
||||
taskManager.on('completed', (t) => states.push(t.status));
|
||||
|
||||
const task = await taskManager.createTask('test', {});
|
||||
await taskManager.executeTask(task.id, 100, 2);
|
||||
|
||||
expect(states).toEqual(['pending', 'running', 'completed']);
|
||||
});
|
||||
|
||||
it('should track progress correctly', async () => {
|
||||
const progressValues: number[] = [];
|
||||
|
||||
taskManager.on('progress', (t) => progressValues.push(t.progress));
|
||||
|
||||
const task = await taskManager.createTask('test', {});
|
||||
await taskManager.executeTask(task.id, 100, 4);
|
||||
|
||||
expect(progressValues).toEqual([25, 50, 75, 100]);
|
||||
});
|
||||
|
||||
it('should record timing information', async () => {
|
||||
const task = await taskManager.createTask('test', {});
|
||||
await taskManager.executeTask(task.id, 100, 2);
|
||||
|
||||
const completed = taskManager.getTask(task.id)!;
|
||||
|
||||
expect(completed.startedAt).toBeDefined();
|
||||
expect(completed.completedAt).toBeDefined();
|
||||
expect(completed.completedAt!.getTime()).toBeGreaterThan(completed.startedAt!.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task Failure', () => {
|
||||
it('should handle task failure', async () => {
|
||||
const task = await taskManager.createTask('test', {});
|
||||
await taskManager.startTask(task.id);
|
||||
await taskManager.failTask(task.id, 'Something went wrong');
|
||||
|
||||
const failed = taskManager.getTask(task.id)!;
|
||||
|
||||
expect(failed.status).toBe('failed');
|
||||
expect(failed.error).toBe('Something went wrong');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user