@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
662 lines • 27.4 kB
JavaScript
/**
* 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