refactor: replace custom test runner with Node.js assert

- Simplify test files by removing custom TestRunner class
- Use standard Node.js assert module across all unit tests
- Update CLAUDE.md with streamlined development instructions
- Update tests/README.md with current testing approach
- Reduce boilerplate in delegation and GLMT test suites (652 lines removed)
This commit is contained in:
kaitranntt committed 2025-11-30 17:42:52 -05:00
1 parent ce9d60d0ca
commit cb7e38d2b1
13 files changed
+1656 -2308

No files matched your search

+260 -332
View File
@@ -1,350 +1,278 @@
#!/usr/bin/env node
'use strict';
const assert = require('assert');
const { DeltaAccumulator } = require('../../../dist/glmt/delta-accumulator');
console.log('[TEST] DeltaAccumulator unit tests');
console.log('');
describe('DeltaAccumulator', () => {
describe('Initialization', () => {
it('has correct initial state', () => {
const acc = new DeltaAccumulator();
assert(acc.messageId.startsWith('msg_'), 'Message ID should start with msg_');
assert.strictEqual(acc.role, 'assistant');
assert.strictEqual(acc.contentBlocks.length, 0);
assert.strictEqual(acc.currentBlockIndex, -1);
assert.strictEqual(acc.messageStarted, false);
assert.strictEqual(acc.finalized, false);
});
let passedTests = 0;
let failedTests = 0;
function test(name, fn) {
try {
fn();
console.log(`[PASS] ${name}`);
passedTests++;
} catch (error) {
console.log(`[FAIL] ${name}`);
console.log(` Error: ${error.message}`);
failedTests++;
}
}
function assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
// Test: Initialization
test('Initial state', () => {
const acc = new DeltaAccumulator();
assert(acc.messageId.startsWith('msg_'), 'Message ID should start with msg_');
assert(acc.role === 'assistant', 'Default role should be assistant');
assert(acc.contentBlocks.length === 0, 'Should have no blocks initially');
assert(acc.currentBlockIndex === -1, 'Current index should be -1');
assert(!acc.messageStarted, 'Message should not be started');
assert(!acc.finalized, 'Should not be finalized');
});
// Test: Start thinking block
test('Start thinking block', () => {
const acc = new DeltaAccumulator();
const block = acc.startBlock('thinking');
assert(block.type === 'thinking', 'Block type should be thinking');
assert(block.index === 0, 'First block index should be 0');
assert(block.started === true, 'Block should be marked as started');
assert(block.stopped === false, 'Block should not be stopped');
assert(acc.contentBlocks.length === 1, 'Should have 1 block');
assert(acc.currentBlockIndex === 0, 'Current index should be 0');
});
// Test: Add delta to thinking block
test('Add delta to thinking block', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Hello ');
acc.addDelta('world');
const block = acc.getCurrentBlock();
assert(block.content === 'Hello world', `Expected 'Hello world', got '${block.content}'`);
assert(acc.thinkingBuffer === 'Hello world', 'Thinking buffer should match');
});
// Test: Start text block
test('Start text block', () => {
const acc = new DeltaAccumulator();
const block = acc.startBlock('text');
assert(block.type === 'text', 'Block type should be text');
assert(block.index === 0, 'First block index should be 0');
});
// Test: Add delta to text block
test('Add delta to text block', () => {
const acc = new DeltaAccumulator();
acc.startBlock('text');
acc.addDelta('Answer: ');
acc.addDelta('42');
const block = acc.getCurrentBlock();
assert(block.content === 'Answer: 42', `Expected 'Answer: 42', got '${block.content}'`);
assert(acc.textBuffer === 'Answer: 42', 'Text buffer should match');
});
// Test: Multiple blocks
test('Multiple blocks (thinking → text)', () => {
const acc = new DeltaAccumulator();
// Thinking block
acc.startBlock('thinking');
acc.addDelta('Analyzing...');
// Text block
acc.startBlock('text');
acc.addDelta('The answer is 42');
assert(acc.contentBlocks.length === 2, 'Should have 2 blocks');
assert(acc.currentBlockIndex === 1, 'Current index should be 1');
assert(acc.contentBlocks[0].type === 'thinking', 'First block should be thinking');
assert(acc.contentBlocks[1].type === 'text', 'Second block should be text');
assert(acc.contentBlocks[0].content === 'Analyzing...', 'Thinking content incorrect');
assert(acc.contentBlocks[1].content === 'The answer is 42', 'Text content incorrect');
});
// Test: Stop current block
test('Stop current block', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Done');
acc.stopCurrentBlock();
const block = acc.getCurrentBlock();
assert(block.stopped === true, 'Block should be marked as stopped');
});
// Test: Get current block (no blocks)
test('Get current block when none exist', () => {
const acc = new DeltaAccumulator();
const block = acc.getCurrentBlock();
assert(block === null, 'Should return null when no blocks');
});
// Test: Update usage statistics
test('Update usage statistics', () => {
const acc = new DeltaAccumulator();
acc.updateUsage({ prompt_tokens: 100, completion_tokens: 50 });
assert(acc.inputTokens === 100, `Expected 100, got ${acc.inputTokens}`);
assert(acc.outputTokens === 50, `Expected 50, got ${acc.outputTokens}`);
});
// Test: Get summary
test('Get summary', () => {
const acc = new DeltaAccumulator();
acc.model = 'glm-4.6';
acc.startBlock('thinking');
acc.addDelta('test');
acc.updateUsage({ prompt_tokens: 10, completion_tokens: 20 });
const summary = acc.getSummary();
assert(summary.messageId === acc.messageId, 'Message ID mismatch');
assert(summary.model === 'glm-4.6', 'Model mismatch');
assert(summary.role === 'assistant', 'Role mismatch');
assert(summary.blockCount === 1, `Expected 1 block, got ${summary.blockCount}`);
assert(summary.usage.input_tokens === 10, 'Input tokens mismatch');
assert(summary.usage.output_tokens === 20, 'Output tokens mismatch');
});
// Test: Thinking config preservation
test('Thinking config preservation', () => {
const config = { thinking: true, effort: 'high' };
const acc = new DeltaAccumulator(config);
assert(acc.thinkingConfig.thinking === true, 'Thinking config not preserved');
assert(acc.thinkingConfig.effort === 'high', 'Effort config not preserved');
});
// Test: Message lifecycle flags
test('Message lifecycle flags', () => {
const acc = new DeltaAccumulator();
assert(!acc.messageStarted, 'Should not be started initially');
acc.messageStarted = true;
assert(acc.messageStarted, 'Should be marked as started');
acc.finalized = true;
assert(acc.finalized, 'Should be marked as finalized');
});
// Test: Finish reason tracking
test('Finish reason tracking', () => {
const acc = new DeltaAccumulator();
assert(acc.finishReason === null, 'Finish reason should be null initially');
acc.finishReason = 'stop';
assert(acc.finishReason === 'stop', 'Finish reason should be updated');
});
// Test: Loop detection - No loop (default threshold 3)
test('Loop detection - No loop detected with default threshold', () => {
const acc = new DeltaAccumulator();
// Add only 2 thinking blocks (below threshold)
acc.startBlock('thinking');
acc.addDelta('Thinking 1');
acc.startBlock('thinking');
acc.addDelta('Thinking 2');
const hasLoop = acc.checkForLoop();
assert(!hasLoop, 'Should not detect loop with only 2 thinking blocks');
assert(!acc.loopDetected, 'loopDetected flag should be false');
});
// Test: Loop detection - Loop detected with 3 consecutive thinking blocks
test('Loop detection - Loop detected with 3 consecutive thinking blocks', () => {
const acc = new DeltaAccumulator();
// Add 3 consecutive thinking blocks with no tool calls
acc.startBlock('thinking');
acc.addDelta('Planning step 1...');
acc.startBlock('thinking');
acc.addDelta('Planning step 2...');
acc.startBlock('thinking');
acc.addDelta('Planning step 3...');
const hasLoop = acc.checkForLoop();
assert(hasLoop, 'Should detect loop with 3 consecutive thinking blocks');
assert(acc.loopDetected, 'loopDetected flag should be true');
const summary = acc.getSummary();
assert(summary.loopDetected === true, 'Summary should reflect loop detection');
});
// Test: Loop detection - No loop when tool calls exist
test('Loop detection - No loop when tool calls present', () => {
const acc = new DeltaAccumulator();
// Add 3 thinking blocks but with a tool call
acc.startBlock('thinking');
acc.addDelta('Thinking 1');
acc.startBlock('thinking');
acc.addDelta('Thinking 2');
// Add a tool call
acc.addToolCallDelta({
index: 0,
id: 'call_123',
type: 'function',
function: { name: 'read_file', arguments: '{"path": "test.js"}' }
it('accepts config options', () => {
const acc = new DeltaAccumulator({}, { loopDetectionThreshold: 5 });
assert.strictEqual(acc.loopDetectionThreshold, 5);
});
});
acc.startBlock('thinking');
acc.addDelta('Thinking 3');
describe('Block management', () => {
it('starts thinking block', () => {
const acc = new DeltaAccumulator();
const block = acc.startBlock('thinking');
assert.strictEqual(block.type, 'thinking');
assert.strictEqual(block.index, 0);
assert.strictEqual(block.started, true);
assert.strictEqual(block.stopped, false);
assert.strictEqual(acc.contentBlocks.length, 1);
assert.strictEqual(acc.currentBlockIndex, 0);
});
const hasLoop = acc.checkForLoop();
assert(!hasLoop, 'Should not detect loop when tool calls exist');
assert(!acc.loopDetected, 'loopDetected flag should be false');
});
it('starts text block', () => {
const acc = new DeltaAccumulator();
const block = acc.startBlock('text');
assert.strictEqual(block.type, 'text');
assert.strictEqual(block.index, 0);
});
// Test: Loop detection - No loop with mixed block types
test('Loop detection - No loop with mixed block types', () => {
const acc = new DeltaAccumulator();
it('adds delta to thinking block', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Hello ');
acc.addDelta('world');
const block = acc.getCurrentBlock();
assert.strictEqual(block.content, 'Hello world');
assert.strictEqual(acc.thinkingBuffer, 'Hello world');
});
// Add thinking, text, thinking pattern (not all consecutive thinking)
acc.startBlock('thinking');
acc.addDelta('Thinking 1');
acc.startBlock('text');
acc.addDelta('Some text');
acc.startBlock('thinking');
acc.addDelta('Thinking 2');
acc.startBlock('thinking');
acc.addDelta('Thinking 3');
it('adds delta to text block', () => {
const acc = new DeltaAccumulator();
acc.startBlock('text');
acc.addDelta('Answer: ');
acc.addDelta('42');
const block = acc.getCurrentBlock();
assert.strictEqual(block.content, 'Answer: 42');
assert.strictEqual(acc.textBuffer, 'Answer: 42');
});
// Last 3 blocks: text, thinking, thinking (not all thinking)
const hasLoop = acc.checkForLoop();
assert(!hasLoop, 'Should not detect loop when blocks are mixed');
});
it('handles multiple blocks (thinking → text)', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Analyzing...');
acc.startBlock('text');
acc.addDelta('The answer is 42');
// Test: Loop detection - Custom threshold
test('Loop detection - Custom threshold (5 blocks)', () => {
const acc = new DeltaAccumulator({}, { loopDetectionThreshold: 5 });
assert.strictEqual(acc.contentBlocks.length, 2);
assert.strictEqual(acc.currentBlockIndex, 1);
assert.strictEqual(acc.contentBlocks[0].type, 'thinking');
assert.strictEqual(acc.contentBlocks[1].type, 'text');
assert.strictEqual(acc.contentBlocks[0].content, 'Analyzing...');
assert.strictEqual(acc.contentBlocks[1].content, 'The answer is 42');
});
// Add 4 thinking blocks (below custom threshold)
for (let i = 0; i < 4; i++) {
acc.startBlock('thinking');
acc.addDelta(`Thinking ${i + 1}`);
}
it('stops current block', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Done');
acc.stopCurrentBlock();
const block = acc.getCurrentBlock();
assert.strictEqual(block.stopped, true);
});
let hasLoop = acc.checkForLoop();
assert(!hasLoop, 'Should not detect loop with 4 blocks when threshold is 5');
// Add 5th thinking block
acc.startBlock('thinking');
acc.addDelta('Thinking 5');
hasLoop = acc.checkForLoop();
assert(hasLoop, 'Should detect loop with 5 consecutive thinking blocks');
});
// Test: Loop detection - Reset state
test('Loop detection - Reset state', () => {
const acc = new DeltaAccumulator();
// Trigger loop detection
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.checkForLoop();
assert(acc.loopDetected, 'Loop should be detected');
// Reset
acc.resetLoopDetection();
assert(!acc.loopDetected, 'Loop detection should be reset');
// ACTUAL BEHAVIOR: After reset, checkForLoop() re-evaluates the blocks
// Since the same 3 thinking blocks still exist with no tool calls,
// it does NOT detect loop again (because the condition already passed once)
// This is CORRECT behavior - reset clears the flag, allowing re-evaluation
const hasLoop = acc.checkForLoop();
assert(hasLoop, 'Should re-detect loop with same pattern'); // Changed expectation
});
// Test: Loop detection - Persistent after first detection
test('Loop detection - Persistent after first detection', () => {
const acc = new DeltaAccumulator();
// Trigger loop
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.checkForLoop();
assert(acc.loopDetected, 'Loop should be detected');
// Add more blocks
acc.startBlock('thinking');
acc.startBlock('thinking');
// Check again - should still return true
const hasLoop = acc.checkForLoop();
assert(hasLoop, 'Loop detection should persist');
});
// Test: Loop detection - Tool call addition tracking
test('Loop detection - Tool calls tracked correctly', () => {
const acc = new DeltaAccumulator();
// Add tool call deltas
acc.addToolCallDelta({
index: 0,
id: 'call_1',
type: 'function',
function: { name: 'test', arguments: '{"a":' }
it('returns null when no blocks exist', () => {
const acc = new DeltaAccumulator();
const block = acc.getCurrentBlock();
assert.strictEqual(block, null);
});
});
acc.addToolCallDelta({
index: 0,
function: { arguments: '1}' }
describe('Usage statistics', () => {
it('updates usage statistics', () => {
const acc = new DeltaAccumulator();
acc.updateUsage({ prompt_tokens: 100, completion_tokens: 50 });
assert.strictEqual(acc.inputTokens, 100);
assert.strictEqual(acc.outputTokens, 50);
});
});
const toolCalls = acc.getToolCalls();
assert(toolCalls.length === 1, 'Should have 1 tool call');
assert(toolCalls[0].function.arguments === '{"a":1}', 'Arguments should accumulate');
describe('Summary', () => {
it('gets correct summary', () => {
const acc = new DeltaAccumulator();
acc.model = 'glm-4.6';
acc.startBlock('thinking');
acc.addDelta('test');
acc.updateUsage({ prompt_tokens: 10, completion_tokens: 20 });
const summary = acc.getSummary();
assert(summary.toolCallCount === 1, 'Summary should show 1 tool call');
const summary = acc.getSummary();
assert.strictEqual(summary.messageId, acc.messageId);
assert.strictEqual(summary.model, 'glm-4.6');
assert.strictEqual(summary.role, 'assistant');
assert.strictEqual(summary.blockCount, 1);
assert.strictEqual(summary.usage.input_tokens, 10);
assert.strictEqual(summary.usage.output_tokens, 20);
});
});
describe('Message lifecycle', () => {
it('tracks lifecycle flags', () => {
const acc = new DeltaAccumulator();
assert.strictEqual(acc.messageStarted, false);
acc.messageStarted = true;
assert.strictEqual(acc.messageStarted, true);
acc.finalized = true;
assert.strictEqual(acc.finalized, true);
});
it('tracks finish reason', () => {
const acc = new DeltaAccumulator();
assert.strictEqual(acc.finishReason, null);
acc.finishReason = 'stop';
assert.strictEqual(acc.finishReason, 'stop');
});
});
describe('Loop detection', () => {
it('does not detect loop below threshold', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Thinking 1');
acc.startBlock('thinking');
acc.addDelta('Thinking 2');
const hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, false);
assert.strictEqual(acc.loopDetected, false);
});
it('detects loop with 3 consecutive thinking blocks', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Planning step 1...');
acc.startBlock('thinking');
acc.addDelta('Planning step 2...');
acc.startBlock('thinking');
acc.addDelta('Planning step 3...');
const hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, true);
assert.strictEqual(acc.loopDetected, true);
const summary = acc.getSummary();
assert.strictEqual(summary.loopDetected, true);
});
it('does not detect loop when tool calls present', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Thinking 1');
acc.startBlock('thinking');
acc.addDelta('Thinking 2');
acc.addToolCallDelta({
index: 0,
id: 'call_123',
type: 'function',
function: { name: 'read_file', arguments: '{"path": "test.js"}' }
});
acc.startBlock('thinking');
acc.addDelta('Thinking 3');
const hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, false);
assert.strictEqual(acc.loopDetected, false);
});
it('does not detect loop with mixed block types', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.addDelta('Thinking 1');
acc.startBlock('text');
acc.addDelta('Some text');
acc.startBlock('thinking');
acc.addDelta('Thinking 2');
acc.startBlock('thinking');
acc.addDelta('Thinking 3');
const hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, false);
});
it('respects custom threshold', () => {
const acc = new DeltaAccumulator({}, { loopDetectionThreshold: 5 });
for (let i = 0; i < 4; i++) {
acc.startBlock('thinking');
acc.addDelta(`Thinking ${i + 1}`);
}
let hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, false);
acc.startBlock('thinking');
acc.addDelta('Thinking 5');
hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, true);
});
it('resets loop detection state', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.checkForLoop();
assert.strictEqual(acc.loopDetected, true);
acc.resetLoopDetection();
assert.strictEqual(acc.loopDetected, false);
const hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, true);
});
it('persists after first detection', () => {
const acc = new DeltaAccumulator();
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.startBlock('thinking');
acc.checkForLoop();
assert.strictEqual(acc.loopDetected, true);
acc.startBlock('thinking');
acc.startBlock('thinking');
const hasLoop = acc.checkForLoop();
assert.strictEqual(hasLoop, true);
});
});
describe('Tool call tracking', () => {
it('tracks tool calls correctly', () => {
const acc = new DeltaAccumulator();
acc.addToolCallDelta({
index: 0,
id: 'call_1',
type: 'function',
function: { name: 'test', arguments: '{"a":' }
});
acc.addToolCallDelta({
index: 0,
function: { arguments: '1}' }
});
const toolCalls = acc.getToolCalls();
assert.strictEqual(toolCalls.length, 1);
assert.strictEqual(toolCalls[0].function.arguments, '{"a":1}');
const summary = acc.getSummary();
assert.strictEqual(summary.toolCallCount, 1);
});
});
});
console.log('');
console.log('═══════════════════════════════════════');
console.log(`TESTS: ${passedTests} passed, ${failedTests} failed`);
console.log('═══════════════════════════════════════');
if (failedTests > 0) {
process.exit(1);
}
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -11,7 +11,7 @@
*/
const assert = require('assert');
const LocaleEnforcer = require('../../../dist/glmt/locale-enforcer').default;
const { LocaleEnforcer } = require('../../../dist/glmt/locale-enforcer');
describe('LocaleEnforcer', () => {
describe('Scenario 1: English prompt → English output', () => {
+151 -270
View File
@@ -1,293 +1,174 @@
#!/usr/bin/env node
'use strict';
/**
* ReasoningEnforcer Unit Tests
*
* Test scenarios:
* 1. Opt-in behavior (enabled vs disabled)
* 2. System message injection
* 3. User message fallback
* 4. Effort level selection (low/medium/high/max)
* 5. Message structure handling (string vs array)
* 6. Edge cases (empty messages, no system/user)
*/
const assert = require('assert');
const ReasoningEnforcer = require('../../../dist/glmt/reasoning-enforcer').default;
/**
* Simple test runner (no external dependencies)
*/
class TestRunner {
constructor() {
this.tests = [];
this.passed = 0;
this.failed = 0;
}
describe('ReasoningEnforcer', () => {
describe('Opt-in behavior', () => {
it('should NOT inject when disabled and thinking=false', () => {
const enforcer = new ReasoningEnforcer({ enabled: false });
const messages = [{ role: 'user', content: 'What is 2+2?' }];
test(name, fn) {
this.tests.push({ name, fn });
}
const result = enforcer.injectInstruction(messages, { thinking: false });
async run() {
console.log('\n=== ReasoningEnforcer Tests ===\n');
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].content, 'What is 2+2?');
});
for (const { name, fn } of this.tests) {
try {
await fn();
console.log(`[OK] ${name}`);
this.passed++;
} catch (error) {
console.error(`[X] ${name}`);
console.error(` Error: ${error.message}`);
this.failed++;
}
}
it('should inject when enabled=true', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'What is 2+2?' }];
console.log(`\n=== Results ===`);
console.log(`Passed: ${this.passed}/${this.tests.length}`);
console.log(`Failed: ${this.failed}/${this.tests.length}`);
const result = enforcer.injectInstruction(messages, { thinking: false });
return this.failed === 0;
}
}
assert.ok(result[0].content.includes('CRITICAL'));
assert.ok(result[0].content.includes('<reasoning_content>'));
});
/**
* Simple assertion helpers
*/
function assertEqual(actual, expected, message) {
if (actual !== expected) {
throw new Error(
`${message || 'Assertion failed'}\n` +
` Expected: ${JSON.stringify(expected)}\n` +
` Actual: ${JSON.stringify(actual)}`
);
}
}
it('should inject when thinking=true (even if enabled=false)', () => {
const enforcer = new ReasoningEnforcer({ enabled: false });
const messages = [{ role: 'user', content: 'What is 2+2?' }];
function assertTrue(condition, message) {
if (!condition) {
throw new Error(message || 'Expected condition to be true');
}
}
const result = enforcer.injectInstruction(messages, { thinking: true });
function assertIncludes(haystack, needle, message) {
if (!haystack.includes(needle)) {
throw new Error(
`${message || 'String does not include expected substring'}\n` +
` Expected substring: ${needle}\n` +
` Actual string: ${haystack.substring(0, 100)}...`
);
}
}
assert.ok(result[0].content.includes('CRITICAL'));
});
});
function assertDeepEqual(actual, expected, message) {
const actualStr = JSON.stringify(actual);
const expectedStr = JSON.stringify(expected);
if (actualStr !== expectedStr) {
throw new Error(
`${message || 'Deep equality assertion failed'}\n` +
` Expected: ${expectedStr}\n` +
` Actual: ${actualStr}`
);
}
}
describe('System message injection', () => {
it('should prepend to system message (string content)', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Calculate 2+2' }
];
// Create test runner
const runner = new TestRunner();
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'medium' });
// Test 1: Opt-in behavior - disabled
runner.test('should NOT inject when disabled and thinking=false', () => {
const enforcer = new ReasoningEnforcer({ enabled: false });
const messages = [
{ role: 'user', content: 'What is 2+2?' }
];
assert.strictEqual(result.length, 2);
assert.ok(result[0].content.startsWith('You are an expert reasoning model'));
assert.ok(result[0].content.includes('You are a helpful assistant'));
assert.strictEqual(result[1].content, 'Calculate 2+2');
});
const result = enforcer.injectInstruction(messages, { thinking: false });
it('should prepend to system message (array content)', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{
role: 'system',
content: [{ type: 'text', text: 'You are a code assistant.' }]
},
{ role: 'user', content: 'Write a function' }
];
assertEqual(result.length, 1);
assertEqual(result[0].content, 'What is 2+2?');
});
// Test 2: Opt-in behavior - enabled
runner.test('should inject when enabled=true', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{ role: 'user', content: 'What is 2+2?' }
];
const result = enforcer.injectInstruction(messages, { thinking: false });
assertIncludes(result[0].content, 'CRITICAL');
assertIncludes(result[0].content, '<reasoning_content>');
});
// Test 3: Opt-in behavior - thinking=true
runner.test('should inject when thinking=true (even if enabled=false)', () => {
const enforcer = new ReasoningEnforcer({ enabled: false });
const messages = [
{ role: 'user', content: 'What is 2+2?' }
];
const result = enforcer.injectInstruction(messages, { thinking: true });
assertIncludes(result[0].content, 'CRITICAL');
});
// Test 4: System message injection - string content
runner.test('should prepend to system message (string content)', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Calculate 2+2' }
];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'medium' });
assertEqual(result.length, 2);
assertTrue(result[0].content.startsWith('You are an expert reasoning model'));
assertIncludes(result[0].content, 'You are a helpful assistant');
assertEqual(result[1].content, 'Calculate 2+2');
});
// Test 5: System message injection - array content
runner.test('should prepend to system message (array content)', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{
role: 'system',
content: [
{ type: 'text', text: 'You are a code assistant.' }
]
},
{ role: 'user', content: 'Write a function' }
];
const result = enforcer.injectInstruction(messages, { thinking: true });
assertTrue(Array.isArray(result[0].content));
assertEqual(result[0].content[0].type, 'text');
assertIncludes(result[0].content[0].text, 'CRITICAL');
assertEqual(result[0].content[1].text, 'You are a code assistant.');
});
// Test 6: User message fallback
runner.test('should prepend to first user message when no system message', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{ role: 'user', content: 'Explain quantum computing' }
];
const result = enforcer.injectInstruction(messages, { thinking: true });
assertEqual(result.length, 1);
assertIncludes(result[0].content, 'CRITICAL');
assertIncludes(result[0].content, 'Explain quantum computing');
});
// Test 7: Effort level - low
runner.test('should use low prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'low' });
assertIncludes(result[0].content.toLowerCase(), 'brief analysis');
});
// Test 8: Effort level - medium
runner.test('should use medium prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'medium' });
assertIncludes(result[0].content.toLowerCase(), 'think step-by-step');
});
// Test 9: Effort level - high
runner.test('should use high prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'high' });
assertIncludes(result[0].content.toLowerCase(), 'think deeply and systematically');
});
// Test 10: Effort level - max
runner.test('should use max prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'max' });
assertIncludes(result[0].content.toLowerCase(), 'exhaustively from first principles');
});
// Test 11: Default effort level
runner.test('should default to medium effort if not specified', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true });
assertIncludes(result[0].content, 'think step-by-step');
});
// Test 12: Empty messages array
runner.test('should handle empty messages array', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [];
const result = enforcer.injectInstruction(messages, { thinking: true });
assertEqual(result.length, 0);
});
// Test 13: No system or user role
runner.test('should handle messages with no system or user role', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [
{ role: 'assistant', content: 'Previous response' }
];
const result = enforcer.injectInstruction(messages, { thinking: true });
assertEqual(result.length, 1);
assertEqual(result[0].content, 'Previous response');
});
const result = enforcer.injectInstruction(messages, { thinking: true });
// Test 14: Immutability
runner.test('should not mutate original messages array', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const originalMessages = [
{ role: 'user', content: 'Test prompt' }
];
const originalCopy = JSON.parse(JSON.stringify(originalMessages));
assert.ok(Array.isArray(result[0].content));
assert.strictEqual(result[0].content[0].type, 'text');
assert.ok(result[0].content[0].text.includes('CRITICAL'));
assert.strictEqual(result[0].content[1].text, 'You are a code assistant.');
});
});
enforcer.injectInstruction(originalMessages, { thinking: true });
describe('User message fallback', () => {
it('should prepend to first user message when no system message', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Explain quantum computing' }];
assertDeepEqual(originalMessages, originalCopy);
});
const result = enforcer.injectInstruction(messages, { thinking: true });
// Test 15: Custom prompts
runner.test('should handle custom prompts via constructor', () => {
const customPrompts = {
low: 'Custom low prompt',
medium: 'Custom medium prompt',
high: 'Custom high prompt',
max: 'Custom max prompt'
};
const enforcer = new ReasoningEnforcer({ enabled: true, prompts: customPrompts });
const messages = [{ role: 'user', content: 'Test' }];
assert.strictEqual(result.length, 1);
assert.ok(result[0].content.includes('CRITICAL'));
assert.ok(result[0].content.includes('Explain quantum computing'));
});
});
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'low' });
describe('Effort levels', () => {
it('should use low prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
assertIncludes(result[0].content, 'Custom low prompt');
});
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'low' });
// Run all tests
runner.run().then(success => {
process.exit(success ? 0 : 1);
assert.ok(result[0].content.toLowerCase().includes('brief analysis'));
});
it('should use medium prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'medium' });
assert.ok(result[0].content.toLowerCase().includes('think step-by-step'));
});
it('should use high prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'high' });
assert.ok(result[0].content.toLowerCase().includes('think deeply and systematically'));
});
it('should use max prompt template', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'max' });
assert.ok(result[0].content.toLowerCase().includes('exhaustively from first principles'));
});
it('should default to medium effort if not specified', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true });
assert.ok(result[0].content.includes('think step-by-step'));
});
});
describe('Edge cases', () => {
it('should handle empty messages array', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const result = enforcer.injectInstruction([], { thinking: true });
assert.strictEqual(result.length, 0);
});
it('should handle messages with no system or user role', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const messages = [{ role: 'assistant', content: 'Previous response' }];
const result = enforcer.injectInstruction(messages, { thinking: true });
assert.strictEqual(result.length, 1);
assert.strictEqual(result[0].content, 'Previous response');
});
it('should not mutate original messages array', () => {
const enforcer = new ReasoningEnforcer({ enabled: true });
const originalMessages = [{ role: 'user', content: 'Test prompt' }];
const originalCopy = JSON.parse(JSON.stringify(originalMessages));
enforcer.injectInstruction(originalMessages, { thinking: true });
assert.deepStrictEqual(originalMessages, originalCopy);
});
it('should handle custom prompts via constructor', () => {
const customPrompts = {
low: 'Custom low prompt',
medium: 'Custom medium prompt',
high: 'Custom high prompt',
max: 'Custom max prompt'
};
const enforcer = new ReasoningEnforcer({ enabled: true, prompts: customPrompts });
const messages = [{ role: 'user', content: 'Test' }];
const result = enforcer.injectInstruction(messages, { thinking: true, effort: 'low' });
assert.ok(result[0].content.includes('Custom low prompt'));
});
});
});
+103 -137
View File
@@ -1,144 +1,110 @@
#!/usr/bin/env node
'use strict';
const assert = require('assert');
const { SSEParser } = require('../../../dist/glmt/sse-parser');
const SSEParser = require('../../../dist/glmt/sse-parser').default;
describe('SSEParser', () => {
describe('Basic parsing', () => {
it('parses single event', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"test": "value"}\n\n');
assert.strictEqual(events.length, 1, `Expected 1 event, got ${events.length}`);
assert.strictEqual(events[0].data.test, 'value');
});
console.log('[TEST] SSEParser unit tests');
console.log('');
it('parses multiple events in one chunk', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"a": 1}\n\ndata: {"b": 2}\n\n');
assert.strictEqual(events.length, 2, `Expected 2 events, got ${events.length}`);
assert.strictEqual(events[0].data.a, 1);
assert.strictEqual(events[1].data.b, 2);
});
let passedTests = 0;
let failedTests = 0;
it('handles event split across chunks', () => {
const parser = new SSEParser();
const events1 = parser.parse('data: {"test":');
assert.strictEqual(events1.length, 0, 'Should not emit incomplete event');
const events2 = parser.parse('"value"}\n\n');
assert.strictEqual(events2.length, 1, `Expected 1 event after completion, got ${events2.length}`);
assert.strictEqual(events2[0].data.test, 'value');
});
function test(name, fn) {
try {
fn();
console.log(`[PASS] ${name}`);
passedTests++;
} catch (error) {
console.log(`[FAIL] ${name}`);
console.log(` Error: ${error.message}`);
failedTests++;
}
}
it('handles empty lines between events', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"a": 1}\n\n\n\ndata: {"b": 2}\n\n');
assert.strictEqual(events.length, 2, `Expected 2 events, got ${events.length}`);
});
});
function assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
describe('[DONE] marker', () => {
it('detects [DONE] marker', () => {
const parser = new SSEParser();
const events = parser.parse('data: [DONE]\n\n');
assert.strictEqual(events.length, 1, `Expected 1 event, got ${events.length}`);
assert.strictEqual(events[0].event, 'done');
assert.strictEqual(events[0].data, null);
});
// Test: Single event
test('Single event parsing', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"test": "value"}\n\n');
assert(events.length === 1, `Expected 1 event, got ${events.length}`);
assert(events[0].data.test === 'value', `Expected test=value, got ${events[0].data.test}`);
it('handles mixed events with [DONE]', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"msg": "hello"}\n\ndata: [DONE]\n\n');
assert.strictEqual(events.length, 2, `Expected 2 events, got ${events.length}`);
assert.strictEqual(events[0].data.msg, 'hello');
assert.strictEqual(events[1].event, 'done');
});
});
describe('Error handling', () => {
it('handles malformed JSON gracefully', () => {
const parser = new SSEParser();
const events = parser.parse('data: {invalid json}\n\ndata: {"valid": true}\n\n');
assert.strictEqual(events.length, 1, `Expected 1 valid event, got ${events.length}`);
assert.strictEqual(events[0].data.valid, true);
});
});
describe('Event fields', () => {
it('parses event with ID field', () => {
const parser = new SSEParser();
const events = parser.parse('id: 123\ndata: {"test": true}\n\n');
assert.strictEqual(events.length, 1);
assert.strictEqual(events[0].id, '123');
assert.strictEqual(events[0].data.test, true);
});
it('parses custom event type', () => {
const parser = new SSEParser();
const events = parser.parse('event: custom\ndata: {"test": true}\n\n');
assert.strictEqual(events.length, 1);
assert.strictEqual(events[0].event, 'custom');
});
});
describe('Reset functionality', () => {
it('resets parser state', () => {
const parser = new SSEParser();
parser.parse('data: {"test": 1}\n\n');
assert.strictEqual(parser.eventCount, 1);
parser.reset();
assert.strictEqual(parser.eventCount, 0);
assert.strictEqual(parser.buffer, '');
});
});
describe('Z.AI stream format', () => {
it('parses real Z.AI stream format', () => {
const parser = new SSEParser();
const chunk = 'data: {"choices":[{"delta":{"role":"assistant","reasoning_content":"test"}}]}\n\n';
const events = parser.parse(chunk);
assert.strictEqual(events.length, 1);
assert.strictEqual(events[0].data.choices[0].delta.reasoning_content, 'test');
});
it('parses multiple Z.AI deltas', () => {
const parser = new SSEParser();
const chunk = 'data: {"choices":[{"delta":{"reasoning_content":"chunk1"}}]}\n\ndata: {"choices":[{"delta":{"reasoning_content":"chunk2"}}]}\n\n';
const events = parser.parse(chunk);
assert.strictEqual(events.length, 2);
assert.strictEqual(events[0].data.choices[0].delta.reasoning_content, 'chunk1');
assert.strictEqual(events[1].data.choices[0].delta.reasoning_content, 'chunk2');
});
});
});
// Test: Multiple events
test('Multiple events in one chunk', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"a": 1}\n\ndata: {"b": 2}\n\n');
assert(events.length === 2, `Expected 2 events, got ${events.length}`);
assert(events[0].data.a === 1, 'First event data incorrect');
assert(events[1].data.b === 2, 'Second event data incorrect');
});
// Test: Split across chunks
test('Event split across chunks', () => {
const parser = new SSEParser();
const events1 = parser.parse('data: {"test":');
assert(events1.length === 0, 'Should not emit incomplete event');
const events2 = parser.parse('"value"}\n\n');
assert(events2.length === 1, `Expected 1 event after completion, got ${events2.length}`);
assert(events2[0].data.test === 'value', 'Split event data incorrect');
});
// Test: [DONE] marker
test('[DONE] marker detection', () => {
const parser = new SSEParser();
const events = parser.parse('data: [DONE]\n\n');
assert(events.length === 1, `Expected 1 event, got ${events.length}`);
assert(events[0].event === 'done', `Expected event=done, got ${events[0].event}`);
assert(events[0].data === null, 'Expected null data for [DONE]');
});
// Test: Mixed content and [DONE]
test('Mixed events with [DONE]', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"msg": "hello"}\n\ndata: [DONE]\n\n');
assert(events.length === 2, `Expected 2 events, got ${events.length}`);
assert(events[0].data.msg === 'hello', 'First event data incorrect');
assert(events[1].event === 'done', 'Second event should be done');
});
// Test: Malformed JSON (should skip gracefully)
test('Malformed JSON handling', () => {
const parser = new SSEParser();
const events = parser.parse('data: {invalid json}\n\ndata: {"valid": true}\n\n');
// Should skip malformed, parse valid
assert(events.length === 1, `Expected 1 valid event, got ${events.length}`);
assert(events[0].data.valid === true, 'Valid event data incorrect');
});
// Test: Empty lines handling
test('Empty lines between events', () => {
const parser = new SSEParser();
const events = parser.parse('data: {"a": 1}\n\n\n\ndata: {"b": 2}\n\n');
assert(events.length === 2, `Expected 2 events, got ${events.length}`);
});
// Test: Event with ID field
test('Event with ID field', () => {
const parser = new SSEParser();
const events = parser.parse('id: 123\ndata: {"test": true}\n\n');
assert(events.length === 1, `Expected 1 event, got ${events.length}`);
assert(events[0].id === '123', `Expected id=123, got ${events[0].id}`);
assert(events[0].data.test === true, 'Event data incorrect');
});
// Test: Custom event type
test('Custom event type', () => {
const parser = new SSEParser();
const events = parser.parse('event: custom\ndata: {"test": true}\n\n');
assert(events.length === 1, `Expected 1 event, got ${events.length}`);
assert(events[0].event === 'custom', `Expected event=custom, got ${events[0].event}`);
});
// Test: Reset functionality
test('Parser reset', () => {
const parser = new SSEParser();
parser.parse('data: {"test": 1}\n\n');
assert(parser.eventCount === 1, 'Event count should be 1');
parser.reset();
assert(parser.eventCount === 0, 'Event count should be 0 after reset');
assert(parser.buffer === '', 'Buffer should be empty after reset');
});
// Test: Real Z.AI stream format
test('Real Z.AI stream format', () => {
const parser = new SSEParser();
const chunk = 'data: {"choices":[{"delta":{"role":"assistant","reasoning_content":"test"}}]}\n\n';
const events = parser.parse(chunk);
assert(events.length === 1, `Expected 1 event, got ${events.length}`);
assert(events[0].data.choices[0].delta.reasoning_content === 'test', 'Z.AI format parsing incorrect');
});
// Test: Multiple Z.AI deltas
test('Multiple Z.AI deltas', () => {
const parser = new SSEParser();
const chunk = 'data: {"choices":[{"delta":{"reasoning_content":"chunk1"}}]}\n\ndata: {"choices":[{"delta":{"reasoning_content":"chunk2"}}]}\n\n';
const events = parser.parse(chunk);
assert(events.length === 2, `Expected 2 events, got ${events.length}`);
assert(events[0].data.choices[0].delta.reasoning_content === 'chunk1', 'First delta incorrect');
assert(events[1].data.choices[0].delta.reasoning_content === 'chunk2', 'Second delta incorrect');
});
console.log('');
console.log('═══════════════════════════════════════');
console.log(`TESTS: ${passedTests} passed, ${failedTests} failed`);
console.log('═══════════════════════════════════════');
if (failedTests > 0) {
process.exit(1);
}