Leon4gr45/builder
0
1import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';2import { TaskManager } from '../task-manager';3 4describe('TaskManager', () => {5 let tm: TaskManager;6 7 beforeEach(() => {8 tm = new TaskManager({ maxConcurrentPerScope: 3, keyTTLMs: 30 * 60 * 1000 });9 });10 11 afterEach(() => {12 tm.dispose();13 });14 15 it('creates a task and returns taskId', () => {16 const taskId = tm.createTask('proj-1', 'session-1', 'sk-key', 'workspace-1');17 expect(taskId).toBeDefined();18 expect(typeof taskId).toBe('string');19 });20 21 it('retrieves a created task', () => {22 const taskId = tm.createTask('proj-1', 'session-1', 'sk-key');23 const task = tm.getTask(taskId);24 expect(task).toBeDefined();25 expect(task!.projectId).toBe('proj-1');26 expect(task!.status).toBe('running');27 });28 29 it('returns undefined for unknown taskId', () => {30 expect(tm.getTask('nonexistent')).toBeUndefined();31 });32 33 it('stores and retrieves API key', () => {34 const taskId = tm.createTask('proj-1', 'session-1', 'sk-secret');35 expect(tm.getApiKey(taskId)).toBe('sk-secret');36 });37 38 it('deletes API key on task completion', () => {39 const taskId = tm.createTask('proj-1', 'session-1', 'sk-secret');40 tm.completeTask(taskId, 'completed');41 expect(tm.getApiKey(taskId)).toBeUndefined();42 });43 44 it('deletes API key on task failure', () => {45 const taskId = tm.createTask('proj-1', 'session-1', 'sk-secret');46 tm.completeTask(taskId, 'failed');47 expect(tm.getApiKey(taskId)).toBeUndefined();48 });49 50 it('deletes API key on task cancellation', () => {51 const taskId = tm.createTask('proj-1', 'session-1', 'sk-secret');52 tm.completeTask(taskId, 'cancelled');53 expect(tm.getApiKey(taskId)).toBeUndefined();54 });55 56 it('enforces concurrent task limit per workspace', () => {57 tm.createTask('proj-1', 'session-1', 'k1', 'ws-1');58 tm.createTask('proj-2', 'session-1', 'k2', 'ws-1');59 tm.createTask('proj-3', 'session-1', 'k3', 'ws-1');60 expect(() => tm.createTask('proj-4', 'session-1', 'k4', 'ws-1')).toThrow(/concurrent task limit/i);61 });62 63 it('enforces concurrent task limit per session when no workspace', () => {64 tm.createTask('proj-1', 'sess-1', 'k1');65 tm.createTask('proj-2', 'sess-1', 'k2');66 tm.createTask('proj-3', 'sess-1', 'k3');67 expect(() => tm.createTask('proj-4', 'sess-1', 'k4')).toThrow(/concurrent task limit/i);68 });69 70 it('allows tasks from different scopes', () => {71 tm.createTask('proj-1', 'sess-1', 'k1', 'ws-1');72 tm.createTask('proj-2', 'sess-1', 'k2', 'ws-1');73 tm.createTask('proj-3', 'sess-1', 'k3', 'ws-1');74 const taskId = tm.createTask('proj-4', 'sess-1', 'k4', 'ws-2');75 expect(taskId).toBeDefined();76 });77 78 it('completed tasks free up slots', () => {79 const t1 = tm.createTask('proj-1', 'sess-1', 'k1', 'ws-1');80 tm.createTask('proj-2', 'sess-1', 'k2', 'ws-1');81 tm.createTask('proj-3', 'sess-1', 'k3', 'ws-1');82 tm.completeTask(t1, 'completed');83 const t4 = tm.createTask('proj-4', 'sess-1', 'k4', 'ws-1');84 expect(t4).toBeDefined();85 });86 87 it('getTasksForSession returns all tasks for a session', () => {88 tm.createTask('proj-1', 'sess-1', 'k1');89 tm.createTask('proj-2', 'sess-1', 'k2');90 tm.createTask('proj-3', 'sess-2', 'k3');91 expect(tm.getTasksForSession('sess-1')).toHaveLength(2);92 expect(tm.getTasksForSession('sess-2')).toHaveLength(1);93 });94 95 it('stores metadata (prompt, model, projectName) on task', () => {96 const taskId = tm.createTask('proj-1', 'sess-1', 'sk-key');97 const task = tm.getTask(taskId)!;98 task.prompt = 'make a button';99 task.model = 'gpt-4o';100 task.projectName = 'My Project';101 102 const retrieved = tm.getTask(taskId)!;103 expect(retrieved.prompt).toBe('make a button');104 expect(retrieved.model).toBe('gpt-4o');105 expect(retrieved.projectName).toBe('My Project');106 });107 108 it('TTL sweep removes expired keys', () => {109 vi.useFakeTimers();110 const shortTTL = new TaskManager({ maxConcurrentPerScope: 3, keyTTLMs: 1000 });111 const taskId = shortTTL.createTask('proj-1', 'sess-1', 'sk-expire');112 vi.advanceTimersByTime(1500);113 shortTTL.sweepExpiredKeys();114 expect(shortTTL.getApiKey(taskId)).toBeUndefined();115 shortTTL.dispose();116 vi.useRealTimers();117 });118});119 