mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 19:14:33 -07:00
feat(core): implement task tracker foundation and service (#19464)
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { TrackerService } from './trackerService.js';
|
||||
import { TaskStatus, TaskType, type TrackerTask } from './trackerTypes.js';
|
||||
|
||||
describe('TrackerService', () => {
|
||||
let testTrackerDir: string;
|
||||
let service: TrackerService;
|
||||
|
||||
beforeEach(async () => {
|
||||
testTrackerDir = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), 'tracker-service-test-'),
|
||||
);
|
||||
service = new TrackerService(testTrackerDir);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fs.rm(testTrackerDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('should create a task with a generated 6-char hex ID', async () => {
|
||||
const taskData: Omit<TrackerTask, 'id'> = {
|
||||
title: 'Test Task',
|
||||
description: 'Test Description',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
};
|
||||
|
||||
const task = await service.createTask(taskData);
|
||||
expect(task.id).toMatch(/^[0-9a-f]{6}$/);
|
||||
expect(task.title).toBe(taskData.title);
|
||||
|
||||
const savedTask = await service.getTask(task.id);
|
||||
expect(savedTask).toEqual(task);
|
||||
});
|
||||
|
||||
it('should list all tasks', async () => {
|
||||
await service.createTask({
|
||||
title: 'Task 1',
|
||||
description: 'Desc 1',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
await service.createTask({
|
||||
title: 'Task 2',
|
||||
description: 'Desc 2',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const tasks = await service.listTasks();
|
||||
expect(tasks.length).toBe(2);
|
||||
expect(tasks.map((t) => t.title)).toContain('Task 1');
|
||||
expect(tasks.map((t) => t.title)).toContain('Task 2');
|
||||
});
|
||||
|
||||
it('should update a task', async () => {
|
||||
const task = await service.createTask({
|
||||
title: 'Original Title',
|
||||
description: 'Original Desc',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const updated = await service.updateTask(task.id, {
|
||||
title: 'New Title',
|
||||
status: TaskStatus.IN_PROGRESS,
|
||||
});
|
||||
expect(updated.title).toBe('New Title');
|
||||
expect(updated.status).toBe('in_progress');
|
||||
expect(updated.description).toBe('Original Desc');
|
||||
|
||||
const retrieved = await service.getTask(task.id);
|
||||
expect(retrieved).toEqual(updated);
|
||||
});
|
||||
|
||||
it('should prevent closing a task if dependencies are not closed', async () => {
|
||||
const dep = await service.createTask({
|
||||
title: 'Dependency',
|
||||
description: 'Must be closed first',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const task = await service.createTask({
|
||||
title: 'Main Task',
|
||||
description: 'Depends on dep',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [dep.id],
|
||||
});
|
||||
|
||||
await expect(
|
||||
service.updateTask(task.id, { status: TaskStatus.CLOSED }),
|
||||
).rejects.toThrow(/Cannot close task/);
|
||||
|
||||
// Close dependency
|
||||
await service.updateTask(dep.id, { status: TaskStatus.CLOSED });
|
||||
|
||||
// Now it should work
|
||||
const updated = await service.updateTask(task.id, {
|
||||
status: TaskStatus.CLOSED,
|
||||
});
|
||||
expect(updated.status).toBe('closed');
|
||||
});
|
||||
|
||||
it('should detect circular dependencies', async () => {
|
||||
const taskA = await service.createTask({
|
||||
title: 'Task A',
|
||||
description: 'A',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const taskB = await service.createTask({
|
||||
title: 'Task B',
|
||||
description: 'B',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [taskA.id],
|
||||
});
|
||||
|
||||
// Try to make A depend on B
|
||||
await expect(
|
||||
service.updateTask(taskA.id, { dependencies: [taskB.id] }),
|
||||
).rejects.toThrow(/Circular dependency detected/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user