UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

277 lines 11 kB
/** * Phase 4C Test Suite - Enhanced Field Disambiguation * * Tests the ability to handle ambiguous field names across tables. * When multiple tables have fields with the same name (e.g., 'name', 'key', 'id'), * the system should intelligently disambiguate or allow explicit table prefixes. * * Key scenarios: * 1. Automatic disambiguation based on primary entity * 2. Explicit table prefixes (flags.name vs events.name) * 3. JOIN queries with ambiguous fields * 4. Error handling for truly ambiguous cases */ import { IntelligentQueryEngine } from './IntelligentQueryEngine.js'; import { OptimizelyAdapter } from './adapters/OptimizelyAdapter.js'; import { getLogger } from '../../logging/Logger.js'; import Database from 'better-sqlite3'; const logger = getLogger(); export class FieldDisambiguationTester { engine; results = []; db; constructor(dbPath = './data/optimizely-cache.db') { // Open database this.db = new Database(dbPath, { readonly: true }); // Initialize engine with configuration this.engine = new IntelligentQueryEngine({ discovery: { autoDiscover: true, discoveryInterval: 3600000, cacheTTL: 60000 }, execution: { defaultStrategy: 'hybrid-sql-first', maxQueryTime: 30000, enableParallel: false, parallelThreshold: 1000 } }); // Register Optimizely adapter const optimizelyAdapter = new OptimizelyAdapter({ database: this.db }); this.engine.registerAdapter(optimizelyAdapter); } /** * Run single test */ async runSingleTest(name, query, expectedBehavior) { const startTime = Date.now(); try { console.info(`Running test: ${name}`); const result = await this.engine.query(query); const success = result.data && (result.data.length > 0 || !!query.aggregations); return { testName: name, query, expectedBehavior, actualResult: result, success: success || false, executionTime: Date.now() - startTime, error: undefined }; } catch (error) { return { testName: name, query, expectedBehavior, actualResult: null, success: false, executionTime: Date.now() - startTime, error: error instanceof Error ? error.message : String(error) }; } } /** * Test automatic disambiguation */ async testAutomaticDisambiguation() { const tests = []; // Test 1: Simple field reference should use primary entity tests.push(await this.runSingleTest('Automatic disambiguation - name field on flags', { find: 'flags', select: ['name', 'key'], where: [{ field: 'name', operator: 'LIKE', value: '%test%' }] }, 'Should automatically resolve "name" to "flags.name"')); // Test 2: Multiple ambiguous fields in single query tests.push(await this.runSingleTest('Multiple ambiguous fields', { find: 'events', select: ['name', 'key', 'description'], where: [ { field: 'name', operator: 'LIKE', value: '%click%' }, { field: 'key', operator: 'LIKE', value: '%event%' } ] }, 'Should resolve all fields to events table')); return tests; } /** * Test explicit table prefixes */ async testExplicitTablePrefixes() { const tests = []; // Test 1: Explicit table prefix tests.push(await this.runSingleTest('Explicit prefix - flags.name', { find: 'flags', select: ['flag.name', 'flag.key', 'flags.description'], where: [{ field: 'flag.archived', operator: '=', value: false }] }, 'Should handle explicit table prefixes correctly')); // Test 2: Mixed explicit and implicit tests.push(await this.runSingleTest('Mixed prefixes', { find: 'events', select: ['event.name', 'key', 'event_type'], where: [{ field: 'event.event_type', operator: '=', value: 'custom' }] }, 'Should handle mix of explicit and implicit field references')); return tests; } /** * Test JOIN disambiguation */ async testJoinDisambiguation() { const tests = []; // Test 1: JOIN with same field names tests.push(await this.runSingleTest('JOIN disambiguation - flags with environments', { find: 'flags', select: ['flag.name', 'flag_environment.environment_key'], where: [ { field: 'flag.archived', operator: '=', value: false }, { field: 'flag_environment.enabled', operator: '=', value: true } ] }, 'Should disambiguate fields in JOIN queries')); // Test 2: Complex multi-table JOIN tests.push(await this.runSingleTest('Multi-table JOIN disambiguation', { find: 'variations', select: [ 'variation.name', 'variation.key', 'flags.name as flag_name', 'flags.key as flag_key' ], where: [ { field: 'variation.archived', operator: '=', value: false } ] }, 'Should handle aliasing and disambiguation in complex JOINs')); return tests; } /** * Test ambiguity detection and errors */ async testAmbiguityErrors() { const tests = []; // Test 1: Truly ambiguous reference in JOIN tests.push(await this.runSingleTest('Ambiguous field in JOIN context', { find: 'flags', select: ['name', 'environment_key'], // ambiguous without prefix where: [] }, 'Should either auto-resolve or provide helpful error')); // Test 2: Non-existent table prefix tests.push(await this.runSingleTest('Invalid table prefix', { find: 'flags', select: ['nonexistent.name'], where: [] }, 'Should provide clear error about invalid table prefix')); return tests; } /** * Test field aliasing */ async testFieldAliasing() { const tests = []; // Test 1: Simple aliasing tests.push(await this.runSingleTest('Simple field aliasing', { find: 'flags', select: ['name as flag_name', 'key as flag_key'], where: [] }, 'Should support field aliasing with AS clause')); // Test 2: Aliasing in JOINs tests.push(await this.runSingleTest('JOIN with aliasing', { find: 'variations', select: [ 'variations.name as variation_name', 'flags.name as flag_name', 'variation.key', 'flags.key as flag_key' ], where: [] }, 'Should support aliasing in JOIN queries')); return tests; } /** * Test aggregation disambiguation */ async testAggregationDisambiguation() { const tests = []; // Test 1: Aggregation with ambiguous groupBy tests.push(await this.runSingleTest('Aggregation groupBy disambiguation', { find: 'flags', select: ['archived'], groupBy: ['archived'], aggregations: [{ field: '*', function: 'COUNT', alias: 'count' }] }, 'Should disambiguate groupBy fields')); // Test 2: Aggregation with JOIN and ambiguous fields tests.push(await this.runSingleTest('JOIN aggregation disambiguation', { find: 'variations', select: ['flag.name', 'COUNT(variations.key) as variation_count'], groupBy: ['flag.name'], where: [] }, 'Should handle disambiguation in aggregated JOINs')); return tests; } /** * Run all tests */ async runAllTests() { console.log('# Phase 4C Field Disambiguation Test Suite\n'); const testGroups = [ { name: 'Automatic Disambiguation', fn: () => this.testAutomaticDisambiguation() }, { name: 'Explicit Table Prefixes', fn: () => this.testExplicitTablePrefixes() }, { name: 'JOIN Disambiguation', fn: () => this.testJoinDisambiguation() }, { name: 'Ambiguity Errors', fn: () => this.testAmbiguityErrors() }, { name: 'Field Aliasing', fn: () => this.testFieldAliasing() }, { name: 'Aggregation Disambiguation', fn: () => this.testAggregationDisambiguation() } ]; for (const group of testGroups) { console.log(`\\n## ${group.name}`); const results = await group.fn(); this.results.push(...results); for (const result of results) { const status = result.success ? '✅' : '❌'; console.log(`${status} ${result.testName}`); if (!result.success && result.error) { console.log(` Error: ${result.error}`); } console.log(` Expected: ${result.expectedBehavior}`); console.log(` Time: ${result.executionTime}ms`); } } this.printSummary(); } /** * Print test summary */ printSummary() { const total = this.results.length; const passed = this.results.filter(r => r.success).length; const failed = total - passed; const successRate = ((passed / total) * 100).toFixed(1); console.log('\\n## Summary'); console.log(`- Total Tests: ${total}`); console.log(`- Passed: ${passed}`); console.log(`- Failed: ${failed}`); console.log(`- Success Rate: ${successRate}%`); if (failed > 0) { console.log('\\n## Failed Tests:'); this.results.filter(r => !r.success).forEach(r => { console.log(`- ${r.testName}: ${r.error || 'Unknown error'}`); }); } // Performance analysis const times = this.results.map(r => r.executionTime); const avgTime = times.reduce((a, b) => a + b, 0) / times.length; const maxTime = Math.max(...times); const minTime = Math.min(...times); console.log('\\n## Performance'); console.log(`- Average: ${avgTime.toFixed(1)}ms`); console.log(`- Min: ${minTime}ms`); console.log(`- Max: ${maxTime}ms`); } } // Run tests if executed directly if (import.meta.url === `file://${process.argv[1]}`) { const tester = new FieldDisambiguationTester(); tester.runAllTests().catch(console.error); } //# sourceMappingURL=test-phase4c-field-disambiguation.js.map