task-queue.test.js 3.62 KB
/**
 * Unit Tests — task-queue.js
 */
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

const { TaskQueue, FAST_TIMEOUT, MAX_CONCURRENT } = require('../../src/engine/../../../server/services/task-queue');

describe('TaskQueue', () => {
  let queue;
  let mockChatService;

  beforeEach(() => {
    // Mock chat service with controllable sessions
    mockChatService = {
      sessions: new Map(),
      getSession(id) { return this.sessions.get(id) || null; },
      createSession(id) {
        const session = {
          id, status: 'disconnected',
          connect: async () => { session.status = 'connected'; },
          sendMessage: async (msg) => ({ result: `Echo: ${msg.substring(0, 50)}` }),
        };
        this.sessions.set(id, session);
        return session;
      },
    };
    queue = new TaskQueue(mockChatService);
  });

  afterEach(() => queue.stop());

  describe('start / stop', () => {
    it('should start heartbeat without error', () => {
      expect(() => queue.start()).not.toThrow();
    });

    it('should stop heartbeat without error', () => {
      queue.start();
      expect(() => queue.stop()).not.toThrow();
    });

    it('should not double-start', () => {
      queue.start();
      queue.start(); // should be a no-op
      queue.stop();
    });
  });

  describe('getTask / listTasks', () => {
    it('should return null for non-existent task', () => {
      expect(queue.getTask('no-such-task')).toBeNull();
    });

    it('should return empty list initially', () => {
      expect(queue.listTasks()).toEqual([]);
    });
  });

  describe('dispatch', () => {
    it('should create a task', async () => {
      const result = await queue.dispatch('Test message', {});
      expect(result.mode).toBe('async');
      expect(result.taskId).toBeDefined();
      expect(result.sessionId).toBeDefined();
    });

    it('should reject when max concurrent reached', async () => {
      // Fill up to MAX_CONCURRENT
      for (let i = 0; i < MAX_CONCURRENT; i++) {
        // Create tasks that stay running (use slow sessions)
        mockChatService.createSession = (id) => {
          const s = {
            id, status: 'disconnected',
            connect: async () => { s.status = 'connected'; },
            sendMessage: () => new Promise(() => {}), // never resolves
          };
          mockChatService.sessions.set(id, s);
          return s;
        };
        await queue.dispatch(`Task ${i}`);
      }

      // Wait for tasks to start
      await new Promise(r => setTimeout(r, 50));

      // Next dispatch should be rejected
      const result = await queue.dispatch('Overflow task');
      expect(result.mode).toBe('rejected');
    });

    it('should track dispatched task', async () => {
      const result = await queue.dispatch('Hello');
      const task = queue.getTask(result.taskId);
      expect(task).not.toBeNull();
      expect(task.status).toBeDefined();
    });
  });

  describe('listTasks', () => {
    it('should filter by status', async () => {
      await queue.dispatch('Test 1');
      await new Promise(r => setTimeout(r, 50));
      const completed = queue.listTasks({ status: 'completed' });
      expect(Array.isArray(completed)).toBe(true);
    });

    it('should respect limit', async () => {
      await queue.dispatch('A');
      await queue.dispatch('B');
      const tasks = queue.listTasks({ limit: 1 });
      expect(tasks).toHaveLength(1);
    });
  });

  describe('constants', () => {
    it('should export FAST_TIMEOUT as 15s', () => {
      expect(FAST_TIMEOUT).toBe(15000);
    });

    it('should export MAX_CONCURRENT as 3', () => {
      expect(MAX_CONCURRENT).toBe(3);
    });
  });
});