| |
| |
| |
|
|
| import { describe, it, expect, beforeEach, afterEach } from 'bun:test' |
| import * as fs from 'fs/promises' |
| import * as path from 'path' |
| import * as os from 'os' |
| import { TaskService } from '../services/taskService.js' |
|
|
| |
| |
| |
|
|
| describe('TaskService', () => { |
| let tmpDir: string |
|
|
| beforeEach(async () => { |
| tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-tasks-')) |
| process.env.CLAUDE_CONFIG_DIR = tmpDir |
| }) |
|
|
| afterEach(async () => { |
| await fs.rm(tmpDir, { recursive: true, force: true }) |
| delete process.env.CLAUDE_CONFIG_DIR |
| }) |
|
|
| it('should return empty list when no tasks dir', async () => { |
| const svc = new TaskService() |
| const tasks = await svc.listTasks() |
| expect(tasks).toEqual([]) |
| }) |
|
|
| it('should list tasks from JSON files', async () => { |
| const tasksDir = path.join(tmpDir, 'tasks') |
| await fs.mkdir(tasksDir, { recursive: true }) |
|
|
| await fs.writeFile(path.join(tasksDir, 'task-001.json'), JSON.stringify({ |
| id: 'task-001', |
| type: 'local_agent', |
| status: 'completed', |
| name: 'code-review', |
| description: 'Review PR #42', |
| createdAt: Date.now() - 60000, |
| completedAt: Date.now(), |
| })) |
|
|
| await fs.writeFile(path.join(tasksDir, 'task-002.json'), JSON.stringify({ |
| id: 'task-002', |
| type: 'in_process_teammate', |
| status: 'running', |
| name: 'frontend-dev', |
| teamName: 'ui-team', |
| createdAt: Date.now(), |
| })) |
|
|
| const svc = new TaskService() |
| const tasks = await svc.listTasks() |
| expect(tasks.length).toBe(2) |
| |
| expect(tasks[0].id).toBe('task-002') |
| expect(tasks[1].id).toBe('task-001') |
| }) |
|
|
| it('should scan nested team task directories', async () => { |
| const teamDir = path.join(tmpDir, 'tasks', 'my-team') |
| await fs.mkdir(teamDir, { recursive: true }) |
|
|
| await fs.writeFile(path.join(teamDir, 'member-1.json'), JSON.stringify({ |
| id: 'member-1', |
| type: 'in_process_teammate', |
| status: 'completed', |
| teamName: 'my-team', |
| })) |
|
|
| const svc = new TaskService() |
| const tasks = await svc.listTasks() |
| expect(tasks.length).toBe(1) |
| expect(tasks[0].teamName).toBe('my-team') |
| }) |
|
|
| it('should get single task by ID', async () => { |
| const tasksDir = path.join(tmpDir, 'tasks') |
| await fs.mkdir(tasksDir, { recursive: true }) |
|
|
| await fs.writeFile(path.join(tasksDir, 'abc.json'), JSON.stringify({ |
| id: 'abc', |
| type: 'local_shell', |
| status: 'failed', |
| name: 'build', |
| })) |
|
|
| const svc = new TaskService() |
| const task = await svc.getTask('abc') |
| expect(task).toBeDefined() |
| expect(task!.status).toBe('failed') |
| }) |
|
|
| it('should return null for unknown task', async () => { |
| const svc = new TaskService() |
| const task = await svc.getTask('nonexistent') |
| expect(task).toBeNull() |
| }) |
|
|
| it('should skip invalid JSON files gracefully', async () => { |
| const tasksDir = path.join(tmpDir, 'tasks') |
| await fs.mkdir(tasksDir, { recursive: true }) |
| await fs.writeFile(path.join(tasksDir, 'bad.json'), 'not json {{{') |
|
|
| const svc = new TaskService() |
| const tasks = await svc.listTasks() |
| expect(tasks).toEqual([]) |
| }) |
| }) |
|
|
| |
| |
| |
|
|
| describe('Tasks API', () => { |
| let server: any |
| let baseUrl: string |
| let tmpDir: string |
|
|
| beforeEach(async () => { |
| tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'claude-tasks-api-')) |
| process.env.CLAUDE_CONFIG_DIR = tmpDir |
| await fs.mkdir(path.join(tmpDir, 'projects'), { recursive: true }) |
|
|
| const port = 15500 + Math.floor(Math.random() * 500) |
| const { startServer } = await import('../../server/index.js') |
| server = startServer(port, '127.0.0.1') |
| baseUrl = `http://127.0.0.1:${port}` |
| }) |
|
|
| afterEach(async () => { |
| server?.stop() |
| await fs.rm(tmpDir, { recursive: true, force: true }) |
| delete process.env.CLAUDE_CONFIG_DIR |
| }) |
|
|
| it('should return empty tasks list', async () => { |
| const res = await fetch(`${baseUrl}/api/tasks`) |
| const data = await res.json() |
| expect(res.status).toBe(200) |
| expect(data.tasks).toEqual([]) |
| }) |
|
|
| it('should return tasks when files exist', async () => { |
| const tasksDir = path.join(tmpDir, 'tasks') |
| await fs.mkdir(tasksDir, { recursive: true }) |
| await fs.writeFile(path.join(tasksDir, 'test.json'), JSON.stringify({ |
| id: 'test', type: 'local_agent', status: 'completed', name: 'test-task', |
| })) |
|
|
| const res = await fetch(`${baseUrl}/api/tasks`) |
| const data = await res.json() |
| expect(data.tasks.length).toBe(1) |
| expect(data.tasks[0].name).toBe('test-task') |
| }) |
|
|
| it('should return 404 for unknown task', async () => { |
| const res = await fetch(`${baseUrl}/api/tasks/nonexistent`) |
| expect(res.status).toBe(404) |
| }) |
|
|
| it('should reject non-GET methods', async () => { |
| const res = await fetch(`${baseUrl}/api/tasks`, { method: 'POST' }) |
| expect(res.status).toBe(405) |
| }) |
| }) |
|
|