/** * 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'); }); }); });