ms365-mcp-server
Version:
Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support
278 lines (277 loc) โข 13.8 kB
JavaScript
import { performanceMonitor } from './batch-performance-monitor.js';
import { logger } from './api.js';
export class BatchTestRunner {
constructor(ms365Ops) {
this.ms365Ops = ms365Ops;
}
/**
* Test Scenario 1: Bulk Email Retrieval
*/
async testBulkEmailRetrieval(messageIds) {
logger.log(`๐งช TEST SCENARIO 1: Bulk Email Retrieval (${messageIds.length} emails)`);
// Traditional approach - sequential calls
const traditionalStart = Date.now();
const traditionalEmails = [];
for (const messageId of messageIds) {
try {
const email = await this.ms365Ops.getEmail(messageId, true);
traditionalEmails.push(email);
}
catch (error) {
logger.log(`โ Traditional: Failed to get email ${messageId}: ${error}`);
}
}
const traditionalMetrics = performanceMonitor.endOperation('bulk_email_retrieval', 'bulk_email_retrieval', messageIds.length, 'traditional', traditionalStart, messageIds.length * 2, // email + attachments call per email
true);
// Wait a moment to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
// Batched approach - native JSON batching
const batchedStart = Date.now();
const batchedEmails = await this.ms365Ops.getEmailsBatch(messageIds, true);
const batchedMetrics = performanceMonitor.endOperation('bulk_email_retrieval', 'bulk_email_retrieval', messageIds.length, 'batched', batchedStart, Math.ceil(messageIds.length / 20), // 20 requests per batch
true);
const comparison = performanceMonitor.comparePerformance('bulk_email_retrieval');
return {
traditional: { emails: traditionalEmails, metrics: traditionalMetrics },
batched: { emails: batchedEmails, metrics: batchedMetrics },
comparison
};
}
/**
* Test Scenario 2: Bulk Email Operations (Mark/Move/Delete)
*/
async testBulkEmailOperations(messageIds) {
logger.log(`๐งช TEST SCENARIO 2: Bulk Email Operations (${messageIds.length} operations)`);
// Create operations for testing
const operations = messageIds.map((id, index) => ({
id: `op_${index}`,
operation: 'mark',
messageId: id,
params: { isRead: true }
}));
// Traditional approach - sequential operations
const traditionalStart = Date.now();
const traditionalResults = [];
for (const op of operations) {
try {
await this.ms365Ops.markEmail(op.messageId, op.params.isRead);
traditionalResults.push({ id: op.id, success: true });
}
catch (error) {
logger.log(`โ Traditional: Failed to mark email ${op.messageId}: ${error}`);
traditionalResults.push({ id: op.id, success: false, error: String(error) });
}
}
const traditionalMetrics = performanceMonitor.endOperation('bulk_email_operations', 'bulk_email_operations', operations.length, 'traditional', traditionalStart, operations.length, // one call per operation
true);
// Wait a moment to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
// Batched approach - native JSON batching
const batchedStart = Date.now();
const batchedResults = await this.ms365Ops.batchEmailOperations(operations);
const batchedMetrics = performanceMonitor.endOperation('bulk_email_operations', 'bulk_email_operations', operations.length, 'batched', batchedStart, Math.ceil(operations.length / 20), // 20 operations per batch
true);
const comparison = performanceMonitor.comparePerformance('bulk_email_operations');
return {
traditional: { results: traditionalResults, metrics: traditionalMetrics },
batched: { results: Array.from(batchedResults.entries()), metrics: batchedMetrics },
comparison
};
}
/**
* Test Scenario 3: Folder + Email Parallel Fetching
*/
async testFolderWithEmails(folderId, emailCount = 10) {
logger.log(`๐งช TEST SCENARIO 3: Folder + Emails Fetching (${emailCount} emails)`);
// Traditional approach - sequential calls
const traditionalStart = Date.now();
// Get folder info first - using existing folder methods
const folders = await this.ms365Ops.listFolders();
const folderInfo = folders.find(f => f.id === folderId || f.displayName.toLowerCase() === folderId.toLowerCase());
// Then get emails
const emailList = await this.ms365Ops.listEmails(folderId, emailCount);
const traditionalResult = {
folder: folderInfo,
emails: emailList.messages
};
const traditionalMetrics = performanceMonitor.endOperation('folder_with_emails', 'folder_with_emails', emailCount + 1, // folder + emails
'traditional', traditionalStart, 2, // folder call + emails call
true);
// Wait a moment to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
// Batched approach - parallel fetching
const batchedStart = Date.now();
const batchedResult = await this.ms365Ops.getFolderWithRecentEmails(folderId, emailCount);
const batchedMetrics = performanceMonitor.endOperation('folder_with_emails', 'folder_with_emails', emailCount + 1, 'batched', batchedStart, 1, // single batch call
true);
const comparison = performanceMonitor.comparePerformance('folder_with_emails');
return {
traditional: { result: traditionalResult, metrics: traditionalMetrics },
batched: { result: batchedResult, metrics: batchedMetrics },
comparison
};
}
/**
* Test Scenario 4: Mixed Operations (Real-world workflow)
*/
async testMixedOperations(messageIds) {
logger.log(`๐งช TEST SCENARIO 4: Mixed Operations Workflow (${messageIds.length} emails)`);
// Take first few emails for different operations
const emailsToMark = messageIds.slice(0, Math.ceil(messageIds.length / 3));
const emailsToMove = messageIds.slice(Math.ceil(messageIds.length / 3), Math.ceil(messageIds.length * 2 / 3));
const emailsToDelete = messageIds.slice(Math.ceil(messageIds.length * 2 / 3));
// Traditional approach
const traditionalStart = Date.now();
const traditionalResults = {
marked: 0,
moved: 0,
deleted: 0,
errors: 0
};
// Mark emails as read
for (const id of emailsToMark) {
try {
await this.ms365Ops.markEmail(id, true);
traditionalResults.marked++;
}
catch (error) {
traditionalResults.errors++;
}
}
// Move emails to archive (simulated)
for (const id of emailsToMove) {
try {
await this.ms365Ops.moveEmail(id, 'archive');
traditionalResults.moved++;
}
catch (error) {
traditionalResults.errors++;
}
}
// Delete emails (simulated - we won't actually delete for testing)
for (const id of emailsToDelete) {
try {
// Simulate delete operation with mark as read instead
await this.ms365Ops.markEmail(id, true);
traditionalResults.deleted++;
}
catch (error) {
traditionalResults.errors++;
}
}
const traditionalMetrics = performanceMonitor.endOperation('mixed_operations', 'mixed_operations', messageIds.length, 'traditional', traditionalStart, messageIds.length, // one call per operation
true);
// Wait a moment to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
// Batched approach
const batchedStart = Date.now();
const mixedOperations = [
...emailsToMark.map((id, index) => ({
id: `mark_${index}`,
operation: 'mark',
messageId: id,
params: { isRead: true }
})),
...emailsToMove.map((id, index) => ({
id: `move_${index}`,
operation: 'move',
messageId: id,
params: { destinationFolderId: 'archive' }
})),
// Simulate delete with mark for testing
...emailsToDelete.map((id, index) => ({
id: `delete_${index}`,
operation: 'mark',
messageId: id,
params: { isRead: true }
}))
];
const batchedResults = await this.ms365Ops.batchEmailOperations(mixedOperations);
const batchedMetrics = performanceMonitor.endOperation('mixed_operations', 'mixed_operations', messageIds.length, 'batched', batchedStart, Math.ceil(mixedOperations.length / 20), // 20 operations per batch
true);
const comparison = performanceMonitor.comparePerformance('mixed_operations');
return {
traditional: { results: traditionalResults, metrics: traditionalMetrics },
batched: { results: Array.from(batchedResults.entries()), metrics: batchedMetrics },
comparison
};
}
/**
* Run all test scenarios
*/
async runAllTests(messageIds) {
logger.log(`๐ STARTING COMPREHENSIVE BATCH PERFORMANCE TESTS`);
logger.log(`๐ง Using ${messageIds.length} test emails`);
performanceMonitor.clear();
const results = {
bulkRetrieval: null,
bulkOperations: null,
folderWithEmails: null,
mixedOperations: null
};
try {
// Test 1: Bulk Email Retrieval
if (messageIds.length >= 5) {
const testIds = messageIds.slice(0, Math.min(10, messageIds.length));
results.bulkRetrieval = await this.testBulkEmailRetrieval(testIds);
}
// Test 2: Bulk Email Operations
if (messageIds.length >= 5) {
const testIds = messageIds.slice(0, Math.min(15, messageIds.length));
results.bulkOperations = await this.testBulkEmailOperations(testIds);
}
// Test 3: Folder + Emails
results.folderWithEmails = await this.testFolderWithEmails('inbox', 10);
// Test 4: Mixed Operations
if (messageIds.length >= 9) {
const testIds = messageIds.slice(0, Math.min(12, messageIds.length));
results.mixedOperations = await this.testMixedOperations(testIds);
}
}
catch (error) {
logger.error('Error during batch testing:', error);
}
// Generate comprehensive report
const report = this.generateTestReport(results);
logger.log(report);
return report;
}
/**
* Generate comprehensive test report
*/
generateTestReport(results) {
let report = `\n๐ BATCH PERFORMANCE TEST RESULTS\n`;
report += `${'='.repeat(50)}\n\n`;
if (results.bulkRetrieval?.comparison) {
const comp = results.bulkRetrieval.comparison;
report += `๐ง BULK EMAIL RETRIEVAL\n`;
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
report += ` ๐ก Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
}
if (results.bulkOperations?.comparison) {
const comp = results.bulkOperations.comparison;
report += `โก BULK EMAIL OPERATIONS\n`;
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
report += ` ๐ก Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
}
if (results.folderWithEmails?.comparison) {
const comp = results.folderWithEmails.comparison;
report += `๐ FOLDER + EMAILS FETCHING\n`;
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
report += ` ๐ก Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
}
if (results.mixedOperations?.comparison) {
const comp = results.mixedOperations.comparison;
report += `๐ MIXED OPERATIONS WORKFLOW\n`;
report += ` Traditional: ${comp.traditional.duration}ms (${comp.traditional.httpCalls} HTTP calls)\n`;
report += ` Batched: ${comp.batched.duration}ms (${comp.batched.httpCalls} HTTP calls)\n`;
report += ` ๐ก Improvement: ${comp.improvement.durationReduction.toFixed(1)}% faster, ${comp.improvement.httpCallReduction.toFixed(1)}% fewer calls\n\n`;
}
report += `๐ OVERALL PERFORMANCE INSIGHTS:\n`;
report += performanceMonitor.generateReport();
return report;
}
}