skill-task-manager.test.js 4.27 KB
/**
 * Unit Tests — skill-task-manager.js
 */
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'fs';
import path from 'path';

const taskManager = require('../../services/skill-task-manager');

describe('skill-task-manager', () => {
  const TMP = '/tmp/test-skill-tasks';
  const SKILL_DIR = path.join(TMP, 'test-skill');

  beforeAll(() => fs.mkdirSync(TMP, { recursive: true }));
  afterAll(() => { try { fs.rmSync(TMP, { recursive: true }); } catch {} });

  describe('initTask', () => {
    it('should create a task file with create pipeline', () => {
      const task = taskManager.initTask(SKILL_DIR, {
        skillName: 'test-skill', domain: 'test', scope: 'unit',
      });
      expect(task.skillName).toBe('test-skill');
      expect(task.pipelineType).toBe('create');
      expect(task.pipeline).toHaveLength(3);
      expect(task.pipeline.map(p => p.step)).toEqual(taskManager.PIPELINE_STEPS);
      expect(task.pipeline.every(p => p.status === 'pending')).toBe(true);
    });

    it('should create a feedback pipeline', () => {
      const fbDir = path.join(TMP, 'feedback-skill');
      const task = taskManager.initTask(fbDir, {
        skillName: 'fb-skill', pipelineType: 'feedback', feedback: 'Needs improvement',
      });
      expect(task.pipelineType).toBe('feedback');
      expect(task.pipeline).toHaveLength(4);
      expect(task.pipeline.map(p => p.step)).toEqual(taskManager.FEEDBACK_PIPELINE_STEPS);
      expect(task.feedback).toBe('Needs improvement');
    });
  });

  describe('getTask', () => {
    it('should retrieve an existing task', () => {
      const task = taskManager.getTask(SKILL_DIR);
      expect(task).not.toBeNull();
      expect(task.skillName).toBe('test-skill');
    });

    it('should return null for non-existent task', () => {
      expect(taskManager.getTask('/tmp/no-such-dir-xyz')).toBeNull();
    });
  });

  describe('updateStep', () => {
    it('should update step to running', () => {
      const task = taskManager.updateStep(SKILL_DIR, 'hunt', 'running');
      expect(task.pipeline[0].status).toBe('running');
    });

    it('should update step to done with completedAt', () => {
      const task = taskManager.updateStep(SKILL_DIR, 'hunt', 'done');
      expect(task.pipeline[0].status).toBe('done');
      expect(task.pipeline[0].completedAt).toBeDefined();
    });

    it('should update step to failed with error', () => {
      const task = taskManager.updateStep(SKILL_DIR, 'digest', 'failed', 'API error');
      expect(task.pipeline[1].status).toBe('failed');
      expect(task.pipeline[1].error).toBe('API error');
    });

    it('should return null for non-existent step', () => {
      expect(taskManager.updateStep(SKILL_DIR, 'nonexistent', 'done')).toBeNull();
    });
  });

  describe('getNextPendingStep', () => {
    it('should return next pending step', () => {
      // hunt=done, digest=failed, skill_create=pending
      const next = taskManager.getNextPendingStep(SKILL_DIR);
      expect(next).toBe('skill_create');
    });
  });

  describe('isCompleted', () => {
    it('should return false when steps are pending', () => {
      expect(taskManager.isCompleted(SKILL_DIR)).toBe(false);
    });

    it('should return true when all steps are done', () => {
      taskManager.updateStep(SKILL_DIR, 'digest', 'done');
      taskManager.updateStep(SKILL_DIR, 'skill_create', 'done');
      expect(taskManager.isCompleted(SKILL_DIR)).toBe(true);
    });
  });

  describe('findIncompleteTasks', () => {
    it('should find incomplete tasks in a directory', () => {
      // Create another incomplete task
      const incDir = path.join(TMP, 'incomplete-skill');
      taskManager.initTask(incDir, { skillName: 'incomplete' });
      const results = taskManager.findIncompleteTasks(TMP);
      expect(results.some(r => r.skillName === 'incomplete')).toBe(true);
    });
  });

  describe('constants', () => {
    it('should export PIPELINE_STEPS', () => {
      expect(taskManager.PIPELINE_STEPS).toEqual(['hunt', 'digest', 'skill_create']);
    });

    it('should export FEEDBACK_PIPELINE_STEPS', () => {
      expect(taskManager.FEEDBACK_PIPELINE_STEPS).toEqual(['re_hunt', 're_digest', 'evolve', 'publish']);
    });

    it('should export TASK_FILE', () => {
      expect(taskManager.TASK_FILE).toBe('.innovator_task.json');
    });
  });
});