#!/usr/bin/env node
/**
* Integration tests for TexasSolver MCP Server
*/
import dotenv from 'dotenv';
import { SolverValidator } from '../../src/solver/validator.js';
import { CommandBuilder, buildSolverCommand } from '../../src/solver/command-builder.js';
import { SolverExecutor } from '../../src/solver/executor.js';
import { StorageManager } from '../../src/storage/manager.js';
import { RangeLoader } from '../../src/ranges/loader.js';
import { RangeParser } from '../../src/ranges/parser.js';
// Load environment
dotenv.config();
const TESTS_PASSED = [];
const TESTS_FAILED = [];
/**
* Test helper function
*/
async function test(name, fn) {
try {
await fn();
TESTS_PASSED.push(name);
console.log(`✓ ${name}`);
} catch (error) {
TESTS_FAILED.push({ name, error: error.message });
console.log(`✗ ${name}`);
console.log(` Error: ${error.message}`);
}
}
/**
* Run all tests
*/
async function runTests() {
console.log('Starting TexasSolver MCP Server Integration Tests\n');
// Test 1: Validator tests
console.log('--- Validator Tests ---');
await test('Validate pot (positive)', () => {
SolverValidator.validatePot(10);
});
await test('Validate pot (should fail on negative)', () => {
try {
SolverValidator.validatePot(-5);
throw new Error('Should have failed');
} catch (error) {
if (!error.message.includes('positive')) throw error;
}
});
await test('Validate stack', () => {
SolverValidator.validateStack(100);
});
await test('Validate board format', () => {
SolverValidator.validateBoard('As,Kh,Qd');
});
await test('Validate empty board (preflop)', () => {
SolverValidator.validateBoard('');
});
await test('Validate range format', () => {
SolverValidator.validateRange('AA-22,AK-AJ');
});
// Test 2: Command Builder tests
console.log('\n--- Command Builder Tests ---');
await test('Build basic command', () => {
const builder = new CommandBuilder();
const cmd = builder
.setPot(10)
.setEffectiveStack(100)
.setBoard('As,Kh,Qd')
.setRange('ip', 'AA-22')
.setRange('oop', 'AA-TT')
.buildTree()
.setSolverConfig({ thread_num: 4 })
.startSolve()
.setDumpRounds(2)
.dumpResult('/tmp/output.json')
.build();
if (!cmd.includes('set_pot 10')) throw new Error('Missing pot');
if (!cmd.includes('set_board As,Kh,Qd')) throw new Error('Missing board');
if (!cmd.includes('set_range_ip AA-22')) throw new Error('Missing IP range');
if (!cmd.includes('build_tree')) throw new Error('Missing build_tree');
if (!cmd.includes('start_solve')) throw new Error('Missing start_solve');
if (!cmd.includes('dump_result /tmp/output.json')) throw new Error('Missing dump_result');
});
await test('Build command with bet sizes', () => {
const builder = new CommandBuilder();
const cmd = builder
.setPot(4)
.setEffectiveStack(100)
.setRange('ip', 'AA')
.setRange('oop', 'AA')
.setBetSizes('ip', 'flop', 'bet', [50])
.setBetSizes('ip', 'flop', 'raise', 'allin')
.buildTree()
.build();
if (!cmd.includes('set_bet_sizes ip,flop,bet,50')) throw new Error('Missing bet');
if (!cmd.includes('set_bet_sizes ip,flop,raise')) throw new Error('Missing allin');
});
// Test 3: Range Parser tests
console.log('\n--- Range Parser Tests ---');
await test('Parse simple range', () => {
const hands = RangeParser.parseRange('AA,KK,QQ');
if (hands.length !== 3) throw new Error(`Expected 3 hands, got ${hands.length}`);
if (hands[0].hand !== 'AA') throw new Error('First hand should be AA');
});
await test('Parse range with weights', () => {
const hands = RangeParser.parseRange('AA:1.0,KK:0.5,QQ');
if (hands[0].weight !== 1.0) throw new Error('AA should have weight 1.0');
if (hands[1].weight !== 0.5) throw new Error('KK should have weight 0.5');
if (hands[2].weight !== 1.0) throw new Error('QQ should have default weight 1.0');
});
await test('Calculate combinations', () => {
const hands = RangeParser.parseRange('AA,KK');
const combos = RangeParser.calculateCombinations(hands);
if (combos !== 12) throw new Error(`Expected 12 combos (6+6), got ${combos}`);
});
await test('Validate hand notation', () => {
if (!RangeParser.isValidHand('AA')) throw new Error('AA should be valid');
if (!RangeParser.isValidHand('AKs')) throw new Error('AKs should be valid');
if (RangeParser.isValidHand('XX')) throw new Error('XX should be invalid');
});
// Test 4: Storage Manager tests
console.log('\n--- Storage Manager Tests ---');
await test('Initialize storage', async () => {
const storage = new StorageManager('./test-data');
await storage.initialize();
});
await test('Generate unique filenames', () => {
const storage = new StorageManager('./test-data');
const file1 = storage.generateFilename('test', 'txt');
const file2 = storage.generateFilename('test', 'txt');
if (file1 === file2) throw new Error('Generated filenames should be unique');
if (!file1.startsWith('test_')) throw new Error('Filename should start with prefix');
if (!file1.endsWith('.txt')) throw new Error('Filename should end with extension');
});
await test('Get command path', () => {
const storage = new StorageManager('./test-data');
const path = storage.getCommandPath();
if (!path.includes('commands')) throw new Error('Path should include commands directory');
});
// Test 5: Solver Executor tests
console.log('\n--- Solver Executor Tests ---');
const solverPath = process.env.TEXAS_SOLVER_PATH;
if (solverPath) {
await test('Validate solver exists', async () => {
const isValid = await SolverExecutor.validateSolver(solverPath);
if (!isValid) throw new Error('Solver should be valid');
});
await test('Get solver info', async () => {
const executor = new SolverExecutor(solverPath);
const info = await executor.getSolverInfo();
if (!info.exists) throw new Error('Solver should exist');
if (!info.is_executable) throw new Error('Solver should be executable');
});
} else {
console.log('⊘ Skipping solver tests (TEXAS_SOLVER_PATH not set)');
}
// Test 6: Range Loader tests
console.log('\n--- Range Loader Tests ---');
if (solverPath) {
await test('Instantiate RangeLoader', () => {
const loader = new RangeLoader(solverPath);
if (!loader.rangesDir) throw new Error('Should set rangesDir');
});
await test('List range files', async () => {
const loader = new RangeLoader(solverPath);
const ranges = await loader.listRangeFiles();
if (!Array.isArray(ranges)) throw new Error('Should return array');
});
} else {
console.log('⊘ Skipping range loader tests (TEXAS_SOLVER_PATH not set)');
}
// Summary
console.log('\n--- Test Summary ---');
console.log(`Passed: ${TESTS_PASSED.length}`);
console.log(`Failed: ${TESTS_FAILED.length}`);
if (TESTS_FAILED.length > 0) {
console.log('\nFailed tests:');
TESTS_FAILED.forEach(({ name, error }) => {
console.log(` - ${name}: ${error}`);
});
process.exit(1);
} else {
console.log('\n✓ All tests passed!');
process.exit(0);
}
}
// Run tests
runTests().catch(error => {
console.error('Test runner error:', error);
process.exit(1);
});