BMAD-METHOD/bmad-claude-integration/tests/unit/message-queue.test.js

191 lines
6.2 KiB
JavaScript

const { describe, test, expect, beforeEach, afterEach } = require('@jest/globals');
const BMADMessageQueue = require('../../core/message-queue');
const path = require('path');
describe('BMADMessageQueue', () => {
let queue;
let tempDir;
beforeEach(async () => {
tempDir = await global.testUtils.createTempDir();
queue = new BMADMessageQueue({ basePath: tempDir });
await queue.initialize();
});
afterEach(async () => {
await global.testUtils.cleanupTempDir(tempDir);
});
describe('Message Operations', () => {
test('should send and retrieve a message', async () => {
const messageData = {
agent: 'test-agent',
type: 'test',
data: { foo: 'bar' }
};
const messageId = await queue.sendMessage(messageData);
expect(messageId).toMatch(/^msg-\d+-[a-f0-9]+$/);
const retrieved = await queue.getMessage(messageId);
expect(retrieved.agent).toBe('test-agent');
expect(retrieved.type).toBe('test');
expect(retrieved.data).toEqual({ foo: 'bar' });
expect(retrieved.status).toBe('pending');
});
test('should update message status', async () => {
const messageId = await queue.sendMessage({
agent: 'test',
type: 'update-test'
});
const updated = await queue.updateMessage(messageId, {
status: 'processing',
progress: 50
});
expect(updated.status).toBe('processing');
expect(updated.progress).toBe(50);
expect(updated.version).toBe(2);
});
test('should mark message as complete', async () => {
const messageId = await queue.sendMessage({
agent: 'test',
type: 'complete-test'
});
await queue.markComplete(messageId, { result: 'success' });
const completed = await queue.getMessage(messageId);
expect(completed.status).toBe('completed');
expect(completed.result).toEqual({ result: 'success' });
expect(completed.completedAt).toBeDefined();
});
test('should mark message as failed', async () => {
const messageId = await queue.sendMessage({
agent: 'test',
type: 'fail-test'
});
await queue.markFailed(messageId, new Error('Test error'));
const failed = await queue.getMessage(messageId);
expect(failed.status).toBe('failed');
expect(failed.error).toBe('Test error');
expect(failed.failedAt).toBeDefined();
});
});
describe('Retry Logic', () => {
test('should retry a message', async () => {
const messageId = await queue.sendMessage({
agent: 'test',
type: 'retry-test'
});
const retried = await queue.retry(messageId);
expect(retried.retries).toBe(1);
expect(retried.status).toBe('retrying');
expect(retried.lastRetry).toBeDefined();
});
test('should respect max retries', async () => {
const messageId = await queue.sendMessage({
agent: 'test',
type: 'max-retry-test'
});
// Simulate max retries
for (let i = 0; i < queue.maxRetries; i++) {
await queue.retry(messageId);
}
const message = await queue.getMessage(messageId);
expect(message.retries).toBe(queue.maxRetries);
});
});
describe('Queue Management', () => {
test('should list messages by status', async () => {
// Create messages with different statuses
const activeId = await queue.sendMessage({ agent: 'test', type: 'active' });
const completedId = await queue.sendMessage({ agent: 'test', type: 'completed' });
await queue.markComplete(completedId, {});
const activeMessages = await queue.listMessages('active');
const completedMessages = await queue.listMessages('completed');
expect(activeMessages.length).toBe(1);
expect(activeMessages[0].id).toBe(activeId);
expect(completedMessages.length).toBe(1);
expect(completedMessages[0].id).toBe(completedId);
});
test('should get queue metrics', async () => {
// Create test messages
await queue.sendMessage({ agent: 'test1', type: 'test' });
await queue.sendMessage({ agent: 'test2', type: 'test' });
const completedId = await queue.sendMessage({ agent: 'test3', type: 'test' });
await queue.markComplete(completedId, {});
const metrics = await queue.getMetrics();
expect(metrics.queueDepth).toBe(2);
expect(metrics.completedCount).toBe(1);
expect(metrics.failedCount).toBe(0);
expect(metrics.avgProcessingTime).toBeGreaterThanOrEqual(0);
});
test('should cleanup old messages', async () => {
// Create an old message
const oldMessageId = await queue.sendMessage({ agent: 'test', type: 'old' });
const message = await queue.getMessage(oldMessageId);
// Manually set timestamp to old value
message.timestamp = Date.now() - (queue.ttl + 1000);
const messagePath = path.join(tempDir, 'queue', 'active', `${oldMessageId}.json`);
const fs = require('fs').promises;
await fs.writeFile(messagePath, JSON.stringify(message));
// Create a new message
await queue.sendMessage({ agent: 'test', type: 'new' });
// Run cleanup
await queue.cleanup();
// Check that old message is gone
await expect(queue.getMessage(oldMessageId)).rejects.toThrow();
// New message should still exist
const activeMessages = await queue.listMessages('active');
expect(activeMessages.length).toBe(1);
expect(activeMessages[0].type).toBe('new');
});
});
describe('Error Handling', () => {
test('should throw error for non-existent message', async () => {
await expect(queue.getMessage('non-existent-id')).rejects.toThrow('Message non-existent-id not found');
});
test('should handle concurrent operations', async () => {
const operations = [];
// Send multiple messages concurrently
for (let i = 0; i < 10; i++) {
operations.push(queue.sendMessage({
agent: `agent-${i}`,
type: 'concurrent',
index: i
}));
}
const messageIds = await Promise.all(operations);
expect(messageIds.length).toBe(10);
expect(new Set(messageIds).size).toBe(10); // All IDs should be unique
});
});
});