UNPKG

@emmahyde/thinking-patterns

Version:

MCP server combining systematic thinking, mental models, debugging approaches, and stochastic algorithms for comprehensive cognitive pattern support

405 lines (404 loc) 18.4 kB
/** * Tests for SequentialThoughtSchema * Tests Zod validation, type inference, and edge cases */ import { SequentialThoughtSchema, CurrentStepSchema } from '../../src/schemas/SequentialThoughtSchema.js'; import { ToolRecommendationSchema, ToolUsageHistorySchema, ToolContextSchema } from '../../src/schemas/ToolSchemas.js'; import { validSequentialThought, validSequentialThoughtWithOptionals, invalidSequentialThought, finalThoughtData } from '../helpers/testFixtures.js'; import { createMockThoughtData, createMockToolRecommendation } from '../helpers/mockFactories.js'; describe('ThoughtSchema', () => { describe('valid input validation', () => { it('should validate minimal valid thought data', () => { const result = SequentialThoughtSchema.parse(validSequentialThought); expect(result).toMatchObject({ thought: expect.any(String), thoughtNumber: expect.any(Number), totalThoughts: expect.any(Number), nextThoughtNeeded: expect.any(Boolean) }); expect(result.thought).toBe(validSequentialThought.thought); expect(result.thoughtNumber).toBe(validSequentialThought.thoughtNumber); expect(result.totalThoughts).toBe(validSequentialThought.totalThoughts); expect(result.nextThoughtNeeded).toBe(validSequentialThought.nextThoughtNeeded); }); it('should validate thought data with all optional fields', () => { const result = SequentialThoughtSchema.parse(validSequentialThoughtWithOptionals); expect(result).toMatchObject({ thought: expect.any(String), thoughtNumber: expect.any(Number), totalThoughts: expect.any(Number), nextThoughtNeeded: expect.any(Boolean) }); expect(result.isRevision).toBe(true); expect(result.revisesThought).toBe(2); expect(result.branchFromThought).toBe(1); expect(result.branchId).toBe("branch-a"); expect(result.needsMoreThoughts).toBe(true); }); it('should validate final thought data', () => { const result = SequentialThoughtSchema.parse(finalThoughtData); expect(result).toMatchObject({ thought: expect.any(String), thoughtNumber: expect.any(Number), totalThoughts: expect.any(Number), nextThoughtNeeded: expect.any(Boolean) }); expect(result.nextThoughtNeeded).toBe(false); expect(result.thoughtNumber).toBe(result.totalThoughts); }); it('should handle undefined optional fields correctly', () => { const input = { thought: "Test thought", thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true, // All optional fields undefined }; const result = SequentialThoughtSchema.parse(input); expect(result.isRevision).toBeUndefined(); expect(result.revisesThought).toBeUndefined(); expect(result.branchFromThought).toBeUndefined(); expect(result.branchId).toBeUndefined(); expect(result.needsMoreThoughts).toBeUndefined(); }); it('should validate thought data with complex nested structures', () => { const complexThought = { thought: "Complex thought with all nested data", thoughtNumber: 2, totalThoughts: 5, nextThoughtNeeded: true, currentStep: { stepDescription: "Current analysis step", recommendedTools: [{ toolName: "sequential_thinking", confidence: 0.9, rationale: "Best for this type of analysis", priority: 1, alternativeTools: ["mental_model", "debugging"] }], expectedOutcome: "Better understanding of the problem", nextStepConditions: ["Analysis complete", "Ready for synthesis"], stepNumber: 2, estimatedDuration: "10 minutes", complexityLevel: "high" }, previousSteps: [{ stepDescription: "Initial problem identification", recommendedTools: [{ toolName: "mental_model", confidence: 0.8, rationale: "Good for understanding problem structure", priority: 1 }], expectedOutcome: "Clear problem definition", nextStepConditions: ["Problem identified"] }], remainingSteps: ["Synthesis", "Conclusion", "Validation"], toolUsageHistory: [{ toolName: "mental_model", usedAt: "2024-01-01T10:00:00Z", effectivenessScore: 0.85 }] }; const result = SequentialThoughtSchema.parse(complexThought); expect(result).toMatchObject({ thought: expect.any(String), thoughtNumber: expect.any(Number), totalThoughts: expect.any(Number), nextThoughtNeeded: expect.any(Boolean) }); expect(result.currentStep).toBeDefined(); expect(result.currentStep?.stepDescription).toBe("Current analysis step"); expect(result.previousSteps).toHaveLength(1); expect(result.remainingSteps).toHaveLength(3); expect(result.toolUsageHistory).toHaveLength(1); }); }); describe('invalid input rejection', () => { it('should reject missing required fields', () => { expect(() => SequentialThoughtSchema.parse(invalidSequentialThought?.missingRequired)) .toThrow(); }); it('should reject invalid field types', () => { expect(() => SequentialThoughtSchema.parse(invalidSequentialThought.invalidTypes)) .toThrow(); }); it('should reject negative numbers', () => { expect(() => SequentialThoughtSchema.parse(invalidSequentialThought.negativeNumbers)) .toThrow(); }); it('should reject invalid optional field values', () => { expect(() => SequentialThoughtSchema.parse(invalidSequentialThought.invalidOptionals)) .toThrow(); }); it('should provide detailed error messages for validation failures', () => { try { SequentialThoughtSchema.parse({ thought: 123, // Invalid type thoughtNumber: "not a number", // Invalid type totalThoughts: -1, // Invalid value nextThoughtNeeded: "yes" // Invalid type }); expect.fail('Should have thrown validation error'); } catch (error) { expect(error.errors).toBeDefined(); expect(error.errors.length).toBeGreaterThan(0); // Check that error includes information about each invalid field const errorMessage = error.toString(); expect(errorMessage).toContain('thought'); expect(errorMessage).toContain('thoughtNumber'); expect(errorMessage).toContain('totalThoughts'); expect(errorMessage).toContain('nextThoughtNeeded'); } }); it('should reject null values for required fields', () => { expect(() => SequentialThoughtSchema.parse({ thought: null, thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true })).toThrow(); }); it('should reject empty strings for thought field', () => { expect(() => SequentialThoughtSchema.parse({ thought: "", thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true })).toThrow(); }); it('should reject zero values for positive number fields', () => { expect(() => SequentialThoughtSchema.parse({ thought: "Valid thought", thoughtNumber: 0, // Should be positive totalThoughts: 3, nextThoughtNeeded: true })).toThrow(); expect(() => SequentialThoughtSchema.parse({ thought: "Valid thought", thoughtNumber: 1, totalThoughts: 0, // Should be positive nextThoughtNeeded: true })).toThrow(); }); }); describe('type inference', () => { it('should infer correct TypeScript types', () => { const validData = { thought: "Type test", thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true }; // Type checking - these should compile without errors const thought = validData.thought; const thoughtNumber = validData.thoughtNumber; const totalThoughts = validData.totalThoughts; const nextNeeded = validData.nextThoughtNeeded; const isRevision = validData.isRevision; expect(thought).toBe("Type test"); expect(thoughtNumber).toBe(1); expect(totalThoughts).toBe(3); expect(nextNeeded).toBe(true); expect(isRevision).toBeUndefined(); }); it('should maintain type safety for optional fields', () => { const parsed = SequentialThoughtSchema.parse(validSequentialThoughtWithOptionals); // TypeScript should know these can be undefined if (parsed.isRevision !== undefined) { expect(typeof parsed.isRevision).toBe('boolean'); } if (parsed.revisesThought !== undefined) { expect(typeof parsed.revisesThought).toBe('number'); } if (parsed.branchId !== undefined) { expect(typeof parsed.branchId).toBe('string'); } }); }); describe('edge cases and boundary conditions', () => { it('should handle very long thought strings', () => { const longThought = "x".repeat(10000); const data = { thought: longThought, thoughtNumber: 1, totalThoughts: 1, nextThoughtNeeded: false }; const result = SequentialThoughtSchema.parse(data); expect(result.thought).toBe(longThought); expect(result.thought.length).toBe(10000); }); it('should handle maximum safe integer values', () => { const data = { thought: "Large numbers test", thoughtNumber: Number.MAX_SAFE_INTEGER, totalThoughts: Number.MAX_SAFE_INTEGER, nextThoughtNeeded: true }; const result = SequentialThoughtSchema.parse(data); expect(result.thoughtNumber).toBe(Number.MAX_SAFE_INTEGER); expect(result.totalThoughts).toBe(Number.MAX_SAFE_INTEGER); }); it('should handle Unicode and special characters in thought text', () => { const unicodeThought = "思考 🤔 with émojis and spëcial çharacters → ★ ♦"; const data = { thought: unicodeThought, thoughtNumber: 1, totalThoughts: 1, nextThoughtNeeded: false }; const result = SequentialThoughtSchema.parse(data); expect(result.thought).toBe(unicodeThought); }); it('should handle thoughtNumber greater than totalThoughts', () => { // This might seem illogical but schema doesn't enforce this business rule const data = { thought: "Boundary test", thoughtNumber: 5, totalThoughts: 3, nextThoughtNeeded: false }; const result = SequentialThoughtSchema.parse(data); expect(result.thoughtNumber).toBe(5); expect(result.totalThoughts).toBe(3); }); it('should handle floating point numbers by rejecting them', () => { expect(() => SequentialThoughtSchema.parse({ thought: "Float test", thoughtNumber: 1.5, // Should be integer totalThoughts: 3, nextThoughtNeeded: true })).toThrow(); }); }); describe('performance and stress testing', () => { it('should handle rapid successive validations', () => { const start = Date.now(); for (let i = 0; i < 1000; i++) { const data = createMockThoughtData({ thoughtNumber: i + 1, totalThoughts: 1000 }); const result = SequentialThoughtSchema.parse(data); expect(result).toMatchObject({ thought: expect.any(String), thoughtNumber: expect.any(Number), totalThoughts: expect.any(Number), nextThoughtNeeded: expect.any(Boolean) }); } const elapsed = Date.now() - start; expect(elapsed).toBeLessThan(1000); // Should complete in under 1 second }); it('should handle validation of large arrays in nested structures', () => { const largeToolHistory = Array.from({ length: 100 }, (_, i) => ({ toolName: `tool_${i}`, usedAt: new Date().toISOString(), effectivenessScore: Math.random() })); const data = { thought: "Large history test", thoughtNumber: 1, totalThoughts: 1, nextThoughtNeeded: false, toolUsageHistory: largeToolHistory }; const result = SequentialThoughtSchema.parse(data); expect(result.toolUsageHistory).toHaveLength(100); }); }); }); describe('ToolRecommendationSchema', () => { it('should validate valid tool recommendation', () => { const recommendation = createMockToolRecommendation(); const result = ToolRecommendationSchema.parse(recommendation); expect(result.toolName).toBe(recommendation.toolName); expect(result.confidence).toBe(recommendation.confidence); expect(result.rationale).toBe(recommendation.rationale); expect(result.priority).toBe(recommendation.priority); }); it('should reject invalid confidence values', () => { expect(() => ToolRecommendationSchema.parse({ toolName: "test", confidence: 1.5, // Must be <= 1 rationale: "test", priority: 1 })).toThrow(); expect(() => ToolRecommendationSchema.parse({ toolName: "test", confidence: -0.1, // Must be >= 0 rationale: "test", priority: 1 })).toThrow(); }); }); describe('CurrentStepSchema', () => { it('should validate current step with all fields', () => { const step = { stepDescription: "Test step", recommendedTools: [createMockToolRecommendation()], expectedOutcome: "Test outcome", nextStepConditions: ["condition1", "condition2"], stepNumber: 1, estimatedDuration: "5 minutes", complexityLevel: "medium" }; const result = CurrentStepSchema.parse(step); expect(result.complexityLevel).toBe("medium"); expect(result.stepNumber).toBe(1); }); it('should reject invalid complexity levels', () => { expect(() => CurrentStepSchema.parse({ stepDescription: "Test step", recommendedTools: [], expectedOutcome: "Test outcome", nextStepConditions: [], complexityLevel: "invalid" // Must be low, medium, or high })).toThrow(); }); }); describe('ToolUsageHistorySchema', () => { it('should validate tool usage history entry', () => { const history = { toolName: "sequential_thinking", usedAt: "2024-01-01T10:00:00Z", effectivenessScore: 0.85 }; const result = ToolUsageHistorySchema.parse(history); expect(result.toolName).toBe("sequential_thinking"); expect(result.usedAt).toBe("2024-01-01T10:00:00Z"); expect(result.effectivenessScore).toBe(0.85); }); it('should validate without effectiveness score', () => { const history = { toolName: "mental_model", usedAt: "2024-01-01T10:00:00Z" }; const result = ToolUsageHistorySchema.parse(history); expect(result.effectivenessScore).toBeUndefined(); }); }); describe('ToolContextSchema', () => { it('should validate tool context', () => { const context = { availableTools: ["sequential_thinking", "mental_model", "debugging"], userPreferences: { style: "detailed", format: "structured" }, sessionHistory: ["Previous thought 1", "Previous thought 2"], problemDomain: "analysis" }; const result = ToolContextSchema.parse(context); expect(result.availableTools).toHaveLength(3); expect(result.userPreferences?.style).toBe("detailed"); }); it('should validate minimal tool context', () => { const context = { availableTools: ["sequential_thinking"] }; const result = ToolContextSchema.parse(context); expect(result.userPreferences).toBeUndefined(); expect(result.sessionHistory).toBeUndefined(); expect(result.problemDomain).toBeUndefined(); }); });