lib.test.ts•12.5 kB
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { SequentialThinkingServer, ThoughtData } from '../lib.js';
// Mock chalk to avoid ESM issues in Jest
jest.mock('chalk', () => {
const chalkMock = {
yellow: (str: string) => str,
green: (str: string) => str,
blue: (str: string) => str,
};
return {
default: chalkMock,
__esModule: true,
};
});
describe('SequentialThinkingServer', () => {
let server: SequentialThinkingServer;
beforeEach(() => {
// Disable thought logging for tests
process.env.DISABLE_THOUGHT_LOGGING = 'true';
server = new SequentialThinkingServer();
});
describe('processThought - validation', () => {
it('should reject input with missing thought', () => {
const input = {
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});
it('should reject input with non-string thought', () => {
const input = {
thought: 123,
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});
it('should reject input with missing thoughtNumber', () => {
const input = {
thought: 'Test thought',
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thoughtNumber');
});
it('should reject input with non-number thoughtNumber', () => {
const input = {
thought: 'Test thought',
thoughtNumber: '1',
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thoughtNumber');
});
it('should reject input with missing totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid totalThoughts');
});
it('should reject input with non-number totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: '3',
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid totalThoughts');
});
it('should reject input with missing nextThoughtNeeded', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
});
it('should reject input with non-boolean nextThoughtNeeded', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: 'true'
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
});
});
describe('processThought - valid inputs', () => {
it('should accept valid basic thought', () => {
const input = {
thought: 'This is my first thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text);
expect(data.thoughtNumber).toBe(1);
expect(data.totalThoughts).toBe(3);
expect(data.nextThoughtNeeded).toBe(true);
expect(data.thoughtHistoryLength).toBe(1);
});
it('should accept thought with optional fields', () => {
const input = {
thought: 'Revising my earlier idea',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: 1,
needsMoreThoughts: false
};
const result = server.processThought(input);
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text);
expect(data.thoughtNumber).toBe(2);
expect(data.thoughtHistoryLength).toBe(1);
});
it('should track multiple thoughts in history', () => {
const input1 = {
thought: 'First thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const input2 = {
thought: 'Second thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true
};
const input3 = {
thought: 'Final thought',
thoughtNumber: 3,
totalThoughts: 3,
nextThoughtNeeded: false
};
server.processThought(input1);
server.processThought(input2);
const result = server.processThought(input3);
const data = JSON.parse(result.content[0].text);
expect(data.thoughtHistoryLength).toBe(3);
expect(data.nextThoughtNeeded).toBe(false);
});
it('should auto-adjust totalThoughts if thoughtNumber exceeds it', () => {
const input = {
thought: 'Thought 5',
thoughtNumber: 5,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
const data = JSON.parse(result.content[0].text);
expect(data.totalThoughts).toBe(5);
});
});
describe('processThought - branching', () => {
it('should track branches correctly', () => {
const input1 = {
thought: 'Main thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const input2 = {
thought: 'Branch A thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
branchFromThought: 1,
branchId: 'branch-a'
};
const input3 = {
thought: 'Branch B thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: false,
branchFromThought: 1,
branchId: 'branch-b'
};
server.processThought(input1);
server.processThought(input2);
const result = server.processThought(input3);
const data = JSON.parse(result.content[0].text);
expect(data.branches).toContain('branch-a');
expect(data.branches).toContain('branch-b');
expect(data.branches.length).toBe(2);
expect(data.thoughtHistoryLength).toBe(3);
});
it('should allow multiple thoughts in same branch', () => {
const input1 = {
thought: 'Branch thought 1',
thoughtNumber: 1,
totalThoughts: 2,
nextThoughtNeeded: true,
branchFromThought: 1,
branchId: 'branch-a'
};
const input2 = {
thought: 'Branch thought 2',
thoughtNumber: 2,
totalThoughts: 2,
nextThoughtNeeded: false,
branchFromThought: 1,
branchId: 'branch-a'
};
server.processThought(input1);
const result = server.processThought(input2);
const data = JSON.parse(result.content[0].text);
expect(data.branches).toContain('branch-a');
expect(data.branches.length).toBe(1);
});
});
describe('processThought - edge cases', () => {
it('should reject empty thought string', () => {
const input = {
thought: '',
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});
it('should handle very long thought strings', () => {
const input = {
thought: 'a'.repeat(10000),
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};
const result = server.processThought(input);
expect(result.isError).toBeUndefined();
});
it('should handle thoughtNumber = 1, totalThoughts = 1', () => {
const input = {
thought: 'Only thought',
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};
const result = server.processThought(input);
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text);
expect(data.thoughtNumber).toBe(1);
expect(data.totalThoughts).toBe(1);
});
it('should handle nextThoughtNeeded = false', () => {
const input = {
thought: 'Final thought',
thoughtNumber: 3,
totalThoughts: 3,
nextThoughtNeeded: false
};
const result = server.processThought(input);
const data = JSON.parse(result.content[0].text);
expect(data.nextThoughtNeeded).toBe(false);
});
});
describe('processThought - response format', () => {
it('should return correct response structure on success', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};
const result = server.processThought(input);
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);
expect(result.content.length).toBe(1);
expect(result.content[0]).toHaveProperty('type', 'text');
expect(result.content[0]).toHaveProperty('text');
});
it('should return correct error structure on failure', () => {
const input = {
thought: 'Test',
thoughtNumber: 1,
totalThoughts: 1
// missing nextThoughtNeeded
};
const result = server.processThought(input);
expect(result).toHaveProperty('isError', true);
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);
const errorData = JSON.parse(result.content[0].text);
expect(errorData).toHaveProperty('error');
expect(errorData).toHaveProperty('status', 'failed');
});
it('should return valid JSON in response', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};
const result = server.processThought(input);
expect(() => JSON.parse(result.content[0].text)).not.toThrow();
});
});
describe('processThought - with logging enabled', () => {
let serverWithLogging: SequentialThinkingServer;
beforeEach(() => {
// Enable thought logging for these tests
delete process.env.DISABLE_THOUGHT_LOGGING;
serverWithLogging = new SequentialThinkingServer();
});
afterEach(() => {
// Reset to disabled for other tests
process.env.DISABLE_THOUGHT_LOGGING = 'true';
});
it('should format and log regular thoughts', () => {
const input = {
thought: 'Test thought with logging',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = serverWithLogging.processThought(input);
expect(result.isError).toBeUndefined();
});
it('should format and log revision thoughts', () => {
const input = {
thought: 'Revised thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: 1
};
const result = serverWithLogging.processThought(input);
expect(result.isError).toBeUndefined();
});
it('should format and log branch thoughts', () => {
const input = {
thought: 'Branch thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: false,
branchFromThought: 1,
branchId: 'branch-a'
};
const result = serverWithLogging.processThought(input);
expect(result.isError).toBeUndefined();
});
});
});