Skip to main content
Glama
full-workflow.test.ts17.3 kB
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; import { handleToolCall } from '../../src/server/tool-handlers.js'; import { createTestContext, cleanupTestContext, type TestContext, } from '../helpers/test-utils.js'; /** * Integration workflow test that simulates a complete planning scenario. * * NOTE: This test calls handleToolCall() directly, bypassing MCP transport. * For E2E tests through the actual MCP protocol, see tests/e2e/mcp-all-tools.test.ts * * This test demonstrates a realistic workflow: * 1. Create a plan * 2. Add requirements * 3. Propose multiple solutions * 4. Compare and select a solution * 5. Record decisions * 6. Create phases with hierarchy * 7. Link entities together * 8. Query and validate the plan * 9. Export the complete plan */ describe('E2E: Complete Planning Workflow', () => { let ctx: TestContext; let planId: string; const requirementIds: string[] = []; const solutionIds: string[] = []; const decisionIds: string[] = []; const phaseIds: string[] = []; beforeAll(async () => { ctx = await createTestContext('e2e-workflow'); }); afterAll(async () => { await cleanupTestContext(ctx); }); describe('Step 1: Plan Creation', () => { it('should create a new project plan', async () => { const result = await handleToolCall( 'plan', { action: 'create', name: 'User Authentication System', description: 'Implement a secure authentication system with OAuth support', author: 'e2e-test', }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.planId).toBeDefined(); expect(parsed.manifest.name).toBe('User Authentication System'); planId = parsed.planId; }); it('should set the plan as active', async () => { const result = await handleToolCall( 'plan', { action: 'set_active', planId, workspacePath: '/e2e-test-workspace' }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.success).toBe(true); }); }); describe('Step 2: Requirements Gathering', () => { it('should add functional requirements', async () => { const requirements = [ { title: 'User Registration', description: 'Users must be able to register with email and password', source: { type: 'user-request' }, acceptanceCriteria: [ 'Email validation', 'Password strength requirements', 'Confirmation email sent', ], priority: 'high', category: 'functional', }, { title: 'OAuth Integration', description: 'Support login via Google and GitHub', source: { type: 'user-request' }, acceptanceCriteria: [ 'Google OAuth works', 'GitHub OAuth works', 'Account linking supported', ], priority: 'medium', category: 'functional', }, { title: 'Session Management', description: 'Secure session handling with JWT tokens', source: { type: 'derived' }, acceptanceCriteria: [ 'JWT tokens issued on login', 'Refresh token rotation', 'Session revocation', ], priority: 'high', category: 'technical', }, ]; for (const req of requirements) { const result = await handleToolCall( 'requirement', { action: 'add', planId, requirement: req }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.requirementId).toBeDefined(); requirementIds.push(parsed.requirementId); } expect(requirementIds).toHaveLength(3); }); it('should list all requirements', async () => { const result = await handleToolCall( 'requirement', { action: 'list', planId }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.requirements).toHaveLength(3); }); }); describe('Step 3: Solution Design', () => { it('should propose multiple authentication solutions', async () => { const solutions = [ { title: 'Custom JWT Implementation', description: 'Build authentication from scratch using JWT', approach: 'Implement custom JWT generation and validation with refresh tokens', addressing: [requirementIds[0], requirementIds[2]], tradeoffs: [ { aspect: 'Control', pros: ['Full control over implementation'], cons: ['More code to maintain'], }, ], evaluation: { effortEstimate: { value: 10, unit: 'days', confidence: 'medium' }, technicalFeasibility: 'high', riskAssessment: 'Medium - requires security expertise', }, }, { title: 'Auth0 Integration', description: 'Use Auth0 as managed authentication service', approach: 'Integrate Auth0 SDK for all authentication flows', addressing: requirementIds, tradeoffs: [ { aspect: 'Cost', pros: ['Faster implementation'], cons: ['Monthly subscription cost'], }, ], evaluation: { effortEstimate: { value: 3, unit: 'days', confidence: 'high' }, technicalFeasibility: 'high', riskAssessment: 'Low - battle-tested solution', }, }, { title: 'Passport.js with Custom Backend', description: 'Use Passport.js strategies with custom session store', approach: 'Leverage Passport.js for OAuth, custom JWT for sessions', addressing: requirementIds, tradeoffs: [ { aspect: 'Flexibility', pros: ['Many strategy options'], cons: ['Configuration complexity'], }, ], evaluation: { effortEstimate: { value: 5, unit: 'days', confidence: 'medium' }, technicalFeasibility: 'high', riskAssessment: 'Low - well-documented library', }, }, ]; for (const sol of solutions) { const result = await handleToolCall( 'solution', { action: 'propose', planId, solution: sol }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.solutionId).toBeDefined(); solutionIds.push(parsed.solutionId); } expect(solutionIds).toHaveLength(3); }); it('should compare all solutions', async () => { const result = await handleToolCall( 'solution', { action: 'compare', planId, solutionIds }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.comparison).toBeDefined(); expect(parsed.comparison.solutions).toHaveLength(3); }); it('should select the best solution', async () => { const result = await handleToolCall( 'solution', { action: 'select', planId, solutionId: solutionIds[2], // Passport.js solution reason: 'Best balance of effort, flexibility, and maintainability', }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.solution.status).toBe('selected'); }); }); describe('Step 4: Decision Recording', () => { it('should record architectural decisions', async () => { const decisions = [ { title: 'Token Storage Strategy', question: 'How should JWT tokens be stored on the client?', context: 'Need secure storage that prevents XSS attacks', decision: 'Use httpOnly cookies for refresh tokens, memory for access tokens', alternativesConsidered: [ { option: 'localStorage', reasoning: 'Rejected due to XSS vulnerability', }, { option: 'sessionStorage', reasoning: 'Rejected - tokens lost on tab close', }, ], consequences: 'Requires CSRF protection. More secure against XSS.', }, { title: 'Password Hashing Algorithm', question: 'Which algorithm for password hashing?', context: 'Need secure, industry-standard hashing', decision: 'Use bcrypt with cost factor 12', alternativesConsidered: [ { option: 'Argon2', reasoning: 'Considered but less library support', }, { option: 'PBKDF2', reasoning: 'Rejected - bcrypt more resistant to GPU attacks', }, ], consequences: 'Proven security. Good library support.', }, ]; for (const dec of decisions) { const result = await handleToolCall( 'decision', { action: 'record', planId, decision: dec }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.decisionId).toBeDefined(); decisionIds.push(parsed.decisionId); } expect(decisionIds).toHaveLength(2); }); }); describe('Step 5: Phase Planning', () => { it('should create implementation phases with hierarchy', async () => { // Create parent phase const mainPhaseResult = await handleToolCall( 'phase', { action: 'add', planId, phase: { title: 'Phase 1: Core Authentication', description: 'Implement basic authentication infrastructure', objectives: ['Set up auth service', 'Implement user model', 'Create login/register endpoints'], deliverables: ['Auth service module', 'User database schema', 'REST API endpoints'], successCriteria: ['All unit tests pass', 'Security audit passed'], }, }, ctx.services ); const mainPhase = JSON.parse(mainPhaseResult.content[0].text); phaseIds.push(mainPhase.phaseId); // Create child phases const subPhases = [ { title: 'Task 1.1: Database Setup', description: 'Create user schema and migrations', objectives: ['Define user model', 'Create migrations'], deliverables: ['User table', 'Migration scripts'], successCriteria: ['Migrations run successfully'], parentId: mainPhase.phaseId, }, { title: 'Task 1.2: JWT Implementation', description: 'Implement JWT token handling', objectives: ['Token generation', 'Token validation', 'Refresh logic'], deliverables: ['JWT utility module'], successCriteria: ['Tokens work correctly'], parentId: mainPhase.phaseId, }, { title: 'Task 1.3: API Endpoints', description: 'Create authentication API', objectives: ['Login endpoint', 'Register endpoint', 'Logout endpoint'], deliverables: ['REST API routes'], successCriteria: ['API tests pass'], parentId: mainPhase.phaseId, }, ]; for (const phase of subPhases) { const result = await handleToolCall( 'phase', { action: 'add', planId, phase }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.phaseId).toBeDefined(); phaseIds.push(parsed.phaseId); } expect(phaseIds).toHaveLength(4); }); it('should retrieve the phase tree', async () => { const result = await handleToolCall( 'phase', { action: 'get_tree', planId }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.tree).toBeDefined(); expect(parsed.tree.length).toBeGreaterThan(0); }); it('should update phase status to in_progress', async () => { const result = await handleToolCall( 'phase', { action: 'update_status', planId, phaseId: phaseIds[1], status: 'in_progress' }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.phase.status).toBe('in_progress'); }); }); describe('Step 6: Entity Linking', () => { it('should link requirements to solutions', async () => { const result = await handleToolCall( 'link', { action: 'create', planId, sourceId: requirementIds[0], targetId: solutionIds[2], relationType: 'implements', metadata: { coverage: 'full' }, }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.linkId).toBeDefined(); }); it('should link phases to requirements', async () => { const result = await handleToolCall( 'link', { action: 'create', planId, sourceId: phaseIds[1], targetId: requirementIds[0], relationType: 'implements', }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.linkId).toBeDefined(); }); it('should get all links for an entity', async () => { const result = await handleToolCall( 'link', { action: 'get', planId, entityId: requirementIds[0] }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.links.length).toBeGreaterThanOrEqual(2); }); }); describe('Step 7: Query and Analysis', () => { it('should search for authentication-related entities', async () => { const result = await handleToolCall( 'query', { action: 'search', planId, query: 'authentication' }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.results.length).toBeGreaterThan(0); }); it('should trace a requirement through the plan', async () => { const result = await handleToolCall( 'query', { action: 'trace', planId, requirementId: requirementIds[0] }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.requirement).toBeDefined(); expect(parsed.requirement.id).toBe(requirementIds[0]); }); it('should validate the plan', async () => { const result = await handleToolCall( 'query', { action: 'validate', planId }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.checksPerformed).toBeDefined(); expect(parsed.summary).toBeDefined(); }); it('should get next actions', async () => { const result = await handleToolCall( 'phase', { action: 'get_next_actions', planId }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.actions).toBeDefined(); }); }); describe('Step 8: Plan Export', () => { it('should export plan as markdown', async () => { const result = await handleToolCall( 'query', { action: 'export', planId, format: 'markdown' }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.format).toBe('markdown'); expect(parsed.content).toContain('User Authentication System'); expect(parsed.content).toContain('Requirements'); }); it('should export plan as JSON', async () => { const result = await handleToolCall( 'query', { action: 'export', planId, format: 'json' }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.format).toBe('json'); expect(parsed.content).toBeDefined(); }); }); describe('Step 9: Plan Summary', () => { it('should retrieve complete plan with all entities', async () => { const result = await handleToolCall( 'plan', { action: 'get', planId, includeEntities: true }, ctx.services ); const parsed = JSON.parse(result.content[0].text); expect(parsed.plan.manifest.name).toBe('User Authentication System'); expect(parsed.plan.entities.requirements).toHaveLength(3); expect(parsed.plan.entities.solutions).toHaveLength(3); expect(parsed.plan.entities.decisions).toHaveLength(2); expect(parsed.plan.entities.phases).toHaveLength(4); }); it('should verify plan statistics', async () => { const result = await handleToolCall( 'plan', { action: 'get', planId }, ctx.services ); const parsed = JSON.parse(result.content[0].text); const stats = parsed.plan.manifest.statistics; expect(stats.totalRequirements).toBe(3); expect(stats.totalSolutions).toBe(3); expect(stats.totalDecisions).toBe(2); expect(stats.totalPhases).toBe(4); }); }); });

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/cppmyjob/cpp-mcp-planner'

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