Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions src/sequentialthinking/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,190 @@ describe('SequentialThinkingServer', () => {

expect(data.nextThoughtNeeded).toBe(false);
});

it('should reject negative 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('thoughtNumber must be >= 1');
});

it('should reject zero thoughtNumber', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 0,
totalThoughts: 3,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('thoughtNumber must be >= 1');
});

it('should reject negative totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: -5,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('totalThoughts must be >= 1');
});

it('should reject zero totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 0,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('totalThoughts must be >= 1');
});

it('should reject NaN thoughtNumber', () => {
const input = {
thought: 'Test thought',
thoughtNumber: NaN,
totalThoughts: 3,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('thoughtNumber must be a valid number');
});

it('should reject Infinity totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: Infinity,
nextThoughtNeeded: true
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('totalThoughts must be a valid number');
});

it('should reject non-boolean isRevision', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true,
isRevision: 'true'
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('isRevision must be a boolean');
});

it('should reject non-number revisesThought', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: '1'
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('revisesThought must be a number');
});

it('should reject negative revisesThought', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: -1
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('revisesThought must be >= 1');
});

it('should reject non-number branchFromThought', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
branchFromThought: '1',
branchId: 'branch-a'
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('branchFromThought must be a number');
});

it('should reject non-string branchId', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
branchFromThought: 1,
branchId: 123
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('branchId must be a string');
});

it('should reject empty branchId', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
branchFromThought: 1,
branchId: ''
};

const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('branchId cannot be empty');
});

it('should accept valid optional fields', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 2,
totalThoughts: 3,
nextThoughtNeeded: true,
isRevision: true,
revisesThought: 1,
branchFromThought: 1,
branchId: 'branch-a',
needsMoreThoughts: true
};

const result = server.processThought(input);
expect(result.isError).toBeUndefined();
});
});

describe('processThought - response format', () => {
Expand Down
66 changes: 62 additions & 4 deletions src/sequentialthinking/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,77 @@ export class SequentialThinkingServer {
private validateThoughtData(input: unknown): ThoughtData {
const data = input as Record<string, unknown>;

if (!data.thought || typeof data.thought !== 'string') {
throw new Error('Invalid thought: must be a string');
// Validate required fields
if (typeof data.thought !== 'string' || data.thought.length === 0) {
throw new Error('Invalid thought: must be a non-empty string');
}
if (!data.thoughtNumber || typeof data.thoughtNumber !== 'number') {

if (typeof data.thoughtNumber !== 'number') {
throw new Error('Invalid thoughtNumber: must be a number');
}
if (!data.totalThoughts || typeof data.totalThoughts !== 'number') {
if (!Number.isFinite(data.thoughtNumber)) {
throw new Error('Invalid thoughtNumber: thoughtNumber must be a valid number');
}
if (data.thoughtNumber < 1) {
throw new Error('Invalid thoughtNumber: thoughtNumber must be >= 1');
}

if (typeof data.totalThoughts !== 'number') {
throw new Error('Invalid totalThoughts: must be a number');
}
if (!Number.isFinite(data.totalThoughts)) {
throw new Error('Invalid totalThoughts: totalThoughts must be a valid number');
}
if (data.totalThoughts < 1) {
throw new Error('Invalid totalThoughts: totalThoughts must be >= 1');
}

if (typeof data.nextThoughtNeeded !== 'boolean') {
throw new Error('Invalid nextThoughtNeeded: must be a boolean');
}

// Validate optional fields if present
if (data.isRevision !== undefined && typeof data.isRevision !== 'boolean') {
throw new Error('Invalid isRevision: isRevision must be a boolean');
}

if (data.revisesThought !== undefined) {
if (typeof data.revisesThought !== 'number') {
throw new Error('Invalid revisesThought: revisesThought must be a number');
}
if (!Number.isFinite(data.revisesThought)) {
throw new Error('Invalid revisesThought: revisesThought must be a valid number');
}
if (data.revisesThought < 1) {
throw new Error('Invalid revisesThought: revisesThought must be >= 1');
}
}

if (data.branchFromThought !== undefined) {
if (typeof data.branchFromThought !== 'number') {
throw new Error('Invalid branchFromThought: branchFromThought must be a number');
}
if (!Number.isFinite(data.branchFromThought)) {
throw new Error('Invalid branchFromThought: branchFromThought must be a valid number');
}
if (data.branchFromThought < 1) {
throw new Error('Invalid branchFromThought: branchFromThought must be >= 1');
}
}

if (data.branchId !== undefined) {
if (typeof data.branchId !== 'string') {
throw new Error('Invalid branchId: branchId must be a string');
}
if (data.branchId.length === 0) {
throw new Error('Invalid branchId: branchId cannot be empty');
}
}

if (data.needsMoreThoughts !== undefined && typeof data.needsMoreThoughts !== 'boolean') {
throw new Error('Invalid needsMoreThoughts: needsMoreThoughts must be a boolean');
}

return {
thought: data.thought,
thoughtNumber: data.thoughtNumber,
Expand Down