Skip to main content
Glama
task-fixtures.ts13.8 kB
/** * @fileoverview Test fixtures for creating valid task data structures * * WHY FIXTURES: * - Ensures all required fields are present (prevents validation errors) * - Provides consistent, realistic test data * - Easy to override specific fields for test scenarios * - Single source of truth for valid task structures * * USAGE: * ```ts * import { createTask, createTasksFile } from '@tm/core/testing'; * * // Create a single task with defaults * const task = createTask({ id: 1, title: 'My Task', status: 'pending' }); * * // Create a complete tasks.json structure * const tasksFile = createTasksFile({ * tasks: [ * createTask({ id: 1, title: 'Task 1' }), * createTask({ id: 2, title: 'Task 2', dependencies: ['1'] }) * ] * }); * ``` */ import type { Subtask, Task, TaskMetadata } from '../common/types/index.js'; /** * File structure for tasks.json * Note: Uses the 'master' tag as the default tag name */ export interface TasksFile { master: { tasks: Task[]; metadata: TaskMetadata; }; } /** * Creates a valid task with all required fields * * DEFAULTS: * - id: Converted to string if number is provided * - status: 'pending' * - priority: 'medium' * - dependencies: [] * - subtasks: [] * - description: Same as title * - details: Empty string * - testStrategy: Empty string */ export function createTask( overrides: Partial<Omit<Task, 'id'>> & { id: number | string; title: string } ): Task { return { id: String(overrides.id), title: overrides.title, description: overrides.description ?? overrides.title, status: overrides.status ?? 'pending', priority: overrides.priority ?? 'medium', dependencies: overrides.dependencies ?? [], details: overrides.details ?? '', testStrategy: overrides.testStrategy ?? '', subtasks: overrides.subtasks ?? [], // Spread any additional optional fields ...(overrides.createdAt && { createdAt: overrides.createdAt }), ...(overrides.updatedAt && { updatedAt: overrides.updatedAt }), ...(overrides.effort && { effort: overrides.effort }), ...(overrides.actualEffort && { actualEffort: overrides.actualEffort }), ...(overrides.tags && { tags: overrides.tags }), ...(overrides.assignee && { assignee: overrides.assignee }), ...(overrides.databaseId && { databaseId: overrides.databaseId }), ...(overrides.complexity && { complexity: overrides.complexity }), ...(overrides.recommendedSubtasks && { recommendedSubtasks: overrides.recommendedSubtasks }), ...(overrides.expansionPrompt && { expansionPrompt: overrides.expansionPrompt }), ...(overrides.complexityReasoning && { complexityReasoning: overrides.complexityReasoning }), // AI implementation metadata fields ...(overrides.relevantFiles && { relevantFiles: overrides.relevantFiles }), ...(overrides.codebasePatterns && { codebasePatterns: overrides.codebasePatterns }), ...(overrides.existingInfrastructure && { existingInfrastructure: overrides.existingInfrastructure }), ...(overrides.scopeBoundaries && { scopeBoundaries: overrides.scopeBoundaries }), ...(overrides.implementationApproach && { implementationApproach: overrides.implementationApproach }), ...(overrides.technicalConstraints && { technicalConstraints: overrides.technicalConstraints }), ...(overrides.acceptanceCriteria && { acceptanceCriteria: overrides.acceptanceCriteria }), ...(overrides.skills && { skills: overrides.skills }), ...(overrides.category && { category: overrides.category }) }; } /** * Creates a valid subtask with all required fields * * DEFAULTS: * - id: Can be number or string * - status: 'pending' * - priority: 'medium' * - dependencies: [] * - description: Same as title * - details: Empty string * - testStrategy: Empty string * - parentId: Derived from id if not provided (e.g., '1.2' -> parentId '1') */ export function createSubtask( overrides: Partial<Omit<Subtask, 'id' | 'parentId'>> & { id: number | string; title: string; parentId?: string; } ): Subtask { const idStr = String(overrides.id); const defaultParentId = idStr.includes('.') ? idStr.split('.')[0] : '1'; return { id: overrides.id, parentId: overrides.parentId ?? defaultParentId, title: overrides.title, description: overrides.description ?? overrides.title, status: overrides.status ?? 'pending', priority: overrides.priority ?? 'medium', dependencies: overrides.dependencies ?? [], details: overrides.details ?? '', testStrategy: overrides.testStrategy ?? '', // Spread any additional optional fields ...(overrides.createdAt && { createdAt: overrides.createdAt }), ...(overrides.updatedAt && { updatedAt: overrides.updatedAt }), ...(overrides.effort && { effort: overrides.effort }), ...(overrides.actualEffort && { actualEffort: overrides.actualEffort }), ...(overrides.tags && { tags: overrides.tags }), ...(overrides.assignee && { assignee: overrides.assignee }), ...(overrides.databaseId && { databaseId: overrides.databaseId }), ...(overrides.complexity && { complexity: overrides.complexity }), ...(overrides.recommendedSubtasks && { recommendedSubtasks: overrides.recommendedSubtasks }), ...(overrides.expansionPrompt && { expansionPrompt: overrides.expansionPrompt }), ...(overrides.complexityReasoning && { complexityReasoning: overrides.complexityReasoning }), // AI implementation metadata fields ...(overrides.relevantFiles && { relevantFiles: overrides.relevantFiles }), ...(overrides.codebasePatterns && { codebasePatterns: overrides.codebasePatterns }), ...(overrides.existingInfrastructure && { existingInfrastructure: overrides.existingInfrastructure }), ...(overrides.scopeBoundaries && { scopeBoundaries: overrides.scopeBoundaries }), ...(overrides.implementationApproach && { implementationApproach: overrides.implementationApproach }), ...(overrides.technicalConstraints && { technicalConstraints: overrides.technicalConstraints }), ...(overrides.acceptanceCriteria && { acceptanceCriteria: overrides.acceptanceCriteria }), ...(overrides.skills && { skills: overrides.skills }), ...(overrides.category && { category: overrides.category }) }; } /** * Creates a complete tasks.json file structure * * DEFAULTS: * - Empty tasks array * - version: '1.0.0' * - lastModified: Current timestamp * - taskCount: Calculated from tasks array * - completedCount: Calculated from tasks array * - description: 'Test tasks' */ export function createTasksFile(overrides?: { tasks?: Task[]; metadata?: Partial<TaskMetadata>; }): TasksFile { const tasks = overrides?.tasks ?? []; const completedTasks = tasks.filter( (t) => t.status === 'done' || t.status === 'completed' || t.status === 'cancelled' ); const defaultMetadata: TaskMetadata = { version: '1.0.0', lastModified: new Date().toISOString(), taskCount: tasks.length, completedCount: completedTasks.length, description: 'Test tasks', ...overrides?.metadata }; return { master: { tasks, metadata: defaultMetadata } }; } /** * Pre-built task scenarios for common test cases */ export const TaskScenarios = { /** * Single pending task with no dependencies */ simplePendingTask: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Simple Task', description: 'A basic pending task' }) ] }), /** * Linear dependency chain: 1 -> 2 -> 3 */ linearDependencyChain: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Step 1', status: 'done' }), createTask({ id: 2, title: 'Step 2', status: 'done', dependencies: ['1'] }), createTask({ id: 3, title: 'Step 3', status: 'pending', dependencies: ['2'] }) ] }), /** * Tasks with mixed statuses */ mixedStatuses: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Done Task', status: 'done' }), createTask({ id: 2, title: 'In Progress Task', status: 'in-progress' }), createTask({ id: 3, title: 'Pending Task', status: 'pending' }), createTask({ id: 4, title: 'Review Task', status: 'review' }) ] }), /** * Task with subtasks */ taskWithSubtasks: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Parent Task', status: 'in-progress', subtasks: [ createSubtask({ id: '1.1', title: 'Subtask 1', status: 'done' }), createSubtask({ id: '1.2', title: 'Subtask 2', status: 'in-progress', dependencies: ['1.1'] }), createSubtask({ id: '1.3', title: 'Subtask 3', status: 'pending', dependencies: ['1.2'] }) ] }) ] }), /** * Complex dependency graph with multiple paths */ complexDependencies: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Foundation', status: 'done' }), createTask({ id: 2, title: 'Build A', status: 'done', dependencies: ['1'] }), createTask({ id: 3, title: 'Build B', status: 'done', dependencies: ['1'] }), createTask({ id: 4, title: 'Integration', status: 'pending', dependencies: ['2', '3'] }) ] }), /** * All tasks completed (for testing "no next task" scenario) */ allCompleted: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Done 1', status: 'done' }), createTask({ id: 2, title: 'Done 2', status: 'done' }), createTask({ id: 3, title: 'Done 3', status: 'done' }) ] }), /** * Empty task list */ empty: () => createTasksFile({ tasks: [] }), /** * Task with rich AI-generated implementation metadata */ taskWithImplementationMetadata: () => createTasksFile({ tasks: [ createTask({ id: 1, title: 'Implement User Authentication', description: 'Add JWT-based authentication to the API', details: 'Implement secure JWT authentication with refresh tokens', testStrategy: 'Unit tests for auth functions, integration tests for flow', category: 'development', skills: ['TypeScript', 'JWT', 'Security'], relevantFiles: [ { path: 'src/auth/auth.service.ts', description: 'Main authentication service', action: 'modify' }, { path: 'src/auth/jwt.strategy.ts', description: 'JWT passport strategy', action: 'create' } ], codebasePatterns: [ 'Use dependency injection for services', 'Follow repository pattern for data access' ], existingInfrastructure: [ { name: 'UserRepository', location: 'src/users/user.repository.ts', usage: 'Use for user lookups during authentication' } ], scopeBoundaries: { included: 'JWT token generation, validation, and refresh', excluded: 'OAuth integration (handled in task 2)' }, implementationApproach: '1. Create JWT strategy\n2. Add auth guards\n3. Implement refresh token flow', technicalConstraints: [ 'Must use RS256 algorithm', 'Tokens must expire in 15 minutes' ], acceptanceCriteria: [ 'Users can login with email/password', 'JWT tokens are issued on successful login', 'Refresh tokens work correctly' ], subtasks: [ createSubtask({ id: '1.1', title: 'Create JWT Strategy', category: 'development', skills: ['TypeScript', 'Passport.js'], relevantFiles: [ { path: 'src/auth/jwt.strategy.ts', description: 'JWT passport strategy implementation', action: 'create' } ], acceptanceCriteria: ['Strategy validates JWT tokens correctly'] }), createSubtask({ id: '1.2', title: 'Implement Auth Guards', category: 'development', implementationApproach: 'Create NestJS guards using JWT strategy', technicalConstraints: ['Must work with role-based access control'] }) ] }) ] }) }; /** * Sample metadata fixtures for testing metadata extraction */ export const MetadataFixtures = { /** * Complete metadata object with all fields populated */ completeMetadata: { details: 'Detailed task requirements and scope', testStrategy: 'Unit and integration tests', relevantFiles: [ { path: 'src/service.ts', description: 'Main service file', action: 'modify' as const } ], codebasePatterns: ['Use dependency injection', 'Follow SOLID principles'], existingInfrastructure: [ { name: 'Logger', location: 'src/common/logger.ts', usage: 'Use for structured logging' } ], scopeBoundaries: { included: 'Core functionality', excluded: 'UI changes' }, implementationApproach: 'Step-by-step implementation guide', technicalConstraints: ['Must be backwards compatible'], acceptanceCriteria: ['Feature works as expected', 'Tests pass'], skills: ['TypeScript', 'Node.js'], category: 'development' as const }, /** * Minimal metadata with only required fields */ minimalMetadata: { details: 'Basic details', testStrategy: 'Basic tests' }, /** * Metadata with invalid/malformed data (for testing robustness) */ malformedMetadata: { details: 123, // Should be string testStrategy: null, // Should be string relevantFiles: 'not-an-array', // Should be array codebasePatterns: [123, null, 'valid'], // Mixed invalid types existingInfrastructure: [{ invalid: 'structure' }], // Missing required fields scopeBoundaries: 'not-an-object', // Should be object category: 'invalid-category', // Invalid enum value skills: { not: 'an-array' } // Should be array }, /** * Empty metadata object */ emptyMetadata: {} };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/eyaltoledano/claude-task-master'

If you have feedback or need assistance with the MCP directory API, please join our Discord server