UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

662 lines 27.4 kB
/** * Phase 4B Date/Time Functions Test Suite * * Tests all date/time filtering capabilities requested by user: * "experiments that were started in the last 30 days or how are we handling that" */ import { DateFunctionHandler } from './DateFunctionHandler.js'; import { SQLBuilder } from './SQLBuilder.js'; import { MultiTableSQLBuilder } from './MultiTableSQLBuilder.js'; import { FieldCatalog } from './FieldCatalog.js'; import { getLogger } from '../../logging/Logger.js'; const logger = getLogger(); class Phase4BDateTestSuite { dateHandler; sqlBuilder; multiTableSQLBuilder; fieldCatalog; constructor() { this.dateHandler = new DateFunctionHandler(); this.fieldCatalog = new FieldCatalog(); this.sqlBuilder = new SQLBuilder(this.fieldCatalog); this.multiTableSQLBuilder = new MultiTableSQLBuilder(this.fieldCatalog); } /** * Helper to create properly typed query conditions */ createCondition(field, operator, value) { return { field, operator, value }; } /** * Run comprehensive Phase 4B test suite */ async runComprehensiveTests() { console.info('🚀 Starting Phase 4B Date/Time Functions Test Suite'); const testResults = []; // Initialize mock field catalog for testing await this.initializeMockFieldCatalog(); // Test Categories const testCategories = { 'Relative Date Functions': [ await this.testRelativeDateQueries(), await this.testDynamicRelativeDates(), await this.testPeriodFunctions() ].flat(), 'Date Range Filtering': [ await this.testDateRangeQueries(), await this.testBetweenOperator() ].flat(), 'Date Component Filtering': [ await this.testYearMonthDayFilters(), await this.testPartialDateMatching() ].flat(), 'Multi-Entity Date Queries': [ await this.testMultiEntityDateQueries(), await this.testComplexJoinDateQueries() ].flat(), 'User Requirement Tests': [ await this.testUserRequirementQueries() ].flat(), 'Edge Cases and Error Handling': [ await this.testErrorHandling(), await this.testInvalidDateFormats() ].flat() }; // Flatten all test results for (const [category, tests] of Object.entries(testCategories)) { testResults.push(...tests); } // Calculate statistics const passedTests = testResults.filter(r => r.success).length; const failedTests = testResults.length - passedTests; const successRate = (passedTests / testResults.length) * 100; // Generate summary with category breakdown const categorySummary = {}; for (const [category, tests] of Object.entries(testCategories)) { const passed = tests.filter(t => t.success).length; categorySummary[category] = { passed, total: tests.length }; } const summary = { dateHandlerStats: this.dateHandler.getStatistics(), testCategories: categorySummary }; console.info(`Phase 4B Test Suite Complete: ${passedTests}/${testResults.length} tests passed (${successRate.toFixed(1)}%)`); return { totalTests: testResults.length, passedTests, failedTests, successRate, results: testResults, summary }; } /** * Test relative date queries (LAST_30_DAYS, LAST_7_DAYS, etc.) */ async testRelativeDateQueries() { const tests = []; const relativeDateTests = [ { name: 'Last 30 Days - Experiments', query: { find: 'experiments', select: ['name', 'created_time'], where: [this.createCondition('created_time', '>', 'LAST_30_DAYS')] }, expectedPattern: /created_time > DATE\('now', '-30 days'\)/ }, { name: 'Last 7 Days - Flags', query: { find: 'flags', select: ['key', 'name', 'created_time'], where: [this.createCondition('created_time', '>=', 'LAST_7_DAYS')] }, expectedPattern: /created_time >= DATE\('now', '-7 days'\)/ }, { name: 'This Month - Events', query: { find: 'events', select: ['name', 'created_time'], where: [this.createCondition('created_time', '>=', 'THIS_MONTH')] }, expectedPattern: /created_time >= DATE\('now', 'start of month'\)/ }, { name: 'This Year - Audiences', query: { find: 'audiences', select: ['name', 'created_time'], where: [this.createCondition('created_time', '>=', 'THIS_YEAR')] }, expectedPattern: /created_time >= DATE\('now', 'start of year'\)/ } ]; for (const test of relativeDateTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test dynamic relative dates (LAST_45_DAYS, LAST_6_MONTHS, etc.) */ async testDynamicRelativeDates() { const tests = []; const dynamicTests = [ { name: 'Dynamic Last N Days', query: { find: 'experiments', select: ['name'], where: [this.createCondition('created_time', '>', 'LAST_45_DAYS')] }, expectedPattern: /created_time > DATE\('now', '-45 days'\)/ }, { name: 'Dynamic Last N Hours', query: { find: 'events', select: ['name'], where: [this.createCondition('created_time', '>', 'LAST_24_HOURS')] }, expectedPattern: /created_time > DATETIME\('now', '-24 hours'\)/ }, { name: 'Dynamic Last N Months', query: { find: 'flags', select: ['key'], where: [this.createCondition('created_time', '>=', 'LAST_6_MONTHS')] }, expectedPattern: /created_time >= DATE\('now', '-6 months'\)/ } ]; for (const test of dynamicTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test period functions (TODAY, YESTERDAY, THIS_WEEK, etc.) */ async testPeriodFunctions() { const tests = []; const periodTests = [ { name: 'Today Filter', query: { find: 'experiments', select: ['name'], where: [this.createCondition('created_time', '>=', 'TODAY')] }, expectedPattern: /created_time >= DATE\('now'\)/ }, { name: 'Yesterday Filter', query: { find: 'flags', select: ['key'], where: [this.createCondition('last_modified', '=', 'YESTERDAY')] }, expectedPattern: /last_modified = DATE\('now', '-1 day'\)/ }, { name: 'This Week Filter', query: { find: 'events', select: ['name'], where: [this.createCondition('created_time', '>=', 'THIS_WEEK')] }, expectedPattern: /created_time >= DATE\('now', 'weekday 0', '-6 days'\)/ } ]; for (const test of periodTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test date range queries with BETWEEN operator */ async testDateRangeQueries() { const tests = []; const rangeTests = [ { name: 'Date Range - Specific Dates', query: { find: 'experiments', select: ['name', 'start_date'], where: [this.createCondition('start_date', 'BETWEEN', ['2025-01-01', '2025-01-31'])] }, expectedPattern: /start_date BETWEEN '2025-01-01' AND '2025-01-31'/ }, { name: 'Date Range - Year Months', query: { find: 'flags', select: ['key', 'created_time'], where: [this.createCondition('created_time', 'BETWEEN', ['2024-12', '2025-01'])] }, expectedPattern: /created_time BETWEEN '2024-12-01' AND '2025-01-01'/ } ]; for (const test of rangeTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test BETWEEN operator with various date formats */ async testBetweenOperator() { const tests = []; const betweenTests = [ { name: 'BETWEEN with ISO Dates', query: { find: 'experiments', select: ['name'], where: [this.createCondition('created_time', 'BETWEEN', ['2025-01-01T00:00:00Z', '2025-01-31T23:59:59Z'])] }, expectedPattern: /created_time BETWEEN '2025-01-01T00:00:00Z' AND '2025-01-31T23:59:59Z'/ }, { name: 'BETWEEN with Relative Dates', query: { find: 'flags', select: ['key'], where: [this.createCondition('created_time', 'BETWEEN', ['LAST_30_DAYS', 'TODAY'])] }, expectedPattern: /created_time BETWEEN DATE\('now', '-30 days'\) AND DATE\('now'\)/ } ]; for (const test of betweenTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test year, month, day component filtering */ async testYearMonthDayFilters() { const tests = []; const componentTests = [ { name: 'Year Filter - 2025', query: { find: 'experiments', select: ['name'], where: [this.createCondition('created_time', 'YEAR', 2025)] }, expectedPattern: /STRFTIME\('%Y', created_time\) = '2025'/ }, { name: 'Month Filter - January', query: { find: 'flags', select: ['key'], where: [this.createCondition('created_time', 'MONTH', 1)] }, expectedPattern: /STRFTIME\('%m', created_time\) = '01'/ }, { name: 'Day Filter - 15th', query: { find: 'events', select: ['name'], where: [this.createCondition('created_time', 'DAY', 15)] }, expectedPattern: /STRFTIME\('%d', created_time\) = '15'/ } ]; for (const test of componentTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test partial date matching (year-only, month-year) */ async testPartialDateMatching() { const tests = []; const partialTests = [ { name: 'Year-Only Comparison', query: { find: 'experiments', select: ['name'], where: [this.createCondition('created_time', '>=', '2025')] }, expectedPattern: /created_time >= '2025-01-01'/ }, { name: 'Month-Year Comparison', query: { find: 'flags', select: ['key'], where: [this.createCondition('created_time', '>=', '2025-01')] }, expectedPattern: /created_time >= '2025-01-01'/ } ]; for (const test of partialTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test multi-entity queries with date filtering */ async testMultiEntityDateQueries() { const tests = []; const multiEntityTests = [ { name: 'Experiments with Pages - Recent Activity', query: { find: 'experiments', select: ['experiments.name', 'pages.edit_url'], where: [ this.createCondition('experiments.created_time', '>', 'LAST_30_DAYS'), this.createCondition('experiments.status', '=', 'running') ] }, expectedPattern: /experiments\.created_time > DATE\('now', '-30 days'\)/, useMultiTable: true } ]; for (const test of multiEntityTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern, test.useMultiTable)); } return tests; } /** * Test complex join queries with date filtering */ async testComplexJoinDateQueries() { const tests = []; const complexTests = [ { name: 'Three-Entity Join with Date Range', query: { find: 'experiments', select: ['experiments.name', 'pages.edit_url', 'events.name'], where: [ this.createCondition('experiments.start_date', 'BETWEEN', ['2025-01-01', '2025-01-31']), this.createCondition('events.event_type', '=', 'click') ], groupBy: ['experiments.name'] }, expectedPattern: /experiments\.start_date BETWEEN '2025-01-01' AND '2025-01-31'/, useMultiTable: true } ]; for (const test of complexTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern, test.useMultiTable)); } return tests; } /** * Test user requirement queries */ async testUserRequirementQueries() { const tests = []; const userRequirementTests = [ { name: 'USER REQUIREMENT: Experiments started in last 30 days', query: { find: 'experiments', select: ['name', 'start_date', 'status'], where: [ this.createCondition('start_date', '>', 'LAST_30_DAYS'), this.createCondition('status', '=', 'running') ], orderBy: [{ field: 'start_date', direction: 'DESC' }] }, expectedPattern: /start_date > DATE\('now', '-30 days'\)/ }, { name: 'USER REQUIREMENT: Flags created this month', query: { find: 'flags', select: ['key', 'name', 'created_time'], where: [this.createCondition('created_time', '>=', 'THIS_MONTH')], orderBy: [{ field: 'created_time', direction: 'DESC' }] }, expectedPattern: /created_time >= DATE\('now', 'start of month'\)/ }, { name: 'USER REQUIREMENT: Events in date range', query: { find: 'events', select: ['name', 'event_type', 'created_time'], where: [this.createCondition('created_time', 'BETWEEN', ['2025-01-01', '2025-01-31'])] }, expectedPattern: /created_time BETWEEN '2025-01-01' AND '2025-01-31'/ } ]; for (const test of userRequirementTests) { tests.push(await this.runSingleTest(test.name, test.query, test.expectedPattern)); } return tests; } /** * Test error handling for invalid dates */ async testErrorHandling() { const tests = []; // Test date handler error cases directly const errorTests = [ { name: 'Invalid Date Format', condition: this.createCondition('created_time', '>', 'invalid-date'), shouldFail: true }, { name: 'Invalid BETWEEN Array', condition: this.createCondition('created_time', 'BETWEEN', ['2025-01-01']), shouldFail: true }, { name: 'Invalid Year Value', condition: this.createCondition('created_time', 'YEAR', 99), shouldFail: true } ]; for (const test of errorTests) { const startTime = Date.now(); try { const result = this.dateHandler.parseDateFilter(test.condition); const success = test.shouldFail ? !result.isValid : result.isValid; tests.push({ testName: test.name, query: { find: 'test', where: [test.condition] }, expectedSQLPattern: /./, actualSQL: result.sqlExpression || 'N/A', success, executionTime: Date.now() - startTime, error: success ? undefined : `Expected failure but got: ${result.isValid ? 'success' : result.error}` }); } catch (error) { tests.push({ testName: test.name, query: { find: 'test', where: [test.condition] }, expectedSQLPattern: /./, actualSQL: 'ERROR', success: test.shouldFail, executionTime: Date.now() - startTime, error: test.shouldFail ? undefined : `Unexpected error: ${error instanceof Error ? error.message : String(error)}` }); } } return tests; } /** * Test invalid date formats */ async testInvalidDateFormats() { const tests = []; const invalidFormats = [ 'not-a-date', '2025-13-01', // Invalid month '2025-01-32', // Invalid day 'LAST_INVALID_DAYS', null, undefined ]; for (const format of invalidFormats) { const testName = `Invalid Format: ${format}`; const startTime = Date.now(); try { const condition = this.createCondition('created_time', '=', format); const result = this.dateHandler.parseDateFilter(condition); tests.push({ testName, query: { find: 'test', where: [condition] }, expectedSQLPattern: /./, actualSQL: result.sqlExpression || 'N/A', success: !result.isValid, // Should fail executionTime: Date.now() - startTime, error: result.isValid ? 'Expected failure but got success' : undefined }); } catch (error) { tests.push({ testName, query: { find: 'test', where: [this.createCondition('created_time', '=', format)] }, expectedSQLPattern: /./, actualSQL: 'ERROR', success: true, // Error is expected executionTime: Date.now() - startTime }); } } return tests; } /** * Run a single test case */ async runSingleTest(testName, query, expectedPattern, useMultiTable = false) { const startTime = Date.now(); try { const actualSQL = useMultiTable ? await this.multiTableSQLBuilder.buildMultiEntitySQL(query) : await this.sqlBuilder.buildSQL(query); const success = expectedPattern.test(actualSQL); return { testName, query, expectedSQLPattern: expectedPattern, actualSQL, success, executionTime: Date.now() - startTime, error: success ? undefined : `SQL pattern mismatch. Expected: ${expectedPattern.source}` }; } catch (error) { return { testName, query, expectedSQLPattern: expectedPattern, actualSQL: 'ERROR', success: false, executionTime: Date.now() - startTime, error: error instanceof Error ? error.message : String(error) }; } } /** * Initialize mock field catalog for testing */ async initializeMockFieldCatalog() { // Mock field registrations for testing const mockFields = [ { entity: 'experiments', field: 'name', location: { physicalLocation: { type: 'column', path: 'name' } } }, { entity: 'experiments', field: 'created_time', location: { physicalLocation: { type: 'column', path: 'created_time' } } }, { entity: 'experiments', field: 'start_date', location: { physicalLocation: { type: 'column', path: 'start_date' } } }, { entity: 'experiments', field: 'status', location: { physicalLocation: { type: 'column', path: 'status' } } }, { entity: 'flags', field: 'key', location: { physicalLocation: { type: 'column', path: 'key' } } }, { entity: 'flags', field: 'name', location: { physicalLocation: { type: 'column', path: 'name' } } }, { entity: 'flags', field: 'created_time', location: { physicalLocation: { type: 'column', path: 'created_time' } } }, { entity: 'flags', field: 'last_modified', location: { physicalLocation: { type: 'column', path: 'last_modified' } } }, { entity: 'events', field: 'name', location: { physicalLocation: { type: 'column', path: 'name' } } }, { entity: 'events', field: 'event_type', location: { physicalLocation: { type: 'column', path: 'event_type' } } }, { entity: 'events', field: 'created_time', location: { physicalLocation: { type: 'column', path: 'created_time' } } }, { entity: 'audiences', field: 'name', location: { physicalLocation: { type: 'column', path: 'name' } } }, { entity: 'audiences', field: 'created_time', location: { physicalLocation: { type: 'column', path: 'created_time' } } }, { entity: 'pages', field: 'edit_url', location: { physicalLocation: { type: 'column', path: 'edit_url' } } } ]; // Initialize catalog if it doesn't exist if (!this.fieldCatalog.catalog) { this.fieldCatalog.catalog = new Map(); } // Register mock fields (this would normally be done by the adapter) for (const mock of mockFields) { this.fieldCatalog.catalog.set(`${mock.entity}.${mock.field}`, mock.location); } } /** * Generate detailed test report */ generateDetailedReport(results) { let report = `\n# Phase 4B Date/Time Functions Test Report\n\n`; report += `## Overall Results\n`; report += `- **Total Tests**: ${results.totalTests}\n`; report += `- **Passed**: ${results.passedTests}\n`; report += `- **Failed**: ${results.failedTests}\n`; report += `- **Success Rate**: ${results.successRate.toFixed(1)}%\n\n`; report += `## Date Handler Statistics\n`; const stats = results.summary.dateHandlerStats; report += `- **Total Functions**: ${stats.totalFunctions}\n`; report += `- **Relative Functions**: ${stats.relativeFunctions}\n`; report += `- **Period Functions**: ${stats.periodFunctions}\n`; report += `- **Supported Patterns**: ${stats.supportedPatterns}\n\n`; report += `## Test Categories\n`; for (const [category, { passed, total }] of Object.entries(results.summary.testCategories)) { const categoryRate = ((passed / total) * 100).toFixed(1); report += `- **${category}**: ${passed}/${total} (${categoryRate}%)\n`; } report += `\n## Failed Tests\n`; const failedTests = results.results.filter(r => !r.success); if (failedTests.length === 0) { report += `All tests passed!\n`; } else { for (const test of failedTests) { report += `\n### ${test.testName}\n`; report += `- **Error**: ${test.error}\n`; report += `- **Expected Pattern**: \`${test.expectedSQLPattern.source}\`\n`; report += `- **Actual SQL**: \`${test.actualSQL}\`\n`; } } report += `\n## Performance Analysis\n`; const avgExecutionTime = results.results.reduce((sum, r) => sum + (r.executionTime || 0), 0) / results.results.length; report += `- **Average Execution Time**: ${avgExecutionTime.toFixed(2)}ms\n`; report += `- **Fastest Test**: ${Math.min(...results.results.map(r => r.executionTime || 0))}ms\n`; report += `- **Slowest Test**: ${Math.max(...results.results.map(r => r.executionTime || 0))}ms\n`; return report; } } // Export for testing export { Phase4BDateTestSuite }; // Self-executing test when run directly if (import.meta.url === `file://${process.argv[1]}`) { const testSuite = new Phase4BDateTestSuite(); testSuite.runComprehensiveTests().then(results => { const report = testSuite.generateDetailedReport(results); console.log(report); if (results.successRate === 100) { console.log('\nPhase 4B Implementation Complete - All Tests Passed!'); process.exit(0); } else { console.log(`\n Phase 4B Implementation Needs Review - ${results.failedTests} tests failed`); process.exit(1); } }).catch(error => { console.error('Test suite execution failed:', error); process.exit(1); }); } //# sourceMappingURL=test-phase4b-date-functions.js.map