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