@emmahyde/thinking-patterns
Version:
MCP server combining systematic thinking, mental models, debugging approaches, and stochastic algorithms for comprehensive cognitive pattern support
294 lines (293 loc) • 13.4 kB
JavaScript
/**
* Tests for SequentialThinkingServer class
* Tests validation logic, error handling, and MCP integration
*/
import { SequentialThinkingServer } from '../../src/servers/SequentialThinkingServer.js';
import { createMockThoughtData, createMockCurrentStep, resetAllMocks } from '../helpers/mockFactories.js';
describe('SequentialThinkingServer', () => {
let server;
beforeEach(() => {
// Reset all mocks before each test
resetAllMocks();
// Create server instance
server = new SequentialThinkingServer();
});
describe('constructor', () => {
it('should initialize correctly', () => {
expect(server).toBeInstanceOf(SequentialThinkingServer);
});
});
describe('process', () => {
it('should process valid thought data successfully', () => {
const validInput = createMockThoughtData({
thought: "I need to analyze this problem systematically",
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
});
const result = server.process(validInput);
expect(result).toHaveProperty('thoughtNumber', 1);
expect(result).toHaveProperty('totalThoughts', 3);
expect(result).toHaveProperty('nextThoughtNeeded', true);
expect(result).toHaveProperty('thought', "I need to analyze this problem systematically");
expect(result).toHaveProperty('status', 'success');
expect(result).toHaveProperty('isRevision', false);
expect(result).toHaveProperty('hasCurrentStep', false);
expect(result).toHaveProperty('stage');
expect(result).toHaveProperty('timestamp');
});
it('should include current step information when provided', () => {
const currentStep = createMockCurrentStep();
const validInput = createMockThoughtData({
thought: "Test thought with current step",
thoughtNumber: 2,
totalThoughts: 4,
nextThoughtNeeded: true,
currentStep: currentStep
});
const result = server.process(validInput);
expect(result.hasCurrentStep).toBe(true);
expect(result.thoughtNumber).toBe(2);
expect(result.totalThoughts).toBe(4);
});
it('should handle revision data correctly', () => {
const validInput = createMockThoughtData({
thought: "This is a revision",
thoughtNumber: 3,
totalThoughts: 5,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: 2
});
const result = server.process(validInput);
expect(result.isRevision).toBe(true);
expect(result.status).toBe('success');
});
it('should handle branch data correctly', () => {
const validInput = createMockThoughtData({
thought: "This is a branch",
thoughtNumber: 3,
totalThoughts: 5,
nextThoughtNeeded: true,
branchFromThought: 2,
branchId: "branch-1"
});
const result = server.process(validInput);
expect(result.branchId).toBe("branch-1");
expect(result.status).toBe('success');
});
it('should determine correct stage for different thought positions', () => {
// Test initial stage
const initialInput = createMockThoughtData({
thoughtNumber: 1,
totalThoughts: 10,
nextThoughtNeeded: true
});
const initialResult = server.process(initialInput);
expect(initialResult.stage).toBe('initial');
// Test middle stage
const middleInput = createMockThoughtData({
thoughtNumber: 5,
totalThoughts: 10,
nextThoughtNeeded: true
});
const middleResult = server.process(middleInput);
expect(middleResult.stage).toBe('middle');
// Test final stage
const finalInput = createMockThoughtData({
thoughtNumber: 9,
totalThoughts: 10,
nextThoughtNeeded: false
});
const finalResult = server.process(finalInput);
expect(finalResult.stage).toBe('final');
});
});
describe('processThought (backward compatibility)', () => {
it('should process valid thought successfully via run method', () => {
const validInput = {
thought: "I need to analyze this problem systematically",
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const response = server.processThought(validInput);
expect(response).toHaveProperty('content');
expect(response.isError).toBeUndefined();
expect(response.content).toHaveLength(1);
expect(response.content[0]).toHaveProperty('type', 'text');
expect(response.content[0]).toHaveProperty('text');
const result = JSON.parse(response.content[0].text);
expect(result).toHaveProperty('thoughtNumber', 1);
expect(result).toHaveProperty('totalThoughts', 3);
expect(result).toHaveProperty('nextThoughtNeeded', true);
expect(result).toHaveProperty('status', 'success');
});
it('should return error response for invalid input', () => {
const invalidInput = {
thought: 123, // Invalid type
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const response = server.processThought(invalidInput);
expect(response).toHaveProperty('isError', true);
expect(response.content).toHaveLength(1);
const error = JSON.parse(response.content[0].text);
expect(error).toHaveProperty('error');
expect(error).toHaveProperty('status', 'failed');
expect(error).toHaveProperty('timestamp');
});
it('should handle null input', () => {
const response = server.processThought(null);
expect(response.isError).toBe(true);
});
it('should handle undefined input', () => {
const response = server.processThought(undefined);
expect(response.isError).toBe(true);
});
it('should handle empty object input', () => {
const response = server.processThought({});
expect(response.isError).toBe(true);
});
it('should preserve error timestamp format', () => {
const response = server.processThought({});
expect(response.isError).toBe(true);
const error = JSON.parse(response.content[0].text);
const timestamp = new Date(error.timestamp);
expect(timestamp.getTime()).not.toBeNaN();
expect(Math.abs(Date.now() - timestamp.getTime())).toBeLessThan(1000);
});
it('should handle very large thought content', () => {
const largeThought = 'x'.repeat(10000);
const validInput = {
thought: largeThought,
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const response = server.processThought(validInput);
expect(response.isError).toBeUndefined();
const result = JSON.parse(response.content[0].text);
expect(result.thoughtNumber).toBe(1);
});
});
describe('determineStage (private method behavior verification)', () => {
it('should return correct stages based on progress', () => {
// Test through process method which calls determineStage internally
// Single thought should be final
const singleInput = createMockThoughtData({
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
});
const singleResult = server.process(singleInput);
expect(singleResult.stage).toBe('final');
// First of many should be initial
const firstInput = createMockThoughtData({
thoughtNumber: 1,
totalThoughts: 10,
nextThoughtNeeded: true
});
const firstResult = server.process(firstInput);
expect(firstResult.stage).toBe('initial');
// Last should be final
const lastInput = createMockThoughtData({
thoughtNumber: 10,
totalThoughts: 10,
nextThoughtNeeded: false
});
const lastResult = server.process(lastInput);
expect(lastResult.stage).toBe('final');
// Two-thought sequence
const firstOfTwoInput = createMockThoughtData({
thoughtNumber: 1,
totalThoughts: 2,
nextThoughtNeeded: true
});
const firstOfTwoResult = server.process(firstOfTwoInput);
expect(firstOfTwoResult.stage).toBe('initial');
const secondOfTwoInput = createMockThoughtData({
thoughtNumber: 2,
totalThoughts: 2,
nextThoughtNeeded: false
});
const secondOfTwoResult = server.process(secondOfTwoInput);
expect(secondOfTwoResult.stage).toBe('final');
// Three-thought sequence (middle)
const middleOfThreeInput = createMockThoughtData({
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true
});
const middleOfThreeResult = server.process(middleOfThreeInput);
expect(middleOfThreeResult.stage).toBe('middle');
});
});
describe('console output', () => {
it('should log formatted output to console.error when not in test environment', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
// Temporarily remove test environment indicators
const originalNodeEnv = process.env.NODE_ENV;
const originalJestWorker = process.env.JEST_WORKER_ID;
delete process.env.NODE_ENV;
delete process.env.JEST_WORKER_ID;
const validInput = createMockThoughtData({
thought: "Test thought for console output",
thoughtNumber: 2,
totalThoughts: 5,
nextThoughtNeeded: true
});
server.process(validInput);
expect(consoleSpy).toHaveBeenCalledTimes(1);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('💭 Sequential Thinking'));
// Restore environment
if (originalNodeEnv !== undefined)
process.env.NODE_ENV = originalNodeEnv;
if (originalJestWorker !== undefined)
process.env.JEST_WORKER_ID = originalJestWorker;
consoleSpy.mockRestore();
});
it('should not log to console.error during tests', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
const validInput = createMockThoughtData({
thought: "Test thought for console output",
thoughtNumber: 2,
totalThoughts: 5,
nextThoughtNeeded: true
});
server.process(validInput);
expect(consoleSpy).toHaveBeenCalledTimes(0);
consoleSpy.mockRestore();
});
});
describe('edge cases', () => {
it('should handle complex thought data with all optional fields', () => {
const complexInput = createMockThoughtData({
thought: "Complex thought with all fields",
thoughtNumber: 3,
totalThoughts: 7,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: 2,
branchFromThought: 1,
branchId: "test-branch",
needsMoreThoughts: true,
currentStep: createMockCurrentStep(),
previousSteps: [
{ stepDescription: "Previous step 1", recommendedTools: [], expectedOutcome: "Outcome 1", nextStepConditions: [] },
{ stepDescription: "Previous step 2", recommendedTools: [], expectedOutcome: "Outcome 2", nextStepConditions: [] }
],
remainingSteps: ["Remaining step 1", "Remaining step 2"],
toolUsageHistory: [
{ toolName: "mental_model", usedAt: new Date().toISOString(), effectivenessScore: 0.8 }
]
});
const result = server.process(complexInput);
expect(result.status).toBe('success');
expect(result.isRevision).toBe(true);
expect(result.branchId).toBe("test-branch");
expect(result.hasCurrentStep).toBe(true);
});
});
});