File size: 4,017 Bytes
7a1ad33 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | /**
* @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/);
});
});
|