/**
* Tests for Task Dependencies Functionality
*
* Note: These are example tests that would require a test framework like Jest or Vitest
* and proper mocking of the ClickUp API to run.
*/
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { TaskServiceDependencies } from '../src/services/clickup/task/task-dependencies';
import {
handleAddTaskDependency,
handleRemoveTaskDependency,
handleGetTaskDependencies,
handleAddBulkDependencies
} from '../src/tools/task/dependencies';
// Mock the ClickUp API client
jest.mock('axios');
describe('Task Dependencies Service', () => {
let taskService: TaskServiceDependencies;
beforeEach(() => {
// Initialize service with mocked dependencies
taskService = new TaskServiceDependencies('test-api-key', 'test-team-id');
});
describe('addTaskDependency', () => {
it('should create a waiting_on dependency between two tasks', async () => {
// Mock the API response
const mockDependency = {
task_id: 'task1',
depends_on: 'task2',
dependency_of: 'task1',
type: 0,
date_created: '2024-01-01',
userid: 'user1',
workspace_id: 'workspace1'
};
// Mock the post method
taskService.client.post = jest.fn().mockResolvedValue({ data: mockDependency });
const result = await taskService.addTaskDependency({
taskId: 'task1',
dependsOnTaskId: 'task2',
dependencyType: 'waiting_on'
});
expect(result.success).toBe(true);
expect(result.data).toEqual(mockDependency);
expect(taskService.client.post).toHaveBeenCalledWith(
'/task/task1/dependency',
{
depends_on: 'task2',
dependency_type: 0
}
);
});
it('should prevent circular dependencies', async () => {
// Mock getTaskDependencies to return existing dependency
taskService.getTaskDependencies = jest.fn().mockResolvedValue({
success: true,
data: {
task: { id: 'task2', name: 'Task 2' },
dependencies: {
waiting_on: [{ task_id: 'task1', task_name: 'Task 1', status: 'open', list: { id: 'list1', name: 'List' } }],
blocking: []
}
}
});
const result = await taskService.addTaskDependency({
taskId: 'task1',
dependsOnTaskId: 'task2',
dependencyType: 'waiting_on'
});
expect(result.success).toBe(false);
expect(result.error?.code).toBe('validation_error');
expect(result.error?.message).toContain('Circular dependency');
});
it('should prevent self-dependencies', async () => {
const result = await taskService.addTaskDependency({
taskId: 'task1',
dependsOnTaskId: 'task1',
dependencyType: 'waiting_on'
});
expect(result.success).toBe(false);
expect(result.error?.code).toBe('validation_error');
expect(result.error?.message).toContain('self-dependency');
});
});
describe('removeTaskDependency', () => {
it('should remove an existing dependency', async () => {
// Mock the delete method
taskService.client.delete = jest.fn().mockResolvedValue({});
const result = await taskService.removeTaskDependency({
taskId: 'task1',
dependencyTaskId: 'task2'
});
expect(result.success).toBe(true);
expect(taskService.client.delete).toHaveBeenCalledWith(
'/task/task1/dependency?depends_on=task2'
);
});
});
describe('getTaskDependencies', () => {
it('should retrieve task dependencies with details', async () => {
// Mock getTask responses
const mockTask = {
id: 'task1',
name: 'Task 1',
dependencies: ['task2', 'task3'],
status: { status: 'open' },
list: { id: 'list1', name: 'List 1' }
};
const mockDependency1 = {
id: 'task2',
name: 'Task 2',
status: { status: 'complete' },
list: { id: 'list1', name: 'List 1' }
};
const mockDependency2 = {
id: 'task3',
name: 'Task 3',
status: { status: 'in_progress' },
list: { id: 'list2', name: 'List 2' }
};
taskService.getTask = jest.fn()
.mockResolvedValueOnce(mockTask)
.mockResolvedValueOnce(mockDependency1)
.mockResolvedValueOnce(mockDependency2);
const result = await taskService.getTaskDependencies({
taskId: 'task1'
});
expect(result.success).toBe(true);
expect(result.data?.task).toEqual({ id: 'task1', name: 'Task 1' });
expect(result.data?.dependencies.waiting_on).toHaveLength(2);
expect(result.data?.dependencies.waiting_on[0]).toEqual({
task_id: 'task2',
task_name: 'Task 2',
status: 'complete',
list: { id: 'list1', name: 'List 1' }
});
});
});
describe('addBulkDependencies', () => {
it('should add multiple dependencies in bulk', async () => {
// Mock successful API calls
taskService.client.post = jest.fn().mockResolvedValue({});
taskService.checkCircularDependency = jest.fn().mockResolvedValue(undefined);
const result = await taskService.addBulkDependencies({
dependencies: [
{ taskId: 'task1', dependsOn: ['task2', 'task3'] },
{ taskId: 'task4', dependsOn: ['task5'] }
],
options: { continueOnError: true }
});
expect(result.success).toBe(true);
expect(result.data?.summary.total).toBe(3);
expect(result.data?.summary.success).toBe(3);
expect(result.data?.summary.failed).toBe(0);
expect(taskService.client.post).toHaveBeenCalledTimes(3);
});
it('should handle partial failures with continueOnError', async () => {
// Mock mixed success/failure
taskService.client.post = jest.fn()
.mockResolvedValueOnce({})
.mockRejectedValueOnce(new Error('Dependency already exists'))
.mockResolvedValueOnce({});
taskService.checkCircularDependency = jest.fn().mockResolvedValue(undefined);
const result = await taskService.addBulkDependencies({
dependencies: [
{ taskId: 'task1', dependsOn: ['task2', 'task3', 'task4'] }
],
options: { continueOnError: true }
});
expect(result.success).toBe(true);
expect(result.data?.summary.total).toBe(3);
expect(result.data?.summary.success).toBe(2);
expect(result.data?.summary.failed).toBe(1);
expect(result.data?.failed[0].error).toContain('Dependency already exists');
});
});
});
describe('Task Dependencies Handlers', () => {
describe('handleAddTaskDependency', () => {
it('should validate required parameters', async () => {
await expect(handleAddTaskDependency({})).rejects.toThrow(
'Either taskId or taskName must be provided'
);
await expect(handleAddTaskDependency({ taskId: 'task1' })).rejects.toThrow(
'Either dependsOnTaskId or dependsOnTaskName must be provided'
);
});
});
describe('handleGetTaskDependencies', () => {
it('should validate required parameters', async () => {
await expect(handleGetTaskDependencies({})).rejects.toThrow(
'Either taskId or taskName must be provided'
);
});
});
describe('handleAddBulkDependencies', () => {
it('should validate dependencies array', async () => {
await expect(handleAddBulkDependencies({})).rejects.toThrow(
'Dependencies array is required and must not be empty'
);
await expect(handleAddBulkDependencies({ dependencies: [] })).rejects.toThrow(
'Dependencies array is required and must not be empty'
);
await expect(handleAddBulkDependencies({
dependencies: [{ dependsOn: ['task1'] }]
})).rejects.toThrow(
'Each dependency item must have a taskId'
);
});
});
});
describe('Integration Tests', () => {
it('should handle create task with dependencies workflow', async () => {
// This would test the full workflow:
// 1. Create task with dependencies parameter
// 2. Verify task is created
// 3. Verify dependencies are added
// 4. Verify get_task returns dependency details
});
it('should handle complex dependency chains', async () => {
// This would test:
// 1. Creating multiple tasks
// 2. Setting up a dependency chain (A -> B -> C -> D)
// 3. Verifying circular dependency prevention
// 4. Testing bulk operations
});
});